diff --git a/pkg/harvester/l10n/en-us.yaml b/pkg/harvester/l10n/en-us.yaml index 57fc6080..dc8327c9 100644 --- a/pkg/harvester/l10n/en-us.yaml +++ b/pkg/harvester/l10n/en-us.yaml @@ -2002,6 +2002,8 @@ harvester: start: Start restart: Restart stop: Stop + deleteDialog: + warning: Deleting this migration plan will also delete the following related resources vmImport: titles: diff --git a/pkg/harvester/models/forklift.konveyor.io.plan.js b/pkg/harvester/models/forklift.konveyor.io.plan.js index d0f5233c..9c921b37 100644 --- a/pkg/harvester/models/forklift.konveyor.io.plan.js +++ b/pkg/harvester/models/forklift.konveyor.io.plan.js @@ -46,6 +46,10 @@ export default class ForkliftPlan extends HarvesterResource { } get stateDescription() { + if (this.metadata?.deletionTimestamp) { + return null; + } + const messages = []; const conditions = this.status?.conditions || []; @@ -67,7 +71,7 @@ export default class ForkliftPlan extends HarvesterResource { get stateObj() { return { error: this.planFailed || this.planCritical, - transitioning: this.isMigrating, + transitioning: this.isMigrating || this.isPendingMigrationStart || !!this.metadata?.deletionTimestamp, message: this.stateDescription, }; } @@ -78,6 +82,14 @@ export default class ForkliftPlan extends HarvesterResource { return !!migration?.started && !migration?.completed; } + // Right after creating a plan, controller status can lag briefly. + // Treat this window as in-progress so we don't flash a succeeded badge. + get isPendingMigrationStart() { + const vmCount = this.spec?.vms?.length || 0; + + return vmCount > 0 && !this.planFailed && !this.planCritical && !this.planCanceled && !this.planSucceeded && !this.isMigrating; + } + get planCanceled() { const history = this.status?.migration?.history || []; @@ -98,6 +110,10 @@ export default class ForkliftPlan extends HarvesterResource { } get stateDisplay() { + if (this.metadata?.deletionTimestamp) { + return 'Terminating'; + } + if (this.planFailed) { return this.t('harvester.addons.vmMigration.plan.states.error'); } @@ -114,10 +130,18 @@ export default class ForkliftPlan extends HarvesterResource { return this.t('harvester.addons.vmMigration.plan.states.inProgress'); } + if (this.isPendingMigrationStart) { + return this.t('harvester.addons.vmMigration.plan.states.inProgress'); + } + return this.t('harvester.addons.vmMigration.plan.states.active'); } get stateBackground() { + if (this.metadata?.deletionTimestamp) { + return 'bg-info'; + } + if (this.planFailed) { return 'bg-error'; } @@ -134,6 +158,10 @@ export default class ForkliftPlan extends HarvesterResource { return 'bg-info'; } + if (this.isPendingMigrationStart) { + return 'bg-info'; + } + return 'bg-success'; } @@ -255,15 +283,4 @@ export default class ForkliftPlan extends HarvesterResource { async deletePlan() { await this.remove(); } - - /** - * Deleting a Plan cascades via ownerReferences set at creation time. - * Kubernetes GC will automatically delete: Migration, NetworkMap, StorageMap. - * Use foreground propagation to ensure children are deleted before the parent. - */ - remove(opt = {}) { - opt.params = { ...(opt.params || {}), propagationPolicy: 'Foreground' }; - - return this._remove(opt); - } } diff --git a/pkg/harvester/pages/c/_cluster/vm-migration/vm-migration-wizard.vue b/pkg/harvester/pages/c/_cluster/vm-migration/vm-migration-wizard.vue index 8c20689c..8a2a004a 100644 --- a/pkg/harvester/pages/c/_cluster/vm-migration/vm-migration-wizard.vue +++ b/pkg/harvester/pages/c/_cluster/vm-migration/vm-migration-wizard.vue @@ -9,6 +9,7 @@ import ConfigureMappingsStep from '@pkg/harvester/components/vm-migration/Config import ReviewMigrationStep from '@pkg/harvester/components/vm-migration/ReviewMigrationStep.vue'; import { PRODUCT_NAME } from '@pkg/harvester/config/harvester'; import { currentRouter } from '@pkg/harvester/utils/router'; +import { exceptionToErrorsArray, stringify } from '@shell/utils/error'; const store = useStore(); const { t } = useI18n(store); @@ -220,7 +221,13 @@ const onFinish = async(buttonCb) => { buttonCb(true); currentRouter().push(migrationListLocation); } catch (err) { - errors.value = [err instanceof Error ? err.message : String(err)]; + errors.value = exceptionToErrorsArray(err).map((e) => { + if (typeof e === 'string') { + return e; + } + + return stringify(e); + }); buttonCb(false); } }; diff --git a/pkg/harvester/promptRemove/forklift.konveyor.io.plan.vue b/pkg/harvester/promptRemove/forklift.konveyor.io.plan.vue new file mode 100644 index 00000000..87408672 --- /dev/null +++ b/pkg/harvester/promptRemove/forklift.konveyor.io.plan.vue @@ -0,0 +1,316 @@ + + +