From bfb35dc8a15989ad6252864050a8a72d3f2fa23b Mon Sep 17 00:00:00 2001 From: "andy.lee" Date: Tue, 25 Jun 2024 15:12:18 +0800 Subject: [PATCH 1/3] show lock icon before encrypted backing image Signed-off-by: andy.lee --- src/routes/backingImage/BackingImageList.js | 10 ++++++++-- src/routes/backingImage/index.js | 3 ++- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/src/routes/backingImage/BackingImageList.js b/src/routes/backingImage/BackingImageList.js index eec4e46b..8b040cc0 100644 --- a/src/routes/backingImage/BackingImageList.js +++ b/src/routes/backingImage/BackingImageList.js @@ -31,9 +31,15 @@ function list({ loading, dataSource, deleteBackingImage, showDiskStateMapDetail, width: 150, sorter: (a, b) => a.name.localeCompare(b.name), render: (text, record) => { + const isEncrypted = record.secret !== '' || record.secretNamespace !== '' return ( -
{ showDiskStateMapDetail(record) }} style={{ width: '100%', cursor: 'pointer' }}> - +
{ showDiskStateMapDetail(record) }} style={{ width: '100%', cursor: 'pointer', display: 'flex', alignItems: 'center', justifyContent: 'center' }}> + {isEncrypted && ( + + + + )} +
) }, diff --git a/src/routes/backingImage/index.js b/src/routes/backingImage/index.js index 8123d17b..85922fbe 100644 --- a/src/routes/backingImage/index.js +++ b/src/routes/backingImage/index.js @@ -93,7 +93,8 @@ class BackingImage extends React.Component { const { uploadFile } = this const { data: settingData } = this.props.setting const { data: volumeData } = this.props.volume - const { data, + const { + data, selected, nodeTags, diskTags, From d92f9a423c7626e948af516e3c7c6dee432445d2 Mon Sep 17 00:00:00 2001 From: "andy.lee" Date: Tue, 25 Jun 2024 17:11:01 +0800 Subject: [PATCH 2/3] add clone type option with encypted/decrypted/ignore in create backing image modal Signed-off-by: andy.lee --- src/models/backingImage.js | 10 +- .../backingImage/BackingImageActions.js | 2 + .../backingImage/BackingImageBulkActions.js | 7 +- src/routes/backingImage/BackingImageList.js | 49 +++- src/routes/backingImage/CreateBackingImage.js | 269 +++++++++++++----- src/routes/backingImage/DiskStateMapDetail.js | 6 +- src/routes/backingImage/index.js | 29 +- .../dashboard/components/resourceOverview.js | 3 +- src/routes/host/HostList.js | 2 +- src/utils/status.js | 21 ++ 10 files changed, 276 insertions(+), 122 deletions(-) diff --git a/src/models/backingImage.js b/src/models/backingImage.js index 1914a08c..58c2fe26 100644 --- a/src/models/backingImage.js +++ b/src/models/backingImage.js @@ -71,14 +71,10 @@ export default { yield put({ type: 'query' }) let data = yield select(state => state.backingImage.data) if (data && data.length > 0) { - let currentBackingImage = data.find((item) => { - return item.name === payload.name - }) + const currentBackingImage = data.find((item) => item.name === payload.name) if (currentBackingImage && currentBackingImage.diskFileStatusMap) { - let diskMap = currentBackingImage.diskFileStatusMap - canUpload = Object.keys(diskMap).some((key) => { - return diskMap[key].state === 'pending' - }) + const diskMap = currentBackingImage.diskFileStatusMap + canUpload = Object.keys(diskMap).some((key) => diskMap[key].state === 'pending') if (canUpload) { break } diff --git a/src/routes/backingImage/BackingImageActions.js b/src/routes/backingImage/BackingImageActions.js index 6eaa8c7a..63f42d09 100644 --- a/src/routes/backingImage/BackingImageActions.js +++ b/src/routes/backingImage/BackingImageActions.js @@ -12,6 +12,8 @@ function actions({ selected, deleteBackingImage, downloadBackingImage, showUpdat case 'delete': confirm({ title: `Are you sure you want to delete backing image ${record.name}?`, + okType: 'danger', + okText: 'Delete', onOk() { deleteBackingImage(record) }, diff --git a/src/routes/backingImage/BackingImageBulkActions.js b/src/routes/backingImage/BackingImageBulkActions.js index cd7e7f31..49ddac67 100644 --- a/src/routes/backingImage/BackingImageBulkActions.js +++ b/src/routes/backingImage/BackingImageBulkActions.js @@ -1,8 +1,7 @@ import React from 'react' import PropTypes from 'prop-types' import { Button, Modal } from 'antd' -import { hasReadyBackingDisk } from '../../utils/status' -import { diskStatusColorMap } from '../../utils/filter' +import { hasReadyBackingDisk, diskStatusColorMap } from '../../utils/status' const confirm = Modal.confirm @@ -14,8 +13,10 @@ function bulkActions({ selectedRows, deleteBackingImages, downloadSelectedBackin case 'delete': confirm({ width: 'fit-content', + okType: 'danger', + okText: 'Delete', title: (<> -

Are you sure to delete below {count} Backing {count === 1 ? 'Image' : 'Images' } ?

+

Are you sure to delete below {count} backing {count === 1 ? 'image' : 'images' } ?

    {selectedRows.map(item =>
  • {item.name}
  • )},
diff --git a/src/routes/backingImage/BackingImageList.js b/src/routes/backingImage/BackingImageList.js index 8b040cc0..f32c613d 100644 --- a/src/routes/backingImage/BackingImageList.js +++ b/src/routes/backingImage/BackingImageList.js @@ -12,14 +12,47 @@ function list({ loading, dataSource, deleteBackingImage, showDiskStateMapDetail, downloadBackingImage, showUpdateMinCopiesCount, } - const state = (record) => { + + const dynamicStateIcon = (record) => { if (record.deletionTimestamp) { // Deleting - return () + return ( + + + + ) + } + if (Object.values(record.diskFileStatusMap).length > 0 + && Object.values(record.diskFileStatusMap).some((diskStatus) => ['starting', 'pending', 'in-progress', 'ready-for-transfer'].includes(diskStatus.state))) { + // some creating states + const state = Object.values(record.diskFileStatusMap)[0]?.state || '' + const percentage = Object.values(record.diskFileStatusMap)[0]?.progress?.toString() || '' + return ( + + + {percentage && {`${percentage} %`}} + + ) } - if (record.diskStateMap && Object.keys(record.diskStateMap).every((key) => record.diskStateMap[key] === 'failed')) { + + return '' + } + + const staticStateIcon = (record) => { + if (record.secret !== '' || record.secretNamespace !== '') { + // encrypted backing image + return ( + + + + ) + } + + if (record.diskFileStatusMap && Object.values(record.diskFileStatusMap).every((diskStatus) => diskStatus.state.includes('failed'))) { + // unavailable backing image return () } + return '' } @@ -31,15 +64,11 @@ function list({ loading, dataSource, deleteBackingImage, showDiskStateMapDetail, width: 150, sorter: (a, b) => a.name.localeCompare(b.name), render: (text, record) => { - const isEncrypted = record.secret !== '' || record.secretNamespace !== '' return (
{ showDiskStateMapDetail(record) }} style={{ width: '100%', cursor: 'pointer', display: 'flex', alignItems: 'center', justifyContent: 'center' }}> - {isEncrypted && ( - - - - )} - + {staticStateIcon(record)} + + {dynamicStateIcon(record)}
) }, diff --git a/src/routes/backingImage/CreateBackingImage.js b/src/routes/backingImage/CreateBackingImage.js index 1fe66f02..4ed425ef 100644 --- a/src/routes/backingImage/CreateBackingImage.js +++ b/src/routes/backingImage/CreateBackingImage.js @@ -1,7 +1,8 @@ import React from 'react' import PropTypes from 'prop-types' import { Form, Input, Select, Upload, Button, Icon, InputNumber, Spin } from 'antd' -import { ModalBlur } from '../../components' +import { ModalBlur, AutoComplete } from '../../components' +import { hasReadyBackingDisk } from '../../utils/status' const FormItem = Form.Item const Option = Select.Option @@ -15,6 +16,53 @@ const formItemLayout = { }, } +const allowDisplayTypes = (creationType, typeArray = []) => typeArray.includes(creationType) + +const genDataFromType = (type, getFieldValue) => { + const payload = { + name: getFieldValue('name'), + sourceType: getFieldValue('sourceType'), + } + + switch (type) { + case 'download': + return { + ...payload, + parameters: { + url: getFieldValue('url'), + }, + expectedChecksum: getFieldValue('expectedChecksum'), + } + case 'upload': + return { + ...payload, + parameters: {}, + fileContainer: getFieldValue('fileContainer'), + expectedChecksum: getFieldValue('expectedChecksum'), + } + case 'volume': + return { + ...payload, + parameters: { + 'volume-name': getFieldValue('volumeName'), + 'export-type': getFieldValue('exportType'), + }, + sourceType: 'export-from-volume', + } + case 'clone': + return { + ...payload, + parameters: { + 'backing-image': getFieldValue('sourceBackingImage'), + encryption: getFieldValue('encryption'), + secret: getFieldValue('secret'), + 'secret-namespace': getFieldValue('secretNamespace'), + }, + } + default: + } +} + const modal = ({ item, volumeNameOptions, @@ -22,6 +70,7 @@ const modal = ({ defaultNumberOfReplicas, nodeTags, diskTags, + backingImageOptions = [], visible, onCancel, onOk, @@ -29,8 +78,8 @@ const modal = ({ getFieldDecorator, validateFields, getFieldsValue, - setFieldsValue, getFieldValue, + setFieldsValue, }, }) => { function handleOk() { @@ -38,9 +87,7 @@ const modal = ({ if (errors) { return } - const data = { - ...getFieldsValue(), - } + const data = genDataFromType(getFieldValue('sourceType'), getFieldValue) onOk(data) }) } @@ -53,26 +100,6 @@ const modal = ({ onOk: handleOk, } - const selectChange = (value) => { - if (value === 'upload') { - setFieldsValue({ - imageURL: '', - volumeName: '', - }) - } else if (value === 'download') { - setFieldsValue({ - fileContainer: null, - volumeName: '', - }) - } else { - setFieldsValue({ - fileContainer: null, - imageURL: '', - expectedChecksum: '', - }) - } - } - const uploadProps = { showUploadList: false, beforeUpload: (file) => { @@ -83,7 +110,19 @@ const modal = ({ }, } - const creationType = getFieldValue('type') + + const autoCompleteProps = { + options: volumeNameOptions, + autoCompleteChange: (value) => { + setFieldsValue({ + volumeName: value, + }) + }, + } + + const creationType = getFieldValue('sourceType') + const availBackingImages = backingImageOptions?.filter(image => hasReadyBackingDisk(image)) || [] + return (
@@ -98,52 +137,71 @@ const modal = ({ ], })()} - - {getFieldDecorator('type', { - valuePropName: 'type', + {getFieldDecorator('sourceType', { + valuePropName: 'sourceType', initialValue: 'download', rules: [ { required: true, }, ], - })( {}}> + )} - - {getFieldDecorator('volumeName', { - initialValue: '', - rules: [ - { - required: creationType === 'volume', - message: 'Please select an existing volume', - }, - ], - })()} - - - {getFieldDecorator('exportType', { - valuePropName: 'exportType', - initialValue: 'raw', - rules: [ - { - required: true, - }, - ], - })()} - - - {getFieldDecorator('imageURL', { - initialValue: item.imageURL, + {/* Display when select type = volume */} + {allowDisplayTypes(creationType, ['volume']) && ( + <> + + {getFieldDecorator('volumeName', { + initialValue: '', + valuePropName: 'value', + rules: [ + { + required: creationType === 'volume', + message: 'Please input volume name', + }, + { + validator: (_rule, value, callback) => { + if (creationType === 'volume') { + if (volumeNameOptions && volumeNameOptions.includes(value)) { + callback() + } else { + callback('Please select an existing Longhorn volume.') + } + } else { + callback() + } + }, + }, + ], + })()} + + + {getFieldDecorator('exportType', { + valuePropName: 'exportType', + initialValue: 'raw', + rules: [ + { + required: true, + }, + ], + })()} + + + )} + {/* Display when select type = download */} + {allowDisplayTypes(creationType, ['download']) && ( + + {getFieldDecorator('url', { + initialValue: item.url, rules: [ { required: creationType === 'download', @@ -151,8 +209,11 @@ const modal = ({ }, ], })()} - - + + )} + {/* Display when select type = upload */} + {allowDisplayTypes(creationType, ['upload']) && ( + {getFieldDecorator('fileContainer', { valuePropName: 'fileContainer', initialValue: null, @@ -162,7 +223,7 @@ const modal = ({ message: 'Please upload backing image file', }, { - validator: (rule, value, callback) => { + validator: (_rule, value, callback) => { if (creationType === 'upload') { let size = 0 if (value && value.size) { @@ -185,17 +246,76 @@ const modal = ({ )} { getFieldsValue().fileContainer && getFieldsValue().fileContainer.file ? getFieldsValue().fileContainer.file.name : ''} - - - {getFieldDecorator('expectedChecksum', { - initialValue: '', - rules: [ - { - required: false, - }, - ], - })()} - + + )} + {allowDisplayTypes(creationType, ['download', 'upload']) && ( + + {getFieldDecorator('expectedChecksum', { + initialValue: '', + rules: [ + { + required: false, + }, + ], + })()} + + )} + + {/* Display when select type = clone */} + {allowDisplayTypes(creationType, ['clone']) && ( + <> + + {getFieldDecorator('sourceBackingImage', { + valuePropName: 'sourceBackingImage', + initialValue: availBackingImages[0]?.name || '', + rules: [ + { + required: creationType === 'clone', + }, + ], + })()} + + + {getFieldDecorator('encryption', { + valuePropName: 'encryption', + initialValue: 'encrypt', + rules: [ + { + required: creationType === 'clone', + }, + ], + })()} + + + {getFieldDecorator('secret', { + initialValue: '', + rules: [ + { + required: creationType === 'clone' && ['encrypt', 'decrypt'].includes(getFieldValue('encryption')), + message: 'Please input secret name', + }, + ], + })()} + + + {getFieldDecorator('secretNamespace', { + initialValue: item.name, + rules: [ + { + required: creationType === 'clone' && ['encrypt', 'decrypt'].includes(getFieldValue('encryption')), + message: 'Please input secret namespace', + }, + ], + })()} + + + )} {getFieldDecorator('minNumberOfCopies', { initialValue: defaultNumberOfReplicas, @@ -241,6 +361,7 @@ modal.propTypes = { defaultNumberOfReplicas: PropTypes.number, nodeTags: PropTypes.array, diskTags: PropTypes.array, + backingImageOptions: PropTypes.array, } export default Form.create()(modal) diff --git a/src/routes/backingImage/DiskStateMapDetail.js b/src/routes/backingImage/DiskStateMapDetail.js index 30f5c306..56b8717e 100644 --- a/src/routes/backingImage/DiskStateMapDetail.js +++ b/src/routes/backingImage/DiskStateMapDetail.js @@ -3,7 +3,7 @@ import PropTypes from 'prop-types' import { Table, Modal, Progress, Tooltip, Card, Icon } from 'antd' import DiskStateMapActions from './DiskStateMapActions' import { ModalBlur, DropOption } from '../../components' -import { diskStatusColorMap } from '../../utils/filter' +import { diskStatusColorMap } from '../../utils/status' import style from './BackingImage.less' const confirm = Modal.confirm @@ -24,9 +24,7 @@ const modal = ({ diskStateMapDeleteLoading, }) => { // update detail list - let currentData = backingImages.find((item) => { - return item.id === selected.id - }) + const currentData = backingImages.find((item) => item.id === selected.id) || {} const modalOpts = { title:

{currentData.name}

, diff --git a/src/routes/backingImage/index.js b/src/routes/backingImage/index.js index 85922fbe..b5438edc 100644 --- a/src/routes/backingImage/index.js +++ b/src/routes/backingImage/index.js @@ -172,48 +172,33 @@ class BackingImage extends React.Component { }, volumeNameOptions, defaultNumberOfReplicas, + backingImageOptions: backingImages, visible: createBackingImageModalVisible, nodeTags, diskTags, tagsLoading, onOk(newBackingImage) { - let params = {} - params.name = newBackingImage.name - if (newBackingImage.type === 'upload') { - params.sourceType = 'upload' - params.parameters = {} - - // notification.warning + const payload = { ...newBackingImage } + if (newBackingImage.sourceType === 'upload') { + delete payload.fileContainer notification.warning({ message: 'Do not refresh or close this page, otherwise the upload will be interrupted.', key: 'uploadNotification', duration: 0, }) - } else if (newBackingImage.type === 'download') { - params.sourceType = 'download' - params.parameters = { - url: newBackingImage.imageURL, - } - } else { - params.sourceType = 'export-from-volume' - params.parameters = { - 'volume-name': newBackingImage.volumeName, - 'export-type': newBackingImage.exportType, - } } params.expectedChecksum = newBackingImage.expectedChecksum - params.diskSelector = newBackingImage.diskSelector params.nodeSelector = newBackingImage.nodeSelector params.minNumberOfCopies = newBackingImage.minNumberOfCopies dispatch({ type: 'backingImage/create', - payload: params, + payload, callback: (record, canUpload) => { + const file = newBackingImage?.fileContainer?.file // to do upload - if (newBackingImage.fileContainer && newBackingImage.fileContainer.file && newBackingImage.type === 'upload' && canUpload) { - let file = newBackingImage.fileContainer.file + if (newBackingImage.sourceType === 'upload' && file && canUpload) { uploadFile(file, record) } else { notification.close('uploadNotification') diff --git a/src/routes/dashboard/components/resourceOverview.js b/src/routes/dashboard/components/resourceOverview.js index 338b82c5..b67f04cb 100755 --- a/src/routes/dashboard/components/resourceOverview.js +++ b/src/routes/dashboard/components/resourceOverview.js @@ -6,7 +6,8 @@ import { formatMib } from '../../../utils/formatter' import ResourceChart from './resourceChart' import ResourceDetail from './resourceDetail' import styles from './resourceOverview.less' -import { nodeStatusColorMap, healthyVolume, inProgressVolume, degradedVolume, detachedVolume, faultedVolume, schedulableNode, unschedulableNode, schedulingDisabledNode, downNode } from '../../../utils/filter' +import { healthyVolume, inProgressVolume, degradedVolume, detachedVolume, faultedVolume, schedulableNode, unschedulableNode, schedulingDisabledNode, downNode } from '../../../utils/filter' +import { nodeStatusColorMap } from '../../../utils/status' class ResourceOverview extends React.Component { constructor(props) { diff --git a/src/routes/host/HostList.js b/src/routes/host/HostList.js index 736299cb..7caee341 100755 --- a/src/routes/host/HostList.js +++ b/src/routes/host/HostList.js @@ -7,7 +7,7 @@ import { sortTable } from '../../utils/sort' import DiskList from './DiskList' import HostActions from './HostActions' import InstanceManagerComponent from './components/InstanceManagerComponent' -import { nodeStatusColorMap } from '../../utils/filter' +import { nodeStatusColorMap } from '../../utils/status' import { byteToGi, getStorageProgressStatus } from './helper/index' import { formatMib } from '../../utils/formatter' import { pagination } from '../../utils/page' diff --git a/src/utils/status.js b/src/utils/status.js index 9a47fd84..b3bcb6f8 100644 --- a/src/utils/status.js +++ b/src/utils/status.js @@ -21,3 +21,24 @@ export function hasReadyBackingDisk(data) { return false } } +// backing image disk status +export const diskStatusColorMap = { + ready: { color: '#27AE5F', bg: 'rgba(39,174,95,.05)' }, // green + starting: { color: '#F1C40F', bg: 'rgba(241,196,15,.05)' }, // yellow + pending: { color: '#F1C40F', bg: 'rgba(241,196,15,.05)' }, // yellow + 'in-progress': { color: '#F1C40F', bg: 'rgba(241,196,15,.05)' }, // yellow + 'ready-for-transfer': { color: '#F1C40F', bg: 'rgba(241,196,15,.05)' }, // yellow + 'failed-and-cleanup': { color: '#F15354', bg: 'rgba(241,83,84,.05)' }, // red + failed: { color: '#F15354', bg: 'rgba(241,83,84,.05)' }, // red +} + +// node status +export const nodeStatusColorMap = { + schedulable: { color: '#27AE5F', bg: 'rgba(39,174,95,.05)' }, // green + unschedulable: { color: '#F1C40F', bg: 'rgba(241,196,15,.05)' }, // yellow + // autoEvicting nodes are a subset of unschedulable nodes. We use the same color to represent both. + autoEvicting: { color: '#F1C40F', bg: 'rgba(241,196,15,.05)' }, // yellow + down: { color: '#F15354', bg: 'rgba(241,83,84,.1)' }, // red + disabled: { color: '#dee1e3', bg: 'rgba(222,225,227,.05)' }, // grey + unknown: { color: '#F15354', bg: 'rgba(241,83,84,.05)' }, // red +} From 046addd27710953c6b92899eb4de52208c7abca5 Mon Sep 17 00:00:00 2001 From: "andy.lee" Date: Tue, 2 Jul 2024 10:56:16 +0800 Subject: [PATCH 3/3] add clone sourceType in filter option Signed-off-by: andy.lee --- src/routes/backingImage/BackingImageList.js | 13 ------ src/routes/backingImage/CreateBackingImage.js | 40 +++++-------------- src/routes/backingImage/DiskStateMapDetail.js | 1 + src/routes/backingImage/index.js | 6 +-- 4 files changed, 13 insertions(+), 47 deletions(-) diff --git a/src/routes/backingImage/BackingImageList.js b/src/routes/backingImage/BackingImageList.js index f32c613d..89d29332 100644 --- a/src/routes/backingImage/BackingImageList.js +++ b/src/routes/backingImage/BackingImageList.js @@ -22,19 +22,6 @@ function list({ loading, dataSource, deleteBackingImage, showDiskStateMapDetail, ) } - if (Object.values(record.diskFileStatusMap).length > 0 - && Object.values(record.diskFileStatusMap).some((diskStatus) => ['starting', 'pending', 'in-progress', 'ready-for-transfer'].includes(diskStatus.state))) { - // some creating states - const state = Object.values(record.diskFileStatusMap)[0]?.state || '' - const percentage = Object.values(record.diskFileStatusMap)[0]?.progress?.toString() || '' - return ( - - - {percentage && {`${percentage} %`}} - - ) - } - return '' } diff --git a/src/routes/backingImage/CreateBackingImage.js b/src/routes/backingImage/CreateBackingImage.js index 4ed425ef..10f0a295 100644 --- a/src/routes/backingImage/CreateBackingImage.js +++ b/src/routes/backingImage/CreateBackingImage.js @@ -1,7 +1,8 @@ +/* eslint-disable react/jsx-props-no-spreading */ import React from 'react' import PropTypes from 'prop-types' import { Form, Input, Select, Upload, Button, Icon, InputNumber, Spin } from 'antd' -import { ModalBlur, AutoComplete } from '../../components' +import { ModalBlur } from '../../components' import { hasReadyBackingDisk } from '../../utils/status' const FormItem = Form.Item @@ -22,6 +23,9 @@ const genDataFromType = (type, getFieldValue) => { const payload = { name: getFieldValue('name'), sourceType: getFieldValue('sourceType'), + minNumberOfCopies: getFieldValue('minNumberOfCopies'), + diskSelector: getFieldValue('diskSelector'), + nodeSelector: getFieldValue('nodeSelector'), } switch (type) { @@ -110,16 +114,6 @@ const modal = ({ }, } - - const autoCompleteProps = { - options: volumeNameOptions, - autoCompleteChange: (value) => { - setFieldsValue({ - volumeName: value, - }) - }, - } - const creationType = getFieldValue('sourceType') const availBackingImages = backingImageOptions?.filter(image => hasReadyBackingDisk(image)) || [] @@ -149,8 +143,8 @@ const modal = ({ })()}
{/* Display when select type = volume */} @@ -159,27 +153,15 @@ const modal = ({ {getFieldDecorator('volumeName', { initialValue: '', - valuePropName: 'value', rules: [ { required: creationType === 'volume', - message: 'Please input volume name', - }, - { - validator: (_rule, value, callback) => { - if (creationType === 'volume') { - if (volumeNameOptions && volumeNameOptions.includes(value)) { - callback() - } else { - callback('Please select an existing Longhorn volume.') - } - } else { - callback() - } - }, + message: 'Please select an existing volume', }, ], - })()} + })()} {getFieldDecorator('exportType', { diff --git a/src/routes/backingImage/DiskStateMapDetail.js b/src/routes/backingImage/DiskStateMapDetail.js index 56b8717e..b99e7113 100644 --- a/src/routes/backingImage/DiskStateMapDetail.js +++ b/src/routes/backingImage/DiskStateMapDetail.js @@ -160,6 +160,7 @@ const modal = ({ {currentData.sourceType === 'download' && 'Download from URL'} {currentData.sourceType === 'upload' && 'Upload'} {currentData.sourceType === 'export-from-volume' && 'Export from a Longhorn volume'} + {currentData.sourceType === 'clone' && 'Clone from existing backing image'}
Parameters During Creation
diff --git a/src/routes/backingImage/index.js b/src/routes/backingImage/index.js index b5438edc..b335ff86 100644 --- a/src/routes/backingImage/index.js +++ b/src/routes/backingImage/index.js @@ -187,11 +187,6 @@ class BackingImage extends React.Component { duration: 0, }) } - params.expectedChecksum = newBackingImage.expectedChecksum - params.diskSelector = newBackingImage.diskSelector - params.nodeSelector = newBackingImage.nodeSelector - params.minNumberOfCopies = newBackingImage.minNumberOfCopies - dispatch({ type: 'backingImage/create', payload, @@ -278,6 +273,7 @@ class BackingImage extends React.Component { { value: 'download', name: 'download' }, { value: 'upload', name: 'upload' }, { value: 'export-from-volume', name: 'export-from-volume' }, + { value: 'clone', name: 'clone' }, ], onSearch(filter) { const { field: filterField, value: filterValue, createdFromValue: createdFromPropValue } = filter