diff --git a/package.json b/package.json
index e04845fd..75c46864 100644
--- a/package.json
+++ b/package.json
@@ -14,9 +14,9 @@
"@vue-flow/minimap": "^1.4.0",
"cache-loader": "^4.1.0",
"color": "4.2.3",
+ "elkjs": "^0.11.0",
"ip": "2.0.1",
"node-polyfill-webpack-plugin": "^3.0.0",
- "elkjs": "^0.11.0",
"vue-draggable-next": "^2.2.1",
"yaml": "^2.5.1"
},
@@ -53,9 +53,9 @@
"agents:generate": "./scripts/generate-agent-and-persona-mds.sh"
},
"devDependencies": {
- "@commitlint/load": "^19.8.1",
"@commitlint/cli": "^19.8.1",
"@commitlint/config-conventional": "^19.8.1",
+ "@commitlint/load": "^19.8.1",
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-promise": "^7.1.0",
"husky": "^9.1.7"
diff --git a/pkg/harvester/components/MappingsCell.vue b/pkg/harvester/components/MappingsCell.vue
index 3429972a..208ec16c 100644
--- a/pkg/harvester/components/MappingsCell.vue
+++ b/pkg/harvester/components/MappingsCell.vue
@@ -18,7 +18,7 @@ defineProps({
:key="'net-' + idx"
class="mapping-entry"
>
-
+
{{ entry }}
-
+
{{ entry }}
diff --git a/pkg/harvester/models/forklift.konveyor.io.plan.js b/pkg/harvester/models/forklift.konveyor.io.plan.js
index 21473435..fd13603a 100644
--- a/pkg/harvester/models/forklift.konveyor.io.plan.js
+++ b/pkg/harvester/models/forklift.konveyor.io.plan.js
@@ -1,7 +1,18 @@
import HarvesterResource from './harvester';
import { HCI } from '../types';
+import { PRODUCT_NAME } from '../config/harvester';
export default class ForkliftPlan extends HarvesterResource {
+ get listLocation() {
+ return {
+ name: `${ PRODUCT_NAME }-c-cluster-forklift`,
+ params: {
+ product: this.$rootGetters['productId'],
+ cluster: this.$rootGetters['clusterId'],
+ },
+ };
+ }
+
get planFailed() {
const conditions = this.status?.conditions || [];
@@ -23,11 +34,22 @@ export default class ForkliftPlan extends HarvesterResource {
}
get stateDescription() {
- if (this.planCritical) {
- return this.criticalMessages.join('; ');
+ 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);
+ }
}
- return null;
+ if (this.planCritical) {
+ messages.push(...this.criticalMessages);
+ }
+
+ return messages.length > 0 ? messages.join(' ') : null;
}
get stateObj() {
@@ -103,31 +125,51 @@ export default class ForkliftPlan extends HarvesterResource {
return 'bg-success';
}
+ get isForkliftDashboard() {
+ const route = this.currentRouter()?.currentRoute?.value;
+
+ return route?.name?.endsWith('-forklift');
+ }
+
get _availableActions() {
- const canStop = this.isMigrating && !this.planCanceled;
- const canStart = !this.planSucceeded && (!this.isMigrating || this.planFailed || this.planCanceled || this.planCritical);
+ if (this.isForkliftDashboard) {
+ const canStop = this.isMigrating && !this.planCanceled;
+ const canStart = !this.planSucceeded && (!this.isMigrating || this.planFailed || this.planCanceled || this.planCritical);
+ const out = [];
- const out = super._availableActions;
+ if (canStart) {
+ out.push({
+ action: 'startMigration',
+ enabled: true,
+ icon: 'icon icon-play',
+ label: 'Start',
+ });
+ }
- if (canStop) {
- out.unshift({
- action: 'stopMigration',
- enabled: true,
- icon: 'icon icon-pause',
- label: 'Stop',
+ if (canStop) {
+ out.push({
+ action: 'stopMigration',
+ enabled: true,
+ icon: 'icon icon-pause',
+ label: 'Stop',
+ });
+ }
+
+ 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;
}
- if (canStart) {
- out.unshift({
- action: 'startMigration',
- enabled: true,
- icon: 'icon icon-play',
- label: 'Start',
- });
- }
-
- return out;
+ return super._availableActions;
}
async stopMigration() {
diff --git a/pkg/harvester/pages/c/_cluster/forklift/configure-mappings.vue b/pkg/harvester/pages/c/_cluster/forklift/configure-mappings.vue
index 30f10e29..8cdf31ad 100644
--- a/pkg/harvester/pages/c/_cluster/forklift/configure-mappings.vue
+++ b/pkg/harvester/pages/c/_cluster/forklift/configure-mappings.vue
@@ -448,7 +448,7 @@ init();
VLAN {{ entry.vlanId || '0' }}
-
+
{{ formatStorageDetail(entry) }}
-
+
{
};
});
+ // If no migration progress but we have VMs in the spec, show them at 0%
+ if (plan.vmProgress.length === 0 && plan.spec?.vms?.length > 0) {
+ plan.vmProgress = plan.spec.vms.map((vm) => ({
+ vmName: vm.name || vm.id || 'Unknown',
+ vmId: vm.id || '',
+ progress: 0,
+ currentStep: 'Initializing migration',
+ errorMsg: '',
+ canceled: false,
+ }));
+ }
+
plan.progress = plan.vmProgress.length > 0 ? Math.round(plan.vmProgress.reduce((sum, vm) => sum + vm.progress, 0) / plan.vmProgress.length * 10) / 10 : 0;
return plan;
@@ -129,7 +141,6 @@ const headers = [
{
...NAME_COL,
labelKey: 'harvester.addons.forklift.dashboard.columns.plan',
- subLabel: 'VM IDs',
},
{ ...FORKLIFT_PLAN_VM_COUNT, width: 105 },
{
@@ -203,9 +214,6 @@ init();
{{ row.metadata.name }}
-
- {{ row.vmIdsDisplay }}
-
@@ -309,6 +317,7 @@ init();
display: flex;
flex-direction: row;
gap: 8px;
+ align-items: center;
}
.vm-id {
diff --git a/pkg/harvester/pages/c/_cluster/forklift/review-migration.vue b/pkg/harvester/pages/c/_cluster/forklift/review-migration.vue
index 8645bdab..0e7a48ce 100644
--- a/pkg/harvester/pages/c/_cluster/forklift/review-migration.vue
+++ b/pkg/harvester/pages/c/_cluster/forklift/review-migration.vue
@@ -113,7 +113,7 @@ const startMigration = async(buttonCb) => {
const inStore = store.getters['currentProduct'].inStore;
try {
- const planName = `${ providerName.value }-plan-${ Math.random().toString(36).substring(2, 7) }`;
+ const planName = `${ providerName.value }`;
const planSpec = {
provider: {
@@ -363,7 +363,7 @@ init();
{{ vm.os }}
-
+
{{ vm.cpus }} vCPU • {{ vm.memGB }} • {{ vm.diskDisplay }}
@@ -433,7 +433,7 @@ init();
.details-grid {
display: grid;
grid-template-columns: minmax(230px, 1fr) 1fr 1fr;
- grid-template-rows: 1fr 1fr;
+ grid-template-rows: minmax(0, 1fr) minmax(0, 1fr);
gap: 12px 128px;
width: 747px;
line-height: 20px;
diff --git a/vue.config.js b/vue.config.js
index 83208dbd..e1774fe3 100644
--- a/vue.config.js
+++ b/vue.config.js
@@ -1,6 +1,21 @@
-const config = require('@rancher/shell/vue.config');
+const path = require('path');
+const baseConfig = require('@rancher/shell/vue.config');
-module.exports = config(__dirname, {
+const shellConfig = baseConfig(__dirname, {
excludes: [],
// excludes: ['harvester']
});
+
+const originalConfigureWebpack = shellConfig.configureWebpack;
+
+shellConfig.configureWebpack = function(config) {
+ if (originalConfigureWebpack) {
+ originalConfigureWebpack(config);
+ }
+
+ // Force all @rancher/icons imports (including from @rancher/shell) to use
+ // the top-level 2.0.60 version so new icons are available everywhere.
+ config.resolve.alias['@rancher/icons'] = path.resolve(__dirname, 'node_modules', '@rancher/icons');
+};
+
+module.exports = shellConfig;