From 9d59bf23c3f6edf56c6eff28479e400bdbb052d2 Mon Sep 17 00:00:00 2001 From: "andy.lee" Date: Wed, 19 Jun 2024 12:14:14 +0800 Subject: [PATCH] Disable create backup option if no available and writable backup target Signed-off-by: andy.lee --- src/models/backup.js | 13 +++++++++---- src/models/backupTarget.js | 2 ++ src/routes/backingImage/BackingImageActions.js | 11 +++++++---- src/routes/backingImage/BackingImageList.js | 17 ++++++++++++++++- src/routes/backingImage/index.js | 6 +++++- src/routes/backupTarget/index.js | 3 +-- src/routes/recurringJob/CreateRecurringJob.js | 11 ++++++----- src/routes/systemBackups/index.js | 4 +++- .../systemBackups/systemBackupsBulkActions.js | 7 +++++-- src/routes/volume/detail/CreateBackupModal.js | 4 ++-- src/routes/volume/detail/Snapshots.js | 12 ++++++------ src/routes/volume/detail/index.js | 7 +++---- src/utils/backupTarget.js | 6 ++++++ src/utils/dataDependency.js | 10 ++++++++-- 14 files changed, 79 insertions(+), 34 deletions(-) diff --git a/src/models/backup.js b/src/models/backup.js index 298b813df..9f3348080 100644 --- a/src/models/backup.js +++ b/src/models/backup.js @@ -23,6 +23,7 @@ export default { lastBackupUrl: '', volumeName: '', backupTargetMessage: '', + backupTargetAvailable: false, previousChecked: false, tagsLoading: true, size: '', @@ -32,7 +33,6 @@ export default { backupVolumesForBulkCreate: [], search: {}, restoreBackupModalVisible: false, - backupTargetAvailable: false, workloadDetailModalVisible: false, createVolumeStandModalVisible: false, bulkCreateVolumeStandModalVisible: false, @@ -104,9 +104,14 @@ export default { payload, }, { call, put }) { const resp = yield call(queryBackupTarget) - if (resp && resp.data && resp.data[0]) { - const backupTargetAvailable = resp.data.some(d => d.available === true) - const backupTargetMessage = backupTargetAvailable ? '' : 'No backup target available' + if (resp && resp.status === 200) { + const backupTargetAvailable = resp?.data?.some(d => d.available === true) || false + const backupTargetMessage = backupTargetAvailable ? '' : 'No backup target is available, please go to Setting -> Backup Target page to create one' + if (payload.history.location.pathname === '/backup' && !backupTargetAvailable) { + message.error(backupTargetMessage, 5) + } else { + message.destroy() + } yield put({ type: 'setBackupTargetAvailable', payload: { backupTargetAvailable, backupTargetMessage } }) } }, diff --git a/src/models/backupTarget.js b/src/models/backupTarget.js index 2a8e2c7b5..efc97e3ef 100644 --- a/src/models/backupTarget.js +++ b/src/models/backupTarget.js @@ -3,6 +3,7 @@ import { message } from 'antd' import { wsChanges, updateState } from '../utils/websocket' import queryString from 'query-string' import { enableQueryData } from '../utils/dataDependency' +import { delay } from 'dva/saga' export default { ws: null, @@ -58,6 +59,7 @@ export default { payload, }, { call, put }) { yield call(updateBackupTarget, payload) + yield delay(1000) yield put({ type: 'query' }) }, *bulkDelete({ diff --git a/src/routes/backingImage/BackingImageActions.js b/src/routes/backingImage/BackingImageActions.js index 38fa6cbfd..bfa4c1900 100644 --- a/src/routes/backingImage/BackingImageActions.js +++ b/src/routes/backingImage/BackingImageActions.js @@ -6,7 +6,7 @@ import { hasReadyBackingDisk } from '../../utils/status' const confirm = Modal.confirm -function actions({ selected, deleteBackingImage, downloadBackingImage, openBackupBackingImageModal }) { +function actions({ selected, deleteBackingImage, downloadBackingImage, openBackupBackingImageModal, backupTargetAvailable, backupTargetMessage }) { const handleMenuClick = (event, record) => { event.domEvent?.stopPropagation?.() switch (event.key) { @@ -29,11 +29,14 @@ function actions({ selected, deleteBackingImage, downloadBackingImage, openBacku } } - const disableDownloadAction = !hasReadyBackingDisk(selected) + const disableAction = !hasReadyBackingDisk(selected) + + const disabledBackupAction = !backupTargetAvailable || !hasReadyBackingDisk(selected) + const backupTargetMessageTooltip = !backupTargetAvailable ? backupTargetMessage : 'Missing disk with ready state' const availableActions = [ - { key: 'download', name: 'Download', disabled: disableDownloadAction, tooltip: disableDownloadAction ? 'Missing disk with ready state' : '' }, - { key: 'backup', name: 'Backup' }, + { key: 'download', name: 'Download', disabled: disableAction, tooltip: disableAction ? 'Missing disk with ready state' : '' }, + { key: 'backup', name: 'Backup', disabled: disabledBackupAction, tooltip: disabledBackupAction ? backupTargetMessageTooltip : '' }, { key: 'delete', name: 'Delete' }, ] diff --git a/src/routes/backingImage/BackingImageList.js b/src/routes/backingImage/BackingImageList.js index 355f17672..049b2854e 100644 --- a/src/routes/backingImage/BackingImageList.js +++ b/src/routes/backingImage/BackingImageList.js @@ -5,11 +5,24 @@ import BackingImageActions from './BackingImageActions' import { pagination } from '../../utils/page' import { formatMib } from '../../utils/formatter' -function list({ loading, dataSource, openBackupBackingImageModal, deleteBackingImage, showDiskStateMapDetail, rowSelection, downloadBackingImage, height }) { +function list({ + loading, + dataSource, + openBackupBackingImageModal, + deleteBackingImage, + showDiskStateMapDetail, + rowSelection, + downloadBackingImage, + height, + backupTargetAvailable, + backupTargetMessage, +}) { const backingImageActionsProps = { deleteBackingImage, downloadBackingImage, openBackupBackingImageModal, + backupTargetAvailable, + backupTargetMessage, } const state = (record) => { if (record.deletionTimestamp) { @@ -111,6 +124,8 @@ list.propTypes = { openBackupBackingImageModal: PropTypes.func, rowSelection: PropTypes.object, height: PropTypes.number, + backupTargetAvailable: PropTypes.bool, + backupTargetMessage: PropTypes.string, } export default list diff --git a/src/routes/backingImage/index.js b/src/routes/backingImage/index.js index 50544e6e6..0f191e216 100644 --- a/src/routes/backingImage/index.js +++ b/src/routes/backingImage/index.js @@ -88,6 +88,7 @@ class BackingImage extends React.Component { const { dispatch, loading, location, backupTarget } = this.props const { uploadFile, handleBackupBackingImageModalOpen, handleBackupBackingImageModalClose } = this const { backupBackingImageModalVisible, selectedBackingImage } = this.state + const { backupTargetAvailable, backupTargetMessage } = this.props.backup const { data: volumeData } = this.props.volume const { data, selected, createBackingImageModalVisible, createBackingImageModalKey, diskStateMapDetailModalVisible, diskStateMapDetailModalKey, diskStateMapDeleteDisabled, diskStateMapDeleteLoading, selectedDiskStateMapRows, selectedDiskStateMapRowKeys, selectedRows } = this.props.backingImage const { backingImageUploadPercent, backingImageUploadStarted } = this.props.app @@ -117,6 +118,8 @@ class BackingImage extends React.Component { const backingImageListProps = { dataSource: backingImages, height: this.state.height, + backupTargetAvailable, + backupTargetMessage, loading, deleteBackingImage(record) { dispatch({ @@ -366,10 +369,11 @@ BackingImage.propTypes = { app: PropTypes.object, backingImage: PropTypes.object, backupTarget: PropTypes.object, + backup: PropTypes.object, loading: PropTypes.bool, location: PropTypes.object, volume: PropTypes.object, dispatch: PropTypes.func, } -export default connect(({ app, volume, backupTarget, backingImage, loading }) => ({ app, volume, backupTarget, backingImage, loading: loading.models.backingImage }))(BackingImage) +export default connect(({ app, volume, backupTarget, backup, backingImage, loading }) => ({ app, volume, backupTarget, backup, backingImage, loading: loading.models.backingImage }))(BackingImage) diff --git a/src/routes/backupTarget/index.js b/src/routes/backupTarget/index.js index dc72b2331..660c97f87 100644 --- a/src/routes/backupTarget/index.js +++ b/src/routes/backupTarget/index.js @@ -70,7 +70,6 @@ class BackupTarget extends React.Component { selectedEditRow: record, editBackupTargetModalVisible: true, }) - this.handleEditModalOpen() } handleEditModalClose = () => { @@ -216,7 +215,7 @@ class BackupTarget extends React.Component { - + {createBackupTargetModalVisible && } {editBackupTargetModalVisible && } diff --git a/src/routes/recurringJob/CreateRecurringJob.js b/src/routes/recurringJob/CreateRecurringJob.js index dd2e40dc4..6b602e7e5 100644 --- a/src/routes/recurringJob/CreateRecurringJob.js +++ b/src/routes/recurringJob/CreateRecurringJob.js @@ -61,9 +61,11 @@ const modal = ({ setFieldsValue, }, }) => { + const isBackupTask = () => getFieldValue('task') === 'backup' || getFieldValue('task') === 'backup-force-create' + function handleOk() { validateFields((errors) => { - if (errors) { + if (errors || (isBackupTask() && getFieldValue('backupTargetName') === '')) { return } const data = { @@ -108,6 +110,7 @@ const modal = ({ delete data.keysForlabels } delete data.defaultGroup + onOk(data) }) } @@ -174,9 +177,6 @@ const modal = ({ return getFieldValue('task') === 'backup' || getFieldValue('task') === 'snapshot' } - const showBackupTargetDropdown = () => { - return getFieldValue('task') === 'backup' || getFieldValue('task') === 'backup-force-create' - } // init params getFieldDecorator('keys', { initialValue: isEdit && item.groups && item.groups.length > 0 ? item.groups.map((group, index) => { return { initialValue: group, index } }) : [{ index: 0, initialValue: '' }] }) @@ -315,7 +315,7 @@ const modal = ({ }
- {showBackupTargetDropdown() + {isBackupTask() && {getFieldDecorator('backupTargetName', { // eslint-disable-next-line no-nested-ternary @@ -323,6 +323,7 @@ const modal = ({ rules: [ { required: true, + message: 'Please select a backup target', }, ], })( diff --git a/src/routes/systemBackups/index.js b/src/routes/systemBackups/index.js index a4fb028e4..16f591778 100644 --- a/src/routes/systemBackups/index.js +++ b/src/routes/systemBackups/index.js @@ -104,6 +104,7 @@ class SystemBackups extends React.Component { const SystemBackupsBulkActionProps = { selectedRows: this.state.selectedSystemBackupsRows, + backupTarget: this.props.backupTarget, deleteSystemBackups() { dispatch({ type: 'systemBackups/bulkDeleteSystemBackup', @@ -264,6 +265,7 @@ SystemBackups.propTypes = { dispatch: PropTypes.func, systemBackups: PropTypes.object, location: PropTypes.object, + backupTarget: PropTypes.object, } -export default connect(({ systemBackups, loading }) => ({ systemBackups, loading: loading.models.systemBackups }))(SystemBackups) +export default connect(({ systemBackups, loading, backupTarget }) => ({ systemBackups, backupTarget, loading: loading.models.systemBackups }))(SystemBackups) diff --git a/src/routes/systemBackups/systemBackupsBulkActions.js b/src/routes/systemBackups/systemBackupsBulkActions.js index e322a0e30..72f96345f 100644 --- a/src/routes/systemBackups/systemBackupsBulkActions.js +++ b/src/routes/systemBackups/systemBackupsBulkActions.js @@ -2,10 +2,12 @@ import React from 'react' import PropTypes from 'prop-types' import { Button, Modal } from 'antd' import style from './systemBackupsBulkActions.less' +import { hasWritableBackupTargets } from '../../utils/backupTarget' const confirm = Modal.confirm -function bulkActions({ selectedRows, deleteSystemBackups, createSystemBackup }) { +function bulkActions({ selectedRows, deleteSystemBackups, createSystemBackup, backupTarget }) { + const createBtnDisable = !hasWritableBackupTargets(backupTarget) const handleClick = (action) => { switch (action) { case 'create': @@ -23,7 +25,7 @@ function bulkActions({ selectedRows, deleteSystemBackups, createSystemBackup }) } } const allActions = [ - { key: 'create', name: 'Create' }, + { key: 'create', name: 'Create', disabled: createBtnDisable }, { key: 'delete', name: 'Delete', disabled: selectedRows.length === 0 }, ] @@ -45,6 +47,7 @@ bulkActions.propTypes = { selectedRows: PropTypes.array, deleteSystemBackups: PropTypes.func, createSystemBackup: PropTypes.func, + backupTarget: PropTypes.object, } export default bulkActions diff --git a/src/routes/volume/detail/CreateBackupModal.js b/src/routes/volume/detail/CreateBackupModal.js index 5c4299584..37124e6c3 100644 --- a/src/routes/volume/detail/CreateBackupModal.js +++ b/src/routes/volume/detail/CreateBackupModal.js @@ -37,11 +37,11 @@ const modal = ({ function handleOk() { validateFields((errors) => { if (errors) return - const labels = getLabels(getFieldsValue) + const backupTargetName = getFieldValue('backupTargetName') const data = { labels, - backupTargetName: getFieldValue('backupTargetName'), + backupTargetName, } onOk(data) }) diff --git a/src/routes/volume/detail/Snapshots.js b/src/routes/volume/detail/Snapshots.js index 70f8b6102..e80531e4c 100644 --- a/src/routes/volume/detail/Snapshots.js +++ b/src/routes/volume/detail/Snapshots.js @@ -225,11 +225,13 @@ class Snapshots extends React.Component { } const upgradingEngine = () => this.props.volume.currentImage !== this.props.volume.image - const disableBackup = !this.props.volume.actions || !this.props.volume.actions.snapshotCreate || !this.props.state || this.props.volume.standby || isRestoring() || upgradingEngine() || !this.props.backupTargetAvailable + const disableBackup = !this.props.volume.actions || !this.props.volume.actions.snapshotCreate || !this.props.state || this.props.volume.standby || isRestoring() || upgradingEngine() || this.props.availBackupTargets.length === 0 + + const createSnapshotDisabled = disabledSnapshotAction(this.props.volume, this.props.state) || this.props.volume.standby || isRestoring() || upgradingEngine() const createBackupTooltipMessage = () => { - if (!this.props.backupTargetAvailable) { - return this.props.backupTargetMessage + if (this.props.availBackupTargets.length === 0) { + return 'No backup target is available and writable.' } if (this.props.volume.standby) { return 'Unable to create backup for DR volume.' @@ -285,7 +287,7 @@ class Snapshots extends React.Component {
Snapshots and Backups
-