From 666a3626e29119b77ef7ef58a84634c37a9dcd86 Mon Sep 17 00:00:00 2001 From: Abhishek Kumar Date: Thu, 11 Sep 2025 01:25:22 +0530 Subject: [PATCH 01/11] ui: allow assigning backup offring during instance deploy Add backup offering selection to Deploy VM wizard and assign selected backup offering to the VM after successful deployment. This enables users to choose a backup offering during VM creation, and the VM is automatically associated with the selected offering post-deployment. Signed-off-by: Abhishek Kumar --- ui/public/locales/en.json | 4 ++ .../widgets/InfiniteScrollSelect.vue | 2 +- ui/src/views/compute/DeployVM.vue | 53 ++++++++++++++++++- 3 files changed, 56 insertions(+), 3 deletions(-) diff --git a/ui/public/locales/en.json b/ui/public/locales/en.json index 394de6ca6d26..a9905f4d4634 100644 --- a/ui/public/locales/en.json +++ b/ui/public/locales/en.json @@ -447,7 +447,9 @@ "label.backup.configure.schedule": "Configure Backup Schedule", "label.backup.offering.assign": "Assign Instance to backup offering", "label.backup.offering.remove": "Remove Instance from backup offering", +"label.backup.offering": "Backup Offering", "label.backup.offerings": "Backup Offerings", +"label.backup.offering.assign.failed": "Failed to assign Backup Offering", "label.backup.repository": "Backup Repository", "label.backup.restore": "Restore Instance backup", "label.backuplimit": "Backup Limits", @@ -2189,6 +2191,7 @@ "label.select.all": "Select all", "label.select.columns": "Select columns", "label.select.a.zone": "Select a Zone", +"label.select.backup.offering": "Select Backup Offering", "label.select.deployment.infrastructure": "Select deployment infrastructure", "label.select.guest.os.type": "Please select the guest OS type", "label.select.network": "Select Network", @@ -3062,6 +3065,7 @@ "message.backup.attach.restore": "Please confirm that you want to restore and attach the volume from the backup?", "message.backup.create": "Are you sure you want to create an Instance backup?", "message.backup.offering.remove": "Are you sure you want to remove Instance from backup offering and delete the backup chain?", +"message.backup.offering.select.assign.instance": "Select a backup offering to assign to the Instance. Assigning a backup offering helps protect your data by enabling automated backups.", "message.backup.restore": "Please confirm that you want to restore the Instance backup?", "message.cancel.shutdown": "Please confirm that you would like to cancel the shutdown on this Management Server. It will resume accepting any new Async Jobs.", "message.cancel.maintenance": "Please confirm that you would like to cancel the maintenance on this Management Server. It will resume accepting any new Async Jobs.", diff --git a/ui/src/components/widgets/InfiniteScrollSelect.vue b/ui/src/components/widgets/InfiniteScrollSelect.vue index c11f96864469..608eeebf1332 100644 --- a/ui/src/components/widgets/InfiniteScrollSelect.vue +++ b/ui/src/components/widgets/InfiniteScrollSelect.vue @@ -77,7 +77,7 @@ - + diff --git a/ui/src/views/compute/DeployVM.vue b/ui/src/views/compute/DeployVM.vue index 7872a405a690..ebf48a251239 100644 --- a/ui/src/views/compute/DeployVM.vue +++ b/ui/src/views/compute/DeployVM.vue @@ -540,6 +540,25 @@ + + + @@ -930,6 +949,7 @@ import SecurityGroupSelection from '@views/compute/wizard/SecurityGroupSelection import TooltipLabel from '@/components/widgets/TooltipLabel' import InstanceNicsNetworkSelectListView from '@/components/view/InstanceNicsNetworkSelectListView' import DetailsInput from '@/components/widgets/DetailsInput' +import InfiniteScrollSelect from '@/components/widgets/InfiniteScrollSelect' export default { name: 'Wizard', @@ -955,7 +975,8 @@ export default { SecurityGroupSelection, TooltipLabel, InstanceNicsNetworkSelectListView, - DetailsInput + DetailsInput, + InfiniteScrollSelect }, props: { visible: { @@ -1135,7 +1156,8 @@ export default { opts: [] }, externalDetailsEnabled: false, - selectedExtensionId: null + selectedExtensionId: null, + selectedBackupOfferingId: null } }, computed: { @@ -1515,6 +1537,17 @@ export default { }, isTemplateHypervisorExternal () { return !!this.template && this.template.hypervisor === 'External' + }, + isUserAllowedToListBackupOfferings () { + return Boolean('listBackupOfferings' in this.$store.getters.apis) + }, + listBackupOfferingApiParams () { + return { + zoneid: this.zone?.id + } + }, + backupOfferingDefaultOption () { + return { id: null, name: '', showicon: false } } }, watch: { @@ -2507,6 +2540,7 @@ export default { duration: 0 }) } + this.assigneVirtualMachineToBackupOfferingIfNeeded(vm) eventBus.emit('vm-refresh-data') }, loadingMessage: `${title} ${this.$t('label.in.progress')}`, @@ -3004,6 +3038,7 @@ export default { this.resetTemplatesList() this.resetIsosList() this.imageType = this.queryIsoId ? 'isoid' : 'templateid' + this.selectedBackupOfferingId = undefined this.fetchZoneOptions() }, onSelectPodId (value) { @@ -3395,6 +3430,20 @@ export default { return } this.form.externaldetails = undefined + }, + assigneVirtualMachineToBackupOfferingIfNeeded (vm) { + if (!this.selectedBackupOfferingId || !vm || !vm.id) { + return + } + postAPI('assignVirtualMachineToBackupOffering', { + virtualmachineid: vm.id, + backupofferingid: this.selectedBackupOfferingId + }).catch(error => { + this.$notification.error({ + message: this.$t('label.backup.offering.assign.failed'), + description: error.message || error + }) + }) } } } From 7221a961a6956b1a391a13c1b58e79e886baae70 Mon Sep 17 00:00:00 2001 From: Abhishek Kumar Date: Fri, 12 Sep 2025 18:50:27 +0530 Subject: [PATCH 02/11] changes for schedules Signed-off-by: Abhishek Kumar --- ui/public/locales/de_DE.json | 1 + ui/public/locales/el_GR.json | 1 + ui/public/locales/en.json | 8 +- ui/public/locales/es.json | 1 + ui/public/locales/ja_JP.json | 1 + ui/public/locales/ko_KR.json | 1 + ui/public/locales/pt_BR.json | 3 +- ui/public/locales/te.json | 1 + ui/public/locales/zh_CN.json | 1 + ui/src/config/section/storage.js | 2 +- ui/src/views/compute/BackupScheduleWizard.vue | 4 +- ui/src/views/compute/DeployVM.vue | 110 +++++++++---- .../views/compute/backup/BackupSchedule.vue | 10 +- ui/src/views/compute/backup/FormSchedule.vue | 17 +- .../wizard/DeployInstanceBackupSelection.vue | 153 ++++++++++++++++++ 15 files changed, 267 insertions(+), 47 deletions(-) create mode 100644 ui/src/views/compute/wizard/DeployInstanceBackupSelection.vue diff --git a/ui/public/locales/de_DE.json b/ui/public/locales/de_DE.json index fd29ac3eb54b..d99ba4157df5 100644 --- a/ui/public/locales/de_DE.json +++ b/ui/public/locales/de_DE.json @@ -259,6 +259,7 @@ "label.available": "Verfügbar", "label.back": "Zurück", "label.backup": "Backup", +"label.backups": "Backup", "label.backup.attach.restore": "Backup-Volume wiederherstellen und anhängen", "label.backup.offering.assign": "VM zum Backup-Angebot zuordnen", "label.backup.offering.remove": "VM vom Backup-Angebot entfernen", diff --git a/ui/public/locales/el_GR.json b/ui/public/locales/el_GR.json index 6b939f52029f..3c0cd6a1706e 100644 --- a/ui/public/locales/el_GR.json +++ b/ui/public/locales/el_GR.json @@ -317,6 +317,7 @@ "label.availableprocessors": "Διαθέσιμοι πυρήνες επεξεργαστή", "label.back": "Πίσω", "label.backup": "Αντίγραφα ασφαλείας", +"label.backups": "Αντίγραφα ασφαλείας", "label.backup.attach.restore": "Επαναφορά και επισύναψη τόμου αντιγράφου ασφαλείας", "label.backup.offering.assign": "Αντιστοίχιση εικονικής μηχανής με προσφορά δημιουργίας αντιγράφων ασφαλείας", "label.backup.offering.remove": "Κατάργηση εικονικής μηχανής από την προσφορά δημιουργίας αντιγράφων ασφαλείας", diff --git a/ui/public/locales/en.json b/ui/public/locales/en.json index a9905f4d4634..879f9753a454 100644 --- a/ui/public/locales/en.json +++ b/ui/public/locales/en.json @@ -252,6 +252,7 @@ "label.add.acl.rule": "Add ACL rule", "label.add.acl": "Add ACL", "label.add.affinity.group": "Add new Affinity Group", +"label.add.backup.schedule": "Add Backup Schedule", "label.add.baremetal.dhcp.device": "Add bare metal DHCP device", "label.add.bgp.peer": "Add BGP Peer", "label.add.bigswitchbcf.device": "Add BigSwitch BCF Controller", @@ -442,16 +443,17 @@ "label.availablevirtualmachinecount": "Available Instances", "label.back": "Back", "label.back.login": "Back to login", -"label.backup": "Backups", +"label.backup": "Backup", +"label.backups": "Backups", "label.backup.attach.restore": "Restore and attach backup volume", "label.backup.configure.schedule": "Configure Backup Schedule", "label.backup.offering.assign": "Assign Instance to backup offering", "label.backup.offering.remove": "Remove Instance from backup offering", -"label.backup.offering": "Backup Offering", "label.backup.offerings": "Backup Offerings", "label.backup.offering.assign.failed": "Failed to assign Backup Offering", "label.backup.repository": "Backup Repository", "label.backup.restore": "Restore Instance backup", +"label.backup.schedule.create.failed": "Failed to create Backup Schedule", "label.backuplimit": "Backup Limits", "label.backup.storage": "Backup Storage", "label.backupstoragelimit": "Backup Storage Limits (GiB)", @@ -3065,7 +3067,7 @@ "message.backup.attach.restore": "Please confirm that you want to restore and attach the volume from the backup?", "message.backup.create": "Are you sure you want to create an Instance backup?", "message.backup.offering.remove": "Are you sure you want to remove Instance from backup offering and delete the backup chain?", -"message.backup.offering.select.assign.instance": "Select a backup offering to assign to the Instance. Assigning a backup offering helps protect your data by enabling automated backups.", +"message.backup.provision.instance": "Select a backup offering to assign to the Instance. You can also add one or more backup schedules to automate backups for this Instance. Assigning a backup offering and schedules helps protect your data by enabling automated and scheduled backups.", "message.backup.restore": "Please confirm that you want to restore the Instance backup?", "message.cancel.shutdown": "Please confirm that you would like to cancel the shutdown on this Management Server. It will resume accepting any new Async Jobs.", "message.cancel.maintenance": "Please confirm that you would like to cancel the maintenance on this Management Server. It will resume accepting any new Async Jobs.", diff --git a/ui/public/locales/es.json b/ui/public/locales/es.json index ab57c9515316..b7a409adad1e 100644 --- a/ui/public/locales/es.json +++ b/ui/public/locales/es.json @@ -222,6 +222,7 @@ "label.available": "Disponible", "label.back": "Volver", "label.backup": "Respaldos", +"label.backups": "Respaldos", "label.backup.attach.restore": "Restaurar y conectar un Volumen de Respaldo", "label.backup.offering.assign": "Asignar instancia a una oferta de respaldo", "label.backup.offering.remove": "remover instancia de una oferta de respaldo", diff --git a/ui/public/locales/ja_JP.json b/ui/public/locales/ja_JP.json index 3ab374a2a069..734a0ea851de 100644 --- a/ui/public/locales/ja_JP.json +++ b/ui/public/locales/ja_JP.json @@ -485,6 +485,7 @@ "label.available.public.ips": "使用できるパブリックIPアドレス", "label.back": "戻る", "label.backup": "バックアップ", + "label.backups": "バックアップ", "label.backup.attach.restore": "復元とバックアップボリュームをアタッチ", "label.backup.offering.assign": "VMをバックアップオファリングに割り当て", "label.backup.offering.remove": "VMバックアップオファリングから削除", diff --git a/ui/public/locales/ko_KR.json b/ui/public/locales/ko_KR.json index cc9683633be7..1f20c95438da 100644 --- a/ui/public/locales/ko_KR.json +++ b/ui/public/locales/ko_KR.json @@ -269,6 +269,7 @@ "label.available": "\uc0ac\uc6a9 \uac00\ub2a5", "label.back": "\ub4a4\ub85c", "label.backup": "\ubc31\uc5c5", +"label.backups": "\ubc31\uc5c5", "label.backup.attach.restore": "\ubc31\uc5c5 \ubcfc\ub968 \ubcf5\uc6d0 \ubc0f \uc5f0\uacb0", "label.backup.offering.assign": "\uac00\uc0c1\uba38\uc2e0\uc5d0 \ubc31\uc5c5 \uc624\ud37c\ub9c1 \ud560\ub2f9", "label.backup.offering.remove": "\uac00\uc0c1\uba38\uc2e0\uc5d0 \ubc31\uc5c5 \uc624\ud37c\ub9c1 \uc81c\uac70", diff --git a/ui/public/locales/pt_BR.json b/ui/public/locales/pt_BR.json index 09b0242ef1a5..1d2d7986c8eb 100644 --- a/ui/public/locales/pt_BR.json +++ b/ui/public/locales/pt_BR.json @@ -292,7 +292,8 @@ "label.availability": "Disponibilidade", "label.available": "Dispon\u00edvel", "label.back": "Voltar", -"label.backup": "Backups", +"label.backup": "Backup", +"label.backups": "Backup", "label.backup.attach.restore": "Restaurar e anexar volume de backup", "label.backup.offering.assign": "Atribuir VM a oferta de backup", "label.backup.offering.remove": "Remover VM de oferta de backup", diff --git a/ui/public/locales/te.json b/ui/public/locales/te.json index 957aed4ee885..5f89bbf7ed74 100644 --- a/ui/public/locales/te.json +++ b/ui/public/locales/te.json @@ -419,6 +419,7 @@ "label.back": "వెనుకకు", "label.back.login": "తిరిగి లాగిన్‌కి", "label.backup": "బ్యాకప్‌లు", + "label.backups": "బ్యాకప్‌లు", "label.backup.attach.restore": "బ్యాకప్ వాల్యూమ్‌ను పునరుద్ధరించండి మరియు అటాచ్ చేయండి", "label.backup.configure.schedule": "బ్యాకప్ షెడ్యూల్‌ను కాన్ఫిగర్ చేయండి", "label.backup.offering.assign": "బ్యాకప్ సమర్పణకు ఉదాహరణను కేటాయించండి", diff --git a/ui/public/locales/zh_CN.json b/ui/public/locales/zh_CN.json index d09f960edf5d..02149360841e 100644 --- a/ui/public/locales/zh_CN.json +++ b/ui/public/locales/zh_CN.json @@ -561,6 +561,7 @@ "label.back": "\u540E\u9000", "label.backup": "\u5907\u4EFD", + "label.backups": "\u5907\u4EFD", "label.backup.attach.restore": "\u6062\u590D\u5E76\u8FDE\u63A5\u5907\u4EFD\u5377", "label.backup.offering.assign": "\u5C06\u865A\u62DF\u673A\u5206\u914D\u7ED9\u5907\u4EFD\u4EA7\u54C1", "label.backup.offering.remove": "\u4ECE\u5907\u4EFD\u4EA7\u54C1\u4E2D\u5220\u9664\u865A\u62DF\u673A", diff --git a/ui/src/config/section/storage.js b/ui/src/config/section/storage.js index 4308832bcf20..df0176e5a2db 100644 --- a/ui/src/config/section/storage.js +++ b/ui/src/config/section/storage.js @@ -415,7 +415,7 @@ export default { }, { name: 'backup', - title: 'label.backup', + title: 'label.backups', icon: 'cloud-upload-outlined', permission: ['listBackups'], params: { listvmdetails: 'true' }, diff --git a/ui/src/views/compute/BackupScheduleWizard.vue b/ui/src/views/compute/BackupScheduleWizard.vue index a37893d2bc1f..650f4915ecee 100644 --- a/ui/src/views/compute/BackupScheduleWizard.vue +++ b/ui/src/views/compute/BackupScheduleWizard.vue @@ -21,13 +21,11 @@ + :resource="resource"/> diff --git a/ui/src/views/compute/DeployVM.vue b/ui/src/views/compute/DeployVM.vue index ebf48a251239..eef3bd9f6227 100644 --- a/ui/src/views/compute/DeployVM.vue +++ b/ui/src/views/compute/DeployVM.vue @@ -541,21 +541,21 @@ @@ -949,7 +949,7 @@ import SecurityGroupSelection from '@views/compute/wizard/SecurityGroupSelection import TooltipLabel from '@/components/widgets/TooltipLabel' import InstanceNicsNetworkSelectListView from '@/components/view/InstanceNicsNetworkSelectListView' import DetailsInput from '@/components/widgets/DetailsInput' -import InfiniteScrollSelect from '@/components/widgets/InfiniteScrollSelect' +import DeployInstanceBackupSelection from '@views/compute/wizard/DeployInstanceBackupSelection' export default { name: 'Wizard', @@ -976,7 +976,7 @@ export default { TooltipLabel, InstanceNicsNetworkSelectListView, DetailsInput, - InfiniteScrollSelect + DeployInstanceBackupSelection }, props: { visible: { @@ -1157,7 +1157,8 @@ export default { }, externalDetailsEnabled: false, selectedExtensionId: null, - selectedBackupOfferingId: null + selectedBackupOffering: null, + backupSchedules: [] } }, computed: { @@ -1538,16 +1539,9 @@ export default { isTemplateHypervisorExternal () { return !!this.template && this.template.hypervisor === 'External' }, - isUserAllowedToListBackupOfferings () { - return Boolean('listBackupOfferings' in this.$store.getters.apis) - }, - listBackupOfferingApiParams () { - return { - zoneid: this.zone?.id - } - }, - backupOfferingDefaultOption () { - return { id: null, name: '', showicon: false } + isUserAllowedBackupOperations () { + return Boolean('listBackupOfferings' in this.$store.getters.apis) && + Boolean('assignVirtualMachineToBackupOffering' in this.$store.getters.apis) } }, watch: { @@ -1705,6 +1699,13 @@ export default { if (this.leaseduration < 1) { this.vm.leaseduration = undefined } + + delete this.vm.backupofferingid + delete this.vm.backupofferingname + if (this.form.backupofferingid && this.selectedBackupOffering) { + this.vm.backupofferingid = this.selectedBackupOffering.id + this.vm.backupofferingname = this.selectedBackupOffering.name + } } } }, @@ -2540,7 +2541,7 @@ export default { duration: 0 }) } - this.assigneVirtualMachineToBackupOfferingIfNeeded(vm) + this.performPostDeployBackupActions(vm) eventBus.emit('vm-refresh-data') }, loadingMessage: `${title} ${this.$t('label.in.progress')}`, @@ -3038,7 +3039,7 @@ export default { this.resetTemplatesList() this.resetIsosList() this.imageType = this.queryIsoId ? 'isoid' : 'templateid' - this.selectedBackupOfferingId = undefined + this.form.backupofferingid = undefined this.fetchZoneOptions() }, onSelectPodId (value) { @@ -3431,18 +3432,63 @@ export default { } this.form.externaldetails = undefined }, - assigneVirtualMachineToBackupOfferingIfNeeded (vm) { - if (!this.selectedBackupOfferingId || !vm || !vm.id) { + onChangeBackupOffering (val) { + if (val && val.id) { + this.selectedBackupOffering = val return } - postAPI('assignVirtualMachineToBackupOffering', { + this.selectedBackupOffering = null + this.backupSchedules = [] + }, + async performPostDeployBackupActions (vm) { + if (!this.isUserAllowedBackupOperations) { + return + } + const assigned = await this.assignVirtualMachineToBackupOfferingIfNeeded(vm) + if (assigned) { + await this.createVirtualMachineBackupSchedulesIfNeeded(vm) + } + }, + assignVirtualMachineToBackupOfferingIfNeeded (vm) { + if (!this.form.backupofferingid || !vm || !vm.id) { + return Promise.resolve(false) + } + return postAPI('assignVirtualMachineToBackupOffering', { virtualmachineid: vm.id, - backupofferingid: this.selectedBackupOfferingId - }).catch(error => { + backupofferingid: this.form.backupofferingid + }).then(() => true).catch(error => { this.$notification.error({ message: this.$t('label.backup.offering.assign.failed'), description: error.message || error }) + return false + }) + }, + createVirtualMachineBackupSchedulesIfNeeded (vm) { + if (!vm || !vm.id || !this.backupSchedules || this.backupSchedules.length === 0) { + return Promise.resolve() + } + const promises = (this.form.backupSchedules).map(item => + this.createVirtualMachineBackupSchedule(vm, item) + ) + return Promise.all(promises) + }, + createVirtualMachineBackupSchedule (vm, item) { + const params = { + virtualmachineid: vm.id, + intervaltype: item.intervaltype, + maxbackups: item.maxbackups, + timezone: item.timezone, + schedule: item.schedule + } + if (item.quiescevm) { + params.quiescevm = item.quiescevm + } + return postAPI('createBackupSchedule', params).catch(error => { + this.$notification.error({ + message: this.$t('label.backup.schedule.create.failed'), + description: error.message || error + }) }) } } diff --git a/ui/src/views/compute/backup/BackupSchedule.vue b/ui/src/views/compute/backup/BackupSchedule.vue index 5de22afd4a63..627bcd8cfbf5 100644 --- a/ui/src/views/compute/backup/BackupSchedule.vue +++ b/ui/src/views/compute/backup/BackupSchedule.vue @@ -102,9 +102,9 @@ export default { type: Object, required: true }, - resource: { - type: Object, - required: true + deleteFn: { + type: Function, + default: null } }, data () { @@ -183,6 +183,10 @@ export default { }, methods: { handleClickDelete (record) { + if (this.deleteFn) { + this.deleteFn(record) + return + } const params = {} params.id = record.id this.actionLoading = true diff --git a/ui/src/views/compute/backup/FormSchedule.vue b/ui/src/views/compute/backup/FormSchedule.vue index 643ae116916d..640fdb4d9f9b 100644 --- a/ui/src/views/compute/backup/FormSchedule.vue +++ b/ui/src/views/compute/backup/FormSchedule.vue @@ -180,13 +180,13 @@ export default { type: Boolean, default: false }, - dataSource: { - type: Object, - required: true - }, resource: { type: Object, required: true + }, + submitFn: { + type: Function, + default: null } }, data () { @@ -226,6 +226,10 @@ export default { }) }, fetchBackupOffering () { + if ('backupoffering' in this.resource) { + this.backupProvider = this.resource.backupoffering.provider + return + } getAPI('listBackupOfferings', { id: this.resource.backupofferingid }).then(json => { if (json.listbackupofferingsresponse && json.listbackupofferingsresponse.backupoffering) { const backupoffering = json.listbackupofferingsresponse.backupoffering[0] @@ -305,6 +309,11 @@ export default { params.schedule = [values.timeSelect.format('mm:HH'), values['day-of-month']].join(':') break } + if (this.submitFn) { + this.submitFn(params) + this.resetForm() + return + } this.actionLoading = true postAPI('createBackupSchedule', params).then(json => { this.$notification.success({ diff --git a/ui/src/views/compute/wizard/DeployInstanceBackupSelection.vue b/ui/src/views/compute/wizard/DeployInstanceBackupSelection.vue new file mode 100644 index 000000000000..148a44f1acf7 --- /dev/null +++ b/ui/src/views/compute/wizard/DeployInstanceBackupSelection.vue @@ -0,0 +1,153 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + + + + From 8d1128e91cc58d34c63dde22dfe90f790eb3ae39 Mon Sep 17 00:00:00 2001 From: Abhishek Kumar Date: Fri, 12 Sep 2025 21:58:06 +0530 Subject: [PATCH 03/11] fix Signed-off-by: Abhishek Kumar --- ui/src/views/compute/DeployVM.vue | 55 +++++++++++++++---- .../wizard/DeployInstanceBackupSelection.vue | 16 +++--- 2 files changed, 52 insertions(+), 19 deletions(-) diff --git a/ui/src/views/compute/DeployVM.vue b/ui/src/views/compute/DeployVM.vue index eef3bd9f6227..0e2370586966 100644 --- a/ui/src/views/compute/DeployVM.vue +++ b/ui/src/views/compute/DeployVM.vue @@ -3446,6 +3446,8 @@ export default { } const assigned = await this.assignVirtualMachineToBackupOfferingIfNeeded(vm) if (assigned) { + // wait for 200ms + await new Promise(resolve => setTimeout(resolve, 200)) await this.createVirtualMachineBackupSchedulesIfNeeded(vm) } }, @@ -3453,22 +3455,46 @@ export default { if (!this.form.backupofferingid || !vm || !vm.id) { return Promise.resolve(false) } - return postAPI('assignVirtualMachineToBackupOffering', { + const params = { virtualmachineid: vm.id, backupofferingid: this.form.backupofferingid - }).then(() => true).catch(error => { - this.$notification.error({ - message: this.$t('label.backup.offering.assign.failed'), - description: error.message || error + } + return new Promise((resolve, reject) => { + postAPI('assignVirtualMachineToBackupOffering', params).then(json => { + const jobId = json.assignvirtualmachinetobackupofferingresponse?.jobid + if (!jobId) { + resolve(false) + return + } + this.$pollJob({ + jobId, + loadingMessage: `${this.$t('label.backup.offering.assign')} ${this.$t('label.in.progress')}`, + successMethod: () => { + resolve(true) + }, + errorMethod: (result) => { + this.$notification.error({ + message: this.$t('label.backup.offering.assign.failed'), + description: result?.jobresult?.errortext || this.$t('error.fetching.async.job.result') + }) + resolve(false) + }, + catchMessage: this.$t('error.fetching.async.job.result') + }) + }).catch(error => { + this.$notification.error({ + message: this.$t('label.backup.offering.assign.failed'), + description: error.message || error + }) + resolve(false) }) - return false }) }, createVirtualMachineBackupSchedulesIfNeeded (vm) { - if (!vm || !vm.id || !this.backupSchedules || this.backupSchedules.length === 0) { + if (!vm || !vm.id || !this.backupSchedules) { return Promise.resolve() } - const promises = (this.form.backupSchedules).map(item => + const promises = (this.backupSchedules || []).map(item => this.createVirtualMachineBackupSchedule(vm, item) ) return Promise.all(promises) @@ -3484,10 +3510,15 @@ export default { if (item.quiescevm) { params.quiescevm = item.quiescevm } - return postAPI('createBackupSchedule', params).catch(error => { - this.$notification.error({ - message: this.$t('label.backup.schedule.create.failed'), - description: error.message || error + return new Promise((resolve, reject) => { + postAPI('createBackupSchedule', params).then(response => { + resolve(response) + }).catch(error => { + this.$notification.error({ + message: this.$t('label.backup.schedule.create.failed'), + description: error.message || error + }) + reject(error) }) }) } diff --git a/ui/src/views/compute/wizard/DeployInstanceBackupSelection.vue b/ui/src/views/compute/wizard/DeployInstanceBackupSelection.vue index 148a44f1acf7..73772e704578 100644 --- a/ui/src/views/compute/wizard/DeployInstanceBackupSelection.vue +++ b/ui/src/views/compute/wizard/DeployInstanceBackupSelection.vue @@ -30,13 +30,15 @@ @change-option="handleChangeBackupOffering" />
- - - {{ $t('label.add.backup.schedule') }} - + + + + {{ $t('label.add.backup.schedule') }} + + Date: Sat, 13 Sep 2025 01:42:23 +0530 Subject: [PATCH 04/11] fix Signed-off-by: Abhishek Kumar --- ui/src/views/compute/DeployVM.vue | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/ui/src/views/compute/DeployVM.vue b/ui/src/views/compute/DeployVM.vue index 0e2370586966..c7b701c5b61e 100644 --- a/ui/src/views/compute/DeployVM.vue +++ b/ui/src/views/compute/DeployVM.vue @@ -3433,12 +3433,15 @@ export default { this.form.externaldetails = undefined }, onChangeBackupOffering (val) { - if (val && val.id) { - this.selectedBackupOffering = val + if (!val || !val.id) { + this.selectedBackupOffering = null + this.backupSchedules = [] return } - this.selectedBackupOffering = null - this.backupSchedules = [] + this.selectedBackupOffering = val + if (this.backupSchedules && this.backupSchedules.length > 0 && !['nas'].includes(val.provider)) { + this.backupSchedules = this.backupSchedules.filter(item => !item.quiescevm) + } }, async performPostDeployBackupActions (vm) { if (!this.isUserAllowedBackupOperations) { @@ -3446,8 +3449,6 @@ export default { } const assigned = await this.assignVirtualMachineToBackupOfferingIfNeeded(vm) if (assigned) { - // wait for 200ms - await new Promise(resolve => setTimeout(resolve, 200)) await this.createVirtualMachineBackupSchedulesIfNeeded(vm) } }, From e73a569b3cfd89810049e4f94760449f0f32151b Mon Sep 17 00:00:00 2001 From: Abhishek Kumar Date: Mon, 15 Sep 2025 10:57:55 +0530 Subject: [PATCH 05/11] Update ui/public/locales/pt_BR.json --- ui/public/locales/pt_BR.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/public/locales/pt_BR.json b/ui/public/locales/pt_BR.json index 1d2d7986c8eb..711dd36deff1 100644 --- a/ui/public/locales/pt_BR.json +++ b/ui/public/locales/pt_BR.json @@ -293,7 +293,7 @@ "label.available": "Dispon\u00edvel", "label.back": "Voltar", "label.backup": "Backup", -"label.backups": "Backup", +"label.backups": "Backups", "label.backup.attach.restore": "Restaurar e anexar volume de backup", "label.backup.offering.assign": "Atribuir VM a oferta de backup", "label.backup.offering.remove": "Remover VM de oferta de backup", From d0d9459ae6de374512b4ef58404dd6d60755b1fe Mon Sep 17 00:00:00 2001 From: Abhishek Kumar Date: Mon, 15 Sep 2025 11:01:16 +0530 Subject: [PATCH 06/11] Update ui/src/views/compute/wizard/DeployInstanceBackupSelection.vue Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- ui/src/views/compute/wizard/DeployInstanceBackupSelection.vue | 1 - 1 file changed, 1 deletion(-) diff --git a/ui/src/views/compute/wizard/DeployInstanceBackupSelection.vue b/ui/src/views/compute/wizard/DeployInstanceBackupSelection.vue index 73772e704578..6f6c0b58d1d2 100644 --- a/ui/src/views/compute/wizard/DeployInstanceBackupSelection.vue +++ b/ui/src/views/compute/wizard/DeployInstanceBackupSelection.vue @@ -141,7 +141,6 @@ export default { schedule.id = 'SCH_' + new Date().getTime() schedule.intervaltype = schedule.intervaltype?.toUpperCase() this.$emit('add-backup-schedule', schedule) - console.log('Added schedule', schedule) this.closeModals() }, handleDeleteBackupSchedule (schedule) { From a828539b2179eadc6b1582adfc88950f468ffba8 Mon Sep 17 00:00:00 2001 From: Abhishek Kumar Date: Mon, 15 Sep 2025 11:15:16 +0530 Subject: [PATCH 07/11] address review Signed-off-by: Abhishek Kumar --- ui/src/main.js | 4 +++- ui/src/utils/plugins.js | 11 +++++++++++ ui/src/views/compute/backup/FormSchedule.vue | 7 ++++++- 3 files changed, 20 insertions(+), 2 deletions(-) diff --git a/ui/src/main.js b/ui/src/main.js index f117fb578108..7441f8010865 100644 --- a/ui/src/main.js +++ b/ui/src/main.js @@ -40,7 +40,8 @@ import { dialogUtilPlugin, cpuArchitectureUtilPlugin, imagesUtilPlugin, - extensionsUtilPlugin + extensionsUtilPlugin, + backupUtilPlugin } from './utils/plugins' import { VueAxios } from './utils/request' import directives from './utils/directives' @@ -63,6 +64,7 @@ vueApp.use(dialogUtilPlugin) vueApp.use(cpuArchitectureUtilPlugin) vueApp.use(imagesUtilPlugin) vueApp.use(extensionsUtilPlugin) +vueApp.use(backupUtilPlugin) vueApp.use(extensions) vueApp.use(directives) diff --git a/ui/src/utils/plugins.js b/ui/src/utils/plugins.js index 61456d98b124..648bc3ae0811 100644 --- a/ui/src/utils/plugins.js +++ b/ui/src/utils/plugins.js @@ -597,3 +597,14 @@ export const extensionsUtilPlugin = { } } } + +export const backupUtilPlugin = { + install (app) { + app.config.globalProperties.$isBackupProviderSupportsQuiesceVm = function (provider) { + if (!provider && typeof provider !== 'string') { + return false + } + return ['nas'].includes(provider.toLowerCase()) + } + } +} diff --git a/ui/src/views/compute/backup/FormSchedule.vue b/ui/src/views/compute/backup/FormSchedule.vue index 640fdb4d9f9b..1833449c3cc7 100644 --- a/ui/src/views/compute/backup/FormSchedule.vue +++ b/ui/src/views/compute/backup/FormSchedule.vue @@ -133,7 +133,7 @@ - +