From 5affc6ab02c06684a7cac1a59e1926a446be42c8 Mon Sep 17 00:00:00 2001 From: "andy.lee" Date: Thu, 6 Jun 2024 12:27:01 +0800 Subject: [PATCH] Refactor and add apply all button Signed-off-by: andy.lee --- .../backup/BulkCreateStandbyVolumeModal.js | 171 +++++++++++------- src/routes/backup/BulkRestoreBackupModal.js | 162 ++++++++++------- src/routes/backup/CreateStandbyVolumeModal.js | 8 +- src/routes/backup/RestoreBackupModal.js | 7 + src/routes/backup/index.js | 2 +- 5 files changed, 217 insertions(+), 133 deletions(-) diff --git a/src/routes/backup/BulkCreateStandbyVolumeModal.js b/src/routes/backup/BulkCreateStandbyVolumeModal.js index 96b3aa6a..e696d9c1 100644 --- a/src/routes/backup/BulkCreateStandbyVolumeModal.js +++ b/src/routes/backup/BulkCreateStandbyVolumeModal.js @@ -1,6 +1,6 @@ -import React, { useState, useEffect } from 'react' +import React, { useState } from 'react' import PropTypes from 'prop-types' -import { Form, InputNumber, Select, Spin, Tabs, Button, Input } from 'antd' +import { Form, InputNumber, Select, message, Spin, Checkbox, Tooltip, Tabs, Button, Input } from 'antd' import { ModalBlur } from '../../components' import { formatMib } from '../../utils/formatter' @@ -34,84 +34,46 @@ const modal = ({ validateFields, getFieldsValue, getFieldValue, + getFieldsError, setFieldsValue, }, }) => { const initConfigs = items.map((i) => ({ - name: `dr-${i.volumeName}`, + name: i.volumeName, size: formatMib(i.size), numberOfReplicas, dataEngine: 'v1', accessMode: i.accessMode || null, backingImage: i.backingImage, + encrypted: false, nodeSelector: [], diskSelector: [], })) const [currentTab, setCurrentTab] = useState(0) const [drVolumeConfigs, setDrVolumeConfigs] = useState(initConfigs) - const [done, setDone] = useState(false) - const lastIndex = items.length - 1 - useEffect(() => { - if (currentTab === lastIndex && done) { - const data = drVolumeConfigs.map((config, index) => ({ - ...config, - standby: true, - frontend: '', - fromBackup: items[index].fromBackup, - size: config.size.replace(/\s/ig, ''), - })) - onOk(data) - } - }, [drVolumeConfigs]) + function handleOk() { + const data = drVolumeConfigs.map((config, index) => ({ + ...config, + standby: true, + frontend: '', + fromBackup: items[index].fromBackup, + size: items[index].size.replace(/\s/ig, ''), + })) + onOk(data) + } - const handleOk = () => { - validateFields((errors) => { - if (errors) { - return - } + const handleEncryptedCheck = (e) => { + const isChecked = e.target.checked + setDrVolumeConfigs(prev => { + const newConfigs = [...prev] const data = { ...getFieldsValue(), - name: getFieldValue('name').trimLeftAndRight(), - } - setDrVolumeConfigs(prev => { - const newConfigs = [...prev] - newConfigs.splice(currentTab, 1, data) - return newConfigs - }) - if (currentTab !== lastIndex) { - const nextIndex = currentTab + 1 - setCurrentTab(nextIndex) - const nextConfig = drVolumeConfigs[nextIndex] - setFieldsValue({ - name: nextConfig.name, - size: nextConfig.size, - numberOfReplicas, - dataEngine: nextConfig.dataEngine, - accessMode: nextConfig.accessMode, - backingImage: nextConfig.backingImage, - nodeSelector: nextConfig.nodeSelector, - diskSelector: nextConfig.diskSelector, - }) - } else if (currentTab === lastIndex) { - setDone(true) + encrypted: isChecked, + name: getFieldValue('name')?.trimLeftAndRight() || '', } - }) - } - - const handlePrevious = () => { - const prevIdx = currentTab - 1 - setCurrentTab(prevIdx) - const prevConfig = drVolumeConfigs[prevIdx] - setFieldsValue({ - name: prevConfig.name, - size: prevConfig.size, - numberOfReplicas: prevConfig.numberOfReplicas, - dataEngine: prevConfig.dataEngine, - accessMode: prevConfig.accessMode, - backingImage: prevConfig.backingImage, - nodeSelector: prevConfig.nodeSelector, - diskSelector: prevConfig.diskSelector, + newConfigs.splice(currentTab, 1, data) + return newConfigs }) } @@ -127,6 +89,73 @@ const modal = ({ }) } + const handleApplyAll = () => { + // only apply below configs to other configs + const currentConfig = { + numberOfReplicas: getFieldValue('numberOfReplicas'), + dataEngine: getFieldValue('dataEngine'), + accessMode: getFieldValue('accessMode'), + encrypted: getFieldValue('encrypted') || false, + nodeSelector: getFieldValue('nodeSelector'), + diskSelector: getFieldValue('diskSelector'), + } + setDrVolumeConfigs(prev => { + const newConfigs = [...prev] + newConfigs.forEach((config, index) => { + if (index !== currentTab) { + newConfigs.splice(index, 1, { ...config, ...currentConfig }) + } + }) + return newConfigs + }) + message.success(`Successfully apply ${getFieldValue('name')} config to all other disaster recovery volumes`, 5) + } + + const allFieldsError = { ...getFieldsError() } + const hasFieldsError = Object.values(allFieldsError).some(fieldError => fieldError !== undefined) || false + + + const handleTabClick = (key) => { + if (hasFieldsError) { + message.error('Please fill in all required fields before switching to another disaster recover volume tab', 5) + return + } + validateFields((errors) => { + if (errors) { + return + } + const data = { + ...getFieldsValue(), + name: getFieldValue('name').trimLeftAndRight(), + fromBackup: items[currentTab]?.fromBackup || '', + } + // replace this config with the current form data when click tab + setDrVolumeConfigs(prev => { + const newConfigs = [...prev] + newConfigs.splice(currentTab, 1, data) + return newConfigs + }) + }) + const newIndex = items.findIndex(i => i.volumeName === key) + + if (newIndex !== -1) { + setCurrentTab(newIndex) + const nextConfig = drVolumeConfigs[newIndex] + setFieldsValue({ + name: nextConfig.name, + size: nextConfig.size, + numberOfReplicas: nextConfig.numberOfReplicas, + dataEngine: nextConfig.dataEngine, + accessMode: nextConfig.accessMode, + backingImage: nextConfig.backingImage, + encrypted: nextConfig.encrypted, + nodeSelector: nextConfig.nodeSelector, + diskSelector: nextConfig.diskSelector, + }) + } + } + + const tooltipTitle = `Apply this ${getFieldValue('name')} config to all the other disaster recovery volumes, this action will overwrite your previous filled in configs` const modalOpts = { title: 'Create Multiple Disaster Recovery Volumes', visible, @@ -134,14 +163,14 @@ const modal = ({ onCancel, width: 700, footer: [ - , - , - + , + , ], } @@ -151,7 +180,7 @@ const modal = ({ return ( - + {items.map(i => )}
@@ -227,6 +256,12 @@ const modal = ({ { backingImages.map(backingImage => ) } )} + + {getFieldDecorator('encrypted', { + valuePropName: 'checked', + initialValue: item.encrypted || false, + })()} + {getFieldDecorator('nodeSelector', { diff --git a/src/routes/backup/BulkRestoreBackupModal.js b/src/routes/backup/BulkRestoreBackupModal.js index 1219b95f..d5237539 100644 --- a/src/routes/backup/BulkRestoreBackupModal.js +++ b/src/routes/backup/BulkRestoreBackupModal.js @@ -1,6 +1,6 @@ -import React, { useState, useEffect } from 'react' +import React, { useState } from 'react' import PropTypes from 'prop-types' -import { Form, Input, InputNumber, Spin, Select, Popover, Alert, Tabs, Button } from 'antd' +import { Form, Input, InputNumber, Spin, Select, message, Popover, Alert, Tabs, Button, Checkbox, Tooltip } from 'antd' import { ModalBlur } from '../../components' const TabPane = Tabs.TabPane @@ -31,74 +31,75 @@ const modal = ({ form: { getFieldDecorator, validateFields, + getFieldsError, getFieldsValue, getFieldValue, setFieldsValue, }, }) => { const initConfigs = items.map((i) => ({ - name: '', + name: i.volumeName, numberOfReplicas: i.numberOfReplicas, dataEngine: 'v1', accessMode: i.accessMode || null, latestBackup: i.backupName, backingImage: i.backingImage, + encrypted: false, restoreVolumeRecurringJob: 'ignored', nodeSelector: [], diskSelector: [], })) + const [currentTab, setCurrentTab] = useState(0) const [restoreBackupConfigs, setRestoreBackupConfigs] = useState(initConfigs) - const [done, setDone] = useState(false) - const lastIndex = items.length - 1 - - useEffect(() => { - if (currentTab === lastIndex && done) { - onOk(restoreBackupConfigs) - } - }, [restoreBackupConfigs]) function handleOk() { - validateFields((errors) => { - if (errors) { - return - } + onOk(restoreBackupConfigs) + } + + const handleFieldChange = () => { + setRestoreBackupConfigs(prev => { + const newConfigs = [...prev] const data = { ...getFieldsValue(), - name: getFieldValue('name').trimLeftAndRight(), + name: getFieldValue('name')?.trimLeftAndRight() || '', fromBackup: items[currentTab]?.fromBackup || '', } - setRestoreBackupConfigs(prev => { - const newConfigs = [...prev] - newConfigs.splice(currentTab, 1, data) - return newConfigs + newConfigs.splice(currentTab, 1, data) + return newConfigs + }) + } + + const handleApplyAll = () => { + // only apply below configs to other configs + const currentConfig = { + numberOfReplicas: getFieldValue('numberOfReplicas'), + dataEngine: getFieldValue('dataEngine'), + accessMode: getFieldValue('accessMode'), + encrypted: getFieldValue('encrypted') || false, + restoreVolumeRecurringJob: getFieldValue('restoreVolumeRecurringJob'), + nodeSelector: getFieldValue('nodeSelector'), + diskSelector: getFieldValue('diskSelector'), + } + setRestoreBackupConfigs(prev => { + const newConfigs = [...prev] + newConfigs.forEach((config, index) => { + if (index !== currentTab) { + newConfigs.splice(index, 1, { ...config, ...currentConfig }) + } }) - if (currentTab !== lastIndex) { - const nextIndex = currentTab + 1 - setCurrentTab(nextIndex) - const nextConfig = restoreBackupConfigs[nextIndex] - setFieldsValue({ - name: nextConfig.name, - numberOfReplicas: nextConfig.numberOfReplicas, - dataEngine: nextConfig.dataEngine, - accessMode: nextConfig.accessMode, - latestBackup: nextConfig.latestBackup, - backingImage: nextConfig.backingImage, - restoreVolumeRecurringJob: nextConfig.restoreVolumeRecurringJob, - nodeSelector: nextConfig.nodeSelector, - diskSelector: nextConfig.diskSelector, - }) - } else if (currentTab === lastIndex) { - setDone(true) - } + return newConfigs }) + message.success(`Successfully apply ${getFieldValue('name')} config to all other restore volumes`, 5) } - const handleFieldChange = () => { + const handleEncryptedCheck = (e) => { + const isChecked = e.target.checked setRestoreBackupConfigs(prev => { const newConfigs = [...prev] const data = { ...getFieldsValue(), + encrypted: isChecked, name: getFieldValue('name')?.trimLeftAndRight() || '', fromBackup: items[currentTab]?.fromBackup || '', } @@ -107,23 +108,54 @@ const modal = ({ }) } - const handlePrevious = () => { - const prevIdx = currentTab - 1 - setCurrentTab(prevIdx) - const prevConfig = restoreBackupConfigs[prevIdx] - setFieldsValue({ - name: prevConfig.name, - numberOfReplicas: prevConfig.numberOfReplicas, - dataEngine: prevConfig.dataEngine, - accessMode: prevConfig.accessMode, - latestBackup: prevConfig.latestBackup, - backingImage: prevConfig.backingImage, - restoreVolumeRecurringJob: prevConfig.restoreVolumeRecurringJob, - nodeSelector: prevConfig.nodeSelector, - diskSelector: prevConfig.diskSelector, + const allFieldsError = { ...getFieldsError() } + const hasFieldsError = Object.values(allFieldsError).some(fieldError => fieldError !== undefined) || false + + const handleTabClick = (key) => { + if (hasFieldsError) { + message.error('Please fill in all required fields before switching to another restore volume tab', 5) + return + } + validateFields((errors) => { + if (errors) { + return + } + const data = { + ...getFieldsValue(), + name: getFieldValue('name').trimLeftAndRight(), + fromBackup: items[currentTab]?.fromBackup || '', + } + // replace this config with the current form data when click tab + setRestoreBackupConfigs(prev => { + const newConfigs = [...prev] + newConfigs.splice(currentTab, 1, data) + return newConfigs + }) }) + + const newIndex = items.findIndex(i => i.backupName === key) + + if (newIndex !== -1) { + setCurrentTab(newIndex) + const nextConfig = restoreBackupConfigs[newIndex] + setFieldsValue({ + name: nextConfig.name, + numberOfReplicas: nextConfig.numberOfReplicas, + dataEngine: nextConfig.dataEngine, + accessMode: nextConfig.accessMode, + latestBackup: nextConfig.latestBackup, + backingImage: nextConfig.backingImage, + encrypted: nextConfig.encrypted, + restoreVolumeRecurringJob: nextConfig.restoreVolumeRecurringJob, + nodeSelector: nextConfig.nodeSelector, + diskSelector: nextConfig.diskSelector, + }) + } } + const showWarning = backupVolumes?.some((backupVolume) => backupVolume.name === getFieldsValue().name) + const alertMessage = `The restore volume name (${getFieldsValue().name}) is the same as that of this backup volume, by which the backups created after restoration reside in this backup volume as well.` + const tooltipTitle = `Apply this ${getFieldValue('name')} config to all the other restore volumes, this action will overwrite your previous filled in configs` const modalOpts = { title: 'Restore Multiple Latest Backups', visible, @@ -134,24 +166,21 @@ const modal = ({ , - , - + , + , ], } - const showWarning = backupVolumes?.some((backupVolume) => backupVolume.name === getFieldsValue().name) - const message = `The restore volume name (${getFieldsValue().name}) is the same as that of this backup volume, by which the backups created after restoration reside in this backup volume as well.` - const item = restoreBackupConfigs[currentTab] || {} const activeKey = item.latestBackup return ( - + {items.map(i => )} @@ -161,11 +190,12 @@ const modal = ({ visible={showWarning} content={
- +
}> {getFieldDecorator('name', { + initialValue: item.name, rules: [ { required: true, @@ -231,6 +261,12 @@ const modal = ({ { backingImages.map(backingImage => ) } )} + + {getFieldDecorator('encrypted', { + valuePropName: 'checked', + initialValue: item.encrypted || false, + })()} + {getFieldDecorator('restoreVolumeRecurringJob', { initialValue: 'ignored', diff --git a/src/routes/backup/CreateStandbyVolumeModal.js b/src/routes/backup/CreateStandbyVolumeModal.js index d0c8f461..a0dae81c 100644 --- a/src/routes/backup/CreateStandbyVolumeModal.js +++ b/src/routes/backup/CreateStandbyVolumeModal.js @@ -1,6 +1,6 @@ import React from 'react' import PropTypes from 'prop-types' -import { Form, Input, InputNumber, Select, Spin, Alert, Popover } from 'antd' +import { Form, Input, InputNumber, Select, Spin, Checkbox, Alert, Popover } from 'antd' import { ModalBlur } from '../../components' import { formatMib } from '../../utils/formatter' @@ -167,6 +167,12 @@ const modal = ({ { backingImages.map(backingImage => ) } )} + + {getFieldDecorator('encrypted', { + valuePropName: 'encrypted', + initialValue: false, + })()} + {getFieldDecorator('nodeSelector', { diff --git a/src/routes/backup/RestoreBackupModal.js b/src/routes/backup/RestoreBackupModal.js index c1fa8707..358de293 100644 --- a/src/routes/backup/RestoreBackupModal.js +++ b/src/routes/backup/RestoreBackupModal.js @@ -50,6 +50,7 @@ const modal = ({ onOk(data) }) } + const modalOpts = { title: `Restore Backup ${item.backupName}`, visible, @@ -141,6 +142,12 @@ const modal = ({ { backingImages.map(backingImage => ) } )} + + {getFieldDecorator('encrypted', { + valuePropName: 'encrypted', + initialValue: false, + })()} + {getFieldDecorator('restoreVolumeRecurringJob', { initialValue: 'ignored', diff --git a/src/routes/backup/index.js b/src/routes/backup/index.js index 8d416615..03c03030 100644 --- a/src/routes/backup/index.js +++ b/src/routes/backup/index.js @@ -250,7 +250,7 @@ function Backup({ backup, loading, setting, backingImage, dispatch, location }) numberOfReplicas: defaultNumberOfReplicas, size, fromBackup: lastBackupUrl, - name: `dr-${volumeName}`, + name: volumeName, backingImage: currentBackupVolume?.backingImageName || '', }, visible: createVolumeStandModalVisible,