feat: add warning message when disabling a device that have not been detached in the backend (#675)

* feat: add wanring message when disabling a device that haven not been detached in the backend

Signed-off-by: Jack Yu <jack.yu@suse.com>

* fix: remove unused en-us key

Signed-off-by: Jack Yu <jack.yu@suse.com>

---------

Signed-off-by: Jack Yu <jack.yu@suse.com>
This commit is contained in:
Jack Yu 2026-01-26 14:07:28 +08:00 committed by GitHub
parent 0647600e88
commit 0781bde188
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 109 additions and 1 deletions

View File

@ -54,6 +54,11 @@ export default {
const vmDevices = this.value?.domain?.devices?.hostDevices || []; const vmDevices = this.value?.domain?.devices?.hostDevices || [];
const otherDevices = this.otherDevices(vmDevices).map(({ name }) => name); const otherDevices = this.otherDevices(vmDevices).map(({ name }) => name);
const vmDeviceNames = vmDevices.map(({ name }) => name);
this.pciDevices.forEach((row) => {
row.allowDisable = !vmDeviceNames.includes(row.metadata.name);
});
vmDevices.forEach(({ name, deviceName }) => { vmDevices.forEach(({ name, deviceName }) => {
const checkName = (deviceName || '').split('/')?.[1]; const checkName = (deviceName || '').split('/')?.[1];

View File

@ -48,6 +48,13 @@ export default {
this[key] = res[key]; this[key] = res[key];
} }
const vmDevices = this.value?.domain?.devices?.hostDevices || [];
const vmDeviceNames = vmDevices.map(({ name }) => name);
this.devices.forEach((row) => {
row.allowDisable = !vmDeviceNames.includes(row.metadata.name);
});
this.selectedDevices = (this.value?.domain?.devices?.hostDevices || []) this.selectedDevices = (this.value?.domain?.devices?.hostDevices || [])
.map(({ name }) => name) .map(({ name }) => name)
.filter((name) => this.enabledDevices.find((device) => device?.metadata?.name === name)); .filter((name) => this.enabledDevices.find((device) => device?.metadata?.name === name));

View File

@ -46,6 +46,13 @@ export default {
this[key] = res[key]; this[key] = res[key];
} }
const vmDevices = this.value?.domain?.devices?.gpus || [];
const vmDeviceNames = vmDevices.map(({ name }) => name);
this.devices.forEach((row) => {
row.allowDisable = !vmDeviceNames.includes(row.metadata.name);
});
const vGpus = this.vm.isOff ? [ const vGpus = this.vm.isOff ? [
...(this.value?.domain?.devices?.gpus || []).map(({ name }) => name), ...(this.value?.domain?.devices?.gpus || []).map(({ name }) => name),
] : [ ] : [

View File

@ -369,6 +369,9 @@ harvester:
claimError: Error enabling passthrough on {name} claimError: Error enabling passthrough on {name}
unclaimError: Error disabling passthrough on {name} unclaimError: Error disabling passthrough on {name}
cantUnclaim: You cannot disable passthrough on a device claimed by another user. cantUnclaim: You cannot disable passthrough on a device claimed by another user.
detachWarning:
title: Cannot Disable Passthrough
message: Please detach the device from the VM and save it first before disabling passthrough.
enableGroup: Enable Group enableGroup: Enable Group
disableGroup: Disable Group disableGroup: Disable Group
labelRequired: "This rule should not be manually altered: it ensures that the PCI devices selected for this virtual machine are available on the virtual machine's host." labelRequired: "This rule should not be manually altered: it ensures that the PCI devices selected for this virtual machine are available on the virtual machine's host."
@ -1821,6 +1824,9 @@ harvester:
vgpu: vgpu:
label: vGPU Devices label: vGPU Devices
noPermission: Please contact system administrator to add Harvester add-ons first. noPermission: Please contact system administrator to add Harvester add-ons first.
detachWarning:
title: Cannot Disable vGPU
message: Please detach the device from the VM and save it first before disabling this vGPU device.
goSetting: goSetting:
prefix: The nvidia-driver-toolkit add-on is not enabled, click prefix: The nvidia-driver-toolkit add-on is not enabled, click
middle: here middle: here
@ -1855,6 +1861,9 @@ harvester:
claimError: Error enabling passthrough on {name} claimError: Error enabling passthrough on {name}
unclaimError: Error disabling passthrough on {name} unclaimError: Error disabling passthrough on {name}
cantUnclaim: You cannot disable passthrough on a device claimed by another user. cantUnclaim: You cannot disable passthrough on a device claimed by another user.
detachWarning:
title: Cannot Disable Passthrough
message: Please detach the device from the VM and save it first before disabling passthrough.
enablePassthroughWarning: 'Please re-enable the USB device if the device path changes in the following situations:<br/>&nbsp1) Re-plugging the USB device.<br/>&nbsp2) Rebooting the node.<br/><br/>An incorrect device path may cause passthrough to fail.' enablePassthroughWarning: 'Please re-enable the USB device if the device path changes in the following situations:<br/>&nbsp1) Re-plugging the USB device.<br/>&nbsp2) Rebooting the node.<br/><br/>An incorrect device path may cause passthrough to fail.'
harvesterVlanConfigMigrateDialog: harvesterVlanConfigMigrateDialog:

View File

@ -64,6 +64,10 @@ export default {
const inStore = this.$store.getters['currentProduct'].inStore; const inStore = this.$store.getters['currentProduct'].inStore;
const rows = this.$store.getters[`${ inStore }/all`](HCI.PCI_DEVICE); const rows = this.$store.getters[`${ inStore }/all`](HCI.PCI_DEVICE);
rows.forEach((row) => {
row.allowDisable = true;
});
return rows; return rows;
} }
}, },

View File

@ -54,7 +54,13 @@ export default {
devices() { devices() {
const inStore = this.$store.getters['currentProduct'].inStore; const inStore = this.$store.getters['currentProduct'].inStore;
return this.$store.getters[`${ inStore }/all`](HCI.USB_DEVICE) || []; const data = this.$store.getters[`${ inStore }/all`](HCI.USB_DEVICE) || [];
data.forEach((row) => {
row.allowDisable = true;
});
return data;
} }
}, },

View File

@ -65,6 +65,10 @@ export default {
const vGpuDevices = this.$store.getters[`${ inStore }/all`](HCI.VGPU_DEVICE) || []; const vGpuDevices = this.$store.getters[`${ inStore }/all`](HCI.VGPU_DEVICE) || [];
const srioVGpuDevices = this.$store.getters[`${ inStore }/all`](HCI.SR_IOVGPU_DEVICE) || []; const srioVGpuDevices = this.$store.getters[`${ inStore }/all`](HCI.SR_IOVGPU_DEVICE) || [];
vGpuDevices.forEach((row) => {
row.allowDisable = true;
});
if (this.hasSRIOVGPUSchema) { if (this.hasSRIOVGPUSchema) {
return vGpuDevices.filter((device) => !!srioVGpuDevices.find((s) => s.isEnabled && s.spec?.nodeName === device.spec?.nodeName)); return vGpuDevices.filter((device) => !!srioVGpuDevices.find((s) => s.isEnabled && s.spec?.nodeName === device.spec?.nodeName));
} }

View File

@ -144,6 +144,12 @@ export default class PCIDevice extends SteveModel {
// 'disable' passthrough deletes claim // 'disable' passthrough deletes claim
// backend should return error if device is in use // backend should return error if device is in use
async disablePassthrough() { async disablePassthrough() {
if (!this.allowDisable) {
this.showDetachWarning();
return;
}
try { try {
if (!this.claimedByMe) { if (!this.claimedByMe) {
throw new Error(this.$rootGetters['i18n/t']('harvester.pci.cantUnclaim', { name: escapeHtml(this.metadata.name) })); throw new Error(this.$rootGetters['i18n/t']('harvester.pci.cantUnclaim', { name: escapeHtml(this.metadata.name) }));
@ -169,4 +175,20 @@ export default class PCIDevice extends SteveModel {
get groupByDevice() { get groupByDevice() {
return this.status?.description; return this.status?.description;
} }
showDetachWarning() {
this.$dispatch('growl/warning', {
title: this.$rootGetters['i18n/t']('harvester.pci.detachWarning.title'),
message: this.$rootGetters['i18n/t']('harvester.pci.detachWarning.message'),
timeout: 5000
}, { root: true });
}
get allowDisable() {
return this._allowDisable;
}
set allowDisable(value) {
this._allowDisable = value;
}
} }

View File

@ -133,6 +133,12 @@ export default class USBDevice extends SteveModel {
// 'disable' passthrough deletes claim // 'disable' passthrough deletes claim
// backend should return error if device is in use // backend should return error if device is in use
async disablePassthrough() { async disablePassthrough() {
if (!this.allowDisable) {
this.showDetachWarning();
return;
}
try { try {
if (!this.claimedByMe) { if (!this.claimedByMe) {
throw new Error(this.$rootGetters['i18n/t']('harvester.usb.cantUnclaim', { name: escapeHtml(this.metadata.name) })); throw new Error(this.$rootGetters['i18n/t']('harvester.usb.cantUnclaim', { name: escapeHtml(this.metadata.name) }));
@ -158,4 +164,20 @@ export default class USBDevice extends SteveModel {
get groupByDevice() { get groupByDevice() {
return this.status?.description; return this.status?.description;
} }
showDetachWarning() {
this.$dispatch('growl/warning', {
title: this.$rootGetters['i18n/t']('harvester.usb.detachWarning.title'),
message: this.$rootGetters['i18n/t']('harvester.usb.detachWarning.message'),
timeout: 5000
}, { root: true });
}
get allowDisable() {
return this._allowDisable;
}
set allowDisable(value) {
this._allowDisable = value;
}
} }

View File

@ -100,6 +100,12 @@ export default class VGpuDevice extends SteveModel {
} }
async disableVGpu() { async disableVGpu() {
if (!this.allowDisable) {
this.showDetachWarning();
return;
}
const { vGPUTypeName, enabled } = this.spec; const { vGPUTypeName, enabled } = this.spec;
try { try {
@ -126,4 +132,20 @@ export default class VGpuDevice extends SteveModel {
get vGpuAvailableTypes() { get vGpuAvailableTypes() {
return this.status?.availableTypes ? Object.keys(this.status.availableTypes) : []; return this.status?.availableTypes ? Object.keys(this.status.availableTypes) : [];
} }
showDetachWarning() {
this.$dispatch('growl/warning', {
title: this.$rootGetters['i18n/t']('harvester.vgpu.detachWarning.title'),
message: this.$rootGetters['i18n/t']('harvester.vgpu.detachWarning.message'),
timeout: 5000
}, { root: true });
}
get allowDisable() {
return this._allowDisable;
}
set allowDisable(value) {
this._allowDisable = value;
}
} }