mirror of
https://github.com/harvester/harvester-ui-extension.git
synced 2026-08-16 04:39:15 +00:00
refactor: forklift UI bug and enhancement (#989)
* fix: hide Virtul machine migration tabs if forklift addon is not enabled Signed-off-by: Andy Lee <andy.lee@suse.com> * fix: rename advanced to configurations Signed-off-by: Andy Lee <andy.lee@suse.com> * fix: fixed column width Signed-off-by: Andy Lee <andy.lee@suse.com> * refactor: review comment Signed-off-by: Andy Lee <andy.lee@suse.com> * revert: power on background color Signed-off-by: Andy Lee <andy.lee@suse.com> --------- Signed-off-by: Andy Lee <andy.lee@suse.com>
This commit is contained in:
parent
7d09cb7142
commit
41cf44d231
@ -402,6 +402,7 @@ defineExpose({ testConnection, clickTestButton });
|
||||
<div v-if="!createOnly && !editMode">
|
||||
<LabeledSelect
|
||||
v-model:value="selectedProvider"
|
||||
required
|
||||
:label="t('harvester.addons.vmMigration.configureProvider.providerSelect')"
|
||||
:options="providerOptions"
|
||||
:reduce="(opt) => opt.value"
|
||||
|
||||
@ -107,18 +107,21 @@ const headers = [
|
||||
labelKey: 'harvester.addons.vmMigration.selectVms.columns.os',
|
||||
value: 'os',
|
||||
sort: ['os'],
|
||||
width: 180,
|
||||
},
|
||||
{
|
||||
name: 'resources',
|
||||
labelKey: 'harvester.addons.vmMigration.selectVms.columns.resources',
|
||||
value: 'resources',
|
||||
sort: false,
|
||||
width: 140,
|
||||
},
|
||||
{
|
||||
name: 'powerState',
|
||||
labelKey: 'harvester.addons.vmMigration.selectVms.columns.powerState',
|
||||
value: 'powerState',
|
||||
sort: ['powerState'],
|
||||
width: 130,
|
||||
},
|
||||
{
|
||||
name: 'network',
|
||||
@ -126,6 +129,7 @@ const headers = [
|
||||
value: 'network',
|
||||
sort: ['network'],
|
||||
subLabel: t('harvester.addons.vmMigration.generic.identifier'),
|
||||
width: 200,
|
||||
},
|
||||
{
|
||||
name: 'datastore',
|
||||
@ -133,6 +137,7 @@ const headers = [
|
||||
value: 'datastore',
|
||||
sort: ['datastore'],
|
||||
subLabel: t('harvester.addons.vmMigration.generic.identifier'),
|
||||
width: 200,
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
@ -1378,7 +1378,7 @@ export function init($plugin, store) {
|
||||
virtualType({
|
||||
name: HCI.FORKLIFT_NETWORK_MAP,
|
||||
labelKey: 'harvester.addons.vmMigration.labels.networkMap',
|
||||
group: 'vmMigration::Advanced',
|
||||
group: 'vmMigration::Configurations',
|
||||
namespaced: true,
|
||||
route: {
|
||||
name: `${ PRODUCT_NAME }-c-cluster-resource`,
|
||||
@ -1405,7 +1405,7 @@ export function init($plugin, store) {
|
||||
virtualType({
|
||||
name: HCI.FORKLIFT_STORAGE_MAP,
|
||||
labelKey: 'harvester.addons.vmMigration.labels.storageMap',
|
||||
group: 'vmMigration::Advanced',
|
||||
group: 'vmMigration::Configurations',
|
||||
namespaced: true,
|
||||
route: {
|
||||
name: `${ PRODUCT_NAME }-c-cluster-resource`,
|
||||
@ -1433,7 +1433,7 @@ export function init($plugin, store) {
|
||||
virtualType({
|
||||
name: HCI.FORKLIFT_PLAN,
|
||||
labelKey: 'harvester.addons.vmMigration.labels.plan',
|
||||
group: 'vmMigration::Advanced',
|
||||
group: 'vmMigration::Configurations',
|
||||
namespaced: true,
|
||||
route: {
|
||||
name: `${ PRODUCT_NAME }-c-cluster-resource`,
|
||||
@ -1459,7 +1459,7 @@ export function init($plugin, store) {
|
||||
virtualType({
|
||||
name: HCI.FORKLIFT_MIGRATION,
|
||||
labelKey: 'harvester.addons.vmMigration.labels.migration',
|
||||
group: 'vmMigration::Advanced',
|
||||
group: 'vmMigration::Configurations',
|
||||
namespaced: true,
|
||||
route: {
|
||||
name: `${ PRODUCT_NAME }-c-cluster-resource`,
|
||||
@ -1479,11 +1479,16 @@ export function init($plugin, store) {
|
||||
}
|
||||
});
|
||||
|
||||
// Register the dashboard entry directly — it's a virtual type with no schema,
|
||||
// so it cannot go through registerAddonSideNav (which filters by schema).
|
||||
basicType(['forklift-create'], 'vmMigration');
|
||||
|
||||
// Enable SideNav based on Forklift Addon Status
|
||||
// The dashboard entry ('forklift-create') is a schema-less virtual type, so it
|
||||
// is registered with requireSchema:false to still be gated by the addon status.
|
||||
registerAddonSideNav(store, PRODUCT_NAME, {
|
||||
addonName: ADD_ONS.FORKLIFT_OPERATOR,
|
||||
resourceType: HCI.ADD_ONS,
|
||||
navGroup: 'vmMigration',
|
||||
requireSchema: false,
|
||||
types: ['forklift-create']
|
||||
});
|
||||
registerAddonSideNav(store, PRODUCT_NAME, {
|
||||
addonName: ADD_ONS.FORKLIFT_OPERATOR,
|
||||
resourceType: HCI.ADD_ONS,
|
||||
@ -1495,7 +1500,7 @@ export function init($plugin, store) {
|
||||
registerAddonSideNav(store, PRODUCT_NAME, {
|
||||
addonName: ADD_ONS.FORKLIFT_OPERATOR,
|
||||
resourceType: HCI.ADD_ONS,
|
||||
navGroup: 'vmMigration::Advanced',
|
||||
navGroup: 'vmMigration::Configurations',
|
||||
types: [
|
||||
HCI.FORKLIFT_NETWORK_MAP,
|
||||
HCI.FORKLIFT_STORAGE_MAP,
|
||||
|
||||
@ -36,7 +36,8 @@ nav:
|
||||
Logging: Logging
|
||||
'Monitoring and Logging': Monitoring and Logging
|
||||
vmimport: Virtual Machine Imports
|
||||
vmMigration: VM Migration
|
||||
vmMigration: Virtual Machine Migration
|
||||
Configurations: Configurations
|
||||
|
||||
resourceTable:
|
||||
groupBy:
|
||||
@ -378,7 +379,7 @@ harvester:
|
||||
vmMigrationMapSourceProvider: Source Provider
|
||||
vmMigrationMapDestProvider: Destination Provider
|
||||
vmMigrationPlanTargetNs: Target Namespace
|
||||
vmMigrationPlanVmCount: VMs
|
||||
vmMigrationPlanVmCount: VM Count
|
||||
vmMigrationMigrationPlan: Plan
|
||||
tab:
|
||||
volume: Volumes
|
||||
@ -1855,7 +1856,7 @@ harvester:
|
||||
connectionFailed: Connection failed
|
||||
providerNotReady: Provider not ready
|
||||
labels:
|
||||
dashboard: Migrations
|
||||
dashboard: Migration Overview
|
||||
provider: Providers
|
||||
networkMap: Network Maps
|
||||
storageMap: Storage Maps
|
||||
@ -1937,8 +1938,8 @@ harvester:
|
||||
network: Network
|
||||
datastore: Datastore
|
||||
dashboard:
|
||||
title: VMware to Harvester Migration
|
||||
description: Migrate virtual machines from VMware vSphere to Harvester using cold migration for data consistency. Create a migration plan to select VMs, map networks and storage, and prepare a migration to Harvester.
|
||||
title: Virtual Machine Migration Overview
|
||||
description: "Migrate virtual machines from VMware vSphere to Harvester using cold migration for data consistency. Create a migration plan to select VMs, map networks and storage, and prepare a migration to Harvester."
|
||||
createPlan: Create Migration
|
||||
tableTitle: Migrations
|
||||
columns:
|
||||
|
||||
@ -14,6 +14,17 @@ export default class ForkliftPlan extends HarvesterResource {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* The base HarvesterResource sets `doneOverride` to `listLocation`, which would
|
||||
* force every single-item delete to redirect to the VM Migration dashboard.
|
||||
* Return undefined so the delete flow stays on the current page (list/dashboard)
|
||||
* and only falls back to the shell's default navigation when a now-invalid
|
||||
* detail page is being viewed.
|
||||
*/
|
||||
get doneOverride() {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
get planFailed() {
|
||||
const conditions = this.status?.conditions || [];
|
||||
|
||||
@ -247,7 +258,7 @@ export default class ForkliftPlan extends HarvesterResource {
|
||||
|
||||
/**
|
||||
* Deleting a Plan cascades via ownerReferences set at creation time.
|
||||
* Kubernetes GC will automatically delete: Migration, NetworkMap, StorageMap, Provider (→ Secret).
|
||||
* Kubernetes GC will automatically delete: Migration, NetworkMap, StorageMap.
|
||||
* Use foreground propagation to ensure children are deleted before the parent.
|
||||
*/
|
||||
remove(opt = {}) {
|
||||
|
||||
@ -11,6 +11,8 @@ import { SCHEMA } from '@shell/config/types';
|
||||
import { useI18n } from '@shell/composables/useI18n';
|
||||
import { HCI } from '../../../../types';
|
||||
import { PRODUCT_NAME } from '../../../../config/harvester';
|
||||
import { ADD_ONS } from '../../../../config/harvester-map';
|
||||
import { currentRouter } from '../../../../utils/router';
|
||||
import { FORKLIFT_PLAN_VM_COUNT } from '../../../../config/table-headers';
|
||||
import { STATE, NAME as NAME_COL, AGE } from '@shell/config/table-headers';
|
||||
|
||||
@ -158,6 +160,29 @@ const headers = [
|
||||
|
||||
const init = async() => {
|
||||
try {
|
||||
// Guard: the Forklift dashboard is only available when the Forklift addon is
|
||||
// enabled. Redirect back to the Harvester dashboard otherwise (e.g. direct URL).
|
||||
let forkliftEnabled = false;
|
||||
|
||||
if (store.getters[`${ inStore.value }/schemaFor`](HCI.ADD_ONS)) {
|
||||
const addons = await store.dispatch(`${ inStore.value }/findAll`, { type: HCI.ADD_ONS });
|
||||
|
||||
forkliftEnabled = addons.find((a) => a.metadata?.name === ADD_ONS.FORKLIFT_OPERATOR)?.spec?.enabled === true;
|
||||
}
|
||||
|
||||
if (!forkliftEnabled) {
|
||||
await currentRouter().replace({
|
||||
name: `${ PRODUCT_NAME }-c-cluster-resource`,
|
||||
params: {
|
||||
product: store.getters['productId'],
|
||||
cluster: store.getters['clusterId'],
|
||||
resource: HCI.DASHBOARD,
|
||||
},
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
await Promise.all([
|
||||
store.dispatch(`${ inStore.value }/findAll`, { type: HCI.FORKLIFT_PLAN }),
|
||||
store.dispatch(`${ inStore.value }/findAll`, { type: HCI.FORKLIFT_NETWORK_MAP }),
|
||||
@ -189,9 +214,10 @@ init();
|
||||
>
|
||||
<template #subHeader>
|
||||
<div class="mmt-5">
|
||||
<p class="text-muted">
|
||||
{{ t('harvester.addons.vmMigration.dashboard.description') }}
|
||||
</p>
|
||||
<p
|
||||
v-clean-html="t('harvester.addons.vmMigration.dashboard.description')"
|
||||
class="text-muted"
|
||||
></p>
|
||||
</div>
|
||||
</template>
|
||||
<template #createButton>
|
||||
@ -213,11 +239,6 @@ init();
|
||||
:search="false"
|
||||
key-field="_key"
|
||||
>
|
||||
<template #header-left>
|
||||
<h3 class="table-title m-0">
|
||||
{{ t('harvester.addons.vmMigration.dashboard.tableTitle') }}
|
||||
</h3>
|
||||
</template>
|
||||
<template #cell:name="{ row }">
|
||||
<div class="plan-name-cell">
|
||||
<div class="plan-name">
|
||||
|
||||
@ -8,9 +8,11 @@
|
||||
* @param {String} config.resourceType - The schema ID for addons.
|
||||
* @param {String} config.navGroup - The group name in the side nav.
|
||||
* @param {Array<String>} config.types - Array of Resource IDs to show/hide.
|
||||
* @param {Boolean} [config.requireSchema=true] - When true, only types with an
|
||||
* accessible schema are shown. Set false for schema-less virtual types.
|
||||
*/
|
||||
export function registerAddonSideNav(store, productName, {
|
||||
addonName, resourceType, navGroup, types
|
||||
addonName, resourceType, navGroup, types, requireSchema = true
|
||||
}) {
|
||||
if (typeof window === 'undefined') {
|
||||
return;
|
||||
@ -57,7 +59,7 @@ export function registerAddonSideNav(store, productName, {
|
||||
|
||||
// Adds or removes the resource IDs from the product visibility whitelist.
|
||||
const setMenuVisibility = (visible) => {
|
||||
const accessibleTypes = visible ? types.filter(hasAccessibleSchema) : [];
|
||||
const accessibleTypes = visible ? (requireSchema ? types.filter(hasAccessibleSchema) : types) : [];
|
||||
|
||||
// Always clear first to remove any previously-registered types that are
|
||||
// no longer accessible (e.g. partial permission changes like types=[A,B] where B is dropped).
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user