mirror of
https://github.com/harvester/harvester-ui-extension.git
synced 2026-10-06 22:26:05 +08:00
feat(forklift): small fixes
Signed-off-by: Marcelo Fukumoto <marcelo.fukumoto@suse.com>
This commit is contained in:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user