From 032700293ca6654204d33e9d794fe61b8a4f3341 Mon Sep 17 00:00:00 2001 From: Andy Lee Date: Mon, 4 May 2026 16:33:59 +0800 Subject: [PATCH] perf: improve vm list page performance (#835) * fix: remove unneeded persistentvolumeclaim type label translation key Signed-off-by: Andy Lee * refactor: improve lockIconTooltipMessage call twice Signed-off-by: Andy Lee * refactor: avoid watch allVMs Signed-off-by: Andy Lee * perf: improve the some functions with pre-created map Signed-off-by: Andy Lee * perf: improve the vm list page Signed-off-by: Andy Lee * refactor: AI comment Signed-off-by: Andy Lee * refactor: based on feedback Signed-off-by: Andy Lee --------- Signed-off-by: Andy Lee --- pkg/harvester/l10n/en-us.yaml | 2 + .../list/kubevirt.io.virtualmachine.vue | 69 +++++------ .../models/kubevirt.io.virtualmachine.js | 116 +++++++----------- 3 files changed, 79 insertions(+), 108 deletions(-) diff --git a/pkg/harvester/l10n/en-us.yaml b/pkg/harvester/l10n/en-us.yaml index f0537908..dbbb4823 100644 --- a/pkg/harvester/l10n/en-us.yaml +++ b/pkg/harvester/l10n/en-us.yaml @@ -2165,11 +2165,13 @@ typeLabel: one { PCI Device } other { PCI Devices } } + persistentvolumeclaim: |- {count, plural, one { Volume } other { Volumes } } + network.harvesterhci.io.clusternetwork: |- {count, plural, one { Cluster Network } diff --git a/pkg/harvester/list/kubevirt.io.virtualmachine.vue b/pkg/harvester/list/kubevirt.io.virtualmachine.vue index 989ac640..4b859023 100644 --- a/pkg/harvester/list/kubevirt.io.virtualmachine.vue +++ b/pkg/harvester/list/kubevirt.io.virtualmachine.vue @@ -12,6 +12,11 @@ import { HCI } from '../types'; import HarvesterVmState from '../formatters/HarvesterVmState'; import ConsoleBar from '../components/VMConsoleBar'; +const ENCRYPTED_VOLUME_TOOLTIP_KEYS = { + all: 'harvester.virtualMachine.volume.lockTooltip.all', + partial: 'harvester.virtualMachine.volume.lockTooltip.partial', +}; + export const VM_HEADERS = [ STATE, { @@ -163,6 +168,12 @@ export default { */ hasBackUpRestoreInProgress() { return !!this.rows.find((r) => r.restoreResource && !r.restoreResource.fromSnapshot && !r.restoreResource.isComplete); + }, + + vmRestartRequiredNames() { + return this.allVMs + .filter((vm) => vm.isRestartRequired) + .map((vm) => vm.metadata.name); } }, @@ -181,53 +192,35 @@ export default { }, watch: { - allVMs: { - handler(neu) { - const vmNames = []; + vmRestartRequiredNames(vmNames) { + const count = vmNames.length; - neu.forEach((vm) => { - if (vm.isRestartRequired) { - vmNames.push(vm.metadata.name); - } - }); - const count = vmNames.length; + if (count === 0 && this.restartNotificationDisplayed) { + this.restartNotificationDisplayed = false; - if ( count === 0 && this.restartNotificationDisplayed) { - this.restartNotificationDisplayed = false; + return; + } - return; + if (count > 0) { + // clear old notification before showing new one + if (this.restartNotificationDisplayed) { + this.$store.dispatch('growl/clear'); } - if (count > 0) { - // clear old notification before showing new one - if (this.restartNotificationDisplayed) { - this.$store.dispatch('growl/clear'); - } - } - - if (count > 0 && vmNames.length > 0) { - this.$store.dispatch('growl/warning', { - title: this.t('harvester.notification.restartRequired.title', { count }), - message: this.t('harvester.notification.restartRequired.message', { vmNames: vmNames.join(', ') }), - timeout: 10000, - }, { root: true }); - this.restartNotificationDisplayed = true; - } - }, - deep: true, + this.$store.dispatch('growl/warning', { + title: this.t('harvester.notification.restartRequired.title', { count }), + message: this.t('harvester.notification.restartRequired.message', { vmNames: vmNames.join(', ') }), + timeout: 10000, + }, { root: true }); + this.restartNotificationDisplayed = true; + } } }, methods: { lockIconTooltipMessage(row) { - const message = ''; + const key = ENCRYPTED_VOLUME_TOOLTIP_KEYS[row.encryptedVolumeType]; - if (row.encryptedVolumeType === 'all') { - return this.t('harvester.virtualMachine.volume.lockTooltip.all'); - } else if (row.encryptedVolumeType === 'partial') { - return this.t('harvester.virtualMachine.volume.lockTooltip.partial'); - } - - return message; + return key ? this.t(key) : ''; } } }; @@ -267,7 +260,7 @@ export default { > {{ scope.row.metadata.name }} uniqueNames.has(pvc.metadata?.name)); +} + const IgnoreMessages = ['pod has unbound immediate PersistentVolumeClaims']; export default class VirtVm extends HarvesterResource { @@ -660,16 +696,13 @@ export default class VirtVm extends HarvesterResource { get podResource() { const inStore = this.productInStore; - const vmiResource = this.$rootGetters[`${ inStore }/byId`](HCI.VMI, this.id); - const podList = this.$rootGetters[`${ inStore }/all`](POD); - return podList.find((P) => { - return ( - vmiResource?.metadata?.name && - vmiResource?.metadata?.name === P.metadata?.ownerReferences?.[0].name - ); - }); + if (!vmiResource?.metadata?.name) { + return undefined; + } + + return getPodByOwnerName(this.$rootGetters, inStore, vmiResource.metadata.name); } get isPaused() { @@ -710,17 +743,13 @@ export default class VirtVm extends HarvesterResource { get vmi() { const inStore = this.productInStore; - const vmis = this.$rootGetters[`${ inStore }/all`](HCI.VMI); - - return vmis.find((VMI) => VMI.id === this.id); + return this.$rootGetters[`${ inStore }/byId`](HCI.VMI, this.id); } get volumes() { - const pvcs = this.$rootGetters[`${ this.productInStore }/all`](PVC); - const volumeClaimNames = this.spec.template.spec.volumes?.map((v) => v.persistentVolumeClaim?.claimName).filter((v) => !!v) || []; - return pvcs.filter((pvc) => volumeClaimNames.includes(pvc.metadata.name)); + return getPvcsByNames(this.$rootGetters, this.productInStore, volumeClaimNames); } get lvmVolumes() { @@ -753,17 +782,6 @@ export default class VirtVm extends HarvesterResource { return { status: 'VMI error', detailedMessage: vmiFailureCond.message }; } - if ((this.vmi || this.isVMCreated) && this.podResource) { - // const podStatus = this.podResource.getPodStatus; - // if (POD_STATUS_ALL_ERROR.includes(podStatus?.status)) { - // return { - // ...podStatus, - // status: 'LAUNCHER_POD_ERROR', - // pod: this.podResource, - // }; - // } - } - return this?.vmi?.status?.phase; } @@ -901,9 +919,7 @@ export default class VirtVm extends HarvesterResource { const inStore = this.productInStore; - const allRestore = this.$rootGetters[`${ inStore }/all`](HCI.RESTORE); - - const res = allRestore.find((O) => O.id === id); + const res = this.$rootGetters[`${ inStore }/byId`](HCI.RESTORE, id); if (res) { const allBackups = this.$rootGetters[`${ inStore }/all`](HCI.BACKUP); @@ -1073,42 +1089,6 @@ export default class VirtVm extends HarvesterResource { return out; } - get warningCount() { - return this.resourcesStatus.warningCount; - } - - get errorCount() { - return this.resourcesStatus.errorCount; - } - - get resourcesStatus() { - const inStore = this.productInStore; - const vmList = this.$rootGetters[`${ inStore }/all`](HCI.VM); - let warningCount = 0; - let errorCount = 0; - - vmList.forEach((vm) => { - const status = vm.actualState; - - if (status === VM_ERROR) { - errorCount += 1; - } else if ( - status === 'Stopping' || - status === 'Waiting' || - status === 'Pending' || - status === 'Starting' || - status === 'Terminating' - ) { - warningCount += 1; - } - }); - - return { - warningCount, - errorCount - }; - } - get volumeClaimTemplates() { return parseVolumeClaimTemplates(this); } @@ -1126,7 +1106,6 @@ export default class VirtVm extends HarvesterResource { get rootImageId() { let imageId = ''; const inStore = this.productInStore; - const pvcs = this.$rootGetters[`${ inStore }/all`](PVC) || []; const volumes = this.spec.template.spec.volumes || []; @@ -1136,9 +1115,7 @@ export default class VirtVm extends HarvesterResource { }); if (!isNoExistingVolume) { - const existingVolume = pvcs.find( - (P) => P.id === `${ this.metadata.namespace }/${ firstVolumeName }` - ); + const existingVolume = this.$rootGetters[`${ inStore }/byId`](PVC, `${ this.metadata.namespace }/${ firstVolumeName }`); if (existingVolume) { return existingVolume?.metadata?.annotations?.[ @@ -1316,8 +1293,7 @@ export default class VirtVm extends HarvesterResource { } get isBackupTargetUnavailable() { - const allSettings = this.$rootGetters['harvester/all'](HCI.SETTING) || []; - const backupTargetSetting = allSettings.find( (O) => O.id === 'backup-target'); + const backupTargetSetting = this.$rootGetters['harvester/byId'](HCI.SETTING, 'backup-target'); return isBackupTargetSettingUnavailable(backupTargetSetting); }