harvester-ui-extension/pkg/harvester/models/forklift.konveyor.io.plan.js
marcelofukumoto 560643d299
feat: forklift UI (#891)
* feat: forklift poc first commit

Signed-off-by: Marcelo Fukumoto <marcelo.fukumoto@suse.com>

* feat: add migration and plan detail pages with progress tracking

Signed-off-by: Marcelo Fukumoto <marcelo.fukumoto@suse.com>

* feat(forklift): UI based on the FIGMA

Signed-off-by: Marcelo Fukumoto <marcelo.fukumoto@suse.com>

* feat(forklift): Fixed config based on new figma changes

Signed-off-by: Marcelo Fukumoto <marcelo.fukumoto@suse.com>

* feat(forklift): New changes added based on new figma

Signed-off-by: Marcelo Fukumoto <marcelo.fukumoto@suse.com>

* feat(forklift): Add the succeded state

Signed-off-by: Marcelo Fukumoto <marcelo.fukumoto@suse.com>

* Merge the new APIs

Signed-off-by: Marcelo Fukumoto <marcelo.fukumoto@suse.com>

* feat(forklift): Applied fix to select the proper data on network. Added condition to show critical

Signed-off-by: Marcelo Fukumoto <marcelo.fukumoto@suse.com>

* feat(forklift): Added icons, critical state, fix grids, action button

Signed-off-by: Marcelo Fukumoto <marcelo.fukumoto@suse.com>

* feat(forklift): Change to use the proper provider instead of a hardcoded

Signed-off-by: Marcelo Fukumoto <marcelo.fukumoto@suse.com>

* feat(forklift): Added pagination UI

Signed-off-by: Marcelo Fukumoto <marcelo.fukumoto@suse.com>

* feat(forklift): FIxed the routing for the extension

Signed-off-by: Marcelo Fukumoto <marcelo.fukumoto@suse.com>

* feat(forklift): route and router doesnt work properly on extensions

Signed-off-by: Marcelo Fukumoto <marcelo.fukumoto@suse.com>

* feat(forklift): fixed to fix the icon on extension

Signed-off-by: Marcelo Fukumoto <marcelo.fukumoto@suse.com>

* feat(forklift): Added overflow hidden to mapping target list

Signed-off-by: Marcelo Fukumoto <marcelo.fukumoto@suse.com>

* feat(forklift): Added better handling on the saving and testing

Signed-off-by: Marcelo Fukumoto <marcelo.fukumoto@suse.com>

* feat(forklift): Remove test flags

Signed-off-by: Marcelo Fukumoto <marcelo.fukumoto@suse.com>

* feat(forklift): Changed based on review

Signed-off-by: Marcelo Fukumoto <marcelo.fukumoto@suse.com>

* feat(forklift): fixed title and button

Signed-off-by: Marcelo Fukumoto <marcelo.fukumoto@suse.com>

* feat(forklift): Design changes

Signed-off-by: Marcelo Fukumoto <marcelo.fukumoto@suse.com>

* feat(forklift): Changed to Wizard

Signed-off-by: Marcelo Fukumoto <marcelo.fukumoto@suse.com>

* feat(forklift): added the provider wizard

Signed-off-by: Marcelo Fukumoto <marcelo.fukumoto@suse.com>

* feat(forklift): Fixed some UI changes

Signed-off-by: Marcelo Fukumoto <marcelo.fukumoto@suse.com>

* feat(forklift): Removed wrong configs used for testing

Signed-off-by: Marcelo Fukumoto <marcelo.fukumoto@suse.com>

* feat(forklift): update size

Signed-off-by: Marcelo Fukumoto <marcelo.fukumoto@suse.com>

* feat(forklift): small fixes

Signed-off-by: Marcelo Fukumoto <marcelo.fukumoto@suse.com>

* feat(forklift): small fixes after Copilot

Signed-off-by: Marcelo Fukumoto <marcelo.fukumoto@suse.com>

* feat(forklift): small fixes after Copilot

Signed-off-by: Marcelo Fukumoto <marcelo.fukumoto@suse.com>

* feat(forklift): reverted settings.json changes

Signed-off-by: Marcelo Fukumoto <marcelo.fukumoto@suse.com>

* feat(forklift): small fixes after Copilot

Signed-off-by: Marcelo Fukumoto <marcelo.fukumoto@suse.com>

* feat(forklift): Fix the way it is registered

Signed-off-by: Marcelo Fukumoto <marcelo.fukumoto@suse.com>

* feat(forklift): Fixed the type for VMware

Signed-off-by: Marcelo Fukumoto <marcelo.fukumoto@suse.com>

* feat(forklift): small changes before rebase

Signed-off-by: Marcelo Fukumoto <marcelo.fukumoto@suse.com>

* feat: Added new API for the vms

Signed-off-by: Marcelo Fukumoto <marcelo.fukumoto@suse.com>

* feat: Fix some labels

Signed-off-by: Marcelo Fukumoto <marcelo.fukumoto@suse.com>

* feat: Added some final fixes to structure

Signed-off-by: Marcelo Fukumoto <marcelo.fukumoto@suse.com>

* feat: added loading to provider steps

Signed-off-by: Marcelo Fukumoto <marcelo.fukumoto@suse.com>

* feat: added comments from copilot review

Signed-off-by: Marcelo Fukumoto <marcelo.fukumoto@suse.com>

---------

Signed-off-by: Marcelo Fukumoto <marcelo.fukumoto@suse.com>
2026-07-13 16:58:35 +08:00

259 lines
6.7 KiB
JavaScript

import HarvesterResource from './harvester';
import { HCI } from '../types';
import { PRODUCT_NAME } from '../config/harvester';
import { randomStr } from '@shell/utils/string';
export default class ForkliftPlan extends HarvesterResource {
get listLocation() {
return {
name: `${ PRODUCT_NAME }-c-cluster-vm-migration`,
params: {
product: this.$rootGetters['productId'],
cluster: this.$rootGetters['clusterId'],
},
};
}
get planFailed() {
const conditions = this.status?.conditions || [];
return conditions.some((c) => c.type === 'Failed' && c.status === 'True');
}
get planCritical() {
const conditions = this.status?.conditions || [];
return conditions.some((c) => c.category === 'Critical' && c.status === 'True');
}
get criticalMessages() {
const conditions = this.status?.conditions || [];
return conditions
.filter((c) => c.category === 'Critical' && c.status === 'True')
.map((c) => c.message);
}
get stateDescription() {
const messages = [];
const conditions = this.status?.conditions || [];
if (this.planFailed) {
const failedMsg = conditions.find((c) => c.type === 'Failed' && c.status === 'True')?.message;
if (failedMsg) {
messages.push(failedMsg);
}
}
if (this.planCritical) {
messages.push(...this.criticalMessages);
}
return messages.length > 0 ? messages.join(' ') : null;
}
get stateObj() {
return {
error: this.planFailed || this.planCritical,
transitioning: this.isMigrating,
message: this.stateDescription,
};
}
get isMigrating() {
const migration = this.status?.migration;
return !!migration?.started && !migration?.completed;
}
get planCanceled() {
const history = this.status?.migration?.history || [];
if (history.length === 0) {
return false;
}
const latest = history[history.length - 1];
const conditions = latest.conditions || [];
return conditions.some((c) => c.type === 'Canceled' && c.status === 'True');
}
get planSucceeded() {
const migration = this.status?.migration;
return !!migration?.completed && !this.planFailed && !this.planCanceled;
}
get stateDisplay() {
if (this.planFailed) {
return this.t('harvester.addons.vmMigration.plan.states.error');
}
if (this.planCritical) {
return this.t('harvester.addons.vmMigration.plan.states.error');
}
if (this.planCanceled) {
return this.t('harvester.addons.vmMigration.plan.states.canceled');
}
if (this.isMigrating) {
return this.t('harvester.addons.vmMigration.plan.states.inProgress');
}
return this.t('harvester.addons.vmMigration.plan.states.active');
}
get stateBackground() {
if (this.planFailed) {
return 'bg-error';
}
if (this.planCritical) {
return 'bg-error';
}
if (this.planCanceled) {
return 'bg-warning';
}
if (this.isMigrating) {
return 'bg-info';
}
return 'bg-success';
}
get isForkliftDashboard() {
const route = this.currentRouter()?.currentRoute?.value;
return route?.name?.endsWith('-vm-migration');
}
get _availableActions() {
const canStop = this.isMigrating && !this.planCanceled;
const canStart = !this.planSucceeded && (!this.isMigrating || this.planFailed || this.planCanceled || this.planCritical);
const out = [];
if (canStart) {
out.push({
action: 'startMigration',
enabled: true,
icon: 'icon icon-play',
label: this.isForkliftDashboard ? this.t('harvester.addons.vmMigration.plan.actions.restart') : this.t('harvester.addons.vmMigration.plan.actions.start'),
});
}
if (canStop) {
out.push({
action: 'stopMigration',
enabled: true,
icon: 'icon icon-pause',
label: this.t('harvester.addons.vmMigration.plan.actions.stop'),
});
}
if (this.isForkliftDashboard) {
out.push({
action: 'promptRemove',
altAction: 'remove',
label: this.t('action.remove'),
icon: 'icon icon-trash',
bulkable: true,
enabled: this.canDelete,
bulkAction: 'promptRemove',
weight: -10,
});
return out;
} else {
out.push(...super._availableActions);
return out;
}
}
async stopMigration() {
const history = this.status?.migration?.history || [];
if (history.length === 0) {
return;
}
const latest = history[history.length - 1];
const migrationName = latest.migration?.name;
if (migrationName) {
const selfUrl = this.linkFor('self');
const url = selfUrl.replace('forklift.konveyor.io.plans', 'forklift.konveyor.io.migrations').replace(`/${ this.metadata.name }`, `/${ migrationName }`);
await this.$dispatch('request', { url, method: 'DELETE' });
}
}
async startMigration() {
const namespace = this.metadata.namespace;
const history = this.status?.migration?.history || [];
// Delete previous migrations before starting a new one
for (const entry of history) {
const name = entry.migration?.name;
if (name) {
const selfUrl = this.linkFor('self');
const url = selfUrl.replace('forklift.konveyor.io.plans', 'forklift.konveyor.io.migrations').replace(`/${ this.metadata.name }`, `/${ name }`);
try {
await this.$dispatch('request', { url, method: 'DELETE' });
} catch (e) {
// Migration may already be gone — ignore 404s
}
}
}
const migration = await this.$dispatch('create', {
type: HCI.FORKLIFT_MIGRATION,
metadata: {
name: `${ this.metadata.name }-migration-${ randomStr(5).toLowerCase() }`,
namespace,
ownerReferences: [
{
apiVersion: 'forklift.konveyor.io/v1beta1',
kind: 'Plan',
name: this.metadata.name,
uid: this.metadata.uid,
blockOwnerDeletion: true,
},
],
},
spec: {
plan: {
apiVersion: 'forklift.konveyor.io/v1beta1',
kind: 'Plan',
name: this.metadata.name,
namespace,
},
},
});
await migration.save();
}
async deletePlan() {
await this.remove();
}
/**
* Deleting a Plan cascades via ownerReferences set at creation time.
* Kubernetes GC will automatically delete: Migration, NetworkMap, StorageMap, Provider (→ Secret).
* Use foreground propagation to ensure children are deleted before the parent.
*/
remove(opt = {}) {
opt.params = { ...(opt.params || {}), propagationPolicy: 'Foreground' };
return this._remove(opt);
}
}