mirror of
https://github.com/harvester/harvester-ui-extension.git
synced 2026-08-16 12:49:14 +00:00
feat(forklift): small fixes
Signed-off-by: Marcelo Fukumoto <marcelo.fukumoto@suse.com>
This commit is contained in:
parent
3ecb6bfc4a
commit
5c7dd1953d
@ -18,7 +18,10 @@ defineProps({
|
||||
:key="'net-' + idx"
|
||||
class="mapping-entry"
|
||||
>
|
||||
<i class="icon icon-network" />
|
||||
<i
|
||||
class="icon icon-network"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<span>{{ entry }}</span>
|
||||
</div>
|
||||
<div
|
||||
@ -26,7 +29,10 @@ defineProps({
|
||||
:key="'stor-' + idx"
|
||||
class="mapping-entry"
|
||||
>
|
||||
<i class="icon icon-datastore" />
|
||||
<i
|
||||
class="icon icon-datastore"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<span>{{ entry }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -2,9 +2,12 @@
|
||||
import { ref, computed, watch } from 'vue';
|
||||
import { useStore } from 'vuex';
|
||||
import Loading from '@shell/components/Loading';
|
||||
import { Banner } from '@components/Banner';
|
||||
import { STORAGE_CLASS, NETWORK_ATTACHMENT } from '@shell/config/types';
|
||||
import { useI18n } from '@shell/composables/useI18n';
|
||||
import { randomStr } from '@shell/utils/string';
|
||||
import { HCI } from '../../types';
|
||||
import { FORKLIFT_NAMESPACE } from '../../config/harvester-map';
|
||||
import MappingColumn from './MappingColumn.vue';
|
||||
|
||||
const props = defineProps({
|
||||
@ -29,6 +32,7 @@ const networkEntries = ref([]);
|
||||
const storageEntries = ref([]);
|
||||
const allNetworkMaps = ref([]);
|
||||
const allStorageMaps = ref([]);
|
||||
const errors = ref([]);
|
||||
const loading = ref(true);
|
||||
|
||||
// Restore from stepData
|
||||
@ -39,12 +43,12 @@ if (props.stepData.storageEntries.length > 0) {
|
||||
storageEntries.value = props.stepData.storageEntries;
|
||||
}
|
||||
|
||||
const NAMESPACE = 'forklift';
|
||||
const NAMESPACE = FORKLIFT_NAMESPACE;
|
||||
|
||||
const harvesterNetworkOptions = computed(() => {
|
||||
const options = [
|
||||
{ label: 'Pod Networking', value: 'pod' },
|
||||
{ label: 'Ignored', value: 'ignored' },
|
||||
{ label: t('harvester.addons.vmMigration.configureMappings.networkMapping.options.podNetworking'), value: 'pod' },
|
||||
{ label: t('harvester.addons.vmMigration.configureMappings.networkMapping.options.ignored'), value: 'ignored' },
|
||||
];
|
||||
|
||||
harvesterNetworks.value.forEach((net) => {
|
||||
@ -167,7 +171,7 @@ const buildNetworkEntries = () => {
|
||||
|
||||
if (!networkMap[netKey]) {
|
||||
networkMap[netKey] = {
|
||||
name: net.name || 'Unknown',
|
||||
name: net.name || t('harvester.addons.vmMigration.generic.unknown'),
|
||||
id: net.id || '',
|
||||
vlanId: net.vlanId || '',
|
||||
target: '',
|
||||
@ -199,7 +203,7 @@ const buildStorageEntries = () => {
|
||||
if (ds && ds.id) {
|
||||
if (!datastoreMap[ds.id]) {
|
||||
datastoreMap[ds.id] = {
|
||||
name: ds.name || 'Unknown',
|
||||
name: ds.name || t('harvester.addons.vmMigration.generic.unknown'),
|
||||
id: ds.id,
|
||||
type: ds.type || '',
|
||||
capacity: 0,
|
||||
@ -352,7 +356,7 @@ const saveAndReturn = async() => {
|
||||
const networkMap = await store.dispatch(`${ inStore }/create`, {
|
||||
type: HCI.FORKLIFT_NETWORK_MAP,
|
||||
metadata: {
|
||||
name: `${ props.providerName }-network-map-${ props.useAllProviderData ? 'default' : Math.random().toString(36).substring(2, 7) }`,
|
||||
name: `${ props.providerName }-network-map-${ props.useAllProviderData ? 'default' : randomStr(5).toLowerCase() }`,
|
||||
namespace: NAMESPACE,
|
||||
ownerReferences: providerOwnerRef,
|
||||
},
|
||||
@ -364,7 +368,7 @@ const saveAndReturn = async() => {
|
||||
const storageMap = await store.dispatch(`${ inStore }/create`, {
|
||||
type: HCI.FORKLIFT_STORAGE_MAP,
|
||||
metadata: {
|
||||
name: `${ props.providerName }-storage-map-${ props.useAllProviderData ? 'default' : Math.random().toString(36).substring(2, 7) }`,
|
||||
name: `${ props.providerName }-storage-map-${ props.useAllProviderData ? 'default' : randomStr(5).toLowerCase() }`,
|
||||
namespace: NAMESPACE,
|
||||
ownerReferences: providerOwnerRef,
|
||||
},
|
||||
@ -473,7 +477,7 @@ const init = async() => {
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
// Data resolution failed — entries will use IDs as fallback
|
||||
errors.value = [e?.message || t('harvester.addons.vmMigration.errors.failedResolveDetails')];
|
||||
}
|
||||
}
|
||||
|
||||
@ -508,6 +512,12 @@ init();
|
||||
v-else
|
||||
class="configure-mappings"
|
||||
>
|
||||
<Banner
|
||||
v-for="(err, i) in errors"
|
||||
:key="i"
|
||||
color="error"
|
||||
:label="err"
|
||||
/>
|
||||
<p class="text-deemphasized line-height-20">
|
||||
{{ t('harvester.addons.vmMigration.configureMappings.description') }}
|
||||
</p>
|
||||
|
||||
@ -11,6 +11,7 @@ import { SECRET } from '@shell/config/types';
|
||||
import { randomStr } from '@shell/utils/string';
|
||||
import { useI18n } from '@shell/composables/useI18n';
|
||||
import { HCI } from '../../types';
|
||||
import { FORKLIFT_NAMESPACE } from '../../config/harvester-map';
|
||||
|
||||
const CREATE_NEW = '__create_new__';
|
||||
|
||||
@ -143,7 +144,7 @@ watch(selectedProvider, (val) => {
|
||||
createdSecret.value = null;
|
||||
} else {
|
||||
const provider = allProviders.value.find(
|
||||
(p) => p.metadata.name === val && p.metadata.namespace === 'forklift'
|
||||
(p) => p.metadata.name === val && p.metadata.namespace === FORKLIFT_NAMESPACE
|
||||
);
|
||||
|
||||
if (provider) {
|
||||
@ -212,7 +213,7 @@ const testConnection = async(buttonCb) => {
|
||||
// Edit mode: update existing provider URL + secret, then poll
|
||||
if (props.editMode && createdProvider.value) {
|
||||
try {
|
||||
const namespace = 'forklift';
|
||||
const namespace = FORKLIFT_NAMESPACE;
|
||||
|
||||
createdProvider.value.spec.url = url.value;
|
||||
await createdProvider.value.save();
|
||||
@ -253,7 +254,7 @@ const testConnection = async(buttonCb) => {
|
||||
connected = true;
|
||||
break;
|
||||
} else {
|
||||
errorMsg = connectionCondition.message || 'Connection failed';
|
||||
errorMsg = connectionCondition.message || t('harvester.addons.vmMigration.errors.connectionFailed');
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -263,7 +264,7 @@ const testConnection = async(buttonCb) => {
|
||||
connected = true;
|
||||
break;
|
||||
} else if (readyCondition.status === 'False') {
|
||||
errorMsg = readyCondition.message || 'Provider not ready';
|
||||
errorMsg = readyCondition.message || t('harvester.addons.vmMigration.errors.providerNotReady');
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -291,7 +292,7 @@ const testConnection = async(buttonCb) => {
|
||||
// For existing providers, just poll for Ready/ConnectionTestSucceeded status
|
||||
if (isExistingProvider.value) {
|
||||
try {
|
||||
const namespace = 'forklift';
|
||||
const namespace = FORKLIFT_NAMESPACE;
|
||||
const maxAttempts = 15;
|
||||
let attempts = 0;
|
||||
let connected = false;
|
||||
@ -316,7 +317,7 @@ const testConnection = async(buttonCb) => {
|
||||
connected = true;
|
||||
break;
|
||||
} else {
|
||||
errorMsg = connectionCondition.message || 'Connection failed';
|
||||
errorMsg = connectionCondition.message || t('harvester.addons.vmMigration.errors.connectionFailed');
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -326,7 +327,7 @@ const testConnection = async(buttonCb) => {
|
||||
connected = true;
|
||||
break;
|
||||
} else if (readyCondition.status === 'False') {
|
||||
errorMsg = readyCondition.message || 'Provider not ready';
|
||||
errorMsg = readyCondition.message || t('harvester.addons.vmMigration.errors.providerNotReady');
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -363,7 +364,7 @@ const testConnection = async(buttonCb) => {
|
||||
createdSecret.value = null;
|
||||
}
|
||||
|
||||
const namespace = 'forklift';
|
||||
const namespace = FORKLIFT_NAMESPACE;
|
||||
const secretName = `${ providerName.value }-creds-${ randomStr(4).toLowerCase() }`;
|
||||
|
||||
// Create Provider first so we have its UID for the ownerReference on the Secret
|
||||
@ -440,7 +441,7 @@ const testConnection = async(buttonCb) => {
|
||||
connected = true;
|
||||
break;
|
||||
} else {
|
||||
errorMsg = connectionCondition.message || 'Connection failed';
|
||||
errorMsg = connectionCondition.message || t('harvester.addons.vmMigration.errors.connectionFailed');
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -450,7 +451,7 @@ const testConnection = async(buttonCb) => {
|
||||
connected = true;
|
||||
break;
|
||||
} else if (readyCondition.status === 'False') {
|
||||
errorMsg = readyCondition.message || 'Provider not ready';
|
||||
errorMsg = readyCondition.message || t('harvester.addons.vmMigration.errors.providerNotReady');
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -498,12 +499,17 @@ const testConnection = async(buttonCb) => {
|
||||
const init = async() => {
|
||||
const inStore = store.getters['currentProduct'].inStore;
|
||||
|
||||
allProviders.value = await store.dispatch(`${ inStore }/findAll`, { type: HCI.FORKLIFT_PROVIDER });
|
||||
try {
|
||||
allProviders.value = await store.dispatch(`${ inStore }/findAll`, { type: HCI.FORKLIFT_PROVIDER });
|
||||
|
||||
allSecrets.value = await store.dispatch(`${ inStore }/findAll`, {
|
||||
type: SECRET,
|
||||
opt: { labelSelector: `ui.forklift/created-for-resource-type=${ HCI.FORKLIFT_PROVIDER }` }
|
||||
});
|
||||
} catch (e) {
|
||||
errors.value = [e?.message || t('harvester.addons.vmMigration.errors.failedLoadProviders')];
|
||||
}
|
||||
|
||||
allSecrets.value = await store.dispatch(`${ inStore }/findAll`, {
|
||||
type: SECRET,
|
||||
opt: { labelSelector: `ui.forklift/created-for-resource-type=${ HCI.FORKLIFT_PROVIDER }` }
|
||||
});
|
||||
loading.value = false;
|
||||
};
|
||||
|
||||
|
||||
@ -77,7 +77,10 @@ const selectOptions = computed(() => {
|
||||
/>
|
||||
</div>
|
||||
<div :class="['mapping-arrow', entry.target ? 'text-success' : 'text-deemphasized']">
|
||||
<i class="icon icon-right-arrow-alt" />
|
||||
<i
|
||||
class="icon icon-right-arrow-alt"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</div>
|
||||
<div class="mapping-target">
|
||||
<LabeledSelect
|
||||
|
||||
@ -7,7 +7,9 @@ import { RcItemCard } from '@components/RcItemCard';
|
||||
import { LabeledInput } from '@components/Form/LabeledInput';
|
||||
import MappingsCell from '../MappingsCell';
|
||||
import { useI18n } from '@shell/composables/useI18n';
|
||||
import { randomStr } from '@shell/utils/string';
|
||||
import { HCI } from '../../types';
|
||||
import { FORKLIFT_NAMESPACE } from '../../config/harvester-map';
|
||||
|
||||
const props = defineProps({
|
||||
providerName: { type: String, default: '' },
|
||||
@ -34,7 +36,7 @@ const loading = ref(true);
|
||||
// Restore persisted state
|
||||
planName.value = props.stepData.planName;
|
||||
|
||||
const NAMESPACE = 'forklift';
|
||||
const NAMESPACE = FORKLIFT_NAMESPACE;
|
||||
const TARGET_NAMESPACE = 'default';
|
||||
|
||||
watch(planName, (val) => {
|
||||
@ -202,7 +204,7 @@ const startMigrationAction = async() => {
|
||||
const migration = await store.dispatch(`${ inStore }/create`, {
|
||||
type: HCI.FORKLIFT_MIGRATION,
|
||||
metadata: {
|
||||
name: `${ planName.value }-migration-${ Math.random().toString(36).substring(2, 7) }`,
|
||||
name: `${ planName.value }-migration-${ randomStr(5).toLowerCase() }`,
|
||||
namespace: NAMESPACE,
|
||||
ownerReferences: [planOwnerRef],
|
||||
},
|
||||
@ -333,14 +335,17 @@ defineExpose({ startMigration: startMigrationAction });
|
||||
:id="vm.id"
|
||||
:key="vm.id"
|
||||
:variant="'small'"
|
||||
:header="{ title: { text: vm.name }, statuses: [{ icon: 'icon-notify-tick', color: 'text-success' }] }"
|
||||
:header="{ title: { text: vm.name } }"
|
||||
>
|
||||
<template #item-card-content>
|
||||
<div class="vm-card-content">
|
||||
<div class="vm-card-specs">
|
||||
<span class="vm-os text-deemphasized">{{ vm.os }}</span>
|
||||
<span class="vm-resources">
|
||||
<i class="icon icon-disk" />
|
||||
<i
|
||||
class="icon icon-disk"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
{{ vm.cpus }} vCPU • {{ vm.memGB }} • {{ vm.diskDisplay }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@ -5,6 +5,7 @@ import {
|
||||
import { useStore } from 'vuex';
|
||||
import Loading from '@shell/components/Loading';
|
||||
import SortableTable from '@shell/components/SortableTable';
|
||||
import { Banner } from '@components/Banner';
|
||||
import { BadgeState } from '@components/BadgeState';
|
||||
import { useI18n } from '@shell/composables/useI18n';
|
||||
|
||||
@ -28,6 +29,7 @@ const datastoreMap = ref({});
|
||||
const sortableTableRef = ref(null);
|
||||
const allVMsSelected = ref(false);
|
||||
const selectedVMIds = ref(new Set());
|
||||
const errors = ref([]);
|
||||
let skipNextSelectionEvent = false;
|
||||
|
||||
const lastFetchedAt = ref(null);
|
||||
@ -81,13 +83,13 @@ if (props.stepData.tableRows.length > 0) {
|
||||
// Sync back to stepData
|
||||
watch(discoveredVMs, (val) => {
|
||||
props.stepData.discoveredVMs = val;
|
||||
}, { deep: true });
|
||||
});
|
||||
watch(selectedVMIds, (val) => {
|
||||
props.stepData.selectedVMIds = val;
|
||||
}, { deep: true });
|
||||
});
|
||||
watch(tableRows, (val) => {
|
||||
props.stepData.tableRows = val;
|
||||
}, { deep: true });
|
||||
});
|
||||
|
||||
const vmCount = computed(() => discoveredVMs.value.length);
|
||||
const selectedCount = computed(() => selectedVMs.value.length);
|
||||
@ -116,7 +118,7 @@ const headers = [
|
||||
labelKey: 'harvester.addons.vmMigration.selectVms.columns.vmName',
|
||||
value: 'vmName',
|
||||
sort: ['vmName'],
|
||||
subLabel: 'Identifier',
|
||||
subLabel: t('harvester.addons.vmMigration.generic.identifier'),
|
||||
},
|
||||
{
|
||||
name: 'os',
|
||||
@ -141,14 +143,14 @@ const headers = [
|
||||
labelKey: 'harvester.addons.vmMigration.selectVms.columns.network',
|
||||
value: 'network',
|
||||
sort: ['network'],
|
||||
subLabel: 'Identifier',
|
||||
subLabel: t('harvester.addons.vmMigration.generic.identifier'),
|
||||
},
|
||||
{
|
||||
name: 'datastore',
|
||||
labelKey: 'harvester.addons.vmMigration.selectVms.columns.datastore',
|
||||
value: 'datastore',
|
||||
sort: ['datastore'],
|
||||
subLabel: 'Identifier',
|
||||
subLabel: t('harvester.addons.vmMigration.generic.identifier'),
|
||||
},
|
||||
];
|
||||
|
||||
@ -337,10 +339,12 @@ const refreshVMs = async() => {
|
||||
const previousSelectedIds = new Set(selectedVMIds.value);
|
||||
|
||||
try {
|
||||
errors.value = [];
|
||||
await fetchVMs();
|
||||
} catch (e) {
|
||||
discoveredVMs.value = [];
|
||||
tableRows.value = [];
|
||||
errors.value = [e?.message || t('harvester.addons.vmMigration.errors.failedRefreshVms')];
|
||||
}
|
||||
|
||||
selectedVMIds.value = new Set(
|
||||
@ -387,6 +391,7 @@ const init = async() => {
|
||||
await fetchVMs();
|
||||
} catch (e) {
|
||||
discoveredVMs.value = [];
|
||||
errors.value = [e?.message || t('harvester.addons.vmMigration.errors.failedLoadVms')];
|
||||
}
|
||||
|
||||
loading.value = false;
|
||||
@ -407,6 +412,12 @@ init();
|
||||
v-else
|
||||
class="select-vms-step"
|
||||
>
|
||||
<Banner
|
||||
v-for="(err, i) in errors"
|
||||
:key="i"
|
||||
color="error"
|
||||
:label="err"
|
||||
/>
|
||||
<p class="text-deemphasized line-height-20">
|
||||
{{ t('harvester.addons.vmMigration.selectVms.discovered', { count: vmCount }) }}
|
||||
<router-link
|
||||
|
||||
@ -94,6 +94,7 @@ export const CSI_SECRETS = {
|
||||
};
|
||||
|
||||
export const FORKLIFT_API_VERSION = 'forklift.konveyor.io/v1beta1';
|
||||
export const FORKLIFT_NAMESPACE = 'forklift';
|
||||
|
||||
// Some harvester CRD type is not equal to model file name, define the mapping here
|
||||
export const HARVESTER_CRD_MAP = {
|
||||
|
||||
@ -1813,6 +1813,18 @@ harvester:
|
||||
'descheduler': 'The virtual machine auto balance optimizes workload scheduling by evicting pods that are not optimally placed according to administrator-defined policies.'
|
||||
|
||||
vmMigration:
|
||||
generic:
|
||||
unknown: Unknown
|
||||
identifier: Identifier
|
||||
podNetwork: Pod Network
|
||||
errors:
|
||||
failedLoadVms: Failed to load virtual machines
|
||||
failedRefreshVms: Failed to refresh virtual machines
|
||||
failedLoadProviders: Failed to load providers
|
||||
failedResolveDetails: Failed to resolve network and datastore details
|
||||
failedDecodeCredentials: Failed to decode provider credentials
|
||||
connectionFailed: Connection failed
|
||||
providerNotReady: Provider not ready
|
||||
labels:
|
||||
dashboard: Migrations
|
||||
provider: Providers
|
||||
@ -1905,6 +1917,10 @@ harvester:
|
||||
plan: Migration Plan
|
||||
progress: Progress
|
||||
mappings: Mappings
|
||||
progress:
|
||||
failed: Failed
|
||||
migration: Migration
|
||||
initializingMigration: Initializing migration
|
||||
configureMappings:
|
||||
title: Set Mappings
|
||||
description: Map VMware networks and datastores to Harvester and Longhorn target resources
|
||||
@ -1916,6 +1932,9 @@ harvester:
|
||||
description: Map VMware port groups to Harvester networks
|
||||
placeholder: Choose a Harvester network
|
||||
template: Use existing network mapping as template
|
||||
options:
|
||||
podNetworking: Pod Networking
|
||||
ignored: Ignored
|
||||
storageMapping:
|
||||
title: Storage Mapping
|
||||
description: Map VMware datastores to Harvester storage classes
|
||||
@ -1939,6 +1958,16 @@ harvester:
|
||||
warningTitle: "Cold Migration Warning"
|
||||
warningMessage: "All source VMs will be powered off, and downtime will occur during migration. Plan your maintenance window accordingly and notify relevant stakeholders"
|
||||
startMigration: Start Migration Plan
|
||||
plan:
|
||||
states:
|
||||
error: Error
|
||||
canceled: Canceled
|
||||
inProgress: In Progress
|
||||
active: Active
|
||||
actions:
|
||||
start: Start
|
||||
restart: Restart
|
||||
stop: Stop
|
||||
|
||||
vmImport:
|
||||
titles:
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
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() {
|
||||
@ -87,22 +88,22 @@ export default class ForkliftPlan extends HarvesterResource {
|
||||
|
||||
get stateDisplay() {
|
||||
if (this.planFailed) {
|
||||
return 'Error';
|
||||
return this.t('harvester.addons.vmMigration.plan.states.error');
|
||||
}
|
||||
|
||||
if (this.planCritical) {
|
||||
return 'Error';
|
||||
return this.t('harvester.addons.vmMigration.plan.states.error');
|
||||
}
|
||||
|
||||
if (this.planCanceled) {
|
||||
return 'Canceled';
|
||||
return this.t('harvester.addons.vmMigration.plan.states.canceled');
|
||||
}
|
||||
|
||||
if (this.isMigrating) {
|
||||
return 'In Progress';
|
||||
return this.t('harvester.addons.vmMigration.plan.states.inProgress');
|
||||
}
|
||||
|
||||
return 'Active';
|
||||
return this.t('harvester.addons.vmMigration.plan.states.active');
|
||||
}
|
||||
|
||||
get stateBackground() {
|
||||
@ -141,7 +142,7 @@ export default class ForkliftPlan extends HarvesterResource {
|
||||
action: 'startMigration',
|
||||
enabled: true,
|
||||
icon: 'icon icon-play',
|
||||
label: this.isForkliftDashboard ? 'Restart' : 'Start',
|
||||
label: this.isForkliftDashboard ? this.t('harvester.addons.vmMigration.plan.actions.restart') : this.t('harvester.addons.vmMigration.plan.actions.start'),
|
||||
});
|
||||
}
|
||||
|
||||
@ -150,7 +151,7 @@ export default class ForkliftPlan extends HarvesterResource {
|
||||
action: 'stopMigration',
|
||||
enabled: true,
|
||||
icon: 'icon icon-pause',
|
||||
label: 'Stop',
|
||||
label: this.t('harvester.addons.vmMigration.plan.actions.stop'),
|
||||
});
|
||||
}
|
||||
|
||||
@ -215,7 +216,7 @@ export default class ForkliftPlan extends HarvesterResource {
|
||||
const migration = await this.$dispatch('create', {
|
||||
type: HCI.FORKLIFT_MIGRATION,
|
||||
metadata: {
|
||||
name: `${ this.metadata.name }-migration-${ Math.random().toString(36).substring(2, 7) }`,
|
||||
name: `${ this.metadata.name }-migration-${ randomStr(5).toLowerCase() }`,
|
||||
namespace,
|
||||
ownerReferences: [
|
||||
{
|
||||
@ -252,7 +253,7 @@ export default class ForkliftPlan extends HarvesterResource {
|
||||
remove() {
|
||||
const opt = { ...arguments };
|
||||
|
||||
opt.params = { propagationPolicy: 'Background' };
|
||||
opt.params = { propagationPolicy: 'Foreground' };
|
||||
|
||||
return this._remove(opt);
|
||||
}
|
||||
|
||||
@ -44,7 +44,7 @@ const rows = computed(() => {
|
||||
const netMap = allNetworkMaps.value.find((m) => m.metadata.name === netMapName && m.metadata.namespace === netMapNs);
|
||||
const storMap = allStorageMaps.value.find((m) => m.metadata.name === storMapName && m.metadata.namespace === storMapNs);
|
||||
|
||||
plan.networkEntries = (netMap?.spec?.map || []).map((e) => `${ e.source?.id || '-' } → ${ e.destination?.type === 'pod' ? 'Pod Network' : (e.destination?.name || '-') }`);
|
||||
plan.networkEntries = (netMap?.spec?.map || []).map((e) => `${ e.source?.id || '-' } → ${ e.destination?.type === 'pod' ? t('harvester.addons.vmMigration.generic.podNetwork') : (e.destination?.name || '-') }`);
|
||||
plan.storageEntries = (storMap?.spec?.map || []).map((e) => `${ e.source?.id || '-' } → ${ e.destination?.storageClass || '-' }`);
|
||||
|
||||
plan.vmIdsDisplay = (plan.spec?.vms || []).map((vm) => vm.id || vm.name || '').filter(Boolean).join(', ') || '-';
|
||||
@ -73,33 +73,33 @@ const rows = computed(() => {
|
||||
}
|
||||
|
||||
if (step.error && !errorMsg) {
|
||||
const reasons = (step.error.reasons || []).join('; ') || 'Error';
|
||||
const reasons = (step.error.reasons || []).join('; ') || t('harvester.addons.vmMigration.plan.states.error');
|
||||
|
||||
errorMsg = `${ step.name || `Step ${ idx + 1 }` }: ${ reasons }`;
|
||||
}
|
||||
|
||||
if (step.phase === 'Failed' && !errorMsg) {
|
||||
errorMsg = `${ step.name || `Step ${ idx + 1 }` }: Failed`;
|
||||
errorMsg = `${ step.name || `Step ${ idx + 1 }` }: ${ t('harvester.addons.vmMigration.dashboard.progress.failed') }`;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Fallback to VM-level error if no step-level error found
|
||||
if (!errorMsg && vm.error) {
|
||||
const reasons = (vm.error.reasons || []).join('; ') || 'Failed';
|
||||
const reasons = (vm.error.reasons || []).join('; ') || t('harvester.addons.vmMigration.dashboard.progress.failed');
|
||||
|
||||
errorMsg = `${ currentStep || vm.error.phase || 'Migration' }: ${ reasons }`;
|
||||
errorMsg = `${ currentStep || vm.error.phase || t('harvester.addons.vmMigration.dashboard.progress.migration') }: ${ reasons }`;
|
||||
}
|
||||
|
||||
overallProgress = Math.round(overallProgress * 10) / 10;
|
||||
|
||||
// If no step-level error but the plan itself is failed, surface it
|
||||
if (!errorMsg && plan.planFailed) {
|
||||
errorMsg = `${ currentStep || 'Migration' }: Failed`;
|
||||
errorMsg = `${ currentStep || t('harvester.addons.vmMigration.dashboard.progress.migration') }: ${ t('harvester.addons.vmMigration.dashboard.progress.failed') }`;
|
||||
}
|
||||
|
||||
return {
|
||||
vmName: vm.name || vm.id || 'Unknown',
|
||||
vmName: vm.name || vm.id || t('harvester.addons.vmMigration.generic.unknown'),
|
||||
vmId: vm.id || '',
|
||||
progress: overallProgress,
|
||||
currentStep,
|
||||
@ -111,10 +111,10 @@ const rows = computed(() => {
|
||||
// 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',
|
||||
vmName: vm.name || vm.id || t('harvester.addons.vmMigration.generic.unknown'),
|
||||
vmId: vm.id || '',
|
||||
progress: 0,
|
||||
currentStep: 'Initializing migration',
|
||||
currentStep: t('harvester.addons.vmMigration.dashboard.progress.initializingMigration'),
|
||||
errorMsg: '',
|
||||
canceled: false,
|
||||
}));
|
||||
|
||||
@ -203,9 +203,13 @@ const init = async() => {
|
||||
);
|
||||
|
||||
if (secret?.data) {
|
||||
stepData.provider.username = atob(secret.data.user || '');
|
||||
stepData.provider.password = atob(secret.data.password || '');
|
||||
stepData.provider.skipTlsVerify = atob(secret.data.insecureSkipVerify || '') === 'true';
|
||||
try {
|
||||
stepData.provider.username = atob(secret.data.user || '');
|
||||
stepData.provider.password = atob(secret.data.password || '');
|
||||
stepData.provider.skipTlsVerify = atob(secret.data.insecureSkipVerify || '') === 'true';
|
||||
} catch (e) {
|
||||
errors.value = [t('harvester.addons.vmMigration.errors.failedDecodeCredentials')];
|
||||
}
|
||||
stepData.provider.createdSecret = secret;
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user