fix: hide VM take backup action if backup target is not available (#512)

* fix: hide VM take backup action if backup target is not available

Signed-off-by: Andy Lee <andy.lee@suse.com>

* refactor: use extracted func

Signed-off-by: Andy Lee <andy.lee@suse.com>

---------

Signed-off-by: Andy Lee <andy.lee@suse.com>
This commit is contained in:
Andy Lee 2025-09-09 14:36:23 +08:00 committed by GitHub
parent 4e8cb31e9d
commit 4aabf0b7a3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 40 additions and 36 deletions

View File

@ -15,6 +15,7 @@ import { HCI } from '../types';
import { sortBy } from '@shell/utils/sort'; import { sortBy } from '@shell/utils/sort';
import { BACKUP_TYPE } from '../config/types'; import { BACKUP_TYPE } from '../config/types';
import { _EDIT, _CREATE } from '@shell/config/query-params'; import { _EDIT, _CREATE } from '@shell/config/query-params';
import { isBackupTargetSettingEmpty, isBackupTargetSettingUnavailable } from '../utils/setting';
export default { export default {
name: 'CreateVMSchedule', name: 'CreateVMSchedule',
@ -93,7 +94,7 @@ export default {
return this.settings.find( (O) => O.id === 'backup-target'); return this.settings.find( (O) => O.id === 'backup-target');
}, },
isEmptyValue() { isEmptyValue() {
return this.getBackupTargetValueIsEmpty(this.backupTargetResource); return isBackupTargetSettingEmpty(this.backupTargetResource);
}, },
canUpdate() { canUpdate() {
return this?.backupTargetResource?.canUpdate; return this?.backupTargetResource?.canUpdate;
@ -109,7 +110,7 @@ export default {
!!this.value.spec.maxFailure; !!this.value.spec.maxFailure;
}, },
isBackupTargetUnAvailable() { isBackupTargetUnAvailable() {
return this.value.spec.vmbackup.type === BACKUP_TYPE.BACKUP && (this.errorMessage || this.isEmptyValue) && this.canUpdate; return this.value.spec.vmbackup.type === BACKUP_TYPE.BACKUP && isBackupTargetSettingUnavailable(this.backupTargetResource);
}, },
vmOptions() { vmOptions() {
const nsVmList = this.$store.getters['harvester/all'](HCI.VM).filter((vm) => vm.metadata.namespace === this.value.metadata.namespace); const nsVmList = this.$store.getters['harvester/all'](HCI.VM).filter((vm) => vm.metadata.namespace === this.value.metadata.namespace);
@ -166,20 +167,6 @@ export default {
onTypeChange(newType) { onTypeChange(newType) {
this.value.metadata.name = `svm${ newType }-${ this.value.spec.vmbackup.source.name }`; this.value.metadata.name = `svm${ newType }-${ this.value.spec.vmbackup.source.name }`;
}, },
getBackupTargetValueIsEmpty(resource) {
let out = true;
if (resource?.value) {
try {
const valueJson = JSON.parse(resource?.value);
out = !valueJson.type;
} catch (e) {}
}
return out;
},
validateFailure(count) { validateFailure(count) {
if (this.value.spec.retain && count > this.value.spec.retain) { if (this.value.spec.retain && count > this.value.spec.retain) {
this.value.spec['maxFailure'] = this.value.spec.retain; this.value.spec['maxFailure'] = this.value.spec.retain;

View File

@ -4,7 +4,7 @@ import Loading from '@shell/components/Loading';
import MessageLink from '@shell/components/MessageLink'; import MessageLink from '@shell/components/MessageLink';
import Masthead from '@shell/components/ResourceList/Masthead'; import Masthead from '@shell/components/ResourceList/Masthead';
import ResourceTable from '@shell/components/ResourceTable'; import ResourceTable from '@shell/components/ResourceTable';
import { isBackupTargetSettingEmpty } from '../utils/setting';
import { STATE, AGE, NAME, NAMESPACE } from '@shell/config/table-headers'; import { STATE, AGE, NAME, NAMESPACE } from '@shell/config/table-headers';
import FilterVMSchedule from '../components/FilterVMSchedule'; import FilterVMSchedule from '../components/FilterVMSchedule';
import { HCI } from '../types'; import { HCI } from '../types';
@ -39,7 +39,7 @@ export default {
this.settings = hash.settings; this.settings = hash.settings;
if (this.$store.getters[`${ inStore }/schemaFor`](HCI.SETTING)) { if (this.$store.getters[`${ inStore }/schemaFor`](HCI.SETTING)) {
const backupTargetResource = hash.settings.find( (O) => O.id === 'backup-target'); const backupTargetResource = hash.settings.find( (O) => O.id === 'backup-target');
const isEmpty = this.getBackupTargetValueIsEmpty(backupTargetResource); const isEmpty = isBackupTargetSettingEmpty(backupTargetResource);
if (backupTargetResource && !isEmpty) { if (backupTargetResource && !isEmpty) {
this.testConnect(); this.testConnect();
@ -78,20 +78,6 @@ export default {
} }
}, },
getBackupTargetValueIsEmpty(resource) {
let out = true;
if (resource?.value) {
try {
const valueJson = JSON.parse(resource?.value);
out = !valueJson.type;
} catch (e) {}
}
return out;
},
getRow(row) { getRow(row) {
return row.status && row.status.source; return row.status && row.status.source;
}, },
@ -183,11 +169,9 @@ export default {
backupTargetResource() { backupTargetResource() {
return this.settings.find((O) => O.id === 'backup-target'); return this.settings.find((O) => O.id === 'backup-target');
}, },
isEmptyValue() { isEmptyValue() {
return this.getBackupTargetValueIsEmpty(this.backupTargetResource); return isBackupTargetSettingEmpty(this.backupTargetResource);
}, },
canUpdate() { canUpdate() {
return this?.backupTargetResource?.canUpdate; return this?.backupTargetResource?.canUpdate;
}, },

View File

@ -14,6 +14,7 @@ import { BACKUP_TYPE } from '../config/types';
import { HCI } from '../types'; import { HCI } from '../types';
import HarvesterResource from './harvester'; import HarvesterResource from './harvester';
import { getVmCPUMemoryValues } from '../utils/cpuMemory'; import { getVmCPUMemoryValues } from '../utils/cpuMemory';
import { isBackupTargetSettingUnavailable } from '../utils/setting';
export const OFF = 'Off'; export const OFF = 'Off';
@ -152,7 +153,7 @@ export default class VirtVm extends HarvesterResource {
}, },
{ {
action: 'backupVM', action: 'backupVM',
enabled: !!this.actions?.backup, enabled: !!this.actions?.backup && !this.isBackupTargetUnavailable,
icon: 'icon icon-backup', icon: 'icon icon-backup',
label: this.t('harvester.action.backup') label: this.t('harvester.action.backup')
}, },
@ -1239,6 +1240,13 @@ export default class VirtVm extends HarvesterResource {
return this.$rootGetters['harvester-common/getFeatureEnabled']('vmMachineTypes'); return this.$rootGetters['harvester-common/getFeatureEnabled']('vmMachineTypes');
} }
get isBackupTargetUnavailable() {
const allSettings = this.$rootGetters['harvester/all'](HCI.SETTING) || [];
const backupTargetSetting = allSettings.find( (O) => O.id === 'backup-target');
return isBackupTargetSettingUnavailable(backupTargetSetting);
}
setInstanceLabels(val) { setInstanceLabels(val) {
if ( !this.spec?.template?.metadata?.labels ) { if ( !this.spec?.template?.metadata?.labels ) {
set(this, 'spec.template.metadata.labels', {}); set(this, 'spec.template.metadata.labels', {});

View File

@ -0,0 +1,25 @@
export function isBackupTargetSettingEmpty(setting = {}) {
let isEmpty = true;
if (setting?.value) {
try {
const valueJson = JSON.parse(setting?.value);
isEmpty = !valueJson.type;
} catch (e) {
// eslint-disable-next-line no-console
console.error('Failed to parse backup target value:', e);
}
}
return isEmpty;
}
export function isBackupTargetSettingUnavailable(setting = {}) {
const errorMessage = setting.errorMessage;
const isEmptyValue = isBackupTargetSettingEmpty(setting);
const canUpdate = setting.canUpdate;
return (errorMessage || isEmptyValue) && canUpdate;
}