mirror of
https://github.com/harvester/harvester-ui-extension.git
synced 2026-08-17 13:19:26 +00:00
feat(forklift): Changed based on review
Signed-off-by: Marcelo Fukumoto <marcelo.fukumoto@suse.com>
This commit is contained in:
parent
4d230ebd42
commit
d075871afc
@ -1468,7 +1468,7 @@ export function init($plugin, store) {
|
|||||||
configureType('forklift-create', { subTypes: [HCI.FORKLIFT_PLAN] });
|
configureType('forklift-create', { subTypes: [HCI.FORKLIFT_PLAN] });
|
||||||
virtualType({
|
virtualType({
|
||||||
name: 'forklift-create',
|
name: 'forklift-create',
|
||||||
labelKey: 'harvester.addons.forklift.labels.plan',
|
labelKey: 'harvester.addons.forklift.labels.dashboard',
|
||||||
group: 'forklift',
|
group: 'forklift',
|
||||||
namespaced: true,
|
namespaced: true,
|
||||||
route: {
|
route: {
|
||||||
|
|||||||
@ -1809,7 +1809,7 @@ harvester:
|
|||||||
|
|
||||||
forklift:
|
forklift:
|
||||||
labels:
|
labels:
|
||||||
dashboard: Create
|
dashboard: Migrations
|
||||||
provider: Providers
|
provider: Providers
|
||||||
networkMap: Network Maps
|
networkMap: Network Maps
|
||||||
storageMap: Storage Maps
|
storageMap: Storage Maps
|
||||||
@ -1822,6 +1822,8 @@ harvester:
|
|||||||
title: Configure Provider
|
title: Configure Provider
|
||||||
description: Connect to your VMware vCenter or ESXi host to discover virtual machines for migration
|
description: Connect to your VMware vCenter or ESXi host to discover virtual machines for migration
|
||||||
connectionDetails: Connection Details
|
connectionDetails: Connection Details
|
||||||
|
providerSelect: Provider
|
||||||
|
createNew: Create new provider
|
||||||
requirementsTitle: Requirements
|
requirementsTitle: Requirements
|
||||||
requirementsText: "To migrate virtual machines from VMWare, you will need a vCenter Server running version 6.5 or higher with a user account that has at least read permissions, and the source environment must be network-accessible from Rancher."
|
requirementsText: "To migrate virtual machines from VMWare, you will need a vCenter Server running version 6.5 or higher with a user account that has at least read permissions, and the source environment must be network-accessible from Rancher."
|
||||||
name: Name
|
name: Name
|
||||||
@ -1836,6 +1838,7 @@ harvester:
|
|||||||
testMissingFields: Please fill in all required fields before testing
|
testMissingFields: Please fill in all required fields before testing
|
||||||
testTimeout: Connection test timed out — the provider did not report a status in time
|
testTimeout: Connection test timed out — the provider did not report a status in time
|
||||||
save: Save Provider and Continue
|
save: Save Provider and Continue
|
||||||
|
saveExisting: Check Provider and Continue
|
||||||
selectVms:
|
selectVms:
|
||||||
title: Select Virtual Machines
|
title: Select Virtual Machines
|
||||||
discovered: "{count} VMs discovered from"
|
discovered: "{count} VMs discovered from"
|
||||||
@ -1872,17 +1875,22 @@ harvester:
|
|||||||
title: Set Mappings
|
title: Set Mappings
|
||||||
description: Map VMware networks and datastores to Harvester and Longhorn target resources
|
description: Map VMware networks and datastores to Harvester and Longhorn target resources
|
||||||
save: Save Mappings and Continue
|
save: Save Mappings and Continue
|
||||||
|
noTemplate: Start from scratch
|
||||||
networkMapping:
|
networkMapping:
|
||||||
title: Network Mapping
|
title: Network Mapping
|
||||||
description: Map VMware port groups to Harvester networks
|
description: Map VMware port groups to Harvester networks
|
||||||
placeholder: Choose a Harvester network...
|
placeholder: Choose a Harvester network...
|
||||||
|
template: Use existing network mapping as template
|
||||||
storageMapping:
|
storageMapping:
|
||||||
title: Storage Mapping
|
title: Storage Mapping
|
||||||
description: Map VMware datastores to Harvester storage classes
|
description: Map VMware datastores to Harvester storage classes
|
||||||
placeholder: Choose a Longhorn Storage Volume
|
placeholder: Choose a Longhorn Storage Volume
|
||||||
|
template: Use existing storage mapping as template
|
||||||
reviewMigration:
|
reviewMigration:
|
||||||
title: Review Migration Plan
|
title: Review Migration Plan
|
||||||
description: Confirm your migration settings before starting the transfer of VMs to the target cluster.
|
description: Confirm your migration settings before starting the transfer of VMs to the target cluster.
|
||||||
|
planName: Name
|
||||||
|
planNamePlaceholder: "e.g. my-migration-plan"
|
||||||
migrationDetails: Migration Details
|
migrationDetails: Migration Details
|
||||||
totalVms: Total VMs
|
totalVms: Total VMs
|
||||||
vcpu: vCPU
|
vcpu: vCPU
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { ref, computed } from 'vue';
|
import { ref, computed, watch } from 'vue';
|
||||||
import { useStore } from 'vuex';
|
import { useStore } from 'vuex';
|
||||||
import Loading from '@shell/components/Loading';
|
import Loading from '@shell/components/Loading';
|
||||||
import Masthead from '@shell/components/ResourceList/Masthead';
|
import Masthead from '@shell/components/ResourceList/Masthead';
|
||||||
@ -25,11 +25,17 @@ const schema = {
|
|||||||
const store = useStore();
|
const store = useStore();
|
||||||
const { t } = useI18n(store);
|
const { t } = useI18n(store);
|
||||||
|
|
||||||
|
const NO_TEMPLATE = '__none__';
|
||||||
|
|
||||||
const vms = ref([]);
|
const vms = ref([]);
|
||||||
const harvesterNetworks = ref([]);
|
const harvesterNetworks = ref([]);
|
||||||
const storageClasses = ref([]);
|
const storageClasses = ref([]);
|
||||||
const networkEntries = ref([]);
|
const networkEntries = ref([]);
|
||||||
const storageEntries = ref([]);
|
const storageEntries = ref([]);
|
||||||
|
const allNetworkMaps = ref([]);
|
||||||
|
const allStorageMaps = ref([]);
|
||||||
|
const selectedNetworkTemplate = ref(NO_TEMPLATE);
|
||||||
|
const selectedStorageTemplate = ref(NO_TEMPLATE);
|
||||||
const errors = ref([]);
|
const errors = ref([]);
|
||||||
const loading = ref(true);
|
const loading = ref(true);
|
||||||
|
|
||||||
@ -66,6 +72,118 @@ const storageClassOptions = computed(() => {
|
|||||||
return options;
|
return options;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const networkTemplateOptions = computed(() => {
|
||||||
|
const options = [
|
||||||
|
{ label: t('harvester.addons.forklift.configureMappings.noTemplate'), value: NO_TEMPLATE }
|
||||||
|
];
|
||||||
|
|
||||||
|
const currentIds = new Set(networkEntries.value.map((e) => e.id).filter(Boolean));
|
||||||
|
|
||||||
|
const sorted = [...allNetworkMaps.value].sort(
|
||||||
|
(a, b) => new Date(b.metadata.creationTimestamp) - new Date(a.metadata.creationTimestamp)
|
||||||
|
);
|
||||||
|
|
||||||
|
sorted.forEach((nm) => {
|
||||||
|
const mapSources = (nm.spec?.map || []).map((m) => m.source?.id).filter(Boolean);
|
||||||
|
const hasOverlap = mapSources.some((id) => currentIds.has(id));
|
||||||
|
|
||||||
|
if (hasOverlap) {
|
||||||
|
options.push({
|
||||||
|
label: nm.metadata.name,
|
||||||
|
value: nm.metadata.name,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return options;
|
||||||
|
});
|
||||||
|
|
||||||
|
const storageTemplateOptions = computed(() => {
|
||||||
|
const options = [
|
||||||
|
{ label: t('harvester.addons.forklift.configureMappings.noTemplate'), value: NO_TEMPLATE }
|
||||||
|
];
|
||||||
|
|
||||||
|
const currentIds = new Set(storageEntries.value.map((e) => e.id).filter(Boolean));
|
||||||
|
|
||||||
|
const sorted = [...allStorageMaps.value].sort(
|
||||||
|
(a, b) => new Date(b.metadata.creationTimestamp) - new Date(a.metadata.creationTimestamp)
|
||||||
|
);
|
||||||
|
|
||||||
|
sorted.forEach((sm) => {
|
||||||
|
const mapSources = (sm.spec?.map || []).map((m) => m.source?.id).filter(Boolean);
|
||||||
|
const hasOverlap = mapSources.some((id) => currentIds.has(id));
|
||||||
|
|
||||||
|
if (hasOverlap) {
|
||||||
|
options.push({
|
||||||
|
label: sm.metadata.name,
|
||||||
|
value: sm.metadata.name,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return options;
|
||||||
|
});
|
||||||
|
|
||||||
|
watch(selectedNetworkTemplate, (val) => {
|
||||||
|
if (val === NO_TEMPLATE) {
|
||||||
|
networkEntries.value.forEach((e) => {
|
||||||
|
e.target = '';
|
||||||
|
});
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const template = allNetworkMaps.value.find((nm) => nm.metadata.name === val);
|
||||||
|
|
||||||
|
if (!template?.spec?.map) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
networkEntries.value.forEach((entry) => {
|
||||||
|
const match = template.spec.map.find(
|
||||||
|
(m) => m.source?.id === entry.id || m.source?.name === entry.name
|
||||||
|
);
|
||||||
|
|
||||||
|
if (match?.destination) {
|
||||||
|
const dest = match.destination;
|
||||||
|
|
||||||
|
if (dest.type === 'pod') {
|
||||||
|
entry.target = 'pod';
|
||||||
|
} else if (dest.type === 'ignored') {
|
||||||
|
entry.target = 'ignored';
|
||||||
|
} else if (dest.type === 'multus' && dest.name) {
|
||||||
|
entry.target = dest.namespace ? `${ dest.namespace }/${ dest.name }` : dest.name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
watch(selectedStorageTemplate, (val) => {
|
||||||
|
if (val === NO_TEMPLATE) {
|
||||||
|
storageEntries.value.forEach((e) => {
|
||||||
|
e.target = '';
|
||||||
|
});
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const template = allStorageMaps.value.find((sm) => sm.metadata.name === val);
|
||||||
|
|
||||||
|
if (!template?.spec?.map) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
storageEntries.value.forEach((entry) => {
|
||||||
|
const match = template.spec.map.find(
|
||||||
|
(m) => m.source?.id === entry.id || m.source?.name === entry.name
|
||||||
|
);
|
||||||
|
|
||||||
|
if (match?.destination?.storageClass) {
|
||||||
|
entry.target = match.destination.storageClass;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
const allNetworksMapped = computed(() => networkEntries.value.length > 0 && networkEntries.value.every((e) => !!e.target));
|
const allNetworksMapped = computed(() => networkEntries.value.length > 0 && networkEntries.value.every((e) => !!e.target));
|
||||||
const allStorageMapped = computed(() => storageEntries.value.length > 0 && storageEntries.value.every((e) => !!e.target));
|
const allStorageMapped = computed(() => storageEntries.value.length > 0 && storageEntries.value.every((e) => !!e.target));
|
||||||
|
|
||||||
@ -159,15 +277,7 @@ const formatStorageDetail = (entry) => {
|
|||||||
return parts.join(' \u2022 ');
|
return parts.join(' \u2022 ');
|
||||||
};
|
};
|
||||||
|
|
||||||
const cancel = async() => {
|
const cancel = () => {
|
||||||
const inStore = store.getters['currentProduct'].inStore;
|
|
||||||
const allProviders = store.getters[`${ inStore }/all`](HCI.FORKLIFT_PROVIDER) || [];
|
|
||||||
const provider = allProviders.find((p) => p.metadata.name === providerName.value && p.metadata.namespace === NAMESPACE);
|
|
||||||
|
|
||||||
if (provider) {
|
|
||||||
await provider.remove();
|
|
||||||
}
|
|
||||||
|
|
||||||
currentRouter().push({
|
currentRouter().push({
|
||||||
name: `${ PRODUCT_NAME }-c-cluster-forklift`,
|
name: `${ PRODUCT_NAME }-c-cluster-forklift`,
|
||||||
params: {
|
params: {
|
||||||
@ -274,19 +384,7 @@ const saveMappings = async(buttonCb) => {
|
|||||||
|
|
||||||
await storageMap.save();
|
await storageMap.save();
|
||||||
|
|
||||||
// Navigate to review page with all mapping data
|
// Navigate to review page
|
||||||
const networkMappingsData = networkEntries.value.map((entry) => ({
|
|
||||||
source: entry.name,
|
|
||||||
target: entry.target === 'pod' ? 'Pod Networking' : entry.target === 'ignored' ? 'Ignored' : entry.target,
|
|
||||||
usedBy: entry.usedBy,
|
|
||||||
}));
|
|
||||||
|
|
||||||
const storageMappingsData = storageEntries.value.map((entry) => ({
|
|
||||||
source: entry.name,
|
|
||||||
target: entry.target,
|
|
||||||
usedBy: entry.usedBy,
|
|
||||||
}));
|
|
||||||
|
|
||||||
currentRouter().push({
|
currentRouter().push({
|
||||||
name: `${ PRODUCT_NAME }-c-cluster-forklift-review-migration`,
|
name: `${ PRODUCT_NAME }-c-cluster-forklift-review-migration`,
|
||||||
params: {
|
params: {
|
||||||
@ -298,8 +396,6 @@ const saveMappings = async(buttonCb) => {
|
|||||||
vms: currentRoute().query.vms,
|
vms: currentRoute().query.vms,
|
||||||
networkMap: networkMap.metadata.name,
|
networkMap: networkMap.metadata.name,
|
||||||
storageMap: storageMap.metadata.name,
|
storageMap: storageMap.metadata.name,
|
||||||
networkMappings: JSON.stringify(networkMappingsData),
|
|
||||||
storageMappings: JSON.stringify(storageMappingsData),
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -325,6 +421,18 @@ const init = async() => {
|
|||||||
storageClasses.value = [];
|
storageClasses.value = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
allNetworkMaps.value = await store.dispatch(`${ inStore }/findAll`, { type: HCI.FORKLIFT_NETWORK_MAP });
|
||||||
|
} catch (e) {
|
||||||
|
allNetworkMaps.value = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
allStorageMaps.value = await store.dispatch(`${ inStore }/findAll`, { type: HCI.FORKLIFT_STORAGE_MAP });
|
||||||
|
} catch (e) {
|
||||||
|
allStorageMaps.value = [];
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await store.dispatch(`${ inStore }/findAll`, { type: HCI.FORKLIFT_PROVIDER });
|
await store.dispatch(`${ inStore }/findAll`, { type: HCI.FORKLIFT_PROVIDER });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@ -436,6 +544,14 @@ init();
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<LabeledSelect
|
||||||
|
v-model:value="selectedNetworkTemplate"
|
||||||
|
:label="t('harvester.addons.forklift.configureMappings.networkMapping.template')"
|
||||||
|
:options="networkTemplateOptions"
|
||||||
|
:reduce="(opt) => opt.value"
|
||||||
|
class="mb-10"
|
||||||
|
/>
|
||||||
|
|
||||||
<RcItemCard
|
<RcItemCard
|
||||||
v-for="entry in networkEntries"
|
v-for="entry in networkEntries"
|
||||||
:id="entry._key"
|
:id="entry._key"
|
||||||
@ -483,6 +599,14 @@ init();
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<LabeledSelect
|
||||||
|
v-model:value="selectedStorageTemplate"
|
||||||
|
:label="t('harvester.addons.forklift.configureMappings.storageMapping.template')"
|
||||||
|
:options="storageTemplateOptions"
|
||||||
|
:reduce="(opt) => opt.value"
|
||||||
|
class="mb-10"
|
||||||
|
/>
|
||||||
|
|
||||||
<RcItemCard
|
<RcItemCard
|
||||||
v-for="entry in storageEntries"
|
v-for="entry in storageEntries"
|
||||||
:id="entry._key"
|
:id="entry._key"
|
||||||
|
|||||||
@ -7,6 +7,7 @@ import { Checkbox } from '@components/Form/Checkbox';
|
|||||||
import { Banner } from '@components/Banner';
|
import { Banner } from '@components/Banner';
|
||||||
import Masthead from '@shell/components/ResourceList/Masthead';
|
import Masthead from '@shell/components/ResourceList/Masthead';
|
||||||
import AsyncButton from '@shell/components/AsyncButton';
|
import AsyncButton from '@shell/components/AsyncButton';
|
||||||
|
import LabeledSelect from '@shell/components/form/LabeledSelect';
|
||||||
import { SCHEMA, SECRET } from '@shell/config/types';
|
import { SCHEMA, SECRET } from '@shell/config/types';
|
||||||
import { randomStr } from '@shell/utils/string';
|
import { randomStr } from '@shell/utils/string';
|
||||||
import { useI18n } from '@shell/composables/useI18n';
|
import { useI18n } from '@shell/composables/useI18n';
|
||||||
@ -24,10 +25,14 @@ const schema = {
|
|||||||
metadata: { name: HCI.FORKLIFT_PROVIDER },
|
metadata: { name: HCI.FORKLIFT_PROVIDER },
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const CREATE_NEW = '__create_new__';
|
||||||
|
|
||||||
const store = useStore();
|
const store = useStore();
|
||||||
const { t } = useI18n(store);
|
const { t } = useI18n(store);
|
||||||
|
|
||||||
|
const allProviders = ref([]);
|
||||||
const allSecrets = ref([]);
|
const allSecrets = ref([]);
|
||||||
|
const selectedProvider = ref(CREATE_NEW);
|
||||||
const providerName = ref('');
|
const providerName = ref('');
|
||||||
const url = ref('');
|
const url = ref('');
|
||||||
const username = ref('');
|
const username = ref('');
|
||||||
@ -43,23 +48,75 @@ const testPassed = ref(false);
|
|||||||
const testing = ref(false);
|
const testing = ref(false);
|
||||||
const saving = ref(false);
|
const saving = ref(false);
|
||||||
|
|
||||||
|
const isExistingProvider = computed(() => selectedProvider.value !== CREATE_NEW);
|
||||||
const isFormValid = computed(() => !!providerName.value && !!url.value && !!username.value && !!password.value);
|
const isFormValid = computed(() => !!providerName.value && !!url.value && !!username.value && !!password.value);
|
||||||
|
|
||||||
// Reset test state when any field changes
|
const providerOptions = computed(() => {
|
||||||
watch([providerName, url, username, password], () => {
|
const options = [
|
||||||
testPassed.value = false;
|
{ label: t('harvester.addons.forklift.configureProvider.createNew'), value: CREATE_NEW }
|
||||||
testResult.value = null;
|
];
|
||||||
|
|
||||||
|
allProviders.value.forEach((p) => {
|
||||||
|
options.push({
|
||||||
|
label: p.metadata.name,
|
||||||
|
value: p.metadata.name,
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
const cancel = async() => {
|
return options;
|
||||||
if (createdProvider.value) {
|
});
|
||||||
await createdProvider.value.remove();
|
|
||||||
|
// When the provider selection changes, populate or clear the fields
|
||||||
|
watch(selectedProvider, (val) => {
|
||||||
|
testPassed.value = false;
|
||||||
|
testResult.value = null;
|
||||||
|
testError.value = null;
|
||||||
|
|
||||||
|
if (val === CREATE_NEW) {
|
||||||
|
providerName.value = '';
|
||||||
|
url.value = '';
|
||||||
|
username.value = '';
|
||||||
|
password.value = '';
|
||||||
|
skipTlsVerify.value = false;
|
||||||
createdProvider.value = null;
|
createdProvider.value = null;
|
||||||
} else if (createdSecret.value) {
|
|
||||||
await createdSecret.value.remove();
|
|
||||||
createdSecret.value = null;
|
createdSecret.value = null;
|
||||||
|
} else {
|
||||||
|
const provider = allProviders.value.find(
|
||||||
|
(p) => p.metadata.name === val && p.metadata.namespace === 'forklift'
|
||||||
|
);
|
||||||
|
|
||||||
|
if (provider) {
|
||||||
|
providerName.value = provider.metadata.name;
|
||||||
|
url.value = provider.spec?.url || '';
|
||||||
|
|
||||||
|
const secretRef = provider.spec?.secret;
|
||||||
|
|
||||||
|
if (secretRef) {
|
||||||
|
const secret = allSecrets.value.find(
|
||||||
|
(s) => s.metadata.name === secretRef.name && s.metadata.namespace === secretRef.namespace
|
||||||
|
);
|
||||||
|
|
||||||
|
if (secret?.data) {
|
||||||
|
username.value = atob(secret.data.user || '');
|
||||||
|
password.value = atob(secret.data.password || '');
|
||||||
|
skipTlsVerify.value = atob(secret.data.insecureSkipVerify || '') === 'true';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
createdProvider.value = provider;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Reset test state when any field changes (only for create new)
|
||||||
|
watch([providerName, url, username, password], () => {
|
||||||
|
if (!isExistingProvider.value) {
|
||||||
|
testPassed.value = false;
|
||||||
|
testResult.value = null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const cancel = () => {
|
||||||
currentRouter().push({
|
currentRouter().push({
|
||||||
name: `${ PRODUCT_NAME }-c-cluster-forklift`,
|
name: `${ PRODUCT_NAME }-c-cluster-forklift`,
|
||||||
params: {
|
params: {
|
||||||
@ -84,6 +141,70 @@ const testConnection = async(buttonCb) => {
|
|||||||
|
|
||||||
const inStore = store.getters['currentProduct'].inStore;
|
const inStore = store.getters['currentProduct'].inStore;
|
||||||
|
|
||||||
|
// For existing providers, just poll for Ready/ConnectionTestSucceeded status
|
||||||
|
if (isExistingProvider.value) {
|
||||||
|
try {
|
||||||
|
const namespace = 'forklift';
|
||||||
|
const maxAttempts = 15;
|
||||||
|
let attempts = 0;
|
||||||
|
let connected = false;
|
||||||
|
let errorMsg = '';
|
||||||
|
|
||||||
|
while (attempts < maxAttempts) {
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
||||||
|
attempts++;
|
||||||
|
|
||||||
|
const refreshed = await store.dispatch(`${ inStore }/find`, {
|
||||||
|
type: HCI.FORKLIFT_PROVIDER,
|
||||||
|
id: `${ namespace }/${ providerName.value }`,
|
||||||
|
opt: { force: true }
|
||||||
|
});
|
||||||
|
|
||||||
|
const conditions = refreshed?.status?.conditions || [];
|
||||||
|
const readyCondition = conditions.find((c) => c.type === 'Ready');
|
||||||
|
const connectionCondition = conditions.find((c) => c.type === 'ConnectionTestSucceeded');
|
||||||
|
|
||||||
|
if (connectionCondition) {
|
||||||
|
if (connectionCondition.status === 'True') {
|
||||||
|
connected = true;
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
errorMsg = connectionCondition.message || 'Connection failed';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (readyCondition) {
|
||||||
|
if (readyCondition.status === 'True') {
|
||||||
|
connected = true;
|
||||||
|
break;
|
||||||
|
} else if (readyCondition.status === 'False') {
|
||||||
|
errorMsg = readyCondition.message || 'Provider not ready';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (connected) {
|
||||||
|
testPassed.value = true;
|
||||||
|
testResult.value = t('harvester.addons.forklift.configureProvider.testSuccess');
|
||||||
|
testing.value = false;
|
||||||
|
buttonCb(true);
|
||||||
|
} else {
|
||||||
|
testError.value = errorMsg || t('harvester.addons.forklift.configureProvider.testTimeout');
|
||||||
|
testing.value = false;
|
||||||
|
buttonCb(false);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
testError.value = err.message || t('harvester.addons.forklift.configureProvider.testFailed');
|
||||||
|
testing.value = false;
|
||||||
|
buttonCb(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// For new providers, create provider + secret then poll
|
||||||
try {
|
try {
|
||||||
// Delete previous provider (cascades to secret via ownerReferences)
|
// Delete previous provider (cascades to secret via ownerReferences)
|
||||||
if (createdProvider.value) {
|
if (createdProvider.value) {
|
||||||
@ -124,10 +245,7 @@ const testConnection = async(buttonCb) => {
|
|||||||
metadata: {
|
metadata: {
|
||||||
name: secretName,
|
name: secretName,
|
||||||
namespace,
|
namespace,
|
||||||
labels: {
|
labels: { 'ui.forklift/created-for-resource-type': 'forklift.konveyor.io.provider' },
|
||||||
createdForProviderType: 'vsphere',
|
|
||||||
createdForResourceType: 'providers',
|
|
||||||
},
|
|
||||||
ownerReferences: [
|
ownerReferences: [
|
||||||
{
|
{
|
||||||
apiVersion: 'forklift.konveyor.io/v1beta1',
|
apiVersion: 'forklift.konveyor.io/v1beta1',
|
||||||
@ -268,7 +386,12 @@ const saveProvider = async(buttonCb) => {
|
|||||||
const init = async() => {
|
const init = async() => {
|
||||||
const inStore = store.getters['currentProduct'].inStore;
|
const inStore = store.getters['currentProduct'].inStore;
|
||||||
|
|
||||||
allSecrets.value = await store.dispatch(`${ inStore }/findAll`, { type: SECRET });
|
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 }` }
|
||||||
|
});
|
||||||
loading.value = false;
|
loading.value = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -308,6 +431,18 @@ init();
|
|||||||
</Banner>
|
</Banner>
|
||||||
|
|
||||||
<div class="mb-20">
|
<div class="mb-20">
|
||||||
|
<LabeledSelect
|
||||||
|
v-model:value="selectedProvider"
|
||||||
|
:label="t('harvester.addons.forklift.configureProvider.providerSelect')"
|
||||||
|
:options="providerOptions"
|
||||||
|
:reduce="(opt) => opt.value"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
v-if="!isExistingProvider"
|
||||||
|
class="mb-20"
|
||||||
|
>
|
||||||
<LabeledInput
|
<LabeledInput
|
||||||
v-model:value="providerName"
|
v-model:value="providerName"
|
||||||
:label="t('harvester.addons.forklift.configureProvider.name')"
|
:label="t('harvester.addons.forklift.configureProvider.name')"
|
||||||
@ -320,6 +455,7 @@ init();
|
|||||||
v-model:value="url"
|
v-model:value="url"
|
||||||
:label="t('harvester.addons.forklift.configureProvider.urlLabel')"
|
:label="t('harvester.addons.forklift.configureProvider.urlLabel')"
|
||||||
:placeholder="t('harvester.addons.forklift.configureProvider.urlPlaceholder')"
|
:placeholder="t('harvester.addons.forklift.configureProvider.urlPlaceholder')"
|
||||||
|
:disabled="isExistingProvider"
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
<p class="text-muted mt-5">
|
<p class="text-muted mt-5">
|
||||||
@ -332,6 +468,7 @@ init();
|
|||||||
<LabeledInput
|
<LabeledInput
|
||||||
v-model:value="username"
|
v-model:value="username"
|
||||||
:label="t('harvester.addons.forklift.fields.username')"
|
:label="t('harvester.addons.forklift.fields.username')"
|
||||||
|
:disabled="isExistingProvider"
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@ -340,6 +477,7 @@ init();
|
|||||||
v-model:value="password"
|
v-model:value="password"
|
||||||
type="password"
|
type="password"
|
||||||
:label="t('harvester.addons.forklift.fields.password')"
|
:label="t('harvester.addons.forklift.fields.password')"
|
||||||
|
:disabled="isExistingProvider"
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@ -349,6 +487,7 @@ init();
|
|||||||
<Checkbox
|
<Checkbox
|
||||||
v-model:value="skipTlsVerify"
|
v-model:value="skipTlsVerify"
|
||||||
:label="t('harvester.addons.forklift.configureProvider.skipSsl')"
|
:label="t('harvester.addons.forklift.configureProvider.skipSsl')"
|
||||||
|
:disabled="isExistingProvider"
|
||||||
/>
|
/>
|
||||||
<p class="text-muted ml-20">
|
<p class="text-muted ml-20">
|
||||||
{{ t('harvester.addons.forklift.configureProvider.skipSslHint') }}
|
{{ t('harvester.addons.forklift.configureProvider.skipSslHint') }}
|
||||||
@ -407,10 +546,10 @@ init();
|
|||||||
</button>
|
</button>
|
||||||
<AsyncButton
|
<AsyncButton
|
||||||
:disabled="!isFormValid || testing || saving"
|
:disabled="!isFormValid || testing || saving"
|
||||||
:action-label="t('harvester.addons.forklift.configureProvider.save')"
|
:action-label="isExistingProvider ? t('harvester.addons.forklift.configureProvider.saveExisting') : t('harvester.addons.forklift.configureProvider.save')"
|
||||||
:waiting-label="t('harvester.addons.forklift.configureProvider.save')"
|
:waiting-label="isExistingProvider ? t('harvester.addons.forklift.configureProvider.saveExisting') : t('harvester.addons.forklift.configureProvider.save')"
|
||||||
:success-label="t('harvester.addons.forklift.configureProvider.save')"
|
:success-label="isExistingProvider ? t('harvester.addons.forklift.configureProvider.saveExisting') : t('harvester.addons.forklift.configureProvider.save')"
|
||||||
:error-label="t('harvester.addons.forklift.configureProvider.save')"
|
:error-label="isExistingProvider ? t('harvester.addons.forklift.configureProvider.saveExisting') : t('harvester.addons.forklift.configureProvider.save')"
|
||||||
@click="saveProvider"
|
@click="saveProvider"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -200,6 +200,8 @@ init();
|
|||||||
:groupable="false"
|
:groupable="false"
|
||||||
:table-actions="false"
|
:table-actions="false"
|
||||||
:search="false"
|
:search="false"
|
||||||
|
default-sort-by="state"
|
||||||
|
:default-sort-descending="true"
|
||||||
key-field="_key"
|
key-field="_key"
|
||||||
>
|
>
|
||||||
<template #header-left>
|
<template #header-left>
|
||||||
|
|||||||
@ -6,6 +6,7 @@ import Masthead from '@shell/components/ResourceList/Masthead';
|
|||||||
import AsyncButton from '@shell/components/AsyncButton';
|
import AsyncButton from '@shell/components/AsyncButton';
|
||||||
import { Banner } from '@components/Banner';
|
import { Banner } from '@components/Banner';
|
||||||
import { RcItemCard } from '@components/RcItemCard';
|
import { RcItemCard } from '@components/RcItemCard';
|
||||||
|
import { LabeledInput } from '@components/Form/LabeledInput';
|
||||||
import MappingsCell from '../../../../components/MappingsCell';
|
import MappingsCell from '../../../../components/MappingsCell';
|
||||||
import { SCHEMA } from '@shell/config/types';
|
import { SCHEMA } from '@shell/config/types';
|
||||||
import { useI18n } from '@shell/composables/useI18n';
|
import { useI18n } from '@shell/composables/useI18n';
|
||||||
@ -29,6 +30,7 @@ const { t } = useI18n(store);
|
|||||||
const vms = ref([]);
|
const vms = ref([]);
|
||||||
const networkMappings = ref([]);
|
const networkMappings = ref([]);
|
||||||
const storageMappings = ref([]);
|
const storageMappings = ref([]);
|
||||||
|
const planName = ref('');
|
||||||
const errors = ref([]);
|
const errors = ref([]);
|
||||||
const loading = ref(true);
|
const loading = ref(true);
|
||||||
|
|
||||||
@ -89,15 +91,7 @@ const vmCards = computed(() => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
const cancel = async() => {
|
const cancel = () => {
|
||||||
const inStore = store.getters['currentProduct'].inStore;
|
|
||||||
const allProviders = store.getters[`${ inStore }/all`](HCI.FORKLIFT_PROVIDER) || [];
|
|
||||||
const provider = allProviders.find((p) => p.metadata.name === providerName.value && p.metadata.namespace === NAMESPACE);
|
|
||||||
|
|
||||||
if (provider) {
|
|
||||||
await provider.remove();
|
|
||||||
}
|
|
||||||
|
|
||||||
currentRouter().push({
|
currentRouter().push({
|
||||||
name: `${ PRODUCT_NAME }-c-cluster-forklift`,
|
name: `${ PRODUCT_NAME }-c-cluster-forklift`,
|
||||||
params: {
|
params: {
|
||||||
@ -111,7 +105,51 @@ const startMigration = async(buttonCb) => {
|
|||||||
const inStore = store.getters['currentProduct'].inStore;
|
const inStore = store.getters['currentProduct'].inStore;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const planName = `${ providerName.value }`;
|
// Rename NetworkMap and StorageMap to use the plan name
|
||||||
|
const allNetworkMaps = store.getters[`${ inStore }/all`](HCI.FORKLIFT_NETWORK_MAP) || [];
|
||||||
|
const allStorageMaps = store.getters[`${ inStore }/all`](HCI.FORKLIFT_STORAGE_MAP) || [];
|
||||||
|
|
||||||
|
const currentNetworkMap = allNetworkMaps.find((nm) => nm.metadata.name === networkMapName.value && nm.metadata.namespace === NAMESPACE);
|
||||||
|
const currentStorageMap = allStorageMaps.find((sm) => sm.metadata.name === storageMapName.value && sm.metadata.namespace === NAMESPACE);
|
||||||
|
|
||||||
|
const newNetworkMapName = `${ planName.value }-network-map`;
|
||||||
|
const newStorageMapName = `${ planName.value }-storage-map`;
|
||||||
|
|
||||||
|
// Recreate NetworkMap with new name
|
||||||
|
let finalNetworkMapName = networkMapName.value;
|
||||||
|
|
||||||
|
if (currentNetworkMap) {
|
||||||
|
const networkMapData = await store.dispatch(`${ inStore }/create`, {
|
||||||
|
type: HCI.FORKLIFT_NETWORK_MAP,
|
||||||
|
metadata: {
|
||||||
|
name: newNetworkMapName,
|
||||||
|
namespace: NAMESPACE,
|
||||||
|
},
|
||||||
|
spec: currentNetworkMap.spec,
|
||||||
|
});
|
||||||
|
|
||||||
|
await networkMapData.save();
|
||||||
|
await currentNetworkMap.remove();
|
||||||
|
finalNetworkMapName = newNetworkMapName;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Recreate StorageMap with new name
|
||||||
|
let finalStorageMapName = storageMapName.value;
|
||||||
|
|
||||||
|
if (currentStorageMap) {
|
||||||
|
const storageMapData = await store.dispatch(`${ inStore }/create`, {
|
||||||
|
type: HCI.FORKLIFT_STORAGE_MAP,
|
||||||
|
metadata: {
|
||||||
|
name: newStorageMapName,
|
||||||
|
namespace: NAMESPACE,
|
||||||
|
},
|
||||||
|
spec: currentStorageMap.spec,
|
||||||
|
});
|
||||||
|
|
||||||
|
await storageMapData.save();
|
||||||
|
await currentStorageMap.remove();
|
||||||
|
finalStorageMapName = newStorageMapName;
|
||||||
|
}
|
||||||
|
|
||||||
const planSpec = {
|
const planSpec = {
|
||||||
provider: {
|
provider: {
|
||||||
@ -132,13 +170,13 @@ const startMigration = async(buttonCb) => {
|
|||||||
network: {
|
network: {
|
||||||
apiVersion: 'forklift.konveyor.io/v1beta1',
|
apiVersion: 'forklift.konveyor.io/v1beta1',
|
||||||
kind: 'NetworkMap',
|
kind: 'NetworkMap',
|
||||||
name: networkMapName.value,
|
name: finalNetworkMapName,
|
||||||
namespace: NAMESPACE,
|
namespace: NAMESPACE,
|
||||||
},
|
},
|
||||||
storage: {
|
storage: {
|
||||||
apiVersion: 'forklift.konveyor.io/v1beta1',
|
apiVersion: 'forklift.konveyor.io/v1beta1',
|
||||||
kind: 'StorageMap',
|
kind: 'StorageMap',
|
||||||
name: storageMapName.value,
|
name: finalStorageMapName,
|
||||||
namespace: NAMESPACE,
|
namespace: NAMESPACE,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -153,7 +191,7 @@ const startMigration = async(buttonCb) => {
|
|||||||
const plan = await store.dispatch(`${ inStore }/create`, {
|
const plan = await store.dispatch(`${ inStore }/create`, {
|
||||||
type: HCI.FORKLIFT_PLAN,
|
type: HCI.FORKLIFT_PLAN,
|
||||||
metadata: {
|
metadata: {
|
||||||
name: planName,
|
name: planName.value,
|
||||||
namespace: NAMESPACE,
|
namespace: NAMESPACE,
|
||||||
},
|
},
|
||||||
spec: planSpec,
|
spec: planSpec,
|
||||||
@ -171,7 +209,7 @@ const startMigration = async(buttonCb) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Create Migration owned by Plan
|
// Create Migration owned by Plan
|
||||||
const migrationName = `${ planName }-migration-${ Math.random().toString(36).substring(2, 7) }`;
|
const migrationName = `${ planName.value }-migration-${ Math.random().toString(36).substring(2, 7) }`;
|
||||||
|
|
||||||
const migration = await store.dispatch(`${ inStore }/create`, {
|
const migration = await store.dispatch(`${ inStore }/create`, {
|
||||||
type: HCI.FORKLIFT_MIGRATION,
|
type: HCI.FORKLIFT_MIGRATION,
|
||||||
@ -184,7 +222,7 @@ const startMigration = async(buttonCb) => {
|
|||||||
plan: {
|
plan: {
|
||||||
apiVersion: 'forklift.konveyor.io/v1beta1',
|
apiVersion: 'forklift.konveyor.io/v1beta1',
|
||||||
kind: 'Plan',
|
kind: 'Plan',
|
||||||
name: planName,
|
name: planName.value,
|
||||||
namespace: NAMESPACE,
|
namespace: NAMESPACE,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -192,16 +230,21 @@ const startMigration = async(buttonCb) => {
|
|||||||
|
|
||||||
await migration.save();
|
await migration.save();
|
||||||
|
|
||||||
// Set Plan as owner of Provider (Provider already owns NetworkMap, StorageMap, Secret)
|
// Set Plan as owner of the newly created NetworkMap and StorageMap
|
||||||
const allProviders = store.getters[`${ inStore }/all`](HCI.FORKLIFT_PROVIDER) || [];
|
const updatedNetworkMaps = store.getters[`${ inStore }/all`](HCI.FORKLIFT_NETWORK_MAP) || [];
|
||||||
const provider = allProviders.find((p) => p.metadata.name === providerName.value && p.metadata.namespace === NAMESPACE);
|
const updatedStorageMaps = store.getters[`${ inStore }/all`](HCI.FORKLIFT_STORAGE_MAP) || [];
|
||||||
|
|
||||||
if (provider) {
|
const newNetworkMap = updatedNetworkMaps.find((nm) => nm.metadata.name === finalNetworkMapName && nm.metadata.namespace === NAMESPACE);
|
||||||
provider.metadata.ownerReferences = [
|
const newStorageMap = updatedStorageMaps.find((sm) => sm.metadata.name === finalStorageMapName && sm.metadata.namespace === NAMESPACE);
|
||||||
...(provider.metadata.ownerReferences || []),
|
|
||||||
planOwnerRef,
|
if (newNetworkMap) {
|
||||||
];
|
newNetworkMap.metadata.ownerReferences = [planOwnerRef];
|
||||||
await provider.save();
|
await newNetworkMap.save();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newStorageMap) {
|
||||||
|
newStorageMap.metadata.ownerReferences = [planOwnerRef];
|
||||||
|
await newStorageMap.save();
|
||||||
}
|
}
|
||||||
|
|
||||||
currentRouter().push({
|
currentRouter().push({
|
||||||
@ -223,6 +266,8 @@ const init = async() => {
|
|||||||
const inStore = store.getters['currentProduct'].inStore;
|
const inStore = store.getters['currentProduct'].inStore;
|
||||||
|
|
||||||
await store.dispatch(`${ inStore }/findAll`, { type: HCI.FORKLIFT_PROVIDER }).catch(() => {});
|
await store.dispatch(`${ inStore }/findAll`, { type: HCI.FORKLIFT_PROVIDER }).catch(() => {});
|
||||||
|
await store.dispatch(`${ inStore }/findAll`, { type: HCI.FORKLIFT_NETWORK_MAP }).catch(() => {});
|
||||||
|
await store.dispatch(`${ inStore }/findAll`, { type: HCI.FORKLIFT_STORAGE_MAP }).catch(() => {});
|
||||||
|
|
||||||
const allProviders = store.getters[`${ inStore }/all`](HCI.FORKLIFT_PROVIDER) || [];
|
const allProviders = store.getters[`${ inStore }/all`](HCI.FORKLIFT_PROVIDER) || [];
|
||||||
const provider = allProviders.find((p) => p.metadata.name === providerName.value && p.metadata.namespace === NAMESPACE);
|
const provider = allProviders.find((p) => p.metadata.name === providerName.value && p.metadata.namespace === NAMESPACE);
|
||||||
@ -250,24 +295,53 @@ const init = async() => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const networkMappingsParam = currentRoute().query.networkMappings;
|
// Build mapping display data from the actual NetworkMap/StorageMap resources
|
||||||
|
const allNetworkMaps = store.getters[`${ inStore }/all`](HCI.FORKLIFT_NETWORK_MAP) || [];
|
||||||
|
const allStorageMaps = store.getters[`${ inStore }/all`](HCI.FORKLIFT_STORAGE_MAP) || [];
|
||||||
|
|
||||||
if (networkMappingsParam) {
|
const networkMap = allNetworkMaps.find((nm) => nm.metadata.name === networkMapName.value && nm.metadata.namespace === NAMESPACE);
|
||||||
try {
|
const storageMap = allStorageMaps.find((sm) => sm.metadata.name === storageMapName.value && sm.metadata.namespace === NAMESPACE);
|
||||||
networkMappings.value = JSON.parse(networkMappingsParam);
|
|
||||||
} catch (e) {
|
if (networkMap?.spec?.map) {
|
||||||
networkMappings.value = [];
|
networkMappings.value = networkMap.spec.map.map((m) => {
|
||||||
}
|
const source = m.source?.name || m.source?.id || 'Unknown';
|
||||||
|
let target = '';
|
||||||
|
|
||||||
|
if (m.destination?.type === 'pod') {
|
||||||
|
target = 'Pod Networking';
|
||||||
|
} else if (m.destination?.type === 'ignored') {
|
||||||
|
target = 'Ignored';
|
||||||
|
} else if (m.destination?.type === 'multus' && m.destination?.name) {
|
||||||
|
target = m.destination.namespace ? `${ m.destination.namespace }/${ m.destination.name }` : m.destination.name;
|
||||||
}
|
}
|
||||||
|
|
||||||
const storageMappingsParam = currentRoute().query.storageMappings;
|
// Find which VMs use this network
|
||||||
|
const sourceId = m.source?.id;
|
||||||
|
const usedBy = vms.value
|
||||||
|
.filter((vm) => vm.networks?.some((n) => n.id === sourceId || n.name === m.source?.name))
|
||||||
|
.map((vm) => vm.name || vm.id);
|
||||||
|
|
||||||
if (storageMappingsParam) {
|
return {
|
||||||
try {
|
source, target, usedBy
|
||||||
storageMappings.value = JSON.parse(storageMappingsParam);
|
};
|
||||||
} catch (e) {
|
});
|
||||||
storageMappings.value = [];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (storageMap?.spec?.map) {
|
||||||
|
storageMappings.value = storageMap.spec.map.map((m) => {
|
||||||
|
const source = m.source?.name || m.source?.id || 'Unknown';
|
||||||
|
const target = m.destination?.storageClass || '';
|
||||||
|
|
||||||
|
// Find which VMs use this datastore
|
||||||
|
const sourceId = m.source?.id;
|
||||||
|
const usedBy = vms.value
|
||||||
|
.filter((vm) => vm.disks?.some((d) => d.datastore?.id === sourceId || d.datastore?.name === m.source?.name))
|
||||||
|
.map((vm) => vm.name || vm.id);
|
||||||
|
|
||||||
|
return {
|
||||||
|
source, target, usedBy
|
||||||
|
};
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
loading.value = false;
|
loading.value = false;
|
||||||
@ -295,6 +369,15 @@ init();
|
|||||||
</template>
|
</template>
|
||||||
</Masthead>
|
</Masthead>
|
||||||
|
|
||||||
|
<div class="mb-20">
|
||||||
|
<LabeledInput
|
||||||
|
v-model:value="planName"
|
||||||
|
:label="t('harvester.addons.forklift.reviewMigration.planName')"
|
||||||
|
:placeholder="t('harvester.addons.forklift.reviewMigration.planNamePlaceholder')"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Migration Details Summary -->
|
<!-- Migration Details Summary -->
|
||||||
<div class="migration-details">
|
<div class="migration-details">
|
||||||
<h3 class="section-title">
|
<h3 class="section-title">
|
||||||
@ -412,6 +495,7 @@ init();
|
|||||||
{{ t('generic.cancel') }}
|
{{ t('generic.cancel') }}
|
||||||
</button>
|
</button>
|
||||||
<AsyncButton
|
<AsyncButton
|
||||||
|
:disabled="!planName"
|
||||||
:action-label="t('harvester.addons.forklift.reviewMigration.startMigration')"
|
:action-label="t('harvester.addons.forklift.reviewMigration.startMigration')"
|
||||||
:waiting-label="t('harvester.addons.forklift.reviewMigration.startMigration')"
|
:waiting-label="t('harvester.addons.forklift.reviewMigration.startMigration')"
|
||||||
:success-label="t('harvester.addons.forklift.reviewMigration.startMigration')"
|
:success-label="t('harvester.addons.forklift.reviewMigration.startMigration')"
|
||||||
|
|||||||
@ -147,15 +147,7 @@ const onSelect = (rows) => {
|
|||||||
selectedVMs.value = rows.map((r) => r._original);
|
selectedVMs.value = rows.map((r) => r._original);
|
||||||
};
|
};
|
||||||
|
|
||||||
const cancel = async() => {
|
const cancel = () => {
|
||||||
const inStore = store.getters['currentProduct'].inStore;
|
|
||||||
const providers = store.getters[`${ inStore }/all`](HCI.FORKLIFT_PROVIDER) || [];
|
|
||||||
const found = providers.find((p) => p.metadata.name === providerName.value && p.metadata.namespace === 'forklift');
|
|
||||||
|
|
||||||
if (found) {
|
|
||||||
await found.remove();
|
|
||||||
}
|
|
||||||
|
|
||||||
currentRouter().push({
|
currentRouter().push({
|
||||||
name: `${ PRODUCT_NAME }-c-cluster-forklift`,
|
name: `${ PRODUCT_NAME }-c-cluster-forklift`,
|
||||||
params: {
|
params: {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user