diff --git a/src/components/Snapshot/Snapshot.js b/src/components/Snapshot/Snapshot.js
index 5482c162..ac340cf9 100644
--- a/src/components/Snapshot/Snapshot.js
+++ b/src/components/Snapshot/Snapshot.js
@@ -31,10 +31,11 @@ function VolumeHead(props) {
Volume Head
- ) : (
+ ) : (
+
+
)
)
}
@@ -49,7 +50,6 @@ VolumeHead.propTypes = {
// render each snapshot action dropdown
function SnapshotIcon(props, snapshotProps) {
function doAction(key) {
- console.log('🚀 ~ doAction ~ key:', key)
snapshotProps.onAction({
type: key,
payload: {
@@ -260,7 +260,6 @@ class Snapshot extends React.Component {
render() {
let props = this.props
let children = null
- console.log('props.snapshotTree', this.props.snapshotTree)
if (props.snapshotTree) {
children = props.snapshotTree.length > 0 ? loop(props.snapshotTree, props) :
if (props.loading || this.state.loadingState !== props.loading) {
diff --git a/src/models/snapshot.js b/src/models/snapshot.js
index 68ab4760..e30aeaeb 100644
--- a/src/models/snapshot.js
+++ b/src/models/snapshot.js
@@ -163,7 +163,6 @@ export default (namespace) => {
*queryVolume({
payload,
}, { put }) {
- console.log('🚀 ~queryVolume payload:', payload)
const data = payload.volume
if (data && data.actions) {
yield put({ type: 'setVolume', payload: data })
@@ -173,7 +172,6 @@ export default (namespace) => {
*querySnapShot({
payload,
}, { call, put }) {
- console.log('🚀 ~querySnapShot payload:', payload)
if (!payload.url) {
yield put({ type: 'setSnapshotData', payload: [] })
yield put({ type: 'setSnapshot', payload: [] })
diff --git a/src/models/volume.js b/src/models/volume.js
index 6ebcb018..b8d5732b 100755
--- a/src/models/volume.js
+++ b/src/models/volume.js
@@ -1,11 +1,13 @@
import { create, deleteVolume, query, execAction, createVolumePV, createVolumePVC, createVolumeAllPVC, volumeActivate, getNodeTags, getDiskTags, expandVolume, cancelExpansion, createRecurringJob, recurringJobAdd, getVolumeRecurringJobList, removeVolumeRecurringJob, updateRecurringJob } from '../services/volume'
import { query as getRecurringJob } from '../services/recurringJob'
import { wsChanges, updateState } from '../utils/websocket'
+import { message } from 'antd'
import { sortVolume } from '../utils/sort'
import { routerRedux } from 'dva/router'
import { getSorter, saveSorter } from '../utils/store'
import queryString from 'query-string'
import { enableQueryData } from '../utils/dataDependency'
+import { sortSnapshots } from '../utils/sort'
export default {
namespace: 'volume',
@@ -13,7 +15,11 @@ export default {
ws: null,
data: [],
resourceType: 'volume',
+ cloneVolumeType: 'volume', // volume or snapshot
+ snapshotsOptions: {},
+ snapshotLoading: true,
selected: null,
+ selectSnapshot: null,
selectedRows: [],
WorkloadDetailModalItem: {},
volumeRecurringJobs: [],
@@ -205,14 +211,18 @@ export default {
*createClonedVolume({
payload,
}, { call, put }) {
- yield put({ type: 'hideVolumeCloneModal' })
- yield call(create, payload)
- yield put({ type: 'query' })
+ yield put({ type: 'hideCloneVolumeModal' })
+ const resp = yield call(create, payload)
+ if (resp && resp.status === 200) {
+ message.success(`New volume (${payload.name}) created successfully`, 5)
+ yield put({ type: 'query' })
+ }
},
- *showCreateVolumeModalBefore({
+ *showCloneVolumeModalBefore({
payload,
}, { call, put }) {
- yield put({ type: 'showCreateVolumeModal' })
+ yield put({ type: 'showCloneVolumeModal', payload })
+
const nodeTags = yield call(getNodeTags, payload)
const diskTags = yield call(getDiskTags, payload)
if (nodeTags.status === 200 && diskTags.status === 200) {
@@ -221,6 +231,34 @@ export default {
yield put({ type: 'changeTagsLoading', payload: { tagsLoading: false } })
}
},
+ *showCreateVolumeModalBefore({
+ payload,
+ }, { call, put, all }) {
+ yield put({ type: 'showCreateVolumeModal' })
+ // TODO: longhorn manager should have an API to get all volume's snapshots or add snapshotList array in GET /v1/volumes
+ const snapshotListRequests = payload.filter(item => item.actions.snapshotList).map(item => call(execAction, item.actions.snapshotList))
+ const snapshotResp = yield all(snapshotListRequests)
+ if (snapshotResp && snapshotResp.length > 0 && snapshotResp.every(resp => resp.status === 200)) {
+ // construct snapshots data by volume name
+ const snapshotsOptions = {}
+ for (const resp of snapshotResp) {
+ if (resp?.links?.self) {
+ const vol = resp?.links.self.split('/').pop()
+ const snapshots = resp.data.filter(d => d.name !== 'volume-head') // no include volume-head
+ sortSnapshots(snapshots)
+ snapshotsOptions[vol] = snapshots
+ }
+ }
+ yield put({ type: 'setSnapshotsData', payload: { snapshotsOptions, snapshotLoading: false } })
+ }
+ const nodeTags = yield call(getNodeTags)
+ const diskTags = yield call(getDiskTags)
+ if (nodeTags.status === 200 && diskTags.status === 200) {
+ yield put({ type: 'changeTagsLoading', payload: { nodeTags: nodeTags.data, diskTags: diskTags.data, tagsLoading: false } })
+ } else {
+ yield put({ type: 'changeTagsLoading', payload: { tagsLoading: false } })
+ }
+ },
*delete({
payload,
}, { call, put }) {
@@ -682,6 +720,9 @@ export default {
updateBackground(state, action) {
return updateState(state, action)
},
+ setSnapshotsData(state, action) {
+ return { ...state, ...action.payload }
+ },
showChangeVolumeModal(state, action) {
return { ...state, changeVolumeActivate: action.payload, changeVolumeModalVisible: true, changeVolumeModalKey: Math.random() }
},
@@ -755,7 +796,7 @@ export default {
hideRecurringJobModal(state) {
return { ...state, recurringJobModalVisible: false, recurringJobModalKey: Math.random() }
},
- showVolumeCloneModal(state, action) {
+ showCloneVolumeModal(state, action) {
return { ...state, ...action.payload, volumeCloneModalVisible: true, volumeCloneModalKey: Math.random() }
},
showAttachHostModal(state, action) {
@@ -782,7 +823,7 @@ export default {
showBulkEngineUpgradeModal(state, action) {
return { ...state, ...action.payload, bulkEngineUpgradeModalVisible: true, bulkEngineUpgradeModalKey: Math.random() }
},
- hideVolumeCloneModal(state) {
+ hideCloneVolumeModal(state) {
return { ...state, volumeCloneModalVisible: false }
},
hideEngineUpgradeModal(state) {
diff --git a/src/router.js b/src/router.js
index 6637123f..2ce5a001 100755
--- a/src/router.js
+++ b/src/router.js
@@ -114,7 +114,6 @@ const Routers = function ({ history, app }) {
- {/*
*/}
diff --git a/src/routes/volume/CloneVolume.js b/src/routes/volume/CloneVolume.js
new file mode 100644
index 00000000..2180e3e6
--- /dev/null
+++ b/src/routes/volume/CloneVolume.js
@@ -0,0 +1,274 @@
+import React from 'react'
+import PropTypes from 'prop-types'
+import {
+ Form,
+ Input,
+ InputNumber,
+ Select,
+ Checkbox,
+ Spin,
+ Alert,
+} from 'antd'
+import { ModalBlur } from '../../components'
+import { frontends } from './helper/index'
+import { formatSize } from '../../utils/formatter'
+
+const FormItem = Form.Item
+const { Option } = Select
+
+const formItemLayout = {
+ labelCol: { span: 6 },
+ wrapperCol: { span: 13 },
+}
+
+const genOkData = (getFieldsValue, getFieldValue, volume, cloneType) => {
+ const dataSourceResult = getFieldValue('dataSource')
+ const dataSource = cloneType === 'volume' ? `vol://${dataSourceResult}` : `snap://${volume.name}/${dataSourceResult}`
+ const data = {
+ ...volume,
+ ...getFieldsValue(),
+ size: volume.size,
+ dataSource,
+ }
+ if (data.unit) {
+ delete data.unit
+ }
+ return data
+}
+
+
+const modal = ({
+ cloneType = 'volume', // 'volume' or 'snapshot'
+ volume = {},
+ snapshot = {},
+ visible,
+ onCancel,
+ onOk,
+ nodeTags,
+ diskTags,
+ defaultDataLocalityOption,
+ defaultDataLocalityValue,
+ backingImageOptions,
+ tagsLoading,
+ v1DataEngineEnabled,
+ v2DataEngineEnabled,
+ form: {
+ getFieldDecorator,
+ validateFields,
+ getFieldsValue,
+ getFieldValue,
+ },
+}) => {
+ function handleOk() {
+ validateFields((errors) => {
+ if (errors) {
+ return
+ }
+ const data = genOkData(getFieldsValue, getFieldValue, volume, cloneType)
+ onOk(data)
+ })
+ }
+
+ const modalOpts = {
+ title: cloneType === 'volume' ? `Clone Volume from ${volume.name}` : `Clone Volume from ${volume.name} snapshot ${snapshot.name}`,
+ visible,
+ onCancel,
+ width: 880,
+ onOk: handleOk,
+ style: { top: 0 },
+ }
+
+ return (
+
+
+
+
+ )
+}
+
+modal.propTypes = {
+ cloneType: PropTypes.string,
+ volume: PropTypes.object,
+ snapshot: PropTypes.object,
+ form: PropTypes.object.isRequired,
+ visible: PropTypes.bool,
+ onCancel: PropTypes.func,
+ onOk: PropTypes.func,
+ nodeTags: PropTypes.array,
+ diskTags: PropTypes.array,
+ defaultDataLocalityOption: PropTypes.array,
+ defaultDataLocalityValue: PropTypes.string,
+ tagsLoading: PropTypes.bool,
+ v1DataEngineEnabled: PropTypes.bool,
+ v2DataEngineEnabled: PropTypes.bool,
+ backingImageOptions: PropTypes.array,
+}
+
+export default Form.create()(modal)
diff --git a/src/routes/volume/CreateVolume.js b/src/routes/volume/CreateVolume.js
index ded55989..649aec32 100644
--- a/src/routes/volume/CreateVolume.js
+++ b/src/routes/volume/CreateVolume.js
@@ -10,9 +10,13 @@ import {
Collapse,
Tooltip,
Icon,
+ Alert,
+ Popover,
} from 'antd'
import { ModalBlur } from '../../components'
import { frontends } from './helper/index'
+import { formatSize } from '../../utils/formatter'
+import { formatDate } from '../../utils/formatDate'
const FormItem = Form.Item
const { Panel } = Collapse
@@ -38,9 +42,64 @@ const formItemLayoutForAdvanced = {
const dataSourceOptions = ['Volume', 'Volume Snapshot']
+const getDataSource = (getFieldValue) => {
+ let dataSource = ''
+ const dataSourceType = getFieldValue('dataSourceType') || ''
+ const dataSourceVol = getFieldValue('dataSourceVolume') || ''
+ const dataSourceSnapshot = getFieldValue('dataSourceSnapshot') || ''
+ if (dataSourceType && dataSourceVol) {
+ switch (dataSourceType) {
+ case 'Volume':
+ dataSource = `vol://${dataSourceVol}`
+ break
+ case 'Volume Snapshot':
+ dataSource = dataSourceSnapshot ? `snap://${dataSourceVol}/${dataSourceSnapshot}` : ''
+ break
+ default:
+ }
+ }
+ return dataSource
+}
+
+const getSize = (getFieldValue, volumeOptions) => {
+ const dataSourceType = getFieldValue('dataSourceType') || ''
+ const dataSourceVol = getFieldValue('dataSourceVolume') || ''
+ const dataSourceSnapshot = getFieldValue('dataSourceSnapshot') || ''
+
+ if (dataSourceType && dataSourceType === dataSourceOptions[0] && dataSourceVol) {
+ const sourceVolSize = volumeOptions.find(vol => vol.name === dataSourceVol)?.size || 0
+ return sourceVolSize
+ }
+
+ if (dataSourceType && dataSourceType === dataSourceOptions[1] && dataSourceVol && dataSourceSnapshot) {
+ const sourceVolSize = volumeOptions.find(vol => vol.name === dataSourceVol)?.size || 0
+ return sourceVolSize
+ }
+
+ return `${getFieldValue('size')}${getFieldValue('unit')}`
+}
+
+const genOkData = (getFieldsValue, getFieldValue, volumeOptions) => {
+ const data = {
+ ...getFieldsValue(),
+ dataSource: getDataSource(getFieldValue),
+ size: getSize(getFieldValue, volumeOptions),
+ snapshotMaxSize: `${getFieldsValue().snapshotMaxSize}${getFieldsValue().snapshotSizeUnit}`,
+ }
+ if (data.dataSourceType) {
+ delete data.dataSourceType
+ }
+ if (data.unit) {
+ delete data.unit
+ }
+ return data
+}
+
+
const modal = ({
item,
- volumes,
+ volumeOptions = [],
+ snapshotsOptions = {},
visible,
onCancel,
onOk,
@@ -50,8 +109,9 @@ const modal = ({
defaultRevisionCounterValue,
defaultSnapshotDataIntegrityOption,
diskTags,
- backingImages,
+ backingImageOptions,
tagsLoading,
+ snapshotLoading,
v1DataEngineEnabled,
v2DataEngineEnabled,
form: {
@@ -62,38 +122,12 @@ const modal = ({
setFieldsValue,
},
}) => {
- console.log('🚀 ~ createVolumes volumes:', volumes)
function handleOk() {
validateFields((errors) => {
if (errors) {
return
}
- let dataSourceValue = ''
- if (getFieldValue('dataSource')) {
- switch (getFieldValue('dataSourceType')) {
- case 'Volume':
- dataSourceValue = `vol://${getFieldValue('dataSource')}`
- break
- case 'Volume Snapshot':
- dataSourceValue = `snap://${123}/${getFieldValue('dataSource')}`
- break
- default:
- }
- }
- const data = {
- ...getFieldsValue(),
- dataSource: dataSourceValue,
- size: `${getFieldsValue().size}${getFieldsValue().unit}`,
- snapshotMaxSize: `${getFieldsValue().snapshotMaxSize}${getFieldsValue().snapshotSizeUnit}`,
- }
-
- if (data.dataSourceType) {
- delete data.dataSourceType
- }
- if (data.unit) {
- delete data.unit
- }
- console.log('🚀 ~ validateFields ~ data:', data)
+ const data = genOkData(getFieldsValue, getFieldValue, volumeOptions)
onOk(data)
})
}
@@ -121,13 +155,34 @@ const modal = ({
size: currentSize,
})
}
+ const displayDataSourceAlert = () => {
+ const selectedVol = getFieldValue('dataSourceVolume')
+ if (selectedVol === '' || selectedVol === undefined) {
+ return false
+ } else {
+ return true
+ }
+ }
+
+ const handleDataSourceVolumeChange = (value) => {
+ const dataSourceVol = volumeOptions.find(vol => vol.name === value)
+ if (dataSourceVol) {
+ // set size field according to the selected data source
+ setFieldsValue({
+ ...getFieldsValue(),
+ size: formatSize(dataSourceVol),
+ })
+ }
+ }
+ const targetVolumeSnaps = snapshotsOptions[getFieldValue('dataSourceVolume')] || []
+ const dataSourceAlertMsg = 'The volume size is set to the selected volume size. Mismatched size will cause create volume failed.'
return (
+ {getFieldValue('dataSourceType') === dataSourceOptions[1] &&