mirror of
https://github.com/harvester/harvester-ui-extension.git
synced 2026-08-16 12:49:14 +00:00
feat(forklift): UI based on the FIGMA
Signed-off-by: Marcelo Fukumoto <marcelo.fukumoto@suse.com>
This commit is contained in:
parent
7d8d74ce17
commit
2259910be1
53
pkg/harvester/components/MappingsCell.vue
Normal file
53
pkg/harvester/components/MappingsCell.vue
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
<script setup>
|
||||||
|
defineProps({
|
||||||
|
networkEntries: {
|
||||||
|
type: Array,
|
||||||
|
default: () => [],
|
||||||
|
},
|
||||||
|
storageEntries: {
|
||||||
|
type: Array,
|
||||||
|
default: () => [],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="mappings-cell">
|
||||||
|
<div
|
||||||
|
v-for="(entry, idx) in networkEntries"
|
||||||
|
:key="'net-' + idx"
|
||||||
|
class="mapping-entry"
|
||||||
|
>
|
||||||
|
<i class="icon icon-cluster" />
|
||||||
|
<span>{{ entry }}</span>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-for="(entry, idx) in storageEntries"
|
||||||
|
:key="'stor-' + idx"
|
||||||
|
class="mapping-entry"
|
||||||
|
>
|
||||||
|
<i class="icon icon-storage" />
|
||||||
|
<span>{{ entry }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.mappings-cell {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mapping-entry {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
font-size: 13px;
|
||||||
|
color: var(--muted);
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -1341,10 +1341,8 @@ export function init($plugin, store) {
|
|||||||
AGE
|
AGE
|
||||||
]);
|
]);
|
||||||
configureType(HCI.FORKLIFT_PROVIDER, {
|
configureType(HCI.FORKLIFT_PROVIDER, {
|
||||||
resource: HCI.FORKLIFT_PROVIDER,
|
resource: HCI.FORKLIFT_PROVIDER,
|
||||||
resourceDetail: HCI.FORKLIFT_PROVIDER,
|
location: {
|
||||||
resourceEdit: HCI.FORKLIFT_PROVIDER,
|
|
||||||
location: {
|
|
||||||
name: `${ PRODUCT_NAME }-c-cluster-resource`,
|
name: `${ PRODUCT_NAME }-c-cluster-resource`,
|
||||||
params: { resource: HCI.FORKLIFT_PROVIDER }
|
params: { resource: HCI.FORKLIFT_PROVIDER }
|
||||||
}
|
}
|
||||||
@ -1352,7 +1350,7 @@ export function init($plugin, store) {
|
|||||||
virtualType({
|
virtualType({
|
||||||
name: HCI.FORKLIFT_PROVIDER,
|
name: HCI.FORKLIFT_PROVIDER,
|
||||||
labelKey: 'harvester.addons.forklift.labels.provider',
|
labelKey: 'harvester.addons.forklift.labels.provider',
|
||||||
group: 'forklift',
|
group: 'forklift::Advanced',
|
||||||
namespaced: true,
|
namespaced: true,
|
||||||
route: {
|
route: {
|
||||||
name: `${ PRODUCT_NAME }-c-cluster-resource`,
|
name: `${ PRODUCT_NAME }-c-cluster-resource`,
|
||||||
@ -1370,10 +1368,8 @@ export function init($plugin, store) {
|
|||||||
AGE
|
AGE
|
||||||
]);
|
]);
|
||||||
configureType(HCI.FORKLIFT_NETWORK_MAP, {
|
configureType(HCI.FORKLIFT_NETWORK_MAP, {
|
||||||
resource: HCI.FORKLIFT_NETWORK_MAP,
|
resource: HCI.FORKLIFT_NETWORK_MAP,
|
||||||
resourceDetail: HCI.FORKLIFT_NETWORK_MAP,
|
location: {
|
||||||
resourceEdit: HCI.FORKLIFT_NETWORK_MAP,
|
|
||||||
location: {
|
|
||||||
name: `${ PRODUCT_NAME }-c-cluster-resource`,
|
name: `${ PRODUCT_NAME }-c-cluster-resource`,
|
||||||
params: { resource: HCI.FORKLIFT_NETWORK_MAP }
|
params: { resource: HCI.FORKLIFT_NETWORK_MAP }
|
||||||
}
|
}
|
||||||
@ -1381,7 +1377,7 @@ export function init($plugin, store) {
|
|||||||
virtualType({
|
virtualType({
|
||||||
name: HCI.FORKLIFT_NETWORK_MAP,
|
name: HCI.FORKLIFT_NETWORK_MAP,
|
||||||
labelKey: 'harvester.addons.forklift.labels.networkMap',
|
labelKey: 'harvester.addons.forklift.labels.networkMap',
|
||||||
group: 'forklift',
|
group: 'forklift::Advanced',
|
||||||
namespaced: true,
|
namespaced: true,
|
||||||
route: {
|
route: {
|
||||||
name: `${ PRODUCT_NAME }-c-cluster-resource`,
|
name: `${ PRODUCT_NAME }-c-cluster-resource`,
|
||||||
@ -1399,10 +1395,8 @@ export function init($plugin, store) {
|
|||||||
AGE
|
AGE
|
||||||
]);
|
]);
|
||||||
configureType(HCI.FORKLIFT_STORAGE_MAP, {
|
configureType(HCI.FORKLIFT_STORAGE_MAP, {
|
||||||
resource: HCI.FORKLIFT_STORAGE_MAP,
|
resource: HCI.FORKLIFT_STORAGE_MAP,
|
||||||
resourceDetail: HCI.FORKLIFT_STORAGE_MAP,
|
location: {
|
||||||
resourceEdit: HCI.FORKLIFT_STORAGE_MAP,
|
|
||||||
location: {
|
|
||||||
name: `${ PRODUCT_NAME }-c-cluster-resource`,
|
name: `${ PRODUCT_NAME }-c-cluster-resource`,
|
||||||
params: { resource: HCI.FORKLIFT_STORAGE_MAP }
|
params: { resource: HCI.FORKLIFT_STORAGE_MAP }
|
||||||
}
|
}
|
||||||
@ -1410,7 +1404,7 @@ export function init($plugin, store) {
|
|||||||
virtualType({
|
virtualType({
|
||||||
name: HCI.FORKLIFT_STORAGE_MAP,
|
name: HCI.FORKLIFT_STORAGE_MAP,
|
||||||
labelKey: 'harvester.addons.forklift.labels.storageMap',
|
labelKey: 'harvester.addons.forklift.labels.storageMap',
|
||||||
group: 'forklift',
|
group: 'forklift::Advanced',
|
||||||
namespaced: true,
|
namespaced: true,
|
||||||
route: {
|
route: {
|
||||||
name: `${ PRODUCT_NAME }-c-cluster-resource`,
|
name: `${ PRODUCT_NAME }-c-cluster-resource`,
|
||||||
@ -1429,10 +1423,8 @@ export function init($plugin, store) {
|
|||||||
AGE
|
AGE
|
||||||
]);
|
]);
|
||||||
configureType(HCI.FORKLIFT_PLAN, {
|
configureType(HCI.FORKLIFT_PLAN, {
|
||||||
resource: HCI.FORKLIFT_PLAN,
|
resource: HCI.FORKLIFT_PLAN,
|
||||||
resourceDetail: HCI.FORKLIFT_PLAN,
|
location: {
|
||||||
resourceEdit: HCI.FORKLIFT_PLAN,
|
|
||||||
location: {
|
|
||||||
name: `${ PRODUCT_NAME }-c-cluster-resource`,
|
name: `${ PRODUCT_NAME }-c-cluster-resource`,
|
||||||
params: { resource: HCI.FORKLIFT_PLAN }
|
params: { resource: HCI.FORKLIFT_PLAN }
|
||||||
}
|
}
|
||||||
@ -1440,7 +1432,7 @@ export function init($plugin, store) {
|
|||||||
virtualType({
|
virtualType({
|
||||||
name: HCI.FORKLIFT_PLAN,
|
name: HCI.FORKLIFT_PLAN,
|
||||||
labelKey: 'harvester.addons.forklift.labels.plan',
|
labelKey: 'harvester.addons.forklift.labels.plan',
|
||||||
group: 'forklift',
|
group: 'forklift::Advanced',
|
||||||
namespaced: true,
|
namespaced: true,
|
||||||
route: {
|
route: {
|
||||||
name: `${ PRODUCT_NAME }-c-cluster-resource`,
|
name: `${ PRODUCT_NAME }-c-cluster-resource`,
|
||||||
@ -1457,10 +1449,8 @@ export function init($plugin, store) {
|
|||||||
AGE
|
AGE
|
||||||
]);
|
]);
|
||||||
configureType(HCI.FORKLIFT_MIGRATION, {
|
configureType(HCI.FORKLIFT_MIGRATION, {
|
||||||
resource: HCI.FORKLIFT_MIGRATION,
|
resource: HCI.FORKLIFT_MIGRATION,
|
||||||
resourceDetail: HCI.FORKLIFT_MIGRATION,
|
location: {
|
||||||
resourceEdit: HCI.FORKLIFT_MIGRATION,
|
|
||||||
location: {
|
|
||||||
name: `${ PRODUCT_NAME }-c-cluster-resource`,
|
name: `${ PRODUCT_NAME }-c-cluster-resource`,
|
||||||
params: { resource: HCI.FORKLIFT_MIGRATION }
|
params: { resource: HCI.FORKLIFT_MIGRATION }
|
||||||
}
|
}
|
||||||
@ -1468,19 +1458,38 @@ export function init($plugin, store) {
|
|||||||
virtualType({
|
virtualType({
|
||||||
name: HCI.FORKLIFT_MIGRATION,
|
name: HCI.FORKLIFT_MIGRATION,
|
||||||
labelKey: 'harvester.addons.forklift.labels.migration',
|
labelKey: 'harvester.addons.forklift.labels.migration',
|
||||||
group: 'forklift',
|
group: 'forklift::Advanced',
|
||||||
namespaced: true,
|
namespaced: true,
|
||||||
route: {
|
route: {
|
||||||
name: `${ PRODUCT_NAME }-c-cluster-resource`,
|
name: `${ PRODUCT_NAME }-c-cluster-resource`,
|
||||||
params: { resource: HCI.FORKLIFT_MIGRATION }
|
params: { resource: HCI.FORKLIFT_MIGRATION }
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
configureType('forklift-create', { subTypes: [HCI.FORKLIFT_PLAN] });
|
||||||
|
virtualType({
|
||||||
|
name: 'forklift-create',
|
||||||
|
labelKey: 'harvester.addons.forklift.labels.plan',
|
||||||
|
group: 'forklift',
|
||||||
|
namespaced: true,
|
||||||
|
route: {
|
||||||
|
name: `${ PRODUCT_NAME }-c-cluster-forklift`,
|
||||||
|
params: {}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// Enable SideNav based on Forklift Addon Status
|
// Enable SideNav based on Forklift Addon Status
|
||||||
registerAddonSideNav(store, PRODUCT_NAME, {
|
registerAddonSideNav(store, PRODUCT_NAME, {
|
||||||
addonName: ADD_ONS.FORKLIFT_OPERATOR,
|
addonName: ADD_ONS.FORKLIFT_OPERATOR,
|
||||||
resourceType: HCI.ADD_ONS,
|
resourceType: HCI.ADD_ONS,
|
||||||
navGroup: 'forklift',
|
navGroup: 'forklift',
|
||||||
|
types: [
|
||||||
|
'forklift-create',
|
||||||
|
]
|
||||||
|
});
|
||||||
|
registerAddonSideNav(store, PRODUCT_NAME, {
|
||||||
|
addonName: ADD_ONS.FORKLIFT_OPERATOR,
|
||||||
|
resourceType: HCI.ADD_ONS,
|
||||||
|
navGroup: 'forklift::Advanced',
|
||||||
types: [
|
types: [
|
||||||
HCI.FORKLIFT_PROVIDER,
|
HCI.FORKLIFT_PROVIDER,
|
||||||
HCI.FORKLIFT_NETWORK_MAP,
|
HCI.FORKLIFT_NETWORK_MAP,
|
||||||
|
|||||||
@ -286,7 +286,7 @@ export const FORKLIFT_PLAN_VM_COUNT = {
|
|||||||
labelKey: 'harvester.tableHeaders.forkliftPlanVmCount',
|
labelKey: 'harvester.tableHeaders.forkliftPlanVmCount',
|
||||||
value: 'spec.vms.length',
|
value: 'spec.vms.length',
|
||||||
sort: 'spec.vms.length',
|
sort: 'spec.vms.length',
|
||||||
align: 'center',
|
align: 'left',
|
||||||
};
|
};
|
||||||
|
|
||||||
// Plan reference column in forklift.konveyor.io.migration list page
|
// Plan reference column in forklift.konveyor.io.migration list page
|
||||||
|
|||||||
@ -1,383 +0,0 @@
|
|||||||
<script>
|
|
||||||
import CreateEditView from '@shell/mixins/create-edit-view';
|
|
||||||
import CruResource from '@shell/components/CruResource';
|
|
||||||
import Tabbed from '@shell/components/Tabbed';
|
|
||||||
import Tab from '@shell/components/Tabbed/Tab';
|
|
||||||
import LabelValue from '@shell/components/LabelValue';
|
|
||||||
import { Banner } from '@components/Banner';
|
|
||||||
import PercentageBar from '@shell/components/PercentageBar';
|
|
||||||
import BadgeState from '@components/BadgeState';
|
|
||||||
import { mapGetters } from 'vuex';
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: 'ForkliftMigrationDetail',
|
|
||||||
|
|
||||||
components: {
|
|
||||||
BadgeState,
|
|
||||||
Banner,
|
|
||||||
CruResource,
|
|
||||||
LabelValue,
|
|
||||||
PercentageBar,
|
|
||||||
Tabbed,
|
|
||||||
Tab,
|
|
||||||
},
|
|
||||||
|
|
||||||
mixins: [CreateEditView],
|
|
||||||
|
|
||||||
inheritAttrs: false,
|
|
||||||
|
|
||||||
props: {
|
|
||||||
value: {
|
|
||||||
type: Object,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
mode: {
|
|
||||||
type: String,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
computed: {
|
|
||||||
...mapGetters({ t: 'i18n/t' }),
|
|
||||||
|
|
||||||
planName() {
|
|
||||||
return this.value.spec?.plan?.name || '-';
|
|
||||||
},
|
|
||||||
|
|
||||||
planNamespace() {
|
|
||||||
return this.value.spec?.plan?.namespace || '-';
|
|
||||||
},
|
|
||||||
|
|
||||||
migrationStarted() {
|
|
||||||
return this.value.status?.started || '-';
|
|
||||||
},
|
|
||||||
|
|
||||||
migrationCompleted() {
|
|
||||||
return this.value.status?.completed || '-';
|
|
||||||
},
|
|
||||||
|
|
||||||
migrationConditions() {
|
|
||||||
return this.value.status?.conditions || [];
|
|
||||||
},
|
|
||||||
|
|
||||||
overallStatus() {
|
|
||||||
const succeeded = this.migrationConditions.find((c) => c.type === 'Succeeded' && c.status === 'True');
|
|
||||||
const running = this.migrationConditions.find((c) => c.type === 'Running' && c.status === 'True');
|
|
||||||
const failed = this.migrationConditions.find((c) => c.type === 'Failed' && c.status === 'True');
|
|
||||||
|
|
||||||
if (succeeded) {
|
|
||||||
return { text: this.t('harvester.addons.forklift.detail.status.succeeded'), color: 'bg-success' };
|
|
||||||
}
|
|
||||||
if (failed) {
|
|
||||||
return { text: this.t('harvester.addons.forklift.detail.status.failed'), color: 'bg-error' };
|
|
||||||
}
|
|
||||||
if (running) {
|
|
||||||
return { text: this.t('harvester.addons.forklift.detail.status.running'), color: 'bg-info' };
|
|
||||||
}
|
|
||||||
|
|
||||||
return { text: this.t('harvester.addons.forklift.detail.status.pending'), color: 'bg-darker' };
|
|
||||||
},
|
|
||||||
|
|
||||||
vms() {
|
|
||||||
return this.value.status?.vms || [];
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
methods: {
|
|
||||||
pipelineSteps(vm) {
|
|
||||||
return vm.pipeline || [];
|
|
||||||
},
|
|
||||||
|
|
||||||
stepProgress(step) {
|
|
||||||
if (step.phase === 'Completed') {
|
|
||||||
return 100;
|
|
||||||
}
|
|
||||||
if (step.phase === 'Pending') {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
const total = step.progress?.total || 0;
|
|
||||||
const completed = step.progress?.completed || 0;
|
|
||||||
|
|
||||||
if (total === 0) {
|
|
||||||
return step.phase === 'Running' ? 50 : 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
return Math.round((completed / total) * 100);
|
|
||||||
},
|
|
||||||
|
|
||||||
stepBadgeColor(phase) {
|
|
||||||
switch (phase) {
|
|
||||||
case 'Completed':
|
|
||||||
return 'bg-success';
|
|
||||||
case 'Running':
|
|
||||||
return 'bg-info';
|
|
||||||
case 'Pending':
|
|
||||||
return 'bg-darker';
|
|
||||||
default:
|
|
||||||
return 'bg-warning';
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
vmOverallProgress(vm) {
|
|
||||||
const steps = vm.pipeline || [];
|
|
||||||
|
|
||||||
if (steps.length === 0) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
const completed = steps.filter((s) => s.phase === 'Completed').length;
|
|
||||||
|
|
||||||
return Math.round((completed / steps.length) * 100);
|
|
||||||
},
|
|
||||||
|
|
||||||
formatBytes(mb) {
|
|
||||||
if (mb >= 1024) {
|
|
||||||
return `${ (mb / 1024).toFixed(1) } GB`;
|
|
||||||
}
|
|
||||||
|
|
||||||
return `${ mb } MB`;
|
|
||||||
},
|
|
||||||
|
|
||||||
formatDiskProgress(step) {
|
|
||||||
const unit = step.annotations?.unit || 'MB';
|
|
||||||
const completed = step.progress?.completed || 0;
|
|
||||||
const total = step.progress?.total || 0;
|
|
||||||
|
|
||||||
if (unit === 'MB') {
|
|
||||||
return `${ this.formatBytes(completed) } / ${ this.formatBytes(total) }`;
|
|
||||||
}
|
|
||||||
|
|
||||||
return `${ completed } / ${ total }`;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<CruResource
|
|
||||||
:done-route="doneRoute"
|
|
||||||
:resource="value"
|
|
||||||
:mode="mode"
|
|
||||||
:can-yaml="false"
|
|
||||||
>
|
|
||||||
<div class="row mb-20">
|
|
||||||
<div class="col span-3">
|
|
||||||
<LabelValue
|
|
||||||
:name="t('harvester.addons.forklift.detail.plan')"
|
|
||||||
:value="planName"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div class="col span-3">
|
|
||||||
<LabelValue
|
|
||||||
:name="t('harvester.addons.forklift.detail.planNamespace')"
|
|
||||||
:value="planNamespace"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div class="col span-3">
|
|
||||||
<LabelValue :name="t('harvester.addons.forklift.detail.status.label')">
|
|
||||||
<template #value>
|
|
||||||
<BadgeState
|
|
||||||
:label="overallStatus.text"
|
|
||||||
:color="overallStatus.color"
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
</LabelValue>
|
|
||||||
</div>
|
|
||||||
<div class="col span-3">
|
|
||||||
<LabelValue
|
|
||||||
:name="t('harvester.addons.forklift.detail.started')"
|
|
||||||
:value="migrationStarted"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<Tabbed
|
|
||||||
v-bind="$attrs"
|
|
||||||
class="mt-15"
|
|
||||||
:side-tabs="true"
|
|
||||||
>
|
|
||||||
<Tab
|
|
||||||
v-for="(vm, vmIdx) in vms"
|
|
||||||
:key="vm.id"
|
|
||||||
:name="'vm-' + vmIdx"
|
|
||||||
:label="vm.name || vm.id"
|
|
||||||
:weight="100 - vmIdx"
|
|
||||||
>
|
|
||||||
<div class="row mb-20">
|
|
||||||
<div class="col span-3">
|
|
||||||
<LabelValue
|
|
||||||
:name="t('harvester.addons.forklift.detail.vmId')"
|
|
||||||
:value="vm.id"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div class="col span-3">
|
|
||||||
<LabelValue :name="t('harvester.addons.forklift.detail.vmPhase')">
|
|
||||||
<template #value>
|
|
||||||
<BadgeState
|
|
||||||
:label="vm.phase"
|
|
||||||
:color="stepBadgeColor(vm.phase)"
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
</LabelValue>
|
|
||||||
</div>
|
|
||||||
<div class="col span-3">
|
|
||||||
<LabelValue
|
|
||||||
:name="t('harvester.addons.forklift.detail.vmStarted')"
|
|
||||||
:value="vm.started || '-'"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div class="col span-3">
|
|
||||||
<LabelValue
|
|
||||||
:name="t('harvester.addons.forklift.detail.vmCompleted')"
|
|
||||||
:value="vm.completed || '-'"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="row mb-20">
|
|
||||||
<div class="col span-12">
|
|
||||||
<LabelValue :name="t('harvester.addons.forklift.detail.overallProgress')">
|
|
||||||
<template #value>
|
|
||||||
<PercentageBar :value="vmOverallProgress(vm)" />
|
|
||||||
</template>
|
|
||||||
</LabelValue>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<h3 class="mb-10">
|
|
||||||
{{ t('harvester.addons.forklift.detail.pipelineSteps') }}
|
|
||||||
</h3>
|
|
||||||
|
|
||||||
<div class="pipeline">
|
|
||||||
<div
|
|
||||||
v-for="(step, stepIdx) in pipelineSteps(vm)"
|
|
||||||
:key="stepIdx"
|
|
||||||
class="pipeline-step"
|
|
||||||
>
|
|
||||||
<div class="step-header">
|
|
||||||
<div class="step-name">
|
|
||||||
<span class="step-index">{{ stepIdx + 1 }}.</span>
|
|
||||||
{{ step.name }}
|
|
||||||
</div>
|
|
||||||
<BadgeState
|
|
||||||
:label="step.phase"
|
|
||||||
:color="stepBadgeColor(step.phase)"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<p class="text-muted step-description">
|
|
||||||
{{ step.description }}
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<PercentageBar
|
|
||||||
:value="stepProgress(step)"
|
|
||||||
class="mb-5"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<div
|
|
||||||
v-if="step.annotations && step.annotations.unit"
|
|
||||||
class="step-disk-progress text-muted"
|
|
||||||
>
|
|
||||||
{{ formatDiskProgress(step) }}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div
|
|
||||||
v-if="step.tasks && step.tasks.length"
|
|
||||||
class="step-tasks mt-10"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
v-for="(task, taskIdx) in step.tasks"
|
|
||||||
:key="taskIdx"
|
|
||||||
class="task-row"
|
|
||||||
>
|
|
||||||
<span class="task-name text-muted">{{ task.name }}</span>
|
|
||||||
<span
|
|
||||||
v-if="task.progress"
|
|
||||||
class="task-progress text-muted"
|
|
||||||
>
|
|
||||||
{{ formatBytes(task.progress.completed || 0) }} / {{ formatBytes(task.progress.total || 0) }}
|
|
||||||
</span>
|
|
||||||
<BadgeState
|
|
||||||
v-if="task.phase"
|
|
||||||
:label="task.phase"
|
|
||||||
:color="stepBadgeColor(task.phase)"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<Banner
|
|
||||||
v-if="vm.conditions && vm.conditions.length"
|
|
||||||
:color="vm.conditions[0].type === 'Succeeded' ? 'success' : 'info'"
|
|
||||||
:label="vm.conditions[0].message"
|
|
||||||
class="mt-20"
|
|
||||||
/>
|
|
||||||
</Tab>
|
|
||||||
</Tabbed>
|
|
||||||
</CruResource>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.pipeline {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pipeline-step {
|
|
||||||
border: 1px solid var(--border);
|
|
||||||
border-radius: var(--border-radius);
|
|
||||||
padding: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.step-header {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
margin-bottom: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.step-name {
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
|
|
||||||
.step-index {
|
|
||||||
color: var(--muted);
|
|
||||||
margin-right: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.step-description {
|
|
||||||
font-size: 13px;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.step-disk-progress {
|
|
||||||
font-size: 12px;
|
|
||||||
text-align: right;
|
|
||||||
}
|
|
||||||
|
|
||||||
.step-tasks {
|
|
||||||
border-top: 1px solid var(--border);
|
|
||||||
padding-top: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.task-row {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 10px;
|
|
||||||
padding: 4px 0;
|
|
||||||
font-size: 12px;
|
|
||||||
|
|
||||||
.task-name {
|
|
||||||
flex: 1;
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.task-progress {
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@ -1,541 +0,0 @@
|
|||||||
<script>
|
|
||||||
import CreateEditView from '@shell/mixins/create-edit-view';
|
|
||||||
import CruResource from '@shell/components/CruResource';
|
|
||||||
import Tabbed from '@shell/components/Tabbed';
|
|
||||||
import Tab from '@shell/components/Tabbed/Tab';
|
|
||||||
import LabelValue from '@shell/components/LabelValue';
|
|
||||||
import { Banner } from '@components/Banner';
|
|
||||||
import PercentageBar from '@shell/components/PercentageBar';
|
|
||||||
import BadgeState from '@components/BadgeState';
|
|
||||||
import { mapGetters } from 'vuex';
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: 'ForkliftPlanDetail',
|
|
||||||
|
|
||||||
components: {
|
|
||||||
BadgeState,
|
|
||||||
Banner,
|
|
||||||
CruResource,
|
|
||||||
LabelValue,
|
|
||||||
PercentageBar,
|
|
||||||
Tabbed,
|
|
||||||
Tab,
|
|
||||||
},
|
|
||||||
|
|
||||||
mixins: [CreateEditView],
|
|
||||||
|
|
||||||
inheritAttrs: false,
|
|
||||||
|
|
||||||
props: {
|
|
||||||
value: {
|
|
||||||
type: Object,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
mode: {
|
|
||||||
type: String,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
computed: {
|
|
||||||
...mapGetters({ t: 'i18n/t' }),
|
|
||||||
|
|
||||||
sourceProvider() {
|
|
||||||
return this.value.spec?.provider?.source?.name || '-';
|
|
||||||
},
|
|
||||||
|
|
||||||
destProvider() {
|
|
||||||
return this.value.spec?.provider?.destination?.name || '-';
|
|
||||||
},
|
|
||||||
|
|
||||||
targetNamespace() {
|
|
||||||
return this.value.spec?.targetNamespace || '-';
|
|
||||||
},
|
|
||||||
|
|
||||||
networkMapName() {
|
|
||||||
return this.value.spec?.map?.network?.name || '-';
|
|
||||||
},
|
|
||||||
|
|
||||||
storageMapName() {
|
|
||||||
return this.value.spec?.map?.storage?.name || '-';
|
|
||||||
},
|
|
||||||
|
|
||||||
plannedVMs() {
|
|
||||||
return this.value.spec?.vms || [];
|
|
||||||
},
|
|
||||||
|
|
||||||
planConditions() {
|
|
||||||
return this.value.status?.conditions || [];
|
|
||||||
},
|
|
||||||
|
|
||||||
overallStatus() {
|
|
||||||
const succeeded = this.planConditions.find((c) => c.type === 'Succeeded' && c.status === 'True');
|
|
||||||
const executing = this.planConditions.find((c) => c.type === 'Executing' && c.status === 'True');
|
|
||||||
const failed = this.planConditions.find((c) => c.type === 'Failed' && c.status === 'True');
|
|
||||||
|
|
||||||
if (succeeded) {
|
|
||||||
return { text: this.t('harvester.addons.forklift.detail.status.succeeded'), color: 'bg-success' };
|
|
||||||
}
|
|
||||||
if (failed) {
|
|
||||||
return { text: this.t('harvester.addons.forklift.detail.status.failed'), color: 'bg-error' };
|
|
||||||
}
|
|
||||||
if (executing) {
|
|
||||||
return { text: this.t('harvester.addons.forklift.detail.status.executing'), color: 'bg-info' };
|
|
||||||
}
|
|
||||||
|
|
||||||
return { text: this.t('harvester.addons.forklift.detail.status.ready'), color: 'bg-darker' };
|
|
||||||
},
|
|
||||||
|
|
||||||
currentMigration() {
|
|
||||||
return this.value.status?.migration || null;
|
|
||||||
},
|
|
||||||
|
|
||||||
currentMigrationVMs() {
|
|
||||||
return this.currentMigration?.vms || [];
|
|
||||||
},
|
|
||||||
|
|
||||||
migrationHistory() {
|
|
||||||
return this.currentMigration?.history || [];
|
|
||||||
},
|
|
||||||
|
|
||||||
hasMigrationInProgress() {
|
|
||||||
return this.currentMigration?.started && !this.currentMigration?.completed;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
methods: {
|
|
||||||
vmOverallProgress(vm) {
|
|
||||||
const steps = vm.pipeline || [];
|
|
||||||
|
|
||||||
if (steps.length === 0) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
const completed = steps.filter((s) => s.phase === 'Completed').length;
|
|
||||||
|
|
||||||
return Math.round((completed / steps.length) * 100);
|
|
||||||
},
|
|
||||||
|
|
||||||
stepProgress(step) {
|
|
||||||
if (step.phase === 'Completed') {
|
|
||||||
return 100;
|
|
||||||
}
|
|
||||||
if (step.phase === 'Pending') {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
const total = step.progress?.total || 0;
|
|
||||||
const completed = step.progress?.completed || 0;
|
|
||||||
|
|
||||||
if (total === 0) {
|
|
||||||
return step.phase === 'Running' ? 50 : 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
return Math.round((completed / total) * 100);
|
|
||||||
},
|
|
||||||
|
|
||||||
stepBadgeColor(phase) {
|
|
||||||
switch (phase) {
|
|
||||||
case 'Completed':
|
|
||||||
return 'bg-success';
|
|
||||||
case 'Running':
|
|
||||||
return 'bg-info';
|
|
||||||
case 'Pending':
|
|
||||||
return 'bg-darker';
|
|
||||||
default:
|
|
||||||
return 'bg-warning';
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
formatBytes(mb) {
|
|
||||||
if (mb >= 1024) {
|
|
||||||
return `${ (mb / 1024).toFixed(1) } GB`;
|
|
||||||
}
|
|
||||||
|
|
||||||
return `${ mb } MB`;
|
|
||||||
},
|
|
||||||
|
|
||||||
formatDiskProgress(step) {
|
|
||||||
const unit = step.annotations?.unit || 'MB';
|
|
||||||
const completed = step.progress?.completed || 0;
|
|
||||||
const total = step.progress?.total || 0;
|
|
||||||
|
|
||||||
if (unit === 'MB') {
|
|
||||||
return `${ this.formatBytes(completed) } / ${ this.formatBytes(total) }`;
|
|
||||||
}
|
|
||||||
|
|
||||||
return `${ completed } / ${ total }`;
|
|
||||||
},
|
|
||||||
|
|
||||||
historyStatus(entry) {
|
|
||||||
const conditions = entry.conditions || [];
|
|
||||||
const succeeded = conditions.find((c) => c.type === 'Succeeded');
|
|
||||||
const executing = conditions.find((c) => c.type === 'Executing');
|
|
||||||
const failed = conditions.find((c) => c.type === 'Failed');
|
|
||||||
|
|
||||||
if (succeeded) {
|
|
||||||
return { text: this.t('harvester.addons.forklift.detail.status.succeeded'), color: 'bg-success' };
|
|
||||||
}
|
|
||||||
if (failed) {
|
|
||||||
return { text: this.t('harvester.addons.forklift.detail.status.failed'), color: 'bg-error' };
|
|
||||||
}
|
|
||||||
if (executing) {
|
|
||||||
return { text: this.t('harvester.addons.forklift.detail.status.executing'), color: 'bg-info' };
|
|
||||||
}
|
|
||||||
|
|
||||||
return { text: this.t('harvester.addons.forklift.detail.status.unknown'), color: 'bg-darker' };
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<CruResource
|
|
||||||
:done-route="doneRoute"
|
|
||||||
:resource="value"
|
|
||||||
:mode="mode"
|
|
||||||
:can-yaml="false"
|
|
||||||
>
|
|
||||||
<!-- Plan Overview -->
|
|
||||||
<div class="row mb-20">
|
|
||||||
<div class="col span-3">
|
|
||||||
<LabelValue :name="t('harvester.addons.forklift.detail.status.label')">
|
|
||||||
<template #value>
|
|
||||||
<BadgeState
|
|
||||||
:label="overallStatus.text"
|
|
||||||
:color="overallStatus.color"
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
</LabelValue>
|
|
||||||
</div>
|
|
||||||
<div class="col span-3">
|
|
||||||
<LabelValue
|
|
||||||
:name="t('harvester.addons.forklift.fields.sourceProvider')"
|
|
||||||
:value="sourceProvider"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div class="col span-3">
|
|
||||||
<LabelValue
|
|
||||||
:name="t('harvester.addons.forklift.fields.destProvider')"
|
|
||||||
:value="destProvider"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div class="col span-3">
|
|
||||||
<LabelValue
|
|
||||||
:name="t('harvester.addons.forklift.fields.targetNamespace')"
|
|
||||||
:value="targetNamespace"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="row mb-20">
|
|
||||||
<div class="col span-3">
|
|
||||||
<LabelValue
|
|
||||||
:name="t('harvester.addons.forklift.fields.networkMap')"
|
|
||||||
:value="networkMapName"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div class="col span-3">
|
|
||||||
<LabelValue
|
|
||||||
:name="t('harvester.addons.forklift.fields.storageMap')"
|
|
||||||
:value="storageMapName"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div class="col span-3">
|
|
||||||
<LabelValue
|
|
||||||
:name="t('harvester.addons.forklift.detail.vmCount')"
|
|
||||||
:value="String(plannedVMs.length)"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<Tabbed
|
|
||||||
v-bind="$attrs"
|
|
||||||
class="mt-15"
|
|
||||||
:side-tabs="true"
|
|
||||||
>
|
|
||||||
<!-- Current Migration Tab -->
|
|
||||||
<Tab
|
|
||||||
name="current"
|
|
||||||
:label="t('harvester.addons.forklift.detail.tabs.currentMigration')"
|
|
||||||
:weight="3"
|
|
||||||
>
|
|
||||||
<template v-if="currentMigration">
|
|
||||||
<div class="row mb-20">
|
|
||||||
<div class="col span-4">
|
|
||||||
<LabelValue
|
|
||||||
:name="t('harvester.addons.forklift.detail.migrationName')"
|
|
||||||
:value="currentMigration.history && currentMigration.history.length ? currentMigration.history[currentMigration.history.length - 1].migration.name : '-'"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div class="col span-4">
|
|
||||||
<LabelValue
|
|
||||||
:name="t('harvester.addons.forklift.detail.started')"
|
|
||||||
:value="currentMigration.started || '-'"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div class="col span-4">
|
|
||||||
<LabelValue
|
|
||||||
:name="t('harvester.addons.forklift.detail.completed')"
|
|
||||||
:value="currentMigration.completed || '-'"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- VM Progress -->
|
|
||||||
<div
|
|
||||||
v-for="vm in currentMigrationVMs"
|
|
||||||
:key="vm.id"
|
|
||||||
class="vm-progress-card mb-20"
|
|
||||||
>
|
|
||||||
<div class="vm-card-header">
|
|
||||||
<div class="vm-card-title">
|
|
||||||
<span class="vm-name">{{ vm.name || vm.id }}</span>
|
|
||||||
<span
|
|
||||||
v-if="vm.name"
|
|
||||||
class="text-muted ml-10"
|
|
||||||
>({{ vm.id }})</span>
|
|
||||||
</div>
|
|
||||||
<BadgeState
|
|
||||||
:label="vm.phase"
|
|
||||||
:color="stepBadgeColor(vm.phase)"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="row mb-10">
|
|
||||||
<div class="col span-12">
|
|
||||||
<PercentageBar :value="vmOverallProgress(vm)" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="pipeline-steps">
|
|
||||||
<div
|
|
||||||
v-for="(step, idx) in (vm.pipeline || [])"
|
|
||||||
:key="idx"
|
|
||||||
class="step-row"
|
|
||||||
>
|
|
||||||
<div class="step-info">
|
|
||||||
<span class="step-num">{{ idx + 1 }}.</span>
|
|
||||||
<span class="step-name">{{ step.name }}</span>
|
|
||||||
<span class="text-muted step-desc">{{ step.description }}</span>
|
|
||||||
</div>
|
|
||||||
<div class="step-status">
|
|
||||||
<span
|
|
||||||
v-if="step.annotations && step.annotations.unit"
|
|
||||||
class="text-muted step-data mr-10"
|
|
||||||
>
|
|
||||||
{{ formatDiskProgress(step) }}
|
|
||||||
</span>
|
|
||||||
<BadgeState
|
|
||||||
:label="step.phase"
|
|
||||||
:color="stepBadgeColor(step.phase)"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<PercentageBar
|
|
||||||
:value="stepProgress(step)"
|
|
||||||
class="step-bar"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<Banner
|
|
||||||
v-if="vm.conditions && vm.conditions.length"
|
|
||||||
:color="vm.conditions[0].type === 'Succeeded' ? 'success' : 'info'"
|
|
||||||
:label="vm.conditions[0].message"
|
|
||||||
class="mt-10"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<Banner
|
|
||||||
v-else
|
|
||||||
color="info"
|
|
||||||
:label="t('harvester.addons.forklift.detail.noCurrentMigration')"
|
|
||||||
/>
|
|
||||||
</Tab>
|
|
||||||
|
|
||||||
<!-- Migration History Tab -->
|
|
||||||
<Tab
|
|
||||||
name="history"
|
|
||||||
:label="t('harvester.addons.forklift.detail.tabs.history')"
|
|
||||||
:weight="2"
|
|
||||||
>
|
|
||||||
<template v-if="migrationHistory.length">
|
|
||||||
<div
|
|
||||||
v-for="(entry, idx) in migrationHistory"
|
|
||||||
:key="idx"
|
|
||||||
class="history-entry mb-15"
|
|
||||||
>
|
|
||||||
<div class="history-header">
|
|
||||||
<div>
|
|
||||||
<strong>{{ entry.migration?.name || t('harvester.addons.forklift.detail.migrationRun', { idx: idx + 1 }) }}</strong>
|
|
||||||
</div>
|
|
||||||
<BadgeState
|
|
||||||
:label="historyStatus(entry).text"
|
|
||||||
:color="historyStatus(entry).color"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div class="history-details">
|
|
||||||
<div class="row">
|
|
||||||
<div class="col span-3">
|
|
||||||
<LabelValue
|
|
||||||
:name="t('harvester.addons.forklift.fields.sourceProvider')"
|
|
||||||
:value="entry.provider?.source?.name || '-'"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div class="col span-3">
|
|
||||||
<LabelValue
|
|
||||||
:name="t('harvester.addons.forklift.fields.destProvider')"
|
|
||||||
:value="entry.provider?.destination?.name || '-'"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div class="col span-3">
|
|
||||||
<LabelValue
|
|
||||||
:name="t('harvester.addons.forklift.fields.networkMap')"
|
|
||||||
:value="entry.map?.network?.name || '-'"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div class="col span-3">
|
|
||||||
<LabelValue
|
|
||||||
:name="t('harvester.addons.forklift.fields.storageMap')"
|
|
||||||
:value="entry.map?.storage?.name || '-'"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<Banner
|
|
||||||
v-if="entry.conditions && entry.conditions.length"
|
|
||||||
:color="entry.conditions[0].type === 'Succeeded' ? 'success' : entry.conditions[0].type === 'Failed' ? 'error' : 'info'"
|
|
||||||
:label="entry.conditions[0].message"
|
|
||||||
class="mt-10"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<Banner
|
|
||||||
v-else
|
|
||||||
color="info"
|
|
||||||
:label="t('harvester.addons.forklift.detail.noHistory')"
|
|
||||||
/>
|
|
||||||
</Tab>
|
|
||||||
|
|
||||||
<!-- Planned VMs Tab -->
|
|
||||||
<Tab
|
|
||||||
name="vms"
|
|
||||||
:label="t('harvester.addons.forklift.titles.vms')"
|
|
||||||
:weight="1"
|
|
||||||
>
|
|
||||||
<table class="sortable-table">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>{{ t('harvester.addons.forklift.fields.vmId') }}</th>
|
|
||||||
<th>{{ t('harvester.addons.forklift.fields.vmName') }}</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
<tr
|
|
||||||
v-for="(vm, idx) in plannedVMs"
|
|
||||||
:key="idx"
|
|
||||||
>
|
|
||||||
<td>{{ vm.id }}</td>
|
|
||||||
<td>{{ vm.name || '-' }}</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</Tab>
|
|
||||||
</Tabbed>
|
|
||||||
</CruResource>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.vm-progress-card {
|
|
||||||
border: 1px solid var(--border);
|
|
||||||
border-radius: var(--border-radius);
|
|
||||||
padding: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.vm-card-header {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.vm-card-title {
|
|
||||||
font-size: 15px;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pipeline-steps {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 8px;
|
|
||||||
margin-top: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.step-row {
|
|
||||||
border: 1px solid var(--border);
|
|
||||||
border-radius: var(--border-radius);
|
|
||||||
padding: 10px 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.step-info {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 5px;
|
|
||||||
margin-bottom: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.step-num {
|
|
||||||
color: var(--muted);
|
|
||||||
font-weight: 600;
|
|
||||||
min-width: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.step-name {
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
|
|
||||||
.step-desc {
|
|
||||||
font-size: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.step-status {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: flex-end;
|
|
||||||
margin-bottom: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.step-data {
|
|
||||||
font-size: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.step-bar {
|
|
||||||
margin-top: 2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.history-entry {
|
|
||||||
border: 1px solid var(--border);
|
|
||||||
border-radius: var(--border-radius);
|
|
||||||
padding: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.history-header {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sortable-table {
|
|
||||||
width: 100%;
|
|
||||||
border-collapse: collapse;
|
|
||||||
|
|
||||||
th, td {
|
|
||||||
padding: 8px 12px;
|
|
||||||
text-align: left;
|
|
||||||
border-bottom: 1px solid var(--border);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@ -1,119 +0,0 @@
|
|||||||
<script>
|
|
||||||
import CruResource from '@shell/components/CruResource';
|
|
||||||
import LabeledSelect from '@shell/components/form/LabeledSelect';
|
|
||||||
import NameNsDescription from '@shell/components/form/NameNsDescription';
|
|
||||||
import CreateEditView from '@shell/mixins/create-edit-view';
|
|
||||||
import FormValidation from '@shell/mixins/form-validation';
|
|
||||||
import { HCI } from '../types';
|
|
||||||
import { mapGetters } from 'vuex';
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: 'EditForkliftMigration',
|
|
||||||
|
|
||||||
emits: ['update:value'],
|
|
||||||
|
|
||||||
components: {
|
|
||||||
CruResource,
|
|
||||||
LabeledSelect,
|
|
||||||
NameNsDescription,
|
|
||||||
},
|
|
||||||
|
|
||||||
mixins: [CreateEditView, FormValidation],
|
|
||||||
|
|
||||||
inheritAttrs: false,
|
|
||||||
|
|
||||||
props: {
|
|
||||||
value: {
|
|
||||||
type: Object,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
mode: {
|
|
||||||
type: String,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
async fetch() {
|
|
||||||
const inStore = this.$store.getters['currentProduct'].inStore;
|
|
||||||
|
|
||||||
this.allPlans = await this.$store.dispatch(`${ inStore }/findAll`, { type: HCI.FORKLIFT_PLAN });
|
|
||||||
},
|
|
||||||
|
|
||||||
data() {
|
|
||||||
if (!this.value.spec) {
|
|
||||||
this.value.spec = { plan: {} };
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
allPlans: [],
|
|
||||||
|
|
||||||
fvFormRuleSets: [
|
|
||||||
{ path: 'metadata.name', rules: ['nameRequired'] },
|
|
||||||
],
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
computed: {
|
|
||||||
...mapGetters({ t: 'i18n/t' }),
|
|
||||||
|
|
||||||
planOptions() {
|
|
||||||
return this.allPlans.map((p) => ({
|
|
||||||
label: p.metadata.name,
|
|
||||||
value: p.metadata.name,
|
|
||||||
}));
|
|
||||||
},
|
|
||||||
|
|
||||||
fvExtraRules() {
|
|
||||||
return { nameRequired: (val) => !val ? this.t('validation.required', { key: this.t('harvester.fields.name') }) : undefined };
|
|
||||||
},
|
|
||||||
|
|
||||||
isFormValid() {
|
|
||||||
return this.fvFormIsValid && !!this.value.spec.plan?.name;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
methods: {
|
|
||||||
updatePlan(val) {
|
|
||||||
const plan = this.allPlans.find((p) => p.metadata.name === val);
|
|
||||||
|
|
||||||
this.value.spec.plan = {
|
|
||||||
name: val,
|
|
||||||
namespace: plan?.metadata?.namespace || this.value.metadata.namespace || 'default',
|
|
||||||
};
|
|
||||||
},
|
|
||||||
}
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<CruResource
|
|
||||||
:done-route="doneRoute"
|
|
||||||
:resource="value"
|
|
||||||
:mode="mode"
|
|
||||||
:errors="errors"
|
|
||||||
:apply-hooks="applyHooks"
|
|
||||||
:validation-passed="isFormValid"
|
|
||||||
@finish="save"
|
|
||||||
@error="e=>errors=e"
|
|
||||||
>
|
|
||||||
<NameNsDescription
|
|
||||||
:value="value"
|
|
||||||
:mode="mode"
|
|
||||||
:rules="{ name: fvGetAndReportPathRules('metadata.name') }"
|
|
||||||
@update:value="$emit('update:value', $event)"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<div class="row mb-20 mt-20">
|
|
||||||
<div class="col span-6">
|
|
||||||
<LabeledSelect
|
|
||||||
:value="value.spec.plan.name"
|
|
||||||
:options="planOptions"
|
|
||||||
:label="t('harvester.addons.forklift.fields.plan')"
|
|
||||||
:mode="mode"
|
|
||||||
required
|
|
||||||
@update:value="updatePlan"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</CruResource>
|
|
||||||
</template>
|
|
||||||
@ -1,199 +0,0 @@
|
|||||||
<script>
|
|
||||||
import CruResource from '@shell/components/CruResource';
|
|
||||||
import Tabbed from '@shell/components/Tabbed';
|
|
||||||
import Tab from '@shell/components/Tabbed/Tab';
|
|
||||||
import { LabeledInput } from '@components/Form/LabeledInput';
|
|
||||||
import LabeledSelect from '@shell/components/form/LabeledSelect';
|
|
||||||
import NameNsDescription from '@shell/components/form/NameNsDescription';
|
|
||||||
import CreateEditView from '@shell/mixins/create-edit-view';
|
|
||||||
import FormValidation from '@shell/mixins/form-validation';
|
|
||||||
import ForkliftProvider from '../mixins/forklift-provider';
|
|
||||||
import { HCI } from '../types';
|
|
||||||
import { mapGetters } from 'vuex';
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: 'EditForkliftNetworkMap',
|
|
||||||
|
|
||||||
emits: ['update:value'],
|
|
||||||
|
|
||||||
components: {
|
|
||||||
CruResource,
|
|
||||||
Tabbed,
|
|
||||||
Tab,
|
|
||||||
LabeledInput,
|
|
||||||
LabeledSelect,
|
|
||||||
NameNsDescription,
|
|
||||||
},
|
|
||||||
|
|
||||||
mixins: [CreateEditView, FormValidation, ForkliftProvider],
|
|
||||||
|
|
||||||
inheritAttrs: false,
|
|
||||||
|
|
||||||
props: {
|
|
||||||
value: {
|
|
||||||
type: Object,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
mode: {
|
|
||||||
type: String,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
async fetch() {
|
|
||||||
const inStore = this.$store.getters['currentProduct'].inStore;
|
|
||||||
|
|
||||||
this.allProviders = await this.$store.dispatch(`${ inStore }/findAll`, { type: HCI.FORKLIFT_PROVIDER });
|
|
||||||
},
|
|
||||||
|
|
||||||
data() {
|
|
||||||
if (!this.value.spec) {
|
|
||||||
this.value.spec = {
|
|
||||||
map: [{ source: { id: '' }, destination: { type: 'pod' } }],
|
|
||||||
provider: {
|
|
||||||
source: {},
|
|
||||||
destination: {},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
fvFormRuleSets: [
|
|
||||||
{ path: 'metadata.name', rules: ['nameRequired'] },
|
|
||||||
],
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
computed: {
|
|
||||||
...mapGetters({ t: 'i18n/t' }),
|
|
||||||
|
|
||||||
destinationTypeOptions() {
|
|
||||||
return [
|
|
||||||
{ label: this.t('harvester.addons.forklift.fields.destNetworkPod'), value: 'pod' },
|
|
||||||
{ label: this.t('harvester.addons.forklift.fields.destNetworkMultus'), value: 'multus' },
|
|
||||||
];
|
|
||||||
},
|
|
||||||
|
|
||||||
fvExtraRules() {
|
|
||||||
return { nameRequired: (val) => !val ? this.t('validation.required', { key: this.t('harvester.fields.name') }) : undefined };
|
|
||||||
},
|
|
||||||
|
|
||||||
isFormValid() {
|
|
||||||
return this.fvFormIsValid &&
|
|
||||||
!!this.value.spec.provider?.source?.name &&
|
|
||||||
!!this.value.spec.provider?.destination?.name;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
methods: {
|
|
||||||
addMapping() {
|
|
||||||
this.value.spec.map.push({ source: { id: '' }, destination: { type: 'pod' } });
|
|
||||||
},
|
|
||||||
|
|
||||||
removeMapping(idx) {
|
|
||||||
this.value.spec.map.splice(idx, 1);
|
|
||||||
},
|
|
||||||
}
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<CruResource
|
|
||||||
:done-route="doneRoute"
|
|
||||||
:resource="value"
|
|
||||||
:mode="mode"
|
|
||||||
:errors="errors"
|
|
||||||
:apply-hooks="applyHooks"
|
|
||||||
:validation-passed="isFormValid"
|
|
||||||
@finish="save"
|
|
||||||
@error="e=>errors=e"
|
|
||||||
>
|
|
||||||
<NameNsDescription
|
|
||||||
:value="value"
|
|
||||||
:mode="mode"
|
|
||||||
:rules="{ name: fvGetAndReportPathRules('metadata.name') }"
|
|
||||||
@update:value="$emit('update:value', $event)"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Tabbed
|
|
||||||
v-bind="$attrs"
|
|
||||||
class="mt-15"
|
|
||||||
:side-tabs="true"
|
|
||||||
>
|
|
||||||
<Tab
|
|
||||||
name="providers"
|
|
||||||
:label="t('harvester.addons.forklift.titles.providers')"
|
|
||||||
:weight="3"
|
|
||||||
>
|
|
||||||
<div class="row mb-20">
|
|
||||||
<div class="col span-6">
|
|
||||||
<LabeledSelect
|
|
||||||
:value="value.spec.provider.source.name"
|
|
||||||
:options="providerOptions"
|
|
||||||
:label="t('harvester.addons.forklift.fields.sourceProvider')"
|
|
||||||
:mode="mode"
|
|
||||||
required
|
|
||||||
@update:value="updateSourceProvider"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div class="col span-6">
|
|
||||||
<LabeledSelect
|
|
||||||
:value="value.spec.provider.destination.name"
|
|
||||||
:options="providerOptions"
|
|
||||||
:label="t('harvester.addons.forklift.fields.destProvider')"
|
|
||||||
:mode="mode"
|
|
||||||
required
|
|
||||||
@update:value="updateDestProvider"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Tab>
|
|
||||||
|
|
||||||
<Tab
|
|
||||||
name="mappings"
|
|
||||||
:label="t('harvester.addons.forklift.titles.networkMappings')"
|
|
||||||
:weight="2"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
v-for="(entry, idx) in value.spec.map"
|
|
||||||
:key="idx"
|
|
||||||
class="row mb-20"
|
|
||||||
>
|
|
||||||
<div class="col span-5">
|
|
||||||
<LabeledInput
|
|
||||||
v-model:value="entry.source.id"
|
|
||||||
:label="t('harvester.addons.forklift.fields.sourceNetworkId')"
|
|
||||||
:placeholder="t('harvester.addons.forklift.placeholders.sourceNetworkId')"
|
|
||||||
:mode="mode"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div class="col span-5">
|
|
||||||
<LabeledSelect
|
|
||||||
v-model:value="entry.destination.type"
|
|
||||||
:options="destinationTypeOptions"
|
|
||||||
:label="t('harvester.addons.forklift.fields.destNetworkType')"
|
|
||||||
:mode="mode"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div class="col span-2">
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="btn role-link"
|
|
||||||
:disabled="value.spec.map.length <= 1"
|
|
||||||
@click="removeMapping(idx)"
|
|
||||||
>
|
|
||||||
{{ t('harvester.addons.forklift.actions.remove') }}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="btn role-tertiary"
|
|
||||||
@click="addMapping"
|
|
||||||
>
|
|
||||||
{{ t('harvester.addons.forklift.actions.addMapping') }}
|
|
||||||
</button>
|
|
||||||
</Tab>
|
|
||||||
</Tabbed>
|
|
||||||
</CruResource>
|
|
||||||
</template>
|
|
||||||
@ -1,303 +0,0 @@
|
|||||||
<script>
|
|
||||||
import CruResource from '@shell/components/CruResource';
|
|
||||||
import Tabbed from '@shell/components/Tabbed';
|
|
||||||
import Tab from '@shell/components/Tabbed/Tab';
|
|
||||||
import { LabeledInput } from '@components/Form/LabeledInput';
|
|
||||||
import LabeledSelect from '@shell/components/form/LabeledSelect';
|
|
||||||
import NameNsDescription from '@shell/components/form/NameNsDescription';
|
|
||||||
import { Checkbox } from '@components/Form/Checkbox';
|
|
||||||
import CreateEditView from '@shell/mixins/create-edit-view';
|
|
||||||
import FormValidation from '@shell/mixins/form-validation';
|
|
||||||
import ForkliftProvider from '../mixins/forklift-provider';
|
|
||||||
import { HCI } from '../types';
|
|
||||||
import { FORKLIFT_API_VERSION } from '../config/harvester-map';
|
|
||||||
import { mapGetters } from 'vuex';
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: 'EditForkliftPlan',
|
|
||||||
|
|
||||||
emits: ['update:value'],
|
|
||||||
|
|
||||||
components: {
|
|
||||||
CruResource,
|
|
||||||
Tabbed,
|
|
||||||
Tab,
|
|
||||||
LabeledInput,
|
|
||||||
LabeledSelect,
|
|
||||||
NameNsDescription,
|
|
||||||
Checkbox,
|
|
||||||
},
|
|
||||||
|
|
||||||
mixins: [CreateEditView, FormValidation, ForkliftProvider],
|
|
||||||
|
|
||||||
inheritAttrs: false,
|
|
||||||
|
|
||||||
props: {
|
|
||||||
value: {
|
|
||||||
type: Object,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
mode: {
|
|
||||||
type: String,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
async fetch() {
|
|
||||||
const inStore = this.$store.getters['currentProduct'].inStore;
|
|
||||||
|
|
||||||
this.allProviders = await this.$store.dispatch(`${ inStore }/findAll`, { type: HCI.FORKLIFT_PROVIDER });
|
|
||||||
this.allNetworkMaps = await this.$store.dispatch(`${ inStore }/findAll`, { type: HCI.FORKLIFT_NETWORK_MAP });
|
|
||||||
this.allStorageMaps = await this.$store.dispatch(`${ inStore }/findAll`, { type: HCI.FORKLIFT_STORAGE_MAP });
|
|
||||||
},
|
|
||||||
|
|
||||||
data() {
|
|
||||||
if (!this.value.spec) {
|
|
||||||
this.value.spec = {
|
|
||||||
map: {
|
|
||||||
network: {},
|
|
||||||
storage: {},
|
|
||||||
},
|
|
||||||
provider: {
|
|
||||||
source: {},
|
|
||||||
destination: {},
|
|
||||||
},
|
|
||||||
targetNamespace: 'default',
|
|
||||||
migrateSharedDisks: true,
|
|
||||||
vms: [{ id: '', name: '' }],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!this.value.metadata.annotations) {
|
|
||||||
this.value.metadata.annotations = {};
|
|
||||||
}
|
|
||||||
this.value.metadata.annotations.populatorLabels = 'True';
|
|
||||||
|
|
||||||
return {
|
|
||||||
allProviders: [],
|
|
||||||
allNetworkMaps: [],
|
|
||||||
allStorageMaps: [],
|
|
||||||
|
|
||||||
fvFormRuleSets: [
|
|
||||||
{ path: 'metadata.name', rules: ['nameRequired'] },
|
|
||||||
{ path: 'spec.targetNamespace', rules: ['targetNsRequired'] },
|
|
||||||
],
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
computed: {
|
|
||||||
...mapGetters({ t: 'i18n/t' }),
|
|
||||||
|
|
||||||
providerOptions() {
|
|
||||||
return this.allProviders.map((p) => ({
|
|
||||||
label: `${ p.metadata.name } (${ p.spec?.type || 'unknown' })`,
|
|
||||||
value: p.metadata.name,
|
|
||||||
}));
|
|
||||||
},
|
|
||||||
|
|
||||||
networkMapOptions() {
|
|
||||||
return this.allNetworkMaps.map((m) => ({
|
|
||||||
label: m.metadata.name,
|
|
||||||
value: m.metadata.name,
|
|
||||||
}));
|
|
||||||
},
|
|
||||||
|
|
||||||
storageMapOptions() {
|
|
||||||
return this.allStorageMaps.map((m) => ({
|
|
||||||
label: m.metadata.name,
|
|
||||||
value: m.metadata.name,
|
|
||||||
}));
|
|
||||||
},
|
|
||||||
|
|
||||||
fvExtraRules() {
|
|
||||||
return {
|
|
||||||
nameRequired: (val) => !val ? this.t('validation.required', { key: this.t('harvester.fields.name') }) : undefined,
|
|
||||||
targetNsRequired: (val) => !val ? this.t('validation.required', { key: this.t('harvester.addons.forklift.fields.targetNamespace') }) : undefined,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
isFormValid() {
|
|
||||||
return this.fvFormIsValid &&
|
|
||||||
!!this.value.spec.provider?.source?.name &&
|
|
||||||
!!this.value.spec.provider?.destination?.name;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
methods: {
|
|
||||||
addVm() {
|
|
||||||
this.value.spec.vms.push({ id: '', name: '' });
|
|
||||||
},
|
|
||||||
|
|
||||||
removeVm(idx) {
|
|
||||||
this.value.spec.vms.splice(idx, 1);
|
|
||||||
},
|
|
||||||
|
|
||||||
updateNetworkMap(val) {
|
|
||||||
this.value.spec.map.network = {
|
|
||||||
apiVersion: FORKLIFT_API_VERSION,
|
|
||||||
kind: 'NetworkMap',
|
|
||||||
name: val,
|
|
||||||
namespace: this.value.metadata.namespace || 'default',
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
updateStorageMap(val) {
|
|
||||||
this.value.spec.map.storage = {
|
|
||||||
apiVersion: FORKLIFT_API_VERSION,
|
|
||||||
kind: 'StorageMap',
|
|
||||||
name: val,
|
|
||||||
namespace: this.value.metadata.namespace || 'default',
|
|
||||||
};
|
|
||||||
},
|
|
||||||
}
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<CruResource
|
|
||||||
:done-route="doneRoute"
|
|
||||||
:resource="value"
|
|
||||||
:mode="mode"
|
|
||||||
:errors="errors"
|
|
||||||
:apply-hooks="applyHooks"
|
|
||||||
:validation-passed="isFormValid"
|
|
||||||
@finish="save"
|
|
||||||
@error="e=>errors=e"
|
|
||||||
>
|
|
||||||
<NameNsDescription
|
|
||||||
:value="value"
|
|
||||||
:mode="mode"
|
|
||||||
:rules="{ name: fvGetAndReportPathRules('metadata.name') }"
|
|
||||||
@update:value="$emit('update:value', $event)"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Tabbed
|
|
||||||
v-bind="$attrs"
|
|
||||||
class="mt-15"
|
|
||||||
:side-tabs="true"
|
|
||||||
>
|
|
||||||
<Tab
|
|
||||||
name="providers"
|
|
||||||
:label="t('harvester.addons.forklift.titles.providers')"
|
|
||||||
:weight="4"
|
|
||||||
>
|
|
||||||
<div class="row mb-20">
|
|
||||||
<div class="col span-6">
|
|
||||||
<LabeledSelect
|
|
||||||
:value="value.spec.provider.source.name"
|
|
||||||
:options="providerOptions"
|
|
||||||
:label="t('harvester.addons.forklift.fields.sourceProvider')"
|
|
||||||
:mode="mode"
|
|
||||||
required
|
|
||||||
@update:value="updateSourceProvider"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div class="col span-6">
|
|
||||||
<LabeledSelect
|
|
||||||
:value="value.spec.provider.destination.name"
|
|
||||||
:options="providerOptions"
|
|
||||||
:label="t('harvester.addons.forklift.fields.destProvider')"
|
|
||||||
:mode="mode"
|
|
||||||
required
|
|
||||||
@update:value="updateDestProvider"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Tab>
|
|
||||||
|
|
||||||
<Tab
|
|
||||||
name="mappings"
|
|
||||||
:label="t('harvester.addons.forklift.titles.mappings')"
|
|
||||||
:weight="3"
|
|
||||||
>
|
|
||||||
<div class="row mb-20">
|
|
||||||
<div class="col span-6">
|
|
||||||
<LabeledSelect
|
|
||||||
:value="value.spec.map.network.name"
|
|
||||||
:options="networkMapOptions"
|
|
||||||
:label="t('harvester.addons.forklift.fields.networkMap')"
|
|
||||||
:mode="mode"
|
|
||||||
required
|
|
||||||
@update:value="updateNetworkMap"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div class="col span-6">
|
|
||||||
<LabeledSelect
|
|
||||||
:value="value.spec.map.storage.name"
|
|
||||||
:options="storageMapOptions"
|
|
||||||
:label="t('harvester.addons.forklift.fields.storageMap')"
|
|
||||||
:mode="mode"
|
|
||||||
required
|
|
||||||
@update:value="updateStorageMap"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Tab>
|
|
||||||
|
|
||||||
<Tab
|
|
||||||
name="vms"
|
|
||||||
:label="t('harvester.addons.forklift.titles.vms')"
|
|
||||||
:weight="2"
|
|
||||||
>
|
|
||||||
<div class="row mb-20">
|
|
||||||
<div class="col span-6">
|
|
||||||
<LabeledInput
|
|
||||||
v-model:value="value.spec.targetNamespace"
|
|
||||||
:label="t('harvester.addons.forklift.fields.targetNamespace')"
|
|
||||||
:mode="mode"
|
|
||||||
:rules="fvGetAndReportPathRules('spec.targetNamespace')"
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div class="col span-6">
|
|
||||||
<Checkbox
|
|
||||||
v-model:value="value.spec.migrateSharedDisks"
|
|
||||||
:label="t('harvester.addons.forklift.fields.migrateSharedDisks')"
|
|
||||||
:mode="mode"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div
|
|
||||||
v-for="(vm, idx) in value.spec.vms"
|
|
||||||
:key="idx"
|
|
||||||
class="row mb-20"
|
|
||||||
>
|
|
||||||
<div class="col span-5">
|
|
||||||
<LabeledInput
|
|
||||||
v-model:value="vm.id"
|
|
||||||
:label="t('harvester.addons.forklift.fields.vmId')"
|
|
||||||
:placeholder="t('harvester.addons.forklift.placeholders.vmId')"
|
|
||||||
:mode="mode"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div class="col span-5">
|
|
||||||
<LabeledInput
|
|
||||||
v-model:value="vm.name"
|
|
||||||
:label="t('harvester.addons.forklift.fields.vmName')"
|
|
||||||
:placeholder="t('harvester.addons.forklift.placeholders.vmName')"
|
|
||||||
:mode="mode"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div class="col span-2">
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="btn role-link"
|
|
||||||
:disabled="value.spec.vms.length <= 1"
|
|
||||||
@click="removeVm(idx)"
|
|
||||||
>
|
|
||||||
{{ t('harvester.addons.forklift.actions.remove') }}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="btn role-tertiary"
|
|
||||||
@click="addVm"
|
|
||||||
>
|
|
||||||
{{ t('harvester.addons.forklift.actions.addVm') }}
|
|
||||||
</button>
|
|
||||||
</Tab>
|
|
||||||
</Tabbed>
|
|
||||||
</CruResource>
|
|
||||||
</template>
|
|
||||||
@ -1,346 +0,0 @@
|
|||||||
<script>
|
|
||||||
import CruResource from '@shell/components/CruResource';
|
|
||||||
import Tabbed from '@shell/components/Tabbed';
|
|
||||||
import Tab from '@shell/components/Tabbed/Tab';
|
|
||||||
import { LabeledInput } from '@components/Form/LabeledInput';
|
|
||||||
import LabeledSelect from '@shell/components/form/LabeledSelect';
|
|
||||||
import NameNsDescription from '@shell/components/form/NameNsDescription';
|
|
||||||
import { RadioGroup } from '@components/Form/Radio';
|
|
||||||
import { Checkbox } from '@components/Form/Checkbox';
|
|
||||||
import CreateEditView from '@shell/mixins/create-edit-view';
|
|
||||||
import FormValidation from '@shell/mixins/form-validation';
|
|
||||||
import { SECRET } from '@shell/config/types';
|
|
||||||
import { randomStr } from '@shell/utils/string';
|
|
||||||
import { mapGetters } from 'vuex';
|
|
||||||
|
|
||||||
const PROVIDER_TYPES = [
|
|
||||||
{ labelKey: 'harvester.addons.forklift.fields.providerTypeVsphere', value: 'vsphere' },
|
|
||||||
{ labelKey: 'harvester.addons.forklift.fields.providerTypeOpenstack', value: 'openstack' },
|
|
||||||
{ labelKey: 'harvester.addons.forklift.fields.providerTypeOpenshift', value: 'openshift' },
|
|
||||||
];
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: 'EditForkliftProvider',
|
|
||||||
|
|
||||||
emits: ['update:value'],
|
|
||||||
|
|
||||||
components: {
|
|
||||||
CruResource,
|
|
||||||
Tabbed,
|
|
||||||
Tab,
|
|
||||||
LabeledInput,
|
|
||||||
LabeledSelect,
|
|
||||||
NameNsDescription,
|
|
||||||
RadioGroup,
|
|
||||||
Checkbox,
|
|
||||||
},
|
|
||||||
|
|
||||||
mixins: [CreateEditView, FormValidation],
|
|
||||||
|
|
||||||
inheritAttrs: false,
|
|
||||||
|
|
||||||
props: {
|
|
||||||
value: {
|
|
||||||
type: Object,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
mode: {
|
|
||||||
type: String,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
async fetch() {
|
|
||||||
const inStore = this.$store.getters['currentProduct'].inStore;
|
|
||||||
|
|
||||||
this.allSecrets = await this.$store.dispatch(`${ inStore }/findAll`, { type: SECRET });
|
|
||||||
},
|
|
||||||
|
|
||||||
data() {
|
|
||||||
if (!this.value.spec) {
|
|
||||||
this.value.spec = {
|
|
||||||
type: 'vsphere',
|
|
||||||
url: '',
|
|
||||||
secret: {},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const initialMode = this.value.spec.secret?.name ? 'existing' : 'new';
|
|
||||||
|
|
||||||
return {
|
|
||||||
allSecrets: [],
|
|
||||||
authMode: initialMode,
|
|
||||||
newUsername: '',
|
|
||||||
newPassword: '',
|
|
||||||
newCaCert: '',
|
|
||||||
skipTlsVerify: true,
|
|
||||||
|
|
||||||
fvFormRuleSets: [
|
|
||||||
{ path: 'metadata.name', rules: ['nameRequired'] },
|
|
||||||
{ path: 'spec.url', rules: ['urlRequired'] },
|
|
||||||
],
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
computed: {
|
|
||||||
...mapGetters({ t: 'i18n/t' }),
|
|
||||||
|
|
||||||
providerTypes() {
|
|
||||||
return PROVIDER_TYPES.map((pt) => ({
|
|
||||||
label: this.t(pt.labelKey),
|
|
||||||
value: pt.value,
|
|
||||||
}));
|
|
||||||
},
|
|
||||||
|
|
||||||
authModeOptions() {
|
|
||||||
return [
|
|
||||||
{ label: this.t('harvester.addons.forklift.fields.createSecret'), value: 'new' },
|
|
||||||
{ label: this.t('harvester.addons.forklift.fields.useSecret'), value: 'existing' }
|
|
||||||
];
|
|
||||||
},
|
|
||||||
|
|
||||||
secretOptions() {
|
|
||||||
const currentNamespace = this.value.metadata.namespace || 'default';
|
|
||||||
|
|
||||||
return this.allSecrets
|
|
||||||
.filter((s) => s.metadata.namespace === currentNamespace)
|
|
||||||
.map((s) => ({
|
|
||||||
label: s.nameDisplay,
|
|
||||||
value: s.metadata.name
|
|
||||||
}));
|
|
||||||
},
|
|
||||||
|
|
||||||
fvExtraRules() {
|
|
||||||
return {
|
|
||||||
nameRequired: (val) => !val ? this.t('validation.required', { key: this.t('harvester.fields.name') }) : undefined,
|
|
||||||
urlRequired: (val) => !val ? this.t('validation.required', { key: this.t('harvester.addons.forklift.fields.url') }) : undefined,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
isFormValid() {
|
|
||||||
if (!this.fvFormIsValid) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Local openshift provider doesn't need auth or URL
|
|
||||||
if (this.value.spec.type === 'openshift') {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.authMode === 'new') {
|
|
||||||
if (!this.newUsername || !this.newPassword) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} else if (!this.value.spec.secret?.name) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
methods: {
|
|
||||||
usernameRule(val) {
|
|
||||||
return !val ? this.t('validation.required', { key: this.t('harvester.addons.forklift.fields.username') }) : undefined;
|
|
||||||
},
|
|
||||||
passwordRule(val) {
|
|
||||||
return !val ? this.t('validation.required', { key: this.t('harvester.addons.forklift.fields.password') }) : undefined;
|
|
||||||
},
|
|
||||||
secretRule(val) {
|
|
||||||
return !val ? this.t('validation.required', { key: this.t('harvester.addons.forklift.fields.selectSecret') }) : undefined;
|
|
||||||
},
|
|
||||||
|
|
||||||
async saveProvider(buttonCb) {
|
|
||||||
const inStore = this.$store.getters['currentProduct'].inStore;
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Local openshift provider uses empty secret
|
|
||||||
if (this.value.spec.type === 'openshift') {
|
|
||||||
this.value.spec.secret = {};
|
|
||||||
this.value.spec.url = '';
|
|
||||||
await this.save(buttonCb);
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.authMode === 'new') {
|
|
||||||
const secretName = `${ this.value.metadata.name }-creds-${ randomStr(4).toLowerCase() }`;
|
|
||||||
const namespace = this.value.metadata.namespace || 'default';
|
|
||||||
|
|
||||||
const newSecret = await this.$store.dispatch(`${ inStore }/create`, {
|
|
||||||
type: SECRET,
|
|
||||||
metadata: {
|
|
||||||
name: secretName,
|
|
||||||
namespace,
|
|
||||||
labels: {
|
|
||||||
createdForProviderType: this.value.spec.type,
|
|
||||||
createdForResourceType: 'providers',
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
newSecret['_type'] = 'Opaque';
|
|
||||||
newSecret['data'] = {
|
|
||||||
user: btoa(this.newUsername),
|
|
||||||
password: btoa(this.newPassword),
|
|
||||||
insecureSkipVerify: btoa(String(this.skipTlsVerify)),
|
|
||||||
url: btoa(this.value.spec.url),
|
|
||||||
};
|
|
||||||
|
|
||||||
if (this.newCaCert) {
|
|
||||||
newSecret.data.cacert = btoa(this.newCaCert);
|
|
||||||
}
|
|
||||||
|
|
||||||
await newSecret.save();
|
|
||||||
|
|
||||||
this.value.spec.secret = {
|
|
||||||
name: secretName,
|
|
||||||
namespace,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
await this.save(buttonCb);
|
|
||||||
} catch (err) {
|
|
||||||
this.errors = [err];
|
|
||||||
buttonCb(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<CruResource
|
|
||||||
:done-route="doneRoute"
|
|
||||||
:resource="value"
|
|
||||||
:mode="mode"
|
|
||||||
:errors="errors"
|
|
||||||
:apply-hooks="applyHooks"
|
|
||||||
:validation-passed="isFormValid"
|
|
||||||
@finish="saveProvider"
|
|
||||||
@error="e=>errors=e"
|
|
||||||
>
|
|
||||||
<NameNsDescription
|
|
||||||
:value="value"
|
|
||||||
:mode="mode"
|
|
||||||
:rules="{ name: fvGetAndReportPathRules('metadata.name') }"
|
|
||||||
@update:value="$emit('update:value', $event)"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Tabbed
|
|
||||||
v-bind="$attrs"
|
|
||||||
class="mt-15"
|
|
||||||
:side-tabs="true"
|
|
||||||
>
|
|
||||||
<Tab
|
|
||||||
name="basic"
|
|
||||||
:label="t('harvester.addons.forklift.titles.basic')"
|
|
||||||
:weight="2"
|
|
||||||
>
|
|
||||||
<div class="row mb-20">
|
|
||||||
<div class="col span-6">
|
|
||||||
<LabeledSelect
|
|
||||||
v-model:value="value.spec.type"
|
|
||||||
:options="providerTypes"
|
|
||||||
:label="t('harvester.addons.forklift.fields.providerType')"
|
|
||||||
:mode="mode"
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
v-if="value.spec.type !== 'openshift'"
|
|
||||||
class="col span-6"
|
|
||||||
>
|
|
||||||
<LabeledInput
|
|
||||||
v-model:value="value.spec.url"
|
|
||||||
:label="t('harvester.addons.forklift.fields.url')"
|
|
||||||
:placeholder="t('harvester.addons.forklift.placeholders.url')"
|
|
||||||
:mode="mode"
|
|
||||||
:rules="fvGetAndReportPathRules('spec.url')"
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Tab>
|
|
||||||
|
|
||||||
<Tab
|
|
||||||
v-if="value.spec.type !== 'openshift'"
|
|
||||||
name="auth"
|
|
||||||
:label="t('harvester.addons.forklift.titles.auth')"
|
|
||||||
:weight="1"
|
|
||||||
>
|
|
||||||
<div class="row mb-20">
|
|
||||||
<div class="col span-12">
|
|
||||||
<RadioGroup
|
|
||||||
v-model:value="authMode"
|
|
||||||
name="authMode"
|
|
||||||
:options="authModeOptions"
|
|
||||||
:mode="mode"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-if="authMode === 'new'">
|
|
||||||
<div class="row mb-20">
|
|
||||||
<div class="col span-6">
|
|
||||||
<LabeledInput
|
|
||||||
v-model:value="newUsername"
|
|
||||||
:label="t('harvester.addons.forklift.fields.username')"
|
|
||||||
:mode="mode"
|
|
||||||
:rules="[usernameRule]"
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div class="col span-6">
|
|
||||||
<LabeledInput
|
|
||||||
v-model:value="newPassword"
|
|
||||||
type="password"
|
|
||||||
:label="t('harvester.addons.forklift.fields.password')"
|
|
||||||
:mode="mode"
|
|
||||||
:rules="[passwordRule]"
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row mb-20">
|
|
||||||
<div class="col span-12">
|
|
||||||
<LabeledInput
|
|
||||||
v-model:value="newCaCert"
|
|
||||||
type="multiline"
|
|
||||||
:label="t('harvester.addons.forklift.fields.caCert')"
|
|
||||||
:placeholder="t('harvester.addons.forklift.placeholders.caCert')"
|
|
||||||
:min-height="100"
|
|
||||||
:mode="mode"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="row mb-20">
|
|
||||||
<div class="col span-12">
|
|
||||||
<Checkbox
|
|
||||||
v-model:value="skipTlsVerify"
|
|
||||||
:label="t('harvester.addons.forklift.fields.insecureSkipVerify')"
|
|
||||||
:mode="mode"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-if="authMode === 'existing'">
|
|
||||||
<div class="row mb-20">
|
|
||||||
<div class="col span-6">
|
|
||||||
<LabeledSelect
|
|
||||||
v-model:value="value.spec.secret.name"
|
|
||||||
:options="secretOptions"
|
|
||||||
:label="t('harvester.addons.forklift.fields.selectSecret')"
|
|
||||||
:mode="mode"
|
|
||||||
:rules="[secretRule]"
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Tab>
|
|
||||||
</Tabbed>
|
|
||||||
</CruResource>
|
|
||||||
</template>
|
|
||||||
@ -1,208 +0,0 @@
|
|||||||
<script>
|
|
||||||
import CruResource from '@shell/components/CruResource';
|
|
||||||
import Tabbed from '@shell/components/Tabbed';
|
|
||||||
import Tab from '@shell/components/Tabbed/Tab';
|
|
||||||
import { LabeledInput } from '@components/Form/LabeledInput';
|
|
||||||
import LabeledSelect from '@shell/components/form/LabeledSelect';
|
|
||||||
import NameNsDescription from '@shell/components/form/NameNsDescription';
|
|
||||||
import CreateEditView from '@shell/mixins/create-edit-view';
|
|
||||||
import FormValidation from '@shell/mixins/form-validation';
|
|
||||||
import ForkliftProvider from '../mixins/forklift-provider';
|
|
||||||
import { HCI } from '../types';
|
|
||||||
import { STORAGE_CLASS } from '@shell/config/types';
|
|
||||||
import { mapGetters } from 'vuex';
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: 'EditForkliftStorageMap',
|
|
||||||
|
|
||||||
emits: ['update:value'],
|
|
||||||
|
|
||||||
components: {
|
|
||||||
CruResource,
|
|
||||||
Tabbed,
|
|
||||||
Tab,
|
|
||||||
LabeledInput,
|
|
||||||
LabeledSelect,
|
|
||||||
NameNsDescription,
|
|
||||||
},
|
|
||||||
|
|
||||||
mixins: [CreateEditView, FormValidation, ForkliftProvider],
|
|
||||||
|
|
||||||
inheritAttrs: false,
|
|
||||||
|
|
||||||
props: {
|
|
||||||
value: {
|
|
||||||
type: Object,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
mode: {
|
|
||||||
type: String,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
async fetch() {
|
|
||||||
const inStore = this.$store.getters['currentProduct'].inStore;
|
|
||||||
|
|
||||||
this.allProviders = await this.$store.dispatch(`${ inStore }/findAll`, { type: HCI.FORKLIFT_PROVIDER });
|
|
||||||
|
|
||||||
try {
|
|
||||||
this.allStorageClasses = await this.$store.dispatch(`${ inStore }/findAll`, { type: STORAGE_CLASS });
|
|
||||||
} catch {
|
|
||||||
this.allStorageClasses = [];
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
data() {
|
|
||||||
if (!this.value.spec) {
|
|
||||||
this.value.spec = {
|
|
||||||
map: [{ source: { id: '' }, destination: { storageClass: '' } }],
|
|
||||||
provider: {
|
|
||||||
source: {},
|
|
||||||
destination: {},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
allStorageClasses: [],
|
|
||||||
|
|
||||||
fvFormRuleSets: [
|
|
||||||
{ path: 'metadata.name', rules: ['nameRequired'] },
|
|
||||||
],
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
computed: {
|
|
||||||
...mapGetters({ t: 'i18n/t' }),
|
|
||||||
|
|
||||||
storageClassOptions() {
|
|
||||||
return this.allStorageClasses.map((sc) => ({
|
|
||||||
label: sc.metadata.name,
|
|
||||||
value: sc.metadata.name,
|
|
||||||
}));
|
|
||||||
},
|
|
||||||
|
|
||||||
fvExtraRules() {
|
|
||||||
return { nameRequired: (val) => !val ? this.t('validation.required', { key: this.t('harvester.fields.name') }) : undefined };
|
|
||||||
},
|
|
||||||
|
|
||||||
isFormValid() {
|
|
||||||
return this.fvFormIsValid &&
|
|
||||||
!!this.value.spec.provider?.source?.name &&
|
|
||||||
!!this.value.spec.provider?.destination?.name;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
methods: {
|
|
||||||
addMapping() {
|
|
||||||
this.value.spec.map.push({ source: { id: '' }, destination: { storageClass: '' } });
|
|
||||||
},
|
|
||||||
|
|
||||||
removeMapping(idx) {
|
|
||||||
this.value.spec.map.splice(idx, 1);
|
|
||||||
},
|
|
||||||
}
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<CruResource
|
|
||||||
:done-route="doneRoute"
|
|
||||||
:resource="value"
|
|
||||||
:mode="mode"
|
|
||||||
:errors="errors"
|
|
||||||
:apply-hooks="applyHooks"
|
|
||||||
:validation-passed="isFormValid"
|
|
||||||
@finish="save"
|
|
||||||
@error="e=>errors=e"
|
|
||||||
>
|
|
||||||
<NameNsDescription
|
|
||||||
:value="value"
|
|
||||||
:mode="mode"
|
|
||||||
:rules="{ name: fvGetAndReportPathRules('metadata.name') }"
|
|
||||||
@update:value="$emit('update:value', $event)"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Tabbed
|
|
||||||
v-bind="$attrs"
|
|
||||||
class="mt-15"
|
|
||||||
:side-tabs="true"
|
|
||||||
>
|
|
||||||
<Tab
|
|
||||||
name="providers"
|
|
||||||
:label="t('harvester.addons.forklift.titles.providers')"
|
|
||||||
:weight="3"
|
|
||||||
>
|
|
||||||
<div class="row mb-20">
|
|
||||||
<div class="col span-6">
|
|
||||||
<LabeledSelect
|
|
||||||
:value="value.spec.provider.source.name"
|
|
||||||
:options="providerOptions"
|
|
||||||
:label="t('harvester.addons.forklift.fields.sourceProvider')"
|
|
||||||
:mode="mode"
|
|
||||||
required
|
|
||||||
@update:value="updateSourceProvider"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div class="col span-6">
|
|
||||||
<LabeledSelect
|
|
||||||
:value="value.spec.provider.destination.name"
|
|
||||||
:options="providerOptions"
|
|
||||||
:label="t('harvester.addons.forklift.fields.destProvider')"
|
|
||||||
:mode="mode"
|
|
||||||
required
|
|
||||||
@update:value="updateDestProvider"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Tab>
|
|
||||||
|
|
||||||
<Tab
|
|
||||||
name="mappings"
|
|
||||||
:label="t('harvester.addons.forklift.titles.storageMappings')"
|
|
||||||
:weight="2"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
v-for="(entry, idx) in value.spec.map"
|
|
||||||
:key="idx"
|
|
||||||
class="row mb-20"
|
|
||||||
>
|
|
||||||
<div class="col span-5">
|
|
||||||
<LabeledInput
|
|
||||||
v-model:value="entry.source.id"
|
|
||||||
:label="t('harvester.addons.forklift.fields.sourceDatastoreId')"
|
|
||||||
:placeholder="t('harvester.addons.forklift.placeholders.sourceDatastoreId')"
|
|
||||||
:mode="mode"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div class="col span-5">
|
|
||||||
<LabeledSelect
|
|
||||||
v-model:value="entry.destination.storageClass"
|
|
||||||
:options="storageClassOptions"
|
|
||||||
:label="t('harvester.addons.forklift.fields.destStorageClass')"
|
|
||||||
:mode="mode"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div class="col span-2">
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="btn role-link"
|
|
||||||
:disabled="value.spec.map.length <= 1"
|
|
||||||
@click="removeMapping(idx)"
|
|
||||||
>
|
|
||||||
{{ t('harvester.addons.forklift.actions.remove') }}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="btn role-tertiary"
|
|
||||||
@click="addMapping"
|
|
||||||
>
|
|
||||||
{{ t('harvester.addons.forklift.actions.addMapping') }}
|
|
||||||
</button>
|
|
||||||
</Tab>
|
|
||||||
</Tabbed>
|
|
||||||
</CruResource>
|
|
||||||
</template>
|
|
||||||
@ -1809,86 +1809,94 @@ harvester:
|
|||||||
|
|
||||||
forklift:
|
forklift:
|
||||||
labels:
|
labels:
|
||||||
|
dashboard: Create
|
||||||
provider: Providers
|
provider: Providers
|
||||||
networkMap: Network Maps
|
networkMap: Network Maps
|
||||||
storageMap: Storage Maps
|
storageMap: Storage Maps
|
||||||
plan: Migration Plans
|
plan: Migration Plans
|
||||||
migration: Migrations
|
migration: Migrations
|
||||||
titles:
|
|
||||||
basic: Basic
|
|
||||||
auth: Authentication
|
|
||||||
providers: Providers
|
|
||||||
networkMappings: Network Mappings
|
|
||||||
storageMappings: Storage Mappings
|
|
||||||
mappings: Mappings
|
|
||||||
vms: Virtual Machines
|
|
||||||
fields:
|
fields:
|
||||||
providerType: Provider Type
|
|
||||||
providerTypeVsphere: vSphere
|
|
||||||
providerTypeOpenstack: OpenStack
|
|
||||||
providerTypeOpenshift: OpenShift / Harvester (Local)
|
|
||||||
url: URL
|
|
||||||
username: Username
|
username: Username
|
||||||
password: Password
|
password: Password
|
||||||
caCert: CA Certificate (PEM)
|
configureProvider:
|
||||||
insecureSkipVerify: Skip TLS Certificate Verification
|
title: Configure Provider
|
||||||
selectSecret: Select Secret
|
description: Connect to your VMware vCenter or ESXi host to discover virtual machines for migration
|
||||||
createSecret: Create New Credentials
|
connectionDetails: Connection Details
|
||||||
useSecret: Use Existing Secret
|
requirementsTitle: Requirements
|
||||||
sourceProvider: Source Provider
|
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."
|
||||||
destProvider: Destination Provider
|
name: Name
|
||||||
sourceNetworkId: Source Network ID
|
urlLabel: vCenter/ESXi URL
|
||||||
destNetworkType: Destination Network Type
|
urlPlaceholder: "https://vcenter.example.com/sdk"
|
||||||
destNetworkPod: Pod Network
|
urlHint: Enter the full URL including https://
|
||||||
destNetworkMultus: Multus
|
skipSsl: Skip SSL certificate verification
|
||||||
sourceDatastoreId: Source Datastore ID
|
skipSslHint: Not recommended for production environments
|
||||||
destStorageClass: Destination Storage Class
|
testConnection: Test Connection
|
||||||
networkMap: Network Map
|
testSuccess: Connection test passed
|
||||||
storageMap: Storage Map
|
testFailed: Connection test failed
|
||||||
|
testMissingFields: Please fill in all required fields before testing
|
||||||
|
testTimeout: Connection test timed out — the provider did not report a status in time
|
||||||
|
save: Save Provider and Continue
|
||||||
|
selectVms:
|
||||||
|
title: Select Virtual Machines
|
||||||
|
discovered: "{count} VMs discovered from"
|
||||||
|
lastSynced: Last synced 5 minutes ago
|
||||||
|
availableVms: Available Virtual Machines
|
||||||
|
selected: selected
|
||||||
|
saveSelection: Save Selection and Continue
|
||||||
|
manualEntry:
|
||||||
|
title: Add Virtual Machines
|
||||||
|
description: No VMs were discovered from the provider inventory. Add VMs manually by entering their IDs and names.
|
||||||
|
vmId: VM ID
|
||||||
|
vmIdPlaceholder: "e.g. vm-60527"
|
||||||
|
add: Add
|
||||||
|
added: VMs added
|
||||||
|
columns:
|
||||||
|
vmName: VM Name
|
||||||
|
vmId: VM ID
|
||||||
|
os: Operating System
|
||||||
|
resources: Resources
|
||||||
|
powerState: Power State
|
||||||
|
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.
|
||||||
|
createPlan: Create Migration Plan
|
||||||
|
tableTitle: Migration Plans
|
||||||
|
columns:
|
||||||
|
status: Status
|
||||||
|
plan: Migration Plan
|
||||||
|
progress: Progress
|
||||||
|
mappings: Mappings
|
||||||
|
configureMappings:
|
||||||
|
title: Set Mappings
|
||||||
|
description: Map VMware networks and datastores to Harvester and Longhorn target resources
|
||||||
|
save: Save Mappings and Continue
|
||||||
|
networkMapping:
|
||||||
|
title: Network Mapping
|
||||||
|
description: Map VMware port groups to Harvester networks
|
||||||
|
placeholder: Choose a Harvester network...
|
||||||
|
storageMapping:
|
||||||
|
title: Storage Mapping
|
||||||
|
description: Map VMware datastores to Harvester storage classes
|
||||||
|
placeholder: Choose a Longhorn Storage Volume
|
||||||
|
reviewMigration:
|
||||||
|
title: Review Migration Plan
|
||||||
|
description: Confirm your migration settings before starting the transfer of VMs to the target cluster.
|
||||||
|
migrationDetails: Migration Details
|
||||||
|
totalVms: Total VMs
|
||||||
|
vcpu: vCPU
|
||||||
|
memory: Memory
|
||||||
|
storage: Storage
|
||||||
|
source: Source
|
||||||
targetNamespace: Target Namespace
|
targetNamespace: Target Namespace
|
||||||
migrateSharedDisks: Migrate Shared Disks
|
migrationMode: Migration Mode
|
||||||
vmId: VM ID
|
coldMigration: Cold Migration
|
||||||
vmName: VM Name
|
vmsWillShutDown: VMs will shut down
|
||||||
plan: Migration Plan
|
virtualMachines: Virtual Machines
|
||||||
placeholders:
|
warningTitle: "Cold Migration Warning"
|
||||||
url: "e.g. https://vcenter/sdk"
|
warningMessage: "All source VMs will be powered off, and downtime will occur during migration. Plan your maintenance window accordingly and notify relevant stakeholders"
|
||||||
caCert: "-----BEGIN CERTIFICATE----- ..."
|
startMigration: Start Migration Plan
|
||||||
sourceNetworkId: "e.g. dvportgroup-13025"
|
|
||||||
sourceDatastoreId: "e.g. datastore-2040"
|
|
||||||
vmId: "e.g. vm-13007"
|
|
||||||
vmName: "e.g. my-vm"
|
|
||||||
actions:
|
|
||||||
addMapping: Add Mapping
|
|
||||||
addVm: Add VM
|
|
||||||
remove: Remove
|
|
||||||
detail:
|
|
||||||
plan: Plan
|
|
||||||
planNamespace: Plan Namespace
|
|
||||||
started: Started
|
|
||||||
completed: Completed
|
|
||||||
vmId: VM ID
|
|
||||||
vmPhase: Phase
|
|
||||||
vmStarted: Started
|
|
||||||
vmCompleted: Completed
|
|
||||||
vmCount: VMs in Plan
|
|
||||||
overallProgress: Overall Progress
|
|
||||||
pipelineSteps: Pipeline Steps
|
|
||||||
migrationName: Migration
|
|
||||||
migrationRun: "Migration #{idx}"
|
|
||||||
noCurrentMigration: No migration is currently running for this plan.
|
|
||||||
noHistory: No migration history available.
|
|
||||||
status:
|
|
||||||
label: Status
|
|
||||||
succeeded: Succeeded
|
|
||||||
failed: Failed
|
|
||||||
running: Running
|
|
||||||
executing: Executing
|
|
||||||
pending: Pending
|
|
||||||
ready: Ready
|
|
||||||
unknown: Unknown
|
|
||||||
tabs:
|
|
||||||
currentMigration: Current Migration
|
|
||||||
history: Migration History
|
|
||||||
|
|
||||||
vmImport:
|
vmImport:
|
||||||
titles:
|
titles:
|
||||||
|
|||||||
@ -1,60 +0,0 @@
|
|||||||
<script>
|
|
||||||
import ResourceTable from '@shell/components/ResourceTable';
|
|
||||||
import Loading from '@shell/components/Loading';
|
|
||||||
import { SCHEMA } from '@shell/config/types';
|
|
||||||
import { HCI } from '../types';
|
|
||||||
|
|
||||||
const schema = {
|
|
||||||
id: HCI.FORKLIFT_MIGRATION,
|
|
||||||
type: SCHEMA,
|
|
||||||
attributes: {
|
|
||||||
kind: HCI.FORKLIFT_MIGRATION,
|
|
||||||
namespaced: true
|
|
||||||
},
|
|
||||||
metadata: { name: HCI.FORKLIFT_MIGRATION },
|
|
||||||
};
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: 'HarvesterForkliftMigration',
|
|
||||||
components: { ResourceTable, Loading },
|
|
||||||
inheritAttrs: false,
|
|
||||||
|
|
||||||
async fetch() {
|
|
||||||
const inStore = this.$store.getters['currentProduct'].inStore;
|
|
||||||
|
|
||||||
this.rows = await this.$store.dispatch(`${ inStore }/findAll`, { type: HCI.FORKLIFT_MIGRATION });
|
|
||||||
|
|
||||||
const configSchema = this.$store.getters[`${ inStore }/schemaFor`](HCI.FORKLIFT_MIGRATION);
|
|
||||||
|
|
||||||
if (!configSchema?.collectionMethods.find((x) => x.toLowerCase() === 'post')) {
|
|
||||||
this.$store.dispatch('type-map/configureType', { match: HCI.FORKLIFT_MIGRATION, isCreatable: false });
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
data() {
|
|
||||||
return { rows: [] };
|
|
||||||
},
|
|
||||||
|
|
||||||
computed: {
|
|
||||||
schema() {
|
|
||||||
return schema;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
typeDisplay() {
|
|
||||||
return this.$store.getters['type-map/labelFor'](schema, 99);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<Loading v-if="$fetchState.pending" />
|
|
||||||
<ResourceTable
|
|
||||||
v-else
|
|
||||||
v-bind="$attrs"
|
|
||||||
:groupable="true"
|
|
||||||
:schema="schema"
|
|
||||||
:rows="rows"
|
|
||||||
key-field="_key"
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
@ -1,60 +0,0 @@
|
|||||||
<script>
|
|
||||||
import ResourceTable from '@shell/components/ResourceTable';
|
|
||||||
import Loading from '@shell/components/Loading';
|
|
||||||
import { SCHEMA } from '@shell/config/types';
|
|
||||||
import { HCI } from '../types';
|
|
||||||
|
|
||||||
const schema = {
|
|
||||||
id: HCI.FORKLIFT_NETWORK_MAP,
|
|
||||||
type: SCHEMA,
|
|
||||||
attributes: {
|
|
||||||
kind: HCI.FORKLIFT_NETWORK_MAP,
|
|
||||||
namespaced: true
|
|
||||||
},
|
|
||||||
metadata: { name: HCI.FORKLIFT_NETWORK_MAP },
|
|
||||||
};
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: 'HarvesterForkliftNetworkMap',
|
|
||||||
components: { ResourceTable, Loading },
|
|
||||||
inheritAttrs: false,
|
|
||||||
|
|
||||||
async fetch() {
|
|
||||||
const inStore = this.$store.getters['currentProduct'].inStore;
|
|
||||||
|
|
||||||
this.rows = await this.$store.dispatch(`${ inStore }/findAll`, { type: HCI.FORKLIFT_NETWORK_MAP });
|
|
||||||
|
|
||||||
const configSchema = this.$store.getters[`${ inStore }/schemaFor`](HCI.FORKLIFT_NETWORK_MAP);
|
|
||||||
|
|
||||||
if (!configSchema?.collectionMethods.find((x) => x.toLowerCase() === 'post')) {
|
|
||||||
this.$store.dispatch('type-map/configureType', { match: HCI.FORKLIFT_NETWORK_MAP, isCreatable: false });
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
data() {
|
|
||||||
return { rows: [] };
|
|
||||||
},
|
|
||||||
|
|
||||||
computed: {
|
|
||||||
schema() {
|
|
||||||
return schema;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
typeDisplay() {
|
|
||||||
return this.$store.getters['type-map/labelFor'](schema, 99);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<Loading v-if="$fetchState.pending" />
|
|
||||||
<ResourceTable
|
|
||||||
v-else
|
|
||||||
v-bind="$attrs"
|
|
||||||
:groupable="true"
|
|
||||||
:schema="schema"
|
|
||||||
:rows="rows"
|
|
||||||
key-field="_key"
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
@ -1,60 +0,0 @@
|
|||||||
<script>
|
|
||||||
import ResourceTable from '@shell/components/ResourceTable';
|
|
||||||
import Loading from '@shell/components/Loading';
|
|
||||||
import { SCHEMA } from '@shell/config/types';
|
|
||||||
import { HCI } from '../types';
|
|
||||||
|
|
||||||
const schema = {
|
|
||||||
id: HCI.FORKLIFT_PLAN,
|
|
||||||
type: SCHEMA,
|
|
||||||
attributes: {
|
|
||||||
kind: HCI.FORKLIFT_PLAN,
|
|
||||||
namespaced: true
|
|
||||||
},
|
|
||||||
metadata: { name: HCI.FORKLIFT_PLAN },
|
|
||||||
};
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: 'HarvesterForkliftPlan',
|
|
||||||
components: { ResourceTable, Loading },
|
|
||||||
inheritAttrs: false,
|
|
||||||
|
|
||||||
async fetch() {
|
|
||||||
const inStore = this.$store.getters['currentProduct'].inStore;
|
|
||||||
|
|
||||||
this.rows = await this.$store.dispatch(`${ inStore }/findAll`, { type: HCI.FORKLIFT_PLAN });
|
|
||||||
|
|
||||||
const configSchema = this.$store.getters[`${ inStore }/schemaFor`](HCI.FORKLIFT_PLAN);
|
|
||||||
|
|
||||||
if (!configSchema?.collectionMethods.find((x) => x.toLowerCase() === 'post')) {
|
|
||||||
this.$store.dispatch('type-map/configureType', { match: HCI.FORKLIFT_PLAN, isCreatable: false });
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
data() {
|
|
||||||
return { rows: [] };
|
|
||||||
},
|
|
||||||
|
|
||||||
computed: {
|
|
||||||
schema() {
|
|
||||||
return schema;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
typeDisplay() {
|
|
||||||
return this.$store.getters['type-map/labelFor'](schema, 99);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<Loading v-if="$fetchState.pending" />
|
|
||||||
<ResourceTable
|
|
||||||
v-else
|
|
||||||
v-bind="$attrs"
|
|
||||||
:groupable="true"
|
|
||||||
:schema="schema"
|
|
||||||
:rows="rows"
|
|
||||||
key-field="_key"
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
@ -1,60 +0,0 @@
|
|||||||
<script>
|
|
||||||
import ResourceTable from '@shell/components/ResourceTable';
|
|
||||||
import Loading from '@shell/components/Loading';
|
|
||||||
import { SCHEMA } from '@shell/config/types';
|
|
||||||
import { HCI } from '../types';
|
|
||||||
|
|
||||||
const schema = {
|
|
||||||
id: HCI.FORKLIFT_PROVIDER,
|
|
||||||
type: SCHEMA,
|
|
||||||
attributes: {
|
|
||||||
kind: HCI.FORKLIFT_PROVIDER,
|
|
||||||
namespaced: true
|
|
||||||
},
|
|
||||||
metadata: { name: HCI.FORKLIFT_PROVIDER },
|
|
||||||
};
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: 'HarvesterForkliftProvider',
|
|
||||||
components: { ResourceTable, Loading },
|
|
||||||
inheritAttrs: false,
|
|
||||||
|
|
||||||
async fetch() {
|
|
||||||
const inStore = this.$store.getters['currentProduct'].inStore;
|
|
||||||
|
|
||||||
this.rows = await this.$store.dispatch(`${ inStore }/findAll`, { type: HCI.FORKLIFT_PROVIDER });
|
|
||||||
|
|
||||||
const configSchema = this.$store.getters[`${ inStore }/schemaFor`](HCI.FORKLIFT_PROVIDER);
|
|
||||||
|
|
||||||
if (!configSchema?.collectionMethods.find((x) => x.toLowerCase() === 'post')) {
|
|
||||||
this.$store.dispatch('type-map/configureType', { match: HCI.FORKLIFT_PROVIDER, isCreatable: false });
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
data() {
|
|
||||||
return { rows: [] };
|
|
||||||
},
|
|
||||||
|
|
||||||
computed: {
|
|
||||||
schema() {
|
|
||||||
return schema;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
typeDisplay() {
|
|
||||||
return this.$store.getters['type-map/labelFor'](schema, 99);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<Loading v-if="$fetchState.pending" />
|
|
||||||
<ResourceTable
|
|
||||||
v-else
|
|
||||||
v-bind="$attrs"
|
|
||||||
:groupable="true"
|
|
||||||
:schema="schema"
|
|
||||||
:rows="rows"
|
|
||||||
key-field="_key"
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
@ -1,60 +0,0 @@
|
|||||||
<script>
|
|
||||||
import ResourceTable from '@shell/components/ResourceTable';
|
|
||||||
import Loading from '@shell/components/Loading';
|
|
||||||
import { SCHEMA } from '@shell/config/types';
|
|
||||||
import { HCI } from '../types';
|
|
||||||
|
|
||||||
const schema = {
|
|
||||||
id: HCI.FORKLIFT_STORAGE_MAP,
|
|
||||||
type: SCHEMA,
|
|
||||||
attributes: {
|
|
||||||
kind: HCI.FORKLIFT_STORAGE_MAP,
|
|
||||||
namespaced: true
|
|
||||||
},
|
|
||||||
metadata: { name: HCI.FORKLIFT_STORAGE_MAP },
|
|
||||||
};
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: 'HarvesterForkliftStorageMap',
|
|
||||||
components: { ResourceTable, Loading },
|
|
||||||
inheritAttrs: false,
|
|
||||||
|
|
||||||
async fetch() {
|
|
||||||
const inStore = this.$store.getters['currentProduct'].inStore;
|
|
||||||
|
|
||||||
this.rows = await this.$store.dispatch(`${ inStore }/findAll`, { type: HCI.FORKLIFT_STORAGE_MAP });
|
|
||||||
|
|
||||||
const configSchema = this.$store.getters[`${ inStore }/schemaFor`](HCI.FORKLIFT_STORAGE_MAP);
|
|
||||||
|
|
||||||
if (!configSchema?.collectionMethods.find((x) => x.toLowerCase() === 'post')) {
|
|
||||||
this.$store.dispatch('type-map/configureType', { match: HCI.FORKLIFT_STORAGE_MAP, isCreatable: false });
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
data() {
|
|
||||||
return { rows: [] };
|
|
||||||
},
|
|
||||||
|
|
||||||
computed: {
|
|
||||||
schema() {
|
|
||||||
return schema;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
typeDisplay() {
|
|
||||||
return this.$store.getters['type-map/labelFor'](schema, 99);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<Loading v-if="$fetchState.pending" />
|
|
||||||
<ResourceTable
|
|
||||||
v-else
|
|
||||||
v-bind="$attrs"
|
|
||||||
:groupable="true"
|
|
||||||
:schema="schema"
|
|
||||||
:rows="rows"
|
|
||||||
key-field="_key"
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
@ -1,47 +0,0 @@
|
|||||||
import { FORKLIFT_API_VERSION } from '../config/harvester-map';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Shared mixin for Forklift edit pages that need to select
|
|
||||||
* source and destination providers from available Provider CRs.
|
|
||||||
*
|
|
||||||
* Components using this mixin must load providers in their own fetch():
|
|
||||||
* this.allProviders = await this.$store.dispatch(`${ inStore }/findAll`, { type: HCI.FORKLIFT_PROVIDER });
|
|
||||||
*/
|
|
||||||
export default {
|
|
||||||
data() {
|
|
||||||
return { allProviders: [] };
|
|
||||||
},
|
|
||||||
|
|
||||||
computed: {
|
|
||||||
providerOptions() {
|
|
||||||
return this.allProviders.map((p) => ({
|
|
||||||
label: `${ p.metadata.name } (${ p.spec?.type || 'unknown' })`,
|
|
||||||
value: p.metadata.name,
|
|
||||||
}));
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
methods: {
|
|
||||||
/**
|
|
||||||
* Build a Forklift object reference for a Provider.
|
|
||||||
*/
|
|
||||||
buildProviderRef(name) {
|
|
||||||
const provider = this.allProviders.find((p) => p.metadata.name === name);
|
|
||||||
|
|
||||||
return {
|
|
||||||
apiVersion: FORKLIFT_API_VERSION,
|
|
||||||
kind: 'Provider',
|
|
||||||
name,
|
|
||||||
namespace: provider?.metadata?.namespace || this.value.metadata.namespace || 'default',
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
updateSourceProvider(val) {
|
|
||||||
this.value.spec.provider.source = this.buildProviderRef(val);
|
|
||||||
},
|
|
||||||
|
|
||||||
updateDestProvider(val) {
|
|
||||||
this.value.spec.provider.destination = this.buildProviderRef(val);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
556
pkg/harvester/pages/c/_cluster/forklift/configure-mappings.vue
Normal file
556
pkg/harvester/pages/c/_cluster/forklift/configure-mappings.vue
Normal file
@ -0,0 +1,556 @@
|
|||||||
|
<script>
|
||||||
|
import { defineComponent, ref, computed } from 'vue';
|
||||||
|
import { useStore } from 'vuex';
|
||||||
|
import { useRoute, useRouter } from 'vue-router';
|
||||||
|
import Loading from '@shell/components/Loading';
|
||||||
|
import Masthead from '@shell/components/ResourceList/Masthead';
|
||||||
|
import AsyncButton from '@shell/components/AsyncButton';
|
||||||
|
import LabeledSelect from '@shell/components/form/LabeledSelect';
|
||||||
|
import { RcItemCard } from '@components/RcItemCard';
|
||||||
|
import { SCHEMA, STORAGE_CLASS, NETWORK_ATTACHMENT } from '@shell/config/types';
|
||||||
|
import { useI18n } from '@shell/composables/useI18n';
|
||||||
|
import { HCI } from '../../../../types';
|
||||||
|
import { PRODUCT_NAME } from '../../../../config/harvester';
|
||||||
|
import vmData from '../../../../utils/vms.json';
|
||||||
|
|
||||||
|
const schema = {
|
||||||
|
id: HCI.FORKLIFT_NETWORK_MAP,
|
||||||
|
type: SCHEMA,
|
||||||
|
attributes: {
|
||||||
|
kind: HCI.FORKLIFT_NETWORK_MAP,
|
||||||
|
namespaced: true
|
||||||
|
},
|
||||||
|
metadata: { name: HCI.FORKLIFT_NETWORK_MAP },
|
||||||
|
};
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'ForkliftConfigureMappings',
|
||||||
|
|
||||||
|
components: {
|
||||||
|
Loading,
|
||||||
|
Masthead,
|
||||||
|
AsyncButton,
|
||||||
|
LabeledSelect,
|
||||||
|
RcItemCard,
|
||||||
|
},
|
||||||
|
|
||||||
|
setup() {
|
||||||
|
const store = useStore();
|
||||||
|
const route = useRoute();
|
||||||
|
const router = useRouter();
|
||||||
|
const { t } = useI18n(store);
|
||||||
|
|
||||||
|
const vms = ref([]);
|
||||||
|
const harvesterNetworks = ref([]);
|
||||||
|
const storageClasses = ref([]);
|
||||||
|
const networkEntries = ref([]);
|
||||||
|
const storageEntries = ref([]);
|
||||||
|
const errors = ref([]);
|
||||||
|
const loading = ref(true);
|
||||||
|
|
||||||
|
const providerName = computed(() => route.query.provider || 'vsphere');
|
||||||
|
const namespace = computed(() => route.query.namespace || 'default');
|
||||||
|
|
||||||
|
const harvesterNetworkOptions = computed(() => {
|
||||||
|
const options = [
|
||||||
|
{ label: 'Pod Networking', value: 'pod' },
|
||||||
|
{ label: 'Ignored', value: 'ignored' },
|
||||||
|
];
|
||||||
|
|
||||||
|
harvesterNetworks.value.forEach((net) => {
|
||||||
|
const ns = net.metadata?.namespace || '';
|
||||||
|
const name = net.metadata?.name || net.id;
|
||||||
|
const fullName = ns ? `${ ns }/${ name }` : name;
|
||||||
|
|
||||||
|
options.push({ label: fullName, value: fullName });
|
||||||
|
});
|
||||||
|
|
||||||
|
return options;
|
||||||
|
});
|
||||||
|
|
||||||
|
const storageClassOptions = computed(() => {
|
||||||
|
const options = storageClasses.value.map((sc) => ({
|
||||||
|
label: sc.metadata?.name || sc.id,
|
||||||
|
value: sc.metadata?.name || sc.id,
|
||||||
|
}));
|
||||||
|
|
||||||
|
if (!options.find((o) => o.value === 'harvester-longhorn')) {
|
||||||
|
options.unshift({ label: 'harvester-longhorn', value: 'harvester-longhorn' });
|
||||||
|
}
|
||||||
|
|
||||||
|
return options;
|
||||||
|
});
|
||||||
|
|
||||||
|
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 canSave = computed(() => {
|
||||||
|
return networkEntries.value.length > 0 && storageEntries.value.length > 0 &&
|
||||||
|
allNetworksMapped.value && allStorageMapped.value;
|
||||||
|
});
|
||||||
|
|
||||||
|
const buildNetworkEntries = () => {
|
||||||
|
const entries = [];
|
||||||
|
|
||||||
|
vms.value.forEach((vm) => {
|
||||||
|
if (vm.networks && vm.networks.length > 0) {
|
||||||
|
vm.networks.forEach((net) => {
|
||||||
|
entries.push({
|
||||||
|
vmId: vm.id,
|
||||||
|
vmName: vm.name || vm.id,
|
||||||
|
name: net.name || 'Unknown',
|
||||||
|
id: net.id || '',
|
||||||
|
vlanId: net.vlanId || '',
|
||||||
|
target: 'pod',
|
||||||
|
_key: `net-${ vm.id }-${ net.id || net.name }`,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
entries.push({
|
||||||
|
vmId: vm.id,
|
||||||
|
vmName: vm.name || vm.id,
|
||||||
|
name: 'Default Network',
|
||||||
|
id: '',
|
||||||
|
vlanId: '',
|
||||||
|
target: 'pod',
|
||||||
|
_key: `net-${ vm.id }-default`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
networkEntries.value = entries;
|
||||||
|
};
|
||||||
|
|
||||||
|
const buildStorageEntries = () => {
|
||||||
|
const entries = [];
|
||||||
|
|
||||||
|
vms.value.forEach((vm) => {
|
||||||
|
if (vm.disks && vm.disks.length > 0) {
|
||||||
|
const seen = new Set();
|
||||||
|
|
||||||
|
vm.disks.forEach((disk) => {
|
||||||
|
const ds = disk.datastore;
|
||||||
|
|
||||||
|
if (ds && ds.id && !seen.has(ds.id)) {
|
||||||
|
seen.add(ds.id);
|
||||||
|
entries.push({
|
||||||
|
vmId: vm.id,
|
||||||
|
vmName: vm.name || vm.id,
|
||||||
|
name: ds.name || 'Unknown',
|
||||||
|
id: ds.id || '',
|
||||||
|
type: ds.type || '',
|
||||||
|
capacity: disk.capacity || 0,
|
||||||
|
target: 'harvester-longhorn',
|
||||||
|
_key: `stor-${ vm.id }-${ ds.id }`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
entries.push({
|
||||||
|
vmId: vm.id,
|
||||||
|
vmName: vm.name || vm.id,
|
||||||
|
name: 'Default Datastore',
|
||||||
|
id: '',
|
||||||
|
type: '',
|
||||||
|
capacity: 0,
|
||||||
|
target: 'harvester-longhorn',
|
||||||
|
_key: `stor-${ vm.id }-default`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
storageEntries.value = entries;
|
||||||
|
};
|
||||||
|
|
||||||
|
const formatStorageDetail = (entry) => {
|
||||||
|
const parts = [];
|
||||||
|
|
||||||
|
if (entry.type) {
|
||||||
|
parts.push(entry.type);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (entry.capacity) {
|
||||||
|
const gb = entry.capacity / (1024 * 1024 * 1024);
|
||||||
|
|
||||||
|
if (gb >= 1024) {
|
||||||
|
parts.push(`${ (gb / 1024).toFixed(1) } TB`);
|
||||||
|
} else {
|
||||||
|
parts.push(`${ Math.round(gb) } GB`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return parts.join(' \u2022 ');
|
||||||
|
};
|
||||||
|
|
||||||
|
const cancel = () => {
|
||||||
|
router.push({
|
||||||
|
name: `${ PRODUCT_NAME }-c-cluster-forklift`,
|
||||||
|
params: {
|
||||||
|
product: route.params.product,
|
||||||
|
cluster: route.params.cluster,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const saveMappings = async(buttonCb) => {
|
||||||
|
const inStore = store.getters['currentProduct'].inStore;
|
||||||
|
|
||||||
|
const providerRef = {
|
||||||
|
source: {
|
||||||
|
apiVersion: 'forklift.konveyor.io/v1beta1',
|
||||||
|
kind: 'Provider',
|
||||||
|
name: providerName.value,
|
||||||
|
namespace: namespace.value,
|
||||||
|
},
|
||||||
|
destination: {
|
||||||
|
apiVersion: 'forklift.konveyor.io/v1beta1',
|
||||||
|
kind: 'Provider',
|
||||||
|
name: 'host',
|
||||||
|
namespace: 'forklift',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Create NetworkMap
|
||||||
|
const networkMapSpec = {
|
||||||
|
map: networkEntries.value.map((entry) => {
|
||||||
|
if (entry.target === 'pod') {
|
||||||
|
return {
|
||||||
|
source: { name: entry.name, id: entry.id },
|
||||||
|
destination: { type: 'pod' },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (entry.target === 'ignored') {
|
||||||
|
return {
|
||||||
|
source: { name: entry.name, id: entry.id },
|
||||||
|
destination: { type: 'ignored' },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
source: { name: entry.name, id: entry.id },
|
||||||
|
destination: {
|
||||||
|
type: 'multus',
|
||||||
|
name: entry.target,
|
||||||
|
namespace: namespace.value,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
provider: providerRef,
|
||||||
|
};
|
||||||
|
|
||||||
|
const networkMap = await store.dispatch(`${ inStore }/create`, {
|
||||||
|
type: HCI.FORKLIFT_NETWORK_MAP,
|
||||||
|
metadata: {
|
||||||
|
name: `${ providerName.value }-network-map-${ Math.random().toString(36).substring(2, 7) }`,
|
||||||
|
namespace: namespace.value,
|
||||||
|
},
|
||||||
|
spec: networkMapSpec,
|
||||||
|
});
|
||||||
|
|
||||||
|
await networkMap.save();
|
||||||
|
|
||||||
|
// Create StorageMap
|
||||||
|
const storageMapSpec = {
|
||||||
|
map: storageEntries.value.map((entry) => ({
|
||||||
|
source: { name: entry.name, id: entry.id },
|
||||||
|
destination: { storageClass: entry.target },
|
||||||
|
})),
|
||||||
|
provider: providerRef,
|
||||||
|
};
|
||||||
|
|
||||||
|
const storageMap = await store.dispatch(`${ inStore }/create`, {
|
||||||
|
type: HCI.FORKLIFT_STORAGE_MAP,
|
||||||
|
metadata: {
|
||||||
|
name: `${ providerName.value }-storage-map-${ Math.random().toString(36).substring(2, 7) }`,
|
||||||
|
namespace: namespace.value,
|
||||||
|
},
|
||||||
|
spec: storageMapSpec,
|
||||||
|
});
|
||||||
|
|
||||||
|
await storageMap.save();
|
||||||
|
|
||||||
|
// Navigate to review page with all mapping data
|
||||||
|
const networkMappingsData = networkEntries.value.map((entry) => ({
|
||||||
|
vmId: entry.vmId,
|
||||||
|
source: entry.name,
|
||||||
|
target: entry.target === 'pod' ? 'Pod Networking' : entry.target === 'ignored' ? 'Ignored' : entry.target,
|
||||||
|
}));
|
||||||
|
|
||||||
|
const storageMappingsData = storageEntries.value.map((entry) => ({
|
||||||
|
vmId: entry.vmId,
|
||||||
|
source: entry.name,
|
||||||
|
target: entry.target,
|
||||||
|
}));
|
||||||
|
|
||||||
|
router.push({
|
||||||
|
name: `${ PRODUCT_NAME }-c-cluster-forklift-review-migration`,
|
||||||
|
params: {
|
||||||
|
product: route.params.product,
|
||||||
|
cluster: route.params.cluster,
|
||||||
|
},
|
||||||
|
query: {
|
||||||
|
provider: providerName.value,
|
||||||
|
namespace: namespace.value,
|
||||||
|
vms: route.query.vms,
|
||||||
|
networkMap: networkMap.metadata.name,
|
||||||
|
storageMap: storageMap.metadata.name,
|
||||||
|
networkMappings: JSON.stringify(networkMappingsData),
|
||||||
|
storageMappings: JSON.stringify(storageMappingsData),
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
buttonCb(true);
|
||||||
|
} catch (err) {
|
||||||
|
errors.value = [err.message || err];
|
||||||
|
buttonCb(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const init = async() => {
|
||||||
|
const inStore = store.getters['currentProduct'].inStore;
|
||||||
|
|
||||||
|
try {
|
||||||
|
harvesterNetworks.value = await store.dispatch(`${ inStore }/findAll`, { type: NETWORK_ATTACHMENT });
|
||||||
|
} catch (e) {
|
||||||
|
harvesterNetworks.value = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
storageClasses.value = await store.dispatch(`${ inStore }/findAll`, { type: STORAGE_CLASS });
|
||||||
|
} catch (e) {
|
||||||
|
storageClasses.value = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const vmsParam = route.query.vms;
|
||||||
|
|
||||||
|
if (vmsParam) {
|
||||||
|
try {
|
||||||
|
const vmIds = JSON.parse(vmsParam);
|
||||||
|
|
||||||
|
vms.value = vmIds.map((id) => {
|
||||||
|
const found = vmData.find((vm) => vm.id === id);
|
||||||
|
|
||||||
|
return found || {
|
||||||
|
id, name: id, networks: [], disks: []
|
||||||
|
};
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
vms.value = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
buildNetworkEntries();
|
||||||
|
buildStorageEntries();
|
||||||
|
loading.value = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
init();
|
||||||
|
|
||||||
|
return {
|
||||||
|
schema,
|
||||||
|
loading,
|
||||||
|
networkEntries,
|
||||||
|
storageEntries,
|
||||||
|
errors,
|
||||||
|
harvesterNetworkOptions,
|
||||||
|
storageClassOptions,
|
||||||
|
canSave,
|
||||||
|
t,
|
||||||
|
formatStorageDetail,
|
||||||
|
cancel,
|
||||||
|
saveMappings,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Loading v-if="loading" />
|
||||||
|
<div
|
||||||
|
v-else
|
||||||
|
class="configure-mappings"
|
||||||
|
>
|
||||||
|
<Masthead
|
||||||
|
:schema="schema"
|
||||||
|
:resource="schema.id"
|
||||||
|
:type-display="t('harvester.addons.forklift.configureMappings.title')"
|
||||||
|
:is-creatable="false"
|
||||||
|
>
|
||||||
|
<template #subHeader>
|
||||||
|
<p class="text-muted line-height-20 mmt-5">
|
||||||
|
{{ t('harvester.addons.forklift.configureMappings.description') }}
|
||||||
|
</p>
|
||||||
|
</template>
|
||||||
|
</Masthead>
|
||||||
|
|
||||||
|
<div class="mappings-columns">
|
||||||
|
<!-- Network Mapping -->
|
||||||
|
<div class="mapping-column">
|
||||||
|
<div>
|
||||||
|
<h3 class="mapping-section-title">
|
||||||
|
{{ t('harvester.addons.forklift.configureMappings.networkMapping.title') }}
|
||||||
|
</h3>
|
||||||
|
<p class="text-muted line-height-20">
|
||||||
|
{{ t('harvester.addons.forklift.configureMappings.networkMapping.description') }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<RcItemCard
|
||||||
|
v-for="entry in networkEntries"
|
||||||
|
:id="entry._key"
|
||||||
|
:key="entry._key"
|
||||||
|
:variant="'small'"
|
||||||
|
:header="{ title: { text: entry.vmId }, statuses: entry.target ? [{ icon: 'icon-notify-tick', color: 'text-success' }] : [] }"
|
||||||
|
>
|
||||||
|
<template #item-card-content>
|
||||||
|
<div class="card-content-row">
|
||||||
|
<div class="mapping-source">
|
||||||
|
<span class="source-name">{{ entry.name }}</span>
|
||||||
|
<span class="source-id text-muted">{{ entry.id }}</span>
|
||||||
|
<span class="source-detail text-muted">VLAN {{ entry.vlanId || '0' }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="mapping-arrow">
|
||||||
|
→
|
||||||
|
</div>
|
||||||
|
<div class="mapping-target">
|
||||||
|
<LabeledSelect
|
||||||
|
v-model:value="entry.target"
|
||||||
|
:options="harvesterNetworkOptions"
|
||||||
|
:placeholder="t('harvester.addons.forklift.configureMappings.networkMapping.placeholder')"
|
||||||
|
:searchable="true"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</RcItemCard>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Storage Mapping -->
|
||||||
|
<div class="mapping-column">
|
||||||
|
<div>
|
||||||
|
<h3 class="mapping-section-title">
|
||||||
|
{{ t('harvester.addons.forklift.configureMappings.storageMapping.title') }}
|
||||||
|
</h3>
|
||||||
|
<p class="text-muted">
|
||||||
|
{{ t('harvester.addons.forklift.configureMappings.storageMapping.description') }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<RcItemCard
|
||||||
|
v-for="entry in storageEntries"
|
||||||
|
:id="entry._key"
|
||||||
|
:key="entry._key"
|
||||||
|
:variant="'small'"
|
||||||
|
:header="{ title: { text: entry.vmId }, statuses: entry.target ? [{ icon: 'icon-notify-tick', color: 'text-success' }] : [] }"
|
||||||
|
>
|
||||||
|
<template #item-card-content>
|
||||||
|
<div class="card-content-row">
|
||||||
|
<div class="mapping-source">
|
||||||
|
<span class="source-name">{{ entry.name }}</span>
|
||||||
|
<span class="source-id text-muted">{{ entry.id }}</span>
|
||||||
|
<span
|
||||||
|
v-if="entry.type || entry.capacity"
|
||||||
|
class="source-detail text-muted"
|
||||||
|
>{{ formatStorageDetail(entry) }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="mapping-arrow">
|
||||||
|
→
|
||||||
|
</div>
|
||||||
|
<div class="mapping-target">
|
||||||
|
<LabeledSelect
|
||||||
|
v-model:value="entry.target"
|
||||||
|
:options="storageClassOptions"
|
||||||
|
:placeholder="t('harvester.addons.forklift.configureMappings.storageMapping.placeholder')"
|
||||||
|
:searchable="true"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</RcItemCard>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="actions-footer">
|
||||||
|
<button
|
||||||
|
class="btn role-secondary"
|
||||||
|
@click="cancel"
|
||||||
|
>
|
||||||
|
{{ t('generic.cancel') }}
|
||||||
|
</button>
|
||||||
|
<AsyncButton
|
||||||
|
:disabled="!canSave"
|
||||||
|
:action-label="t('harvester.addons.forklift.configureMappings.save')"
|
||||||
|
:waiting-label="t('harvester.addons.forklift.configureMappings.save')"
|
||||||
|
:success-label="t('harvester.addons.forklift.configureMappings.save')"
|
||||||
|
:error-label="t('harvester.addons.forklift.configureMappings.save')"
|
||||||
|
@click="saveMappings"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.configure-mappings {
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mappings-columns {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
gap: 64px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mapping-column {
|
||||||
|
display: flex;
|
||||||
|
gap: 12px;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mapping-section-title {
|
||||||
|
font-weight: 600;
|
||||||
|
margin: 0 0 5px 0;
|
||||||
|
line-height: 28px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-content-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 40px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mapping-source {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
min-width: 160px;
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 20px;
|
||||||
|
|
||||||
|
.source-name {
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mapping-arrow {
|
||||||
|
color: var(--muted);
|
||||||
|
font-size: 16px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mapping-target {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.actions-footer {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: 10px;
|
||||||
|
margin-top: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.line-height-20 {
|
||||||
|
line-height: 20px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
477
pkg/harvester/pages/c/_cluster/forklift/configure-provider.vue
Normal file
477
pkg/harvester/pages/c/_cluster/forklift/configure-provider.vue
Normal file
@ -0,0 +1,477 @@
|
|||||||
|
<script>
|
||||||
|
import { defineComponent, ref, computed } from 'vue';
|
||||||
|
import { useStore } from 'vuex';
|
||||||
|
import { useRoute, useRouter } from 'vue-router';
|
||||||
|
import { LabeledInput } from '@components/Form/LabeledInput';
|
||||||
|
import { Checkbox } from '@components/Form/Checkbox';
|
||||||
|
import { Banner } from '@components/Banner';
|
||||||
|
import Masthead from '@shell/components/ResourceList/Masthead';
|
||||||
|
import AsyncButton from '@shell/components/AsyncButton';
|
||||||
|
import { SCHEMA, SECRET } from '@shell/config/types';
|
||||||
|
import { randomStr } from '@shell/utils/string';
|
||||||
|
import { useI18n } from '@shell/composables/useI18n';
|
||||||
|
import { HCI } from '../../../../types';
|
||||||
|
import { PRODUCT_NAME } from '../../../../config/harvester';
|
||||||
|
|
||||||
|
const schema = {
|
||||||
|
id: HCI.FORKLIFT_PROVIDER,
|
||||||
|
type: SCHEMA,
|
||||||
|
attributes: {
|
||||||
|
kind: HCI.FORKLIFT_PROVIDER,
|
||||||
|
namespaced: true
|
||||||
|
},
|
||||||
|
metadata: { name: HCI.FORKLIFT_PROVIDER },
|
||||||
|
};
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'ForkliftConfigureProvider',
|
||||||
|
|
||||||
|
components: {
|
||||||
|
LabeledInput,
|
||||||
|
Checkbox,
|
||||||
|
Banner,
|
||||||
|
Masthead,
|
||||||
|
AsyncButton,
|
||||||
|
},
|
||||||
|
|
||||||
|
setup() {
|
||||||
|
const store = useStore();
|
||||||
|
const route = useRoute();
|
||||||
|
const router = useRouter();
|
||||||
|
const { t } = useI18n(store);
|
||||||
|
|
||||||
|
const allSecrets = ref([]);
|
||||||
|
const providerName = ref('');
|
||||||
|
const url = ref('');
|
||||||
|
const username = ref('');
|
||||||
|
const password = ref('');
|
||||||
|
const skipTlsVerify = ref(false);
|
||||||
|
const testResult = ref(null);
|
||||||
|
const testError = ref(null);
|
||||||
|
const errors = ref([]);
|
||||||
|
const createdProvider = ref(null);
|
||||||
|
const createdSecret = ref(null);
|
||||||
|
const loading = ref(true);
|
||||||
|
|
||||||
|
const isFormValid = computed(() => !!providerName.value && !!url.value && !!username.value && !!password.value);
|
||||||
|
|
||||||
|
const cancel = () => {
|
||||||
|
router.push({
|
||||||
|
name: `${ PRODUCT_NAME }-c-cluster-forklift`,
|
||||||
|
params: {
|
||||||
|
product: route.params.product,
|
||||||
|
cluster: route.params.cluster,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const testConnection = async(buttonCb) => {
|
||||||
|
testResult.value = null;
|
||||||
|
testError.value = null;
|
||||||
|
|
||||||
|
if (!providerName.value || !url.value || !username.value || !password.value) {
|
||||||
|
testError.value = t('harvester.addons.forklift.configureProvider.testMissingFields');
|
||||||
|
buttonCb(false);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const inStore = store.getters['currentProduct'].inStore;
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (createdProvider.value) {
|
||||||
|
await createdProvider.value.remove();
|
||||||
|
createdProvider.value = null;
|
||||||
|
}
|
||||||
|
if (createdSecret.value) {
|
||||||
|
await createdSecret.value.remove();
|
||||||
|
createdSecret.value = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const namespace = 'default';
|
||||||
|
const secretName = `${ providerName.value }-creds-${ randomStr(4).toLowerCase() }`;
|
||||||
|
|
||||||
|
const newSecret = await store.dispatch(`${ inStore }/create`, {
|
||||||
|
type: SECRET,
|
||||||
|
metadata: {
|
||||||
|
name: secretName,
|
||||||
|
namespace,
|
||||||
|
labels: {
|
||||||
|
createdForProviderType: 'vsphere',
|
||||||
|
createdForResourceType: 'providers',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
newSecret['_type'] = 'Opaque';
|
||||||
|
newSecret['data'] = {
|
||||||
|
user: btoa(username.value),
|
||||||
|
password: btoa(password.value),
|
||||||
|
insecureSkipVerify: btoa(String(skipTlsVerify.value)),
|
||||||
|
url: btoa(url.value),
|
||||||
|
};
|
||||||
|
|
||||||
|
await newSecret.save();
|
||||||
|
createdSecret.value = newSecret;
|
||||||
|
|
||||||
|
const provider = await store.dispatch(`${ inStore }/create`, {
|
||||||
|
type: HCI.FORKLIFT_PROVIDER,
|
||||||
|
metadata: {
|
||||||
|
name: providerName.value,
|
||||||
|
namespace,
|
||||||
|
},
|
||||||
|
spec: {
|
||||||
|
type: 'vsphere',
|
||||||
|
url: url.value,
|
||||||
|
secret: {
|
||||||
|
name: secretName,
|
||||||
|
namespace,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
await provider.save();
|
||||||
|
createdProvider.value = provider;
|
||||||
|
|
||||||
|
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) {
|
||||||
|
testResult.value = t('harvester.addons.forklift.configureProvider.testSuccess');
|
||||||
|
buttonCb(true);
|
||||||
|
} else {
|
||||||
|
if (createdProvider.value) {
|
||||||
|
await createdProvider.value.remove();
|
||||||
|
createdProvider.value = null;
|
||||||
|
}
|
||||||
|
if (createdSecret.value) {
|
||||||
|
await createdSecret.value.remove();
|
||||||
|
createdSecret.value = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
testError.value = errorMsg || t('harvester.addons.forklift.configureProvider.testTimeout');
|
||||||
|
buttonCb(false);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
if (createdProvider.value) {
|
||||||
|
try {
|
||||||
|
await createdProvider.value.remove();
|
||||||
|
} catch (e) {}
|
||||||
|
createdProvider.value = null;
|
||||||
|
}
|
||||||
|
if (createdSecret.value) {
|
||||||
|
try {
|
||||||
|
await createdSecret.value.remove();
|
||||||
|
} catch (e) {}
|
||||||
|
createdSecret.value = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
testError.value = err.message || t('harvester.addons.forklift.configureProvider.testFailed');
|
||||||
|
buttonCb(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const saveProvider = async(buttonCb) => {
|
||||||
|
const inStore = store.getters['currentProduct'].inStore;
|
||||||
|
|
||||||
|
errors.value = [];
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (createdProvider.value) {
|
||||||
|
router.push({
|
||||||
|
name: `${ PRODUCT_NAME }-c-cluster-forklift-select-vms`,
|
||||||
|
params: {
|
||||||
|
product: route.params.product,
|
||||||
|
cluster: route.params.cluster,
|
||||||
|
},
|
||||||
|
query: {
|
||||||
|
provider: providerName.value,
|
||||||
|
namespace: createdProvider.value.metadata.namespace,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const namespace = 'default';
|
||||||
|
const secretName = `${ providerName.value }-creds-${ randomStr(4).toLowerCase() }`;
|
||||||
|
|
||||||
|
const newSecret = await store.dispatch(`${ inStore }/create`, {
|
||||||
|
type: SECRET,
|
||||||
|
metadata: {
|
||||||
|
name: secretName,
|
||||||
|
namespace,
|
||||||
|
labels: {
|
||||||
|
createdForProviderType: 'vsphere',
|
||||||
|
createdForResourceType: 'providers',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
newSecret['_type'] = 'Opaque';
|
||||||
|
newSecret['data'] = {
|
||||||
|
user: btoa(username.value),
|
||||||
|
password: btoa(password.value),
|
||||||
|
insecureSkipVerify: btoa(String(skipTlsVerify.value)),
|
||||||
|
url: btoa(url.value),
|
||||||
|
};
|
||||||
|
|
||||||
|
await newSecret.save();
|
||||||
|
|
||||||
|
const provider = await store.dispatch(`${ inStore }/create`, {
|
||||||
|
type: HCI.FORKLIFT_PROVIDER,
|
||||||
|
metadata: {
|
||||||
|
name: providerName.value,
|
||||||
|
namespace,
|
||||||
|
},
|
||||||
|
spec: {
|
||||||
|
type: 'vsphere',
|
||||||
|
url: url.value,
|
||||||
|
secret: {
|
||||||
|
name: secretName,
|
||||||
|
namespace,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
await provider.save();
|
||||||
|
|
||||||
|
router.push({
|
||||||
|
name: `${ PRODUCT_NAME }-c-cluster-forklift-select-vms`,
|
||||||
|
params: {
|
||||||
|
product: route.params.product,
|
||||||
|
cluster: route.params.cluster,
|
||||||
|
},
|
||||||
|
query: {
|
||||||
|
provider: providerName.value,
|
||||||
|
namespace,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
errors.value = [err.message || err];
|
||||||
|
buttonCb(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const init = async() => {
|
||||||
|
const inStore = store.getters['currentProduct'].inStore;
|
||||||
|
|
||||||
|
allSecrets.value = await store.dispatch(`${ inStore }/findAll`, { type: SECRET });
|
||||||
|
loading.value = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
init();
|
||||||
|
|
||||||
|
return {
|
||||||
|
schema,
|
||||||
|
allSecrets,
|
||||||
|
providerName,
|
||||||
|
url,
|
||||||
|
username,
|
||||||
|
password,
|
||||||
|
skipTlsVerify,
|
||||||
|
testResult,
|
||||||
|
testError,
|
||||||
|
errors,
|
||||||
|
loading,
|
||||||
|
isFormValid,
|
||||||
|
t,
|
||||||
|
cancel,
|
||||||
|
testConnection,
|
||||||
|
saveProvider,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="configure-provider">
|
||||||
|
<Masthead
|
||||||
|
:schema="schema"
|
||||||
|
:resource="schema.id"
|
||||||
|
:type-display="t('harvester.addons.forklift.configureProvider.title')"
|
||||||
|
:is-creatable="false"
|
||||||
|
>
|
||||||
|
<template #subHeader>
|
||||||
|
<div class="mmt-5">
|
||||||
|
<p class="text-muted">
|
||||||
|
{{ t('harvester.addons.forklift.configureProvider.description') }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</Masthead>
|
||||||
|
|
||||||
|
<h3 class="mmt-3 mmb-3 table-title">
|
||||||
|
<b>{{ t('harvester.addons.forklift.configureProvider.connectionDetails') }}</b>
|
||||||
|
</h3>
|
||||||
|
|
||||||
|
<Banner
|
||||||
|
class="requirements-banner mt-0 mmb-3"
|
||||||
|
color="info"
|
||||||
|
>
|
||||||
|
<div class="requirements-banner-content">
|
||||||
|
<span class="requirements-banner-title">{{ t('harvester.addons.forklift.configureProvider.requirementsTitle') }}</span>
|
||||||
|
<br>
|
||||||
|
<span>{{ t('harvester.addons.forklift.configureProvider.requirementsText') }}</span>
|
||||||
|
</div>
|
||||||
|
</Banner>
|
||||||
|
|
||||||
|
<div class="mb-20">
|
||||||
|
<LabeledInput
|
||||||
|
v-model:value="providerName"
|
||||||
|
:label="t('harvester.addons.forklift.configureProvider.name')"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-20">
|
||||||
|
<LabeledInput
|
||||||
|
v-model:value="url"
|
||||||
|
:label="t('harvester.addons.forklift.configureProvider.urlLabel')"
|
||||||
|
:placeholder="t('harvester.addons.forklift.configureProvider.urlPlaceholder')"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
<p class="text-muted mt-5">
|
||||||
|
{{ t('harvester.addons.forklift.configureProvider.urlHint') }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row mb-20">
|
||||||
|
<div class="col span-6">
|
||||||
|
<LabeledInput
|
||||||
|
v-model:value="username"
|
||||||
|
:label="t('harvester.addons.forklift.fields.username')"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="col span-6">
|
||||||
|
<LabeledInput
|
||||||
|
v-model:value="password"
|
||||||
|
type="password"
|
||||||
|
:label="t('harvester.addons.forklift.fields.password')"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-20">
|
||||||
|
<Checkbox
|
||||||
|
v-model:value="skipTlsVerify"
|
||||||
|
:label="t('harvester.addons.forklift.configureProvider.skipSsl')"
|
||||||
|
/>
|
||||||
|
<p class="text-muted ml-20">
|
||||||
|
{{ t('harvester.addons.forklift.configureProvider.skipSslHint') }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-20">
|
||||||
|
<AsyncButton
|
||||||
|
mode="test"
|
||||||
|
:action-label="t('harvester.addons.forklift.configureProvider.testConnection')"
|
||||||
|
:waiting-label="t('harvester.addons.forklift.configureProvider.testConnection')"
|
||||||
|
:success-label="t('harvester.addons.forklift.configureProvider.testConnection')"
|
||||||
|
:error-label="t('harvester.addons.forklift.configureProvider.testConnection')"
|
||||||
|
class="btn role-secondary"
|
||||||
|
@click="testConnection"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Banner
|
||||||
|
v-if="testResult"
|
||||||
|
color="success"
|
||||||
|
class="mb-10"
|
||||||
|
>
|
||||||
|
{{ testResult }}
|
||||||
|
</Banner>
|
||||||
|
<Banner
|
||||||
|
v-if="testError"
|
||||||
|
color="error"
|
||||||
|
class="mb-10"
|
||||||
|
>
|
||||||
|
{{ testError }}
|
||||||
|
</Banner>
|
||||||
|
<Banner
|
||||||
|
v-for="(err, i) in errors"
|
||||||
|
:key="i"
|
||||||
|
color="error"
|
||||||
|
class="mb-10"
|
||||||
|
>
|
||||||
|
{{ err }}
|
||||||
|
</Banner>
|
||||||
|
|
||||||
|
<div class="provider-actions">
|
||||||
|
<button
|
||||||
|
class="btn role-secondary"
|
||||||
|
@click="cancel"
|
||||||
|
>
|
||||||
|
{{ t('generic.cancel') }}
|
||||||
|
</button>
|
||||||
|
<AsyncButton
|
||||||
|
:disabled="!isFormValid"
|
||||||
|
:action-label="t('harvester.addons.forklift.configureProvider.save')"
|
||||||
|
:waiting-label="t('harvester.addons.forklift.configureProvider.save')"
|
||||||
|
:success-label="t('harvester.addons.forklift.configureProvider.save')"
|
||||||
|
:error-label="t('harvester.addons.forklift.configureProvider.save')"
|
||||||
|
@click="saveProvider"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.configure-provider {
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.requirements-banner {
|
||||||
|
font-weight: 400;
|
||||||
|
|
||||||
|
.requirements-banner-title {
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.provider-actions {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: 10px;
|
||||||
|
margin-top: 30px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
342
pkg/harvester/pages/c/_cluster/forklift/index.vue
Normal file
342
pkg/harvester/pages/c/_cluster/forklift/index.vue
Normal file
@ -0,0 +1,342 @@
|
|||||||
|
<script>
|
||||||
|
import { defineComponent, ref, computed } from 'vue';
|
||||||
|
import { useStore } from 'vuex';
|
||||||
|
import { useRoute } from 'vue-router';
|
||||||
|
import Loading from '@shell/components/Loading';
|
||||||
|
import Masthead from '@shell/components/ResourceList/Masthead';
|
||||||
|
import ResourceTable from '@shell/components/ResourceTable';
|
||||||
|
import PercentageBar from '@shell/components/PercentageBar';
|
||||||
|
import MappingsCell from '../../../../components/MappingsCell';
|
||||||
|
import { SCHEMA } from '@shell/config/types';
|
||||||
|
import { useI18n } from '@shell/composables/useI18n';
|
||||||
|
import { HCI } from '../../../../types';
|
||||||
|
import { PRODUCT_NAME } from '../../../../config/harvester';
|
||||||
|
import { FORKLIFT_PLAN_VM_COUNT } from '../../../../config/table-headers';
|
||||||
|
import { STATE, NAME as NAME_COL, AGE } from '@shell/config/table-headers';
|
||||||
|
|
||||||
|
const schema = {
|
||||||
|
id: HCI.FORKLIFT_PLAN,
|
||||||
|
type: SCHEMA,
|
||||||
|
attributes: {
|
||||||
|
kind: HCI.FORKLIFT_PLAN,
|
||||||
|
namespaced: true
|
||||||
|
},
|
||||||
|
metadata: { name: HCI.FORKLIFT_PLAN },
|
||||||
|
};
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'ForkliftMigrationDashboard',
|
||||||
|
|
||||||
|
components: {
|
||||||
|
Loading,
|
||||||
|
Masthead,
|
||||||
|
MappingsCell,
|
||||||
|
PercentageBar,
|
||||||
|
ResourceTable,
|
||||||
|
},
|
||||||
|
|
||||||
|
setup() {
|
||||||
|
const store = useStore();
|
||||||
|
const route = useRoute();
|
||||||
|
const { t } = useI18n(store);
|
||||||
|
|
||||||
|
const loading = ref(true);
|
||||||
|
|
||||||
|
const inStore = computed(() => store.getters['currentProduct'].inStore);
|
||||||
|
|
||||||
|
const allPlans = computed(() => store.getters[`${ inStore.value }/all`](HCI.FORKLIFT_PLAN));
|
||||||
|
const allNetworkMaps = computed(() => store.getters[`${ inStore.value }/all`](HCI.FORKLIFT_NETWORK_MAP));
|
||||||
|
const allStorageMaps = computed(() => store.getters[`${ inStore.value }/all`](HCI.FORKLIFT_STORAGE_MAP));
|
||||||
|
|
||||||
|
const rows = computed(() => {
|
||||||
|
return allPlans.value.map((plan) => {
|
||||||
|
const netMapName = plan.spec?.map?.network?.name;
|
||||||
|
const netMapNs = plan.spec?.map?.network?.namespace || plan.metadata.namespace;
|
||||||
|
const storMapName = plan.spec?.map?.storage?.name;
|
||||||
|
const storMapNs = plan.spec?.map?.storage?.namespace || plan.metadata.namespace;
|
||||||
|
|
||||||
|
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.storageEntries = (storMap?.spec?.map || []).map((e) => `${ e.source?.id || '-' } → ${ e.destination?.storageClass || '-' }`);
|
||||||
|
plan.networkDisplay = plan.networkEntries.join(', ') || '-';
|
||||||
|
plan.storageDisplay = plan.storageEntries.join(', ') || '-';
|
||||||
|
|
||||||
|
plan.vmIdsDisplay = (plan.spec?.vms || []).map((vm) => vm.id || vm.name || '').filter(Boolean).join(', ') || '-';
|
||||||
|
|
||||||
|
const vms = plan.status?.migration?.vms || [];
|
||||||
|
|
||||||
|
plan.vmProgress = vms.map((vm) => {
|
||||||
|
const pipeline = vm.pipeline || [];
|
||||||
|
const totalSteps = pipeline.length;
|
||||||
|
const completedSteps = pipeline.filter((s) => s.phase === 'Completed').length;
|
||||||
|
|
||||||
|
return {
|
||||||
|
name: vm.id || vm.name || 'Unknown',
|
||||||
|
steps: pipeline.map((step) => ({
|
||||||
|
name: step.name || 'Unknown',
|
||||||
|
progress: step.phase === 'Completed' ? 100 : (step.progress?.completed && step.progress?.total ? Math.round((step.progress.completed / step.progress.total) * 100) : 0),
|
||||||
|
hasError: !!step.error,
|
||||||
|
reason: (step.error?.reasons || []).join('; ') || '',
|
||||||
|
})),
|
||||||
|
progress: totalSteps > 0 ? Math.round((completedSteps / totalSteps) * 100) : 0,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
let totalSteps = 0;
|
||||||
|
let completedSteps = 0;
|
||||||
|
|
||||||
|
vms.forEach((vm) => {
|
||||||
|
const pipeline = vm.pipeline || [];
|
||||||
|
|
||||||
|
totalSteps += pipeline.length;
|
||||||
|
completedSteps += pipeline.filter((s) => s.phase === 'Completed').length;
|
||||||
|
});
|
||||||
|
|
||||||
|
plan.progress = totalSteps > 0 ? Math.round((completedSteps / totalSteps) * 100) : 0;
|
||||||
|
|
||||||
|
return plan;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const createLocation = computed(() => ({
|
||||||
|
name: `${ PRODUCT_NAME }-c-cluster-forklift-configure-provider`,
|
||||||
|
params: {
|
||||||
|
product: route.params.product,
|
||||||
|
cluster: route.params.cluster,
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
const headers = [
|
||||||
|
{ ...STATE, labelKey: 'harvester.addons.forklift.dashboard.columns.status' },
|
||||||
|
{
|
||||||
|
...NAME_COL,
|
||||||
|
labelKey: 'harvester.addons.forklift.dashboard.columns.plan',
|
||||||
|
subLabel: 'VM IDs',
|
||||||
|
},
|
||||||
|
{ ...FORKLIFT_PLAN_VM_COUNT, width: 105 },
|
||||||
|
{
|
||||||
|
name: 'progress',
|
||||||
|
labelKey: 'harvester.addons.forklift.dashboard.columns.progress',
|
||||||
|
value: 'progress',
|
||||||
|
width: 299,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'mappings',
|
||||||
|
labelKey: 'harvester.addons.forklift.dashboard.columns.mappings',
|
||||||
|
value: 'mappingsDisplay',
|
||||||
|
width: 387,
|
||||||
|
},
|
||||||
|
{ ...AGE },
|
||||||
|
];
|
||||||
|
|
||||||
|
const init = async() => {
|
||||||
|
await Promise.all([
|
||||||
|
store.dispatch(`${ inStore.value }/findAll`, { type: HCI.FORKLIFT_PLAN }),
|
||||||
|
store.dispatch(`${ inStore.value }/findAll`, { type: HCI.FORKLIFT_NETWORK_MAP }),
|
||||||
|
store.dispatch(`${ inStore.value }/findAll`, { type: HCI.FORKLIFT_STORAGE_MAP }),
|
||||||
|
]);
|
||||||
|
|
||||||
|
loading.value = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
init();
|
||||||
|
|
||||||
|
return {
|
||||||
|
schema,
|
||||||
|
loading,
|
||||||
|
rows,
|
||||||
|
createLocation,
|
||||||
|
headers,
|
||||||
|
t,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Loading v-if="loading" />
|
||||||
|
<div v-else>
|
||||||
|
<Masthead
|
||||||
|
:schema="schema"
|
||||||
|
:resource="schema.id"
|
||||||
|
:type-display="t('harvester.addons.forklift.dashboard.title')"
|
||||||
|
>
|
||||||
|
<template #subHeader>
|
||||||
|
<div class="mmt-5">
|
||||||
|
<p class="text-muted">
|
||||||
|
{{ t('harvester.addons.forklift.dashboard.description') }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template #createButton>
|
||||||
|
<router-link
|
||||||
|
:to="createLocation"
|
||||||
|
class="btn role-primary"
|
||||||
|
>
|
||||||
|
{{ t('harvester.addons.forklift.dashboard.createPlan') }}
|
||||||
|
</router-link>
|
||||||
|
</template>
|
||||||
|
</Masthead>
|
||||||
|
|
||||||
|
<ResourceTable
|
||||||
|
:schema="schema"
|
||||||
|
:rows="rows"
|
||||||
|
:headers="headers"
|
||||||
|
:groupable="false"
|
||||||
|
:table-actions="false"
|
||||||
|
:search="false"
|
||||||
|
key-field="_key"
|
||||||
|
>
|
||||||
|
<template #header-left>
|
||||||
|
<h3 class="table-title m-0">
|
||||||
|
{{ t('harvester.addons.forklift.dashboard.tableTitle') }}
|
||||||
|
</h3>
|
||||||
|
</template>
|
||||||
|
<template #cell:name="{ row }">
|
||||||
|
<div class="plan-name-cell">
|
||||||
|
<div class="plan-name">
|
||||||
|
{{ row.metadata.name }}
|
||||||
|
</div>
|
||||||
|
<div class="plan-vms text-muted">
|
||||||
|
{{ row.vmIdsDisplay }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template #cell:vmCount="{ row }">
|
||||||
|
{{ (row.spec.vms || []).length }} VMs
|
||||||
|
</template>
|
||||||
|
<template #cell:progress="{ row }">
|
||||||
|
<div
|
||||||
|
v-if="row.vmProgress && row.vmProgress.length"
|
||||||
|
class="progress-steps"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
v-for="vm in row.vmProgress"
|
||||||
|
:key="vm.name"
|
||||||
|
class="vm-progress"
|
||||||
|
>
|
||||||
|
<div class="vm-name">
|
||||||
|
{{ vm.name }}
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-for="step in vm.steps"
|
||||||
|
:key="step.name"
|
||||||
|
class="progress-step"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="step-name"
|
||||||
|
:class="{ 'text-error': step.hasError }"
|
||||||
|
>
|
||||||
|
{{ step.name }}
|
||||||
|
</span>
|
||||||
|
<PercentageBar
|
||||||
|
:model-value="step.progress"
|
||||||
|
:color-stops="step.hasError ? { 0: '--error' } : { 99: '--primary', 100: '--success' }"
|
||||||
|
preferred-direction="MORE"
|
||||||
|
class="step-bar"
|
||||||
|
/>
|
||||||
|
<span class="step-pct">{{ step.progress }}%</span>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-for="step in vm.steps.filter(s => s.hasError)"
|
||||||
|
:key="`error-${step.name}`"
|
||||||
|
class="step-error-msg"
|
||||||
|
>
|
||||||
|
<span class="text-error">{{ step.reason }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<span v-else>-</span>
|
||||||
|
</template>
|
||||||
|
<template #cell:mappings="{ row }">
|
||||||
|
<MappingsCell
|
||||||
|
:network-entries="row.networkEntries"
|
||||||
|
:storage-entries="row.storageEntries"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</ResourceTable>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.plan-name-cell {
|
||||||
|
.plan-name {
|
||||||
|
font-weight: 600;
|
||||||
|
line-height: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.plan-vms {
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 20px;
|
||||||
|
color: var(--muted);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-steps {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vm-progress {
|
||||||
|
&:not(:last-child) {
|
||||||
|
padding-bottom: 6px;
|
||||||
|
border-bottom: 1px solid var(--border);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.vm-name {
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 12px;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-step {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
padding-right: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.step-error-msg {
|
||||||
|
margin-top: 4px;
|
||||||
|
font-size: 12px;
|
||||||
|
|
||||||
|
.text-error {
|
||||||
|
color: var(--error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.step-name {
|
||||||
|
font-size: 12px;
|
||||||
|
width: 110px;
|
||||||
|
min-width: 110px;
|
||||||
|
max-width: 110px;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
|
||||||
|
&.text-error {
|
||||||
|
color: var(--error);
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.step-bar {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 80px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.step-pct {
|
||||||
|
font-size: 12px;
|
||||||
|
min-width: 35px;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-title {
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
544
pkg/harvester/pages/c/_cluster/forklift/review-migration.vue
Normal file
544
pkg/harvester/pages/c/_cluster/forklift/review-migration.vue
Normal file
@ -0,0 +1,544 @@
|
|||||||
|
<script>
|
||||||
|
import { defineComponent, ref, computed } from 'vue';
|
||||||
|
import { useStore } from 'vuex';
|
||||||
|
import { useRoute, useRouter } from 'vue-router';
|
||||||
|
import Loading from '@shell/components/Loading';
|
||||||
|
import Masthead from '@shell/components/ResourceList/Masthead';
|
||||||
|
import AsyncButton from '@shell/components/AsyncButton';
|
||||||
|
import { Banner } from '@components/Banner';
|
||||||
|
import { RcItemCard } from '@components/RcItemCard';
|
||||||
|
import MappingsCell from '../../../../components/MappingsCell';
|
||||||
|
import { SCHEMA } from '@shell/config/types';
|
||||||
|
import { useI18n } from '@shell/composables/useI18n';
|
||||||
|
import { HCI } from '../../../../types';
|
||||||
|
import { PRODUCT_NAME } from '../../../../config/harvester';
|
||||||
|
import vmData from '../../../../utils/vms.json';
|
||||||
|
|
||||||
|
const schema = {
|
||||||
|
id: HCI.FORKLIFT_PLAN,
|
||||||
|
type: SCHEMA,
|
||||||
|
attributes: {
|
||||||
|
kind: HCI.FORKLIFT_PLAN,
|
||||||
|
namespaced: true
|
||||||
|
},
|
||||||
|
metadata: { name: HCI.FORKLIFT_PLAN },
|
||||||
|
};
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'ForkliftReviewMigration',
|
||||||
|
|
||||||
|
components: {
|
||||||
|
Loading,
|
||||||
|
Masthead,
|
||||||
|
AsyncButton,
|
||||||
|
Banner,
|
||||||
|
MappingsCell,
|
||||||
|
RcItemCard,
|
||||||
|
},
|
||||||
|
|
||||||
|
setup() {
|
||||||
|
const store = useStore();
|
||||||
|
const route = useRoute();
|
||||||
|
const router = useRouter();
|
||||||
|
const { t } = useI18n(store);
|
||||||
|
|
||||||
|
const vms = ref([]);
|
||||||
|
const networkMappings = ref([]);
|
||||||
|
const storageMappings = ref([]);
|
||||||
|
const errors = ref([]);
|
||||||
|
const loading = ref(true);
|
||||||
|
|
||||||
|
const providerName = computed(() => route.query.provider || 'vsphere');
|
||||||
|
const namespace = computed(() => route.query.namespace || 'default');
|
||||||
|
const networkMapName = computed(() => route.query.networkMap || '');
|
||||||
|
const storageMapName = computed(() => route.query.storageMap || '');
|
||||||
|
|
||||||
|
const totalVCpu = computed(() => vms.value.reduce((sum, vm) => sum + (vm.cpuCount || vm.numCPU || 0), 0));
|
||||||
|
|
||||||
|
const totalMemoryGB = computed(() => {
|
||||||
|
const totalMB = vms.value.reduce((sum, vm) => sum + (vm.memoryMB || vm.memory || 0), 0);
|
||||||
|
|
||||||
|
return Math.round(totalMB / 1024);
|
||||||
|
});
|
||||||
|
|
||||||
|
const totalStorageGB = computed(() => {
|
||||||
|
let totalBytes = 0;
|
||||||
|
|
||||||
|
vms.value.forEach((vm) => {
|
||||||
|
if (vm.disks && vm.disks.length > 0) {
|
||||||
|
totalBytes += vm.disks.reduce((sum, d) => sum + (d.capacity || 0), 0);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return Math.round(totalBytes / (1024 * 1024 * 1024));
|
||||||
|
});
|
||||||
|
|
||||||
|
const vmCards = computed(() => {
|
||||||
|
return vms.value.map((vm) => {
|
||||||
|
const cpus = vm.cpuCount || vm.numCPU || 0;
|
||||||
|
const memMB = vm.memoryMB || vm.memory || 0;
|
||||||
|
const memGB = memMB ? `${ Math.round(memMB / 1024) } GB` : '-';
|
||||||
|
|
||||||
|
let totalDiskBytes = 0;
|
||||||
|
|
||||||
|
if (vm.disks && vm.disks.length > 0) {
|
||||||
|
totalDiskBytes = vm.disks.reduce((sum, d) => sum + (d.capacity || 0), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
const diskDisplay = totalDiskBytes ? `${ Math.round(totalDiskBytes / (1024 * 1024 * 1024)) } GB` : '-';
|
||||||
|
const os = vm.guestName || vm.guestOS || vm.os || '-';
|
||||||
|
|
||||||
|
const vmNetworkMappings = networkMappings.value.filter((m) => m.vmId === vm.id);
|
||||||
|
const vmStorageMappings = storageMappings.value.filter((m) => m.vmId === vm.id);
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: vm.id,
|
||||||
|
name: vm.name || vm.id,
|
||||||
|
os,
|
||||||
|
cpus,
|
||||||
|
memGB,
|
||||||
|
diskDisplay,
|
||||||
|
networkMappings: vmNetworkMappings,
|
||||||
|
storageMappings: vmStorageMappings,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const cancel = () => {
|
||||||
|
router.push({
|
||||||
|
name: `${ PRODUCT_NAME }-c-cluster-forklift`,
|
||||||
|
params: {
|
||||||
|
product: route.params.product,
|
||||||
|
cluster: route.params.cluster,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const startMigration = async(buttonCb) => {
|
||||||
|
const inStore = store.getters['currentProduct'].inStore;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const planName = `${ providerName.value }-plan-${ Math.random().toString(36).substring(2, 7) }`;
|
||||||
|
|
||||||
|
const planSpec = {
|
||||||
|
provider: {
|
||||||
|
source: {
|
||||||
|
apiVersion: 'forklift.konveyor.io/v1beta1',
|
||||||
|
kind: 'Provider',
|
||||||
|
name: providerName.value,
|
||||||
|
namespace: namespace.value,
|
||||||
|
},
|
||||||
|
destination: {
|
||||||
|
apiVersion: 'forklift.konveyor.io/v1beta1',
|
||||||
|
kind: 'Provider',
|
||||||
|
name: 'host',
|
||||||
|
namespace: 'forklift',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
map: {
|
||||||
|
network: {
|
||||||
|
apiVersion: 'forklift.konveyor.io/v1beta1',
|
||||||
|
kind: 'NetworkMap',
|
||||||
|
name: networkMapName.value,
|
||||||
|
namespace: namespace.value,
|
||||||
|
},
|
||||||
|
storage: {
|
||||||
|
apiVersion: 'forklift.konveyor.io/v1beta1',
|
||||||
|
kind: 'StorageMap',
|
||||||
|
name: storageMapName.value,
|
||||||
|
namespace: namespace.value,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
targetNamespace: namespace.value,
|
||||||
|
vms: vms.value.map((vm) => ({
|
||||||
|
id: vm.id,
|
||||||
|
name: vm.name || vm.id,
|
||||||
|
})),
|
||||||
|
warm: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
const plan = await store.dispatch(`${ inStore }/create`, {
|
||||||
|
type: HCI.FORKLIFT_PLAN,
|
||||||
|
metadata: {
|
||||||
|
name: planName,
|
||||||
|
namespace: namespace.value,
|
||||||
|
},
|
||||||
|
spec: planSpec,
|
||||||
|
});
|
||||||
|
|
||||||
|
await plan.save();
|
||||||
|
|
||||||
|
const migrationName = `${ planName }-migration-${ Math.random().toString(36).substring(2, 7) }`;
|
||||||
|
|
||||||
|
const migration = await store.dispatch(`${ inStore }/create`, {
|
||||||
|
type: HCI.FORKLIFT_MIGRATION,
|
||||||
|
metadata: {
|
||||||
|
name: migrationName,
|
||||||
|
namespace: namespace.value,
|
||||||
|
},
|
||||||
|
spec: {
|
||||||
|
plan: {
|
||||||
|
apiVersion: 'forklift.konveyor.io/v1beta1',
|
||||||
|
kind: 'Plan',
|
||||||
|
name: planName,
|
||||||
|
namespace: namespace.value,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
await migration.save();
|
||||||
|
|
||||||
|
router.push({
|
||||||
|
name: `${ PRODUCT_NAME }-c-cluster-forklift`,
|
||||||
|
params: {
|
||||||
|
product: route.params.product,
|
||||||
|
cluster: route.params.cluster,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
buttonCb(true);
|
||||||
|
} catch (err) {
|
||||||
|
errors.value = [err.message || err];
|
||||||
|
buttonCb(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const init = async() => {
|
||||||
|
const vmsParam = route.query.vms;
|
||||||
|
|
||||||
|
if (vmsParam) {
|
||||||
|
try {
|
||||||
|
const vmIds = JSON.parse(vmsParam);
|
||||||
|
|
||||||
|
vms.value = vmIds.map((id) => {
|
||||||
|
const found = vmData.find((vm) => vm.id === id);
|
||||||
|
|
||||||
|
return found || {
|
||||||
|
id, name: id, networks: [], disks: [], cpuCount: 0, memoryMB: 0, guestName: ''
|
||||||
|
};
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
vms.value = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const networkMappingsParam = route.query.networkMappings;
|
||||||
|
|
||||||
|
if (networkMappingsParam) {
|
||||||
|
try {
|
||||||
|
networkMappings.value = JSON.parse(networkMappingsParam);
|
||||||
|
} catch (e) {
|
||||||
|
networkMappings.value = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const storageMappingsParam = route.query.storageMappings;
|
||||||
|
|
||||||
|
if (storageMappingsParam) {
|
||||||
|
try {
|
||||||
|
storageMappings.value = JSON.parse(storageMappingsParam);
|
||||||
|
} catch (e) {
|
||||||
|
storageMappings.value = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
loading.value = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
init();
|
||||||
|
|
||||||
|
return {
|
||||||
|
schema,
|
||||||
|
loading,
|
||||||
|
vms,
|
||||||
|
errors,
|
||||||
|
providerName,
|
||||||
|
namespace,
|
||||||
|
totalVCpu,
|
||||||
|
totalMemoryGB,
|
||||||
|
totalStorageGB,
|
||||||
|
vmCards,
|
||||||
|
t,
|
||||||
|
cancel,
|
||||||
|
startMigration,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Loading v-if="loading" />
|
||||||
|
<div
|
||||||
|
v-else
|
||||||
|
class="review-migration"
|
||||||
|
>
|
||||||
|
<Masthead
|
||||||
|
:schema="schema"
|
||||||
|
:resource="schema.id"
|
||||||
|
:type-display="t('harvester.addons.forklift.reviewMigration.title')"
|
||||||
|
:is-creatable="false"
|
||||||
|
>
|
||||||
|
<template #subHeader>
|
||||||
|
<p class="text-muted mt-5">
|
||||||
|
{{ t('harvester.addons.forklift.reviewMigration.description') }}
|
||||||
|
</p>
|
||||||
|
</template>
|
||||||
|
</Masthead>
|
||||||
|
|
||||||
|
<!-- Migration Details Summary -->
|
||||||
|
<div class="migration-details">
|
||||||
|
<h3 class="section-title">
|
||||||
|
{{ t('harvester.addons.forklift.reviewMigration.migrationDetails') }}
|
||||||
|
</h3>
|
||||||
|
<div class="details-grid">
|
||||||
|
<div class="detail-column">
|
||||||
|
<div class="detail-item">
|
||||||
|
<span class="detail-label">{{ t('harvester.addons.forklift.reviewMigration.totalVms') }}</span>
|
||||||
|
<span class="detail-value detail-value-large">{{ vms.length }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="detail-column">
|
||||||
|
<div class="detail-item">
|
||||||
|
<span class="detail-label">{{ t('harvester.addons.forklift.reviewMigration.source') }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="detail-item">
|
||||||
|
<span class="detail-value">{{ providerName }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="detail-column">
|
||||||
|
<div class="detail-item">
|
||||||
|
<span class="detail-label">{{ t('harvester.addons.forklift.reviewMigration.targetNamespace') }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="detail-item">
|
||||||
|
<span class="detail-value">{{ namespace }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="detail-column">
|
||||||
|
<div class="detail-item">
|
||||||
|
<span class="detail-label">{{ t('harvester.addons.forklift.reviewMigration.vcpu') }}</span>
|
||||||
|
<span class="detail-value">{{ totalVCpu }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="detail-item">
|
||||||
|
<span class="detail-label">{{ t('harvester.addons.forklift.reviewMigration.memory') }}</span>
|
||||||
|
<span class="detail-value">{{ totalMemoryGB }} GB</span>
|
||||||
|
</div>
|
||||||
|
<div class="detail-item">
|
||||||
|
<span class="detail-label">{{ t('harvester.addons.forklift.reviewMigration.storage') }}</span>
|
||||||
|
<span class="detail-value">{{ totalStorageGB }} GB</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="detail-column grid-column-2">
|
||||||
|
<div class="detail-item">
|
||||||
|
<span class="detail-label">{{ t('harvester.addons.forklift.reviewMigration.migrationMode') }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="detail-item">
|
||||||
|
<span class="detail-value">
|
||||||
|
{{ t('harvester.addons.forklift.reviewMigration.coldMigration') }}
|
||||||
|
<span
|
||||||
|
class="ml-10 small-text"
|
||||||
|
>{{ t('harvester.addons.forklift.reviewMigration.vmsWillShutDown') }}</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Virtual Machines -->
|
||||||
|
<div class="vm-section">
|
||||||
|
<h3 class="section-title">
|
||||||
|
{{ t('harvester.addons.forklift.reviewMigration.virtualMachines') }} ({{ vms.length }})
|
||||||
|
</h3>
|
||||||
|
|
||||||
|
<div class="vm-cards-grid">
|
||||||
|
<RcItemCard
|
||||||
|
v-for="vm in vmCards"
|
||||||
|
:id="vm.id"
|
||||||
|
:key="vm.id"
|
||||||
|
:variant="'small'"
|
||||||
|
:header="{ title: { text: vm.name }, statuses: [{ icon: 'icon-notify-tick', color: 'text-success' }] }"
|
||||||
|
>
|
||||||
|
<template #item-card-content>
|
||||||
|
<div class="vm-card-content">
|
||||||
|
<div class="vm-card-specs">
|
||||||
|
<span class="vm-os text-muted">{{ vm.os }}</span>
|
||||||
|
<span class="vm-resources">
|
||||||
|
<i class="icon icon-dock" />
|
||||||
|
{{ vm.cpus }} vCPU • {{ vm.memGB }} • {{ vm.diskDisplay }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<MappingsCell
|
||||||
|
:network-entries="vm.networkMappings.map(m => `${m.source} → ${m.target}`)"
|
||||||
|
:storage-entries="vm.storageMappings.map(m => `${m.source} → ${m.target}`)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</RcItemCard>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Cold Migration Warning -->
|
||||||
|
<Banner
|
||||||
|
color="warning"
|
||||||
|
class="mt-20"
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<span class="banner-title">{{ t('harvester.addons.forklift.reviewMigration.warningTitle') }}</span><br>
|
||||||
|
{{ t('harvester.addons.forklift.reviewMigration.warningMessage') }}
|
||||||
|
</div>
|
||||||
|
</Banner>
|
||||||
|
|
||||||
|
<!-- Error banner -->
|
||||||
|
<Banner
|
||||||
|
v-for="(err, i) in errors"
|
||||||
|
:key="i"
|
||||||
|
color="error"
|
||||||
|
:label="err"
|
||||||
|
class="mt-10"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<!-- Actions -->
|
||||||
|
<div class="actions-footer">
|
||||||
|
<button
|
||||||
|
class="btn role-secondary"
|
||||||
|
@click="cancel"
|
||||||
|
>
|
||||||
|
{{ t('generic.cancel') }}
|
||||||
|
</button>
|
||||||
|
<AsyncButton
|
||||||
|
:action-label="t('harvester.addons.forklift.reviewMigration.startMigration')"
|
||||||
|
:waiting-label="t('harvester.addons.forklift.reviewMigration.startMigration')"
|
||||||
|
:success-label="t('harvester.addons.forklift.reviewMigration.startMigration')"
|
||||||
|
:error-label="t('harvester.addons.forklift.reviewMigration.startMigration')"
|
||||||
|
@click="startMigration"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.review-migration {
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-title {
|
||||||
|
font-weight: 600;
|
||||||
|
margin: 0 0 15px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.migration-details {
|
||||||
|
margin-bottom: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.details-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: minmax(230px, 1fr) 1fr 1fr;
|
||||||
|
grid-template-rows: 1fr 1fr;
|
||||||
|
gap: 12px 128px;
|
||||||
|
width: 747px;
|
||||||
|
line-height: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-column {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 4px;
|
||||||
|
|
||||||
|
&.grid-column-2 {
|
||||||
|
grid-column: span 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-item {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: space-between;
|
||||||
|
|
||||||
|
.detail-label {
|
||||||
|
font-size: 14px;
|
||||||
|
color: var(--muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-value {
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 14px;
|
||||||
|
|
||||||
|
&.detail-value-large {
|
||||||
|
font-size: 24px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.vm-section {
|
||||||
|
margin-top: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vm-cards-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(380px, 1fr));
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vm-card-content {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 16px;
|
||||||
|
line-height: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vm-card-specs {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
gap: 16px;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.vm-os {
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vm-resources {
|
||||||
|
font-size: 14px;
|
||||||
|
color: var(--muted);
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.vm-card-mappings {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 4px;
|
||||||
|
border-top: 1px solid var(--border);
|
||||||
|
padding-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mapping-line {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
font-size: 14px;
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
color: var(--muted);
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.small-text {
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 16px;
|
||||||
|
color: #973C00;
|
||||||
|
}
|
||||||
|
|
||||||
|
.actions-footer {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: 10px;
|
||||||
|
margin-top: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.banner-title {
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
518
pkg/harvester/pages/c/_cluster/forklift/select-vms.vue
Normal file
518
pkg/harvester/pages/c/_cluster/forklift/select-vms.vue
Normal file
@ -0,0 +1,518 @@
|
|||||||
|
<script>
|
||||||
|
import { defineComponent, ref, computed } from 'vue';
|
||||||
|
import { useStore } from 'vuex';
|
||||||
|
import { useRoute, useRouter } from 'vue-router';
|
||||||
|
import Loading from '@shell/components/Loading';
|
||||||
|
import Masthead from '@shell/components/ResourceList/Masthead';
|
||||||
|
import SortableTable from '@shell/components/SortableTable';
|
||||||
|
import { LabeledInput } from '@components/Form/LabeledInput';
|
||||||
|
import { SCHEMA } from '@shell/config/types';
|
||||||
|
import { useI18n } from '@shell/composables/useI18n';
|
||||||
|
import { HCI } from '../../../../types';
|
||||||
|
import { PRODUCT_NAME } from '../../../../config/harvester';
|
||||||
|
import vmData from '../../../../utils/vms.json';
|
||||||
|
|
||||||
|
const schema = {
|
||||||
|
id: HCI.FORKLIFT_PROVIDER,
|
||||||
|
type: SCHEMA,
|
||||||
|
attributes: {
|
||||||
|
kind: HCI.FORKLIFT_PROVIDER,
|
||||||
|
namespaced: true
|
||||||
|
},
|
||||||
|
metadata: { name: HCI.FORKLIFT_PROVIDER },
|
||||||
|
};
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'ForkliftSelectVms',
|
||||||
|
|
||||||
|
components: {
|
||||||
|
Loading,
|
||||||
|
Masthead,
|
||||||
|
SortableTable,
|
||||||
|
LabeledInput,
|
||||||
|
},
|
||||||
|
|
||||||
|
setup() {
|
||||||
|
const store = useStore();
|
||||||
|
const route = useRoute();
|
||||||
|
const router = useRouter();
|
||||||
|
const { t } = useI18n(store);
|
||||||
|
|
||||||
|
const allProviders = ref([]);
|
||||||
|
const provider = ref(null);
|
||||||
|
const discoveredVMs = ref([]);
|
||||||
|
const selectedVMs = ref([]);
|
||||||
|
const manualVMs = ref([]);
|
||||||
|
const newVmId = ref('');
|
||||||
|
const tableRows = ref([]);
|
||||||
|
const loading = ref(true);
|
||||||
|
|
||||||
|
const providerName = computed(() => provider.value?.metadata?.name || route.query.provider || '');
|
||||||
|
const vmCount = computed(() => discoveredVMs.value.length);
|
||||||
|
const showManualEntry = computed(() => discoveredVMs.value.length === 0);
|
||||||
|
const selectedCount = computed(() => showManualEntry.value ? manualVMs.value.length : selectedVMs.value.length);
|
||||||
|
|
||||||
|
const manualHeaders = [
|
||||||
|
{
|
||||||
|
name: 'id',
|
||||||
|
labelKey: 'harvester.addons.forklift.selectVms.columns.vmId',
|
||||||
|
value: 'id',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const headers = [
|
||||||
|
{
|
||||||
|
name: 'vmName',
|
||||||
|
labelKey: 'harvester.addons.forklift.selectVms.columns.vmName',
|
||||||
|
value: 'vmName',
|
||||||
|
subLabel: 'Identifier',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'os',
|
||||||
|
labelKey: 'harvester.addons.forklift.selectVms.columns.os',
|
||||||
|
value: 'os',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'resources',
|
||||||
|
labelKey: 'harvester.addons.forklift.selectVms.columns.resources',
|
||||||
|
value: 'resources',
|
||||||
|
sort: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'powerState',
|
||||||
|
labelKey: 'harvester.addons.forklift.selectVms.columns.powerState',
|
||||||
|
value: 'powerState',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'network',
|
||||||
|
labelKey: 'harvester.addons.forklift.selectVms.columns.network',
|
||||||
|
value: 'network',
|
||||||
|
subLabel: 'Identifier',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'datastore',
|
||||||
|
labelKey: 'harvester.addons.forklift.selectVms.columns.datastore',
|
||||||
|
value: 'datastore',
|
||||||
|
subLabel: 'Identifier',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const buildTableRows = () => {
|
||||||
|
return discoveredVMs.value.map((vm) => {
|
||||||
|
const cpus = vm.cpuCount || vm.numCPU || '-';
|
||||||
|
const memMB = vm.memoryMB || vm.memory || 0;
|
||||||
|
const memGB = memMB ? `${ Math.round(memMB / 1024) } GB` : '-';
|
||||||
|
|
||||||
|
let totalDiskBytes = 0;
|
||||||
|
|
||||||
|
if (vm.disks && vm.disks.length > 0) {
|
||||||
|
totalDiskBytes = vm.disks.reduce((sum, d) => sum + (d.capacity || 0), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
const diskDisplay = totalDiskBytes ? `${ Math.round(totalDiskBytes / (1024 * 1024 * 1024)) } GB` : '-';
|
||||||
|
const powerState = vm.powerState || vm.status?.phase || '-';
|
||||||
|
|
||||||
|
let networks = [];
|
||||||
|
|
||||||
|
if (vm.networks && vm.networks.length > 0) {
|
||||||
|
networks = vm.networks.map((n) => ({
|
||||||
|
name: n.name || n.id || n,
|
||||||
|
id: n.id || '',
|
||||||
|
vlanId: n.vlanId || '',
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
const datastores = [];
|
||||||
|
|
||||||
|
if (vm.disks && vm.disks.length > 0) {
|
||||||
|
const seen = new Set();
|
||||||
|
|
||||||
|
vm.disks.forEach((d) => {
|
||||||
|
const dsId = d.datastore?.id || d.datastore?.name;
|
||||||
|
|
||||||
|
if (dsId && !seen.has(dsId)) {
|
||||||
|
seen.add(dsId);
|
||||||
|
datastores.push({
|
||||||
|
name: d.datastore?.name || dsId,
|
||||||
|
id: d.datastore?.id || '',
|
||||||
|
type: d.datastore?.type || '',
|
||||||
|
capacity: d.capacity || 0,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
_original: vm,
|
||||||
|
_key: vm.id || vm.vmId || vm.metadata?.name,
|
||||||
|
vmName: vm.name || vm.metadata?.name || '-',
|
||||||
|
vmId: vm.id || vm.vmId || vm.metadata?.name || '-',
|
||||||
|
os: vm.guestName || vm.guestOS || vm.os || '-',
|
||||||
|
resourcesDisplay: `${ cpus } vCPU`,
|
||||||
|
resourcesSub: `${ memGB } • ${ diskDisplay }`,
|
||||||
|
powerState,
|
||||||
|
powerStateClass: powerState.toLowerCase().includes('on') || powerState.toLowerCase().includes('running') ? 'power-on' : 'power-off',
|
||||||
|
networks,
|
||||||
|
network: networks.map((n) => n.name).join(', ') || '-',
|
||||||
|
datastores,
|
||||||
|
datastore: datastores.map((d) => d.name).join(', ') || '-',
|
||||||
|
};
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const addManualVm = () => {
|
||||||
|
if (!newVmId.value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
manualVMs.value.push({
|
||||||
|
id: newVmId.value.trim(),
|
||||||
|
name: newVmId.value.trim(),
|
||||||
|
_key: `manual-${ newVmId.value.trim() }`,
|
||||||
|
});
|
||||||
|
|
||||||
|
newVmId.value = '';
|
||||||
|
};
|
||||||
|
|
||||||
|
const removeManualVm = (index) => {
|
||||||
|
manualVMs.value.splice(index, 1);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onSelect = (rows) => {
|
||||||
|
selectedVMs.value = rows.map((r) => r._original);
|
||||||
|
};
|
||||||
|
|
||||||
|
const cancel = () => {
|
||||||
|
router.push({
|
||||||
|
name: `${ PRODUCT_NAME }-c-cluster-forklift`,
|
||||||
|
params: {
|
||||||
|
product: route.params.product,
|
||||||
|
cluster: route.params.cluster,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const saveSelection = () => {
|
||||||
|
let vmIds;
|
||||||
|
|
||||||
|
if (showManualEntry.value) {
|
||||||
|
vmIds = manualVMs.value.map((vm) => vm.id);
|
||||||
|
} else {
|
||||||
|
vmIds = selectedVMs.value.map((vm) => vm.id || vm.vmId || vm.metadata?.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
router.push({
|
||||||
|
name: `${ PRODUCT_NAME }-c-cluster-forklift-configure-mappings`,
|
||||||
|
params: {
|
||||||
|
product: route.params.product,
|
||||||
|
cluster: route.params.cluster,
|
||||||
|
},
|
||||||
|
query: {
|
||||||
|
provider: providerName.value,
|
||||||
|
namespace: provider.value?.metadata?.namespace || 'default',
|
||||||
|
vms: JSON.stringify(vmIds),
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const init = async() => {
|
||||||
|
const inStore = store.getters['currentProduct'].inStore;
|
||||||
|
|
||||||
|
allProviders.value = await store.dispatch(`${ inStore }/findAll`, { type: HCI.FORKLIFT_PROVIDER });
|
||||||
|
|
||||||
|
const queryProvider = route.query.provider;
|
||||||
|
const namespace = route.query.namespace || 'default';
|
||||||
|
|
||||||
|
if (queryProvider) {
|
||||||
|
provider.value = allProviders.value.find(
|
||||||
|
(p) => p.metadata.name === queryProvider && p.metadata.namespace === namespace
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (provider.value) {
|
||||||
|
try {
|
||||||
|
const inventoryType = 'forklift-inventory.konveyor.io.vspherevm';
|
||||||
|
|
||||||
|
discoveredVMs.value = await store.dispatch(`${ inStore }/findAll`, { type: inventoryType });
|
||||||
|
} catch (e) {
|
||||||
|
discoveredVMs.value = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (discoveredVMs.value.length === 0) {
|
||||||
|
discoveredVMs.value = vmData;
|
||||||
|
}
|
||||||
|
|
||||||
|
tableRows.value = buildTableRows();
|
||||||
|
loading.value = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
init();
|
||||||
|
|
||||||
|
return {
|
||||||
|
schema,
|
||||||
|
loading,
|
||||||
|
provider,
|
||||||
|
providerName,
|
||||||
|
discoveredVMs,
|
||||||
|
selectedVMs,
|
||||||
|
manualVMs,
|
||||||
|
newVmId,
|
||||||
|
tableRows,
|
||||||
|
vmCount,
|
||||||
|
showManualEntry,
|
||||||
|
selectedCount,
|
||||||
|
manualHeaders,
|
||||||
|
headers,
|
||||||
|
t,
|
||||||
|
addManualVm,
|
||||||
|
removeManualVm,
|
||||||
|
onSelect,
|
||||||
|
cancel,
|
||||||
|
saveSelection,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Loading v-if="loading" />
|
||||||
|
<div
|
||||||
|
v-else
|
||||||
|
>
|
||||||
|
<Masthead
|
||||||
|
:schema="schema"
|
||||||
|
:resource="schema.id"
|
||||||
|
:type-display="t('harvester.addons.forklift.selectVms.title')"
|
||||||
|
:is-creatable="false"
|
||||||
|
>
|
||||||
|
<template #subHeader>
|
||||||
|
<p class="text-muted mmt-5">
|
||||||
|
{{ t('harvester.addons.forklift.selectVms.discovered', { count: vmCount }) }}
|
||||||
|
<a
|
||||||
|
v-if="providerName"
|
||||||
|
class="provider-link"
|
||||||
|
>{{ providerName }}</a>
|
||||||
|
<br>
|
||||||
|
<span class="text-small">{{ t('harvester.addons.forklift.selectVms.lastSynced') }}</span>
|
||||||
|
</p>
|
||||||
|
</template>
|
||||||
|
</Masthead>
|
||||||
|
|
||||||
|
<!-- Manual VM entry when no discovered VMs -->
|
||||||
|
<div v-if="showManualEntry">
|
||||||
|
<h3 class="m-0 mb-10">
|
||||||
|
{{ t('harvester.addons.forklift.selectVms.manualEntry.title') }}
|
||||||
|
</h3>
|
||||||
|
<p class="text-muted mb-20">
|
||||||
|
{{ t('harvester.addons.forklift.selectVms.manualEntry.description') }}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div class="manual-vm-form">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col span-6">
|
||||||
|
<LabeledInput
|
||||||
|
v-model:value="newVmId"
|
||||||
|
:label="t('harvester.addons.forklift.selectVms.manualEntry.vmId')"
|
||||||
|
:placeholder="t('harvester.addons.forklift.selectVms.manualEntry.vmIdPlaceholder')"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="col span-2 add-btn-col">
|
||||||
|
<button
|
||||||
|
class="btn role-primary"
|
||||||
|
:disabled="!newVmId"
|
||||||
|
@click="addManualVm"
|
||||||
|
>
|
||||||
|
{{ t('harvester.addons.forklift.selectVms.manualEntry.add') }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<SortableTable
|
||||||
|
:rows="manualVMs"
|
||||||
|
:headers="manualHeaders"
|
||||||
|
:search="false"
|
||||||
|
:table-actions="false"
|
||||||
|
:row-actions="false"
|
||||||
|
:groupable="false"
|
||||||
|
key-field="_key"
|
||||||
|
class="mt-20"
|
||||||
|
>
|
||||||
|
<template #header-left>
|
||||||
|
<span class="text-muted">{{ manualVMs.length }} {{ t('harvester.addons.forklift.selectVms.manualEntry.added') }}</span>
|
||||||
|
</template>
|
||||||
|
<template #cell:id="{ row }">
|
||||||
|
<div class="manual-vm-row">
|
||||||
|
<span>{{ row.id }}</span>
|
||||||
|
<button
|
||||||
|
class="btn btn-sm role-link"
|
||||||
|
@click="removeManualVm(manualVMs.indexOf(row))"
|
||||||
|
>
|
||||||
|
<i class="icon icon-close" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</SortableTable>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Discovered VMs table -->
|
||||||
|
<SortableTable
|
||||||
|
v-else
|
||||||
|
:rows="tableRows"
|
||||||
|
:headers="headers"
|
||||||
|
:search="true"
|
||||||
|
:table-actions="true"
|
||||||
|
:row-actions="false"
|
||||||
|
:groupable="false"
|
||||||
|
key-field="_key"
|
||||||
|
@selection="onSelect"
|
||||||
|
>
|
||||||
|
<template #header-left>
|
||||||
|
<div class="vm-table-title">
|
||||||
|
<h3 class="m-0">
|
||||||
|
{{ t('harvester.addons.forklift.selectVms.availableVms') }}
|
||||||
|
</h3>
|
||||||
|
<span class="text-muted">{{ selectedCount }} {{ t('harvester.addons.forklift.selectVms.selected') }}</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template #cell:vmName="{ row }">
|
||||||
|
<div class="vm-name-cell">
|
||||||
|
<span class="vm-name">{{ row.vmName }}</span>
|
||||||
|
<span class="vm-id text-muted">{{ row.vmId }}</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template #cell:resources="{ row }">
|
||||||
|
<span>{{ row.resourcesDisplay }}</span><br>
|
||||||
|
<span class="text-muted">{{ row.resourcesSub }}</span>
|
||||||
|
</template>
|
||||||
|
<template #cell:powerState="{ row }">
|
||||||
|
<span
|
||||||
|
class="power-badge"
|
||||||
|
:class="row.powerStateClass"
|
||||||
|
>{{ row.powerState }}</span>
|
||||||
|
</template>
|
||||||
|
<template #cell:network="{ row }">
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
v-for="(net, i) in row.networks"
|
||||||
|
:key="i"
|
||||||
|
:class="{ 'mt-4': i > 0 }"
|
||||||
|
>
|
||||||
|
<span>{{ net.name }}</span><br>
|
||||||
|
<span class="text-muted">{{ net.id }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template #cell:datastore="{ row }">
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
v-for="(ds, i) in row.datastores"
|
||||||
|
:key="i"
|
||||||
|
:class="{ 'mt-4': i > 0 }"
|
||||||
|
>
|
||||||
|
<span>{{ ds.name }}</span><br>
|
||||||
|
<span class="text-muted">{{ ds.id }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</SortableTable>
|
||||||
|
|
||||||
|
<div class="actions-footer">
|
||||||
|
<button
|
||||||
|
class="btn role-secondary"
|
||||||
|
@click="cancel"
|
||||||
|
>
|
||||||
|
{{ t('generic.cancel') }}
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="btn role-primary"
|
||||||
|
:disabled="selectedCount === 0"
|
||||||
|
@click="saveSelection"
|
||||||
|
>
|
||||||
|
{{ t('harvester.addons.forklift.selectVms.saveSelection') }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.provider-link {
|
||||||
|
color: var(--primary);
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vm-table-title {
|
||||||
|
h4 {
|
||||||
|
margin: 0;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-muted {
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.vm-name-cell {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
.vm-name {
|
||||||
|
font-weight: 600;
|
||||||
|
line-height: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vm-id {
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.power-badge {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 2px 8px;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 600;
|
||||||
|
|
||||||
|
&.power-on {
|
||||||
|
background-color: #d4edda;
|
||||||
|
color: #155724;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.power-off {
|
||||||
|
background-color: #fff3cd;
|
||||||
|
color: #856404;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.manual-vm-form {
|
||||||
|
.add-btn-col {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-end;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.manual-vm-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.multi-line-cell {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
line-height: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mt-4 {
|
||||||
|
margin-top: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.actions-footer {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: 10px;
|
||||||
|
margin-top: 30px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -16,6 +16,11 @@ import HarvesterMembers from '../pages/c/_cluster/members/index.vue';
|
|||||||
import ProjectNamespaces from '../pages/c/_cluster/projectsnamespaces.vue';
|
import ProjectNamespaces from '../pages/c/_cluster/projectsnamespaces.vue';
|
||||||
import HarvesterAlertmanagerReceiver from '../pages/c/_cluster/alertmanagerconfig/_alertmanagerconfigid/receiver.vue';
|
import HarvesterAlertmanagerReceiver from '../pages/c/_cluster/alertmanagerconfig/_alertmanagerconfigid/receiver.vue';
|
||||||
import HarvesterUnsupported from '../pages/c/_cluster/unsupported/index.vue';
|
import HarvesterUnsupported from '../pages/c/_cluster/unsupported/index.vue';
|
||||||
|
import ForkliftDashboard from '../pages/c/_cluster/forklift/index.vue';
|
||||||
|
import ForkliftConfigureProvider from '../pages/c/_cluster/forklift/configure-provider.vue';
|
||||||
|
import ForkliftSelectVms from '../pages/c/_cluster/forklift/select-vms.vue';
|
||||||
|
import ForkliftConfigureMappings from '../pages/c/_cluster/forklift/configure-mappings.vue';
|
||||||
|
import ForkliftReviewMigration from '../pages/c/_cluster/forklift/review-migration.vue';
|
||||||
|
|
||||||
const routes = [
|
const routes = [
|
||||||
{
|
{
|
||||||
@ -83,6 +88,26 @@ const routes = [
|
|||||||
name: `${ PRODUCT_NAME }-c-cluster-projectsnamespaces`,
|
name: `${ PRODUCT_NAME }-c-cluster-projectsnamespaces`,
|
||||||
path: `/:product/c/:cluster/projectsnamespaces`,
|
path: `/:product/c/:cluster/projectsnamespaces`,
|
||||||
component: ProjectNamespaces,
|
component: ProjectNamespaces,
|
||||||
|
}, {
|
||||||
|
name: `${ PRODUCT_NAME }-c-cluster-forklift`,
|
||||||
|
path: `/:product/c/:cluster/forklift`,
|
||||||
|
component: ForkliftDashboard,
|
||||||
|
}, {
|
||||||
|
name: `${ PRODUCT_NAME }-c-cluster-forklift-configure-provider`,
|
||||||
|
path: `/:product/c/:cluster/forklift/configure-provider`,
|
||||||
|
component: ForkliftConfigureProvider,
|
||||||
|
}, {
|
||||||
|
name: `${ PRODUCT_NAME }-c-cluster-forklift-select-vms`,
|
||||||
|
path: `/:product/c/:cluster/forklift/select-vms`,
|
||||||
|
component: ForkliftSelectVms,
|
||||||
|
}, {
|
||||||
|
name: `${ PRODUCT_NAME }-c-cluster-forklift-configure-mappings`,
|
||||||
|
path: `/:product/c/:cluster/forklift/configure-mappings`,
|
||||||
|
component: ForkliftConfigureMappings,
|
||||||
|
}, {
|
||||||
|
name: `${ PRODUCT_NAME }-c-cluster-forklift-review-migration`,
|
||||||
|
path: `/:product/c/:cluster/forklift/review-migration`,
|
||||||
|
component: ForkliftReviewMigration,
|
||||||
}, {
|
}, {
|
||||||
name: `${ PRODUCT_NAME }-c-cluster-resource`,
|
name: `${ PRODUCT_NAME }-c-cluster-resource`,
|
||||||
path: `/:product/c/:cluster/:resource`,
|
path: `/:product/c/:cluster/:resource`,
|
||||||
|
|||||||
272
pkg/harvester/utils/datastores.json
Normal file
272
pkg/harvester/utils/datastores.json
Normal file
@ -0,0 +1,272 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"id": "datastore-16879",
|
||||||
|
"parent": {
|
||||||
|
"kind": "Folder",
|
||||||
|
"id": "group-s1004"
|
||||||
|
},
|
||||||
|
"path": "/PG Devlab/datastore/Content Library Datastore",
|
||||||
|
"revision": 646,
|
||||||
|
"name": "Content Library Datastore",
|
||||||
|
"selfLink": "providers/vsphere/eaac340a-8953-479d-9463-85a1a100ab9d/datastores/datastore-16879",
|
||||||
|
"type": "NFS41",
|
||||||
|
"capacity": 65542688866304,
|
||||||
|
"free": 65166035648512,
|
||||||
|
"maintenance": "normal"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "datastore-2036",
|
||||||
|
"parent": {
|
||||||
|
"kind": "Folder",
|
||||||
|
"id": "group-s1004"
|
||||||
|
},
|
||||||
|
"path": "/PG Devlab/datastore/Mako14-Spinner1",
|
||||||
|
"revision": 460,
|
||||||
|
"name": "Mako14-Spinner1",
|
||||||
|
"selfLink": "providers/vsphere/eaac340a-8953-479d-9463-85a1a100ab9d/datastores/datastore-2036",
|
||||||
|
"type": "VMFS",
|
||||||
|
"capacity": 0,
|
||||||
|
"free": 0,
|
||||||
|
"maintenance": "normal"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "datastore-16880",
|
||||||
|
"parent": {
|
||||||
|
"kind": "Folder",
|
||||||
|
"id": "group-s1004"
|
||||||
|
},
|
||||||
|
"path": "/PG Devlab/datastore/VCSA Services",
|
||||||
|
"revision": 631,
|
||||||
|
"name": "VCSA Services",
|
||||||
|
"selfLink": "providers/vsphere/eaac340a-8953-479d-9463-85a1a100ab9d/datastores/datastore-16880",
|
||||||
|
"type": "NFS41",
|
||||||
|
"capacity": 1545487384576,
|
||||||
|
"free": 1545007792128,
|
||||||
|
"maintenance": "normal"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "datastore-12038",
|
||||||
|
"parent": {
|
||||||
|
"kind": "Folder",
|
||||||
|
"id": "group-s1004"
|
||||||
|
},
|
||||||
|
"path": "/PG Devlab/datastore/Mako13-Spinner1",
|
||||||
|
"revision": 1,
|
||||||
|
"name": "Mako13-Spinner1",
|
||||||
|
"selfLink": "providers/vsphere/eaac340a-8953-479d-9463-85a1a100ab9d/datastores/datastore-12038",
|
||||||
|
"type": "VMFS",
|
||||||
|
"capacity": 8001524072448,
|
||||||
|
"free": 7993813893120,
|
||||||
|
"maintenance": "normal"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "datastore-2045",
|
||||||
|
"parent": {
|
||||||
|
"kind": "Folder",
|
||||||
|
"id": "group-s1004"
|
||||||
|
},
|
||||||
|
"path": "/PG Devlab/datastore/Mako15-Spinner1",
|
||||||
|
"revision": 10,
|
||||||
|
"name": "Mako15-Spinner1",
|
||||||
|
"selfLink": "providers/vsphere/eaac340a-8953-479d-9463-85a1a100ab9d/datastores/datastore-2045",
|
||||||
|
"type": "VMFS",
|
||||||
|
"capacity": 8001524072448,
|
||||||
|
"free": 7883079024640,
|
||||||
|
"maintenance": "normal"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "datastore-2043",
|
||||||
|
"parent": {
|
||||||
|
"kind": "Folder",
|
||||||
|
"id": "group-s1004"
|
||||||
|
},
|
||||||
|
"path": "/PG Devlab/datastore/Mako13-Local",
|
||||||
|
"revision": 107,
|
||||||
|
"name": "Mako13-Local",
|
||||||
|
"selfLink": "providers/vsphere/eaac340a-8953-479d-9463-85a1a100ab9d/datastores/datastore-2043",
|
||||||
|
"type": "VMFS",
|
||||||
|
"capacity": 8001524072448,
|
||||||
|
"free": 7444761673728,
|
||||||
|
"maintenance": "normal"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "datastore-2044",
|
||||||
|
"parent": {
|
||||||
|
"kind": "Folder",
|
||||||
|
"id": "group-s1004"
|
||||||
|
},
|
||||||
|
"path": "/PG Devlab/datastore/Mako14-Local",
|
||||||
|
"revision": 411,
|
||||||
|
"name": "Mako14-Local",
|
||||||
|
"selfLink": "providers/vsphere/eaac340a-8953-479d-9463-85a1a100ab9d/datastores/datastore-2044",
|
||||||
|
"type": "VMFS",
|
||||||
|
"capacity": 8001524072448,
|
||||||
|
"free": 7953250779136,
|
||||||
|
"maintenance": "normal"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "datastore-27001",
|
||||||
|
"parent": {
|
||||||
|
"kind": "Folder",
|
||||||
|
"id": "group-s1004"
|
||||||
|
},
|
||||||
|
"path": "/PG Devlab/datastore/Heartbeat-01",
|
||||||
|
"revision": 644,
|
||||||
|
"name": "Heartbeat-01",
|
||||||
|
"selfLink": "providers/vsphere/eaac340a-8953-479d-9463-85a1a100ab9d/datastores/datastore-27001",
|
||||||
|
"type": "NFS41",
|
||||||
|
"capacity": 1545227599872,
|
||||||
|
"free": 1545007792128,
|
||||||
|
"maintenance": "normal"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "datastore-27002",
|
||||||
|
"parent": {
|
||||||
|
"kind": "Folder",
|
||||||
|
"id": "group-s1004"
|
||||||
|
},
|
||||||
|
"path": "/PG Devlab/datastore/Heartbeat-02",
|
||||||
|
"revision": 642,
|
||||||
|
"name": "Heartbeat-02",
|
||||||
|
"selfLink": "providers/vsphere/eaac340a-8953-479d-9463-85a1a100ab9d/datastores/datastore-27002",
|
||||||
|
"type": "NFS41",
|
||||||
|
"capacity": 1545007972352,
|
||||||
|
"free": 1545007792128,
|
||||||
|
"maintenance": "normal"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "datastore-60001",
|
||||||
|
"parent": {
|
||||||
|
"kind": "Folder",
|
||||||
|
"id": "group-s1004"
|
||||||
|
},
|
||||||
|
"path": "/PG Devlab/datastore/NFS VM Datastore",
|
||||||
|
"revision": 460,
|
||||||
|
"name": "NFS VM Datastore",
|
||||||
|
"selfLink": "providers/vsphere/eaac340a-8953-479d-9463-85a1a100ab9d/datastores/datastore-60001",
|
||||||
|
"type": "NFS41",
|
||||||
|
"capacity": 65169610178560,
|
||||||
|
"free": 65166043054080,
|
||||||
|
"maintenance": "normal"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "datastore-14753",
|
||||||
|
"parent": {
|
||||||
|
"kind": "Folder",
|
||||||
|
"id": "group-s1004"
|
||||||
|
},
|
||||||
|
"path": "/PG Devlab/datastore/Content Library Datastore-DEAD",
|
||||||
|
"revision": 1,
|
||||||
|
"name": "Content Library Datastore-DEAD",
|
||||||
|
"selfLink": "providers/vsphere/eaac340a-8953-479d-9463-85a1a100ab9d/datastores/datastore-14753",
|
||||||
|
"type": "NFS41",
|
||||||
|
"capacity": 76523271618560,
|
||||||
|
"free": 76438520856576,
|
||||||
|
"maintenance": "normal"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "datastore-2039",
|
||||||
|
"parent": {
|
||||||
|
"kind": "Folder",
|
||||||
|
"id": "group-s1004"
|
||||||
|
},
|
||||||
|
"path": "/PG Devlab/datastore/Mako15-Local",
|
||||||
|
"revision": 263,
|
||||||
|
"name": "Mako15-Local",
|
||||||
|
"selfLink": "providers/vsphere/eaac340a-8953-479d-9463-85a1a100ab9d/datastores/datastore-2039",
|
||||||
|
"type": "VMFS",
|
||||||
|
"capacity": 8001524072448,
|
||||||
|
"free": 7828817313792,
|
||||||
|
"maintenance": "normal"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "datastore-2035",
|
||||||
|
"parent": {
|
||||||
|
"kind": "StoragePod",
|
||||||
|
"id": "group-p2042"
|
||||||
|
},
|
||||||
|
"path": "/Mako13-NVMe2",
|
||||||
|
"revision": 855,
|
||||||
|
"name": "Mako13-NVMe2",
|
||||||
|
"selfLink": "providers/vsphere/eaac340a-8953-479d-9463-85a1a100ab9d/datastores/datastore-2035",
|
||||||
|
"type": "VMFS",
|
||||||
|
"capacity": 1600143753216,
|
||||||
|
"free": 443096760320,
|
||||||
|
"maintenance": "normal"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "datastore-2034",
|
||||||
|
"parent": {
|
||||||
|
"kind": "StoragePod",
|
||||||
|
"id": "group-p2042"
|
||||||
|
},
|
||||||
|
"path": "/Mako13-NVMe1",
|
||||||
|
"revision": 44,
|
||||||
|
"name": "Mako13-NVMe1",
|
||||||
|
"selfLink": "providers/vsphere/eaac340a-8953-479d-9463-85a1a100ab9d/datastores/datastore-2034",
|
||||||
|
"type": "VMFS",
|
||||||
|
"capacity": 1600143753216,
|
||||||
|
"free": 350873452544,
|
||||||
|
"maintenance": "normal"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "datastore-2037",
|
||||||
|
"parent": {
|
||||||
|
"kind": "StoragePod",
|
||||||
|
"id": "group-p2042"
|
||||||
|
},
|
||||||
|
"path": "/Mako14-NVMe1",
|
||||||
|
"revision": 450,
|
||||||
|
"name": "Mako14-NVMe1",
|
||||||
|
"selfLink": "providers/vsphere/eaac340a-8953-479d-9463-85a1a100ab9d/datastores/datastore-2037",
|
||||||
|
"type": "VMFS",
|
||||||
|
"capacity": 1600143753216,
|
||||||
|
"free": 466680283136,
|
||||||
|
"maintenance": "normal"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "datastore-2040",
|
||||||
|
"parent": {
|
||||||
|
"kind": "StoragePod",
|
||||||
|
"id": "group-p2042"
|
||||||
|
},
|
||||||
|
"path": "/Mako15-NVMe1",
|
||||||
|
"revision": 648,
|
||||||
|
"name": "Mako15-NVMe1",
|
||||||
|
"selfLink": "providers/vsphere/eaac340a-8953-479d-9463-85a1a100ab9d/datastores/datastore-2040",
|
||||||
|
"type": "VMFS",
|
||||||
|
"capacity": 1600143753216,
|
||||||
|
"free": 380083634176,
|
||||||
|
"maintenance": "normal"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "datastore-2038",
|
||||||
|
"parent": {
|
||||||
|
"kind": "StoragePod",
|
||||||
|
"id": "group-p2042"
|
||||||
|
},
|
||||||
|
"path": "/Mako14-NVMe2",
|
||||||
|
"revision": 623,
|
||||||
|
"name": "Mako14-NVMe2",
|
||||||
|
"selfLink": "providers/vsphere/eaac340a-8953-479d-9463-85a1a100ab9d/datastores/datastore-2038",
|
||||||
|
"type": "VMFS",
|
||||||
|
"capacity": 1600143753216,
|
||||||
|
"free": 685895581696,
|
||||||
|
"maintenance": "normal"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "datastore-2041",
|
||||||
|
"parent": {
|
||||||
|
"kind": "StoragePod",
|
||||||
|
"id": "group-p2042"
|
||||||
|
},
|
||||||
|
"path": "/Mako15-NVMe2",
|
||||||
|
"revision": 642,
|
||||||
|
"name": "Mako15-NVMe2",
|
||||||
|
"selfLink": "providers/vsphere/eaac340a-8953-479d-9463-85a1a100ab9d/datastores/datastore-2041",
|
||||||
|
"type": "VMFS",
|
||||||
|
"capacity": 1600143753216,
|
||||||
|
"free": 383375114240,
|
||||||
|
"maintenance": "normal"
|
||||||
|
}
|
||||||
|
]
|
||||||
332
pkg/harvester/utils/networks.json
Normal file
332
pkg/harvester/utils/networks.json
Normal file
@ -0,0 +1,332 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"id": "dvs-5001",
|
||||||
|
"parent": {
|
||||||
|
"kind": "Folder",
|
||||||
|
"id": "group-n1005"
|
||||||
|
},
|
||||||
|
"path": "/PG Devlab/network/Production-VDS",
|
||||||
|
"revision": 1,
|
||||||
|
"name": "Production-VDS",
|
||||||
|
"selfLink": "providers/vsphere/eaac340a-8953-479d-9463-85a1a100ab9d/networks/dvs-5001",
|
||||||
|
"variant": "DvSwitch",
|
||||||
|
"vlanId": "",
|
||||||
|
"host": [
|
||||||
|
{
|
||||||
|
"Host": {
|
||||||
|
"kind": "Host",
|
||||||
|
"id": "host-1010"
|
||||||
|
},
|
||||||
|
"PNIC": [
|
||||||
|
"vmnic2"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Host": {
|
||||||
|
"kind": "Host",
|
||||||
|
"id": "host-1012"
|
||||||
|
},
|
||||||
|
"PNIC": [
|
||||||
|
"vmnic2"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Host": {
|
||||||
|
"kind": "Host",
|
||||||
|
"id": "host-2003"
|
||||||
|
},
|
||||||
|
"PNIC": [
|
||||||
|
"vmnic2"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "dvs-13021",
|
||||||
|
"parent": {
|
||||||
|
"kind": "Folder",
|
||||||
|
"id": "group-n1005"
|
||||||
|
},
|
||||||
|
"path": "/PG Devlab/network/DSwitch",
|
||||||
|
"revision": 1,
|
||||||
|
"name": "DSwitch",
|
||||||
|
"selfLink": "providers/vsphere/eaac340a-8953-479d-9463-85a1a100ab9d/networks/dvs-13021",
|
||||||
|
"variant": "DvSwitch",
|
||||||
|
"vlanId": "",
|
||||||
|
"host": [
|
||||||
|
{
|
||||||
|
"Host": {
|
||||||
|
"kind": "Host",
|
||||||
|
"id": "host-1010"
|
||||||
|
},
|
||||||
|
"PNIC": [
|
||||||
|
"vmnic0"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Host": {
|
||||||
|
"kind": "Host",
|
||||||
|
"id": "host-1012"
|
||||||
|
},
|
||||||
|
"PNIC": [
|
||||||
|
"vmnic0"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Host": {
|
||||||
|
"kind": "Host",
|
||||||
|
"id": "host-2003"
|
||||||
|
},
|
||||||
|
"PNIC": [
|
||||||
|
"vmnic0"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "dvportgroup-13026",
|
||||||
|
"parent": {
|
||||||
|
"kind": "Folder",
|
||||||
|
"id": "group-n1005"
|
||||||
|
},
|
||||||
|
"path": "/PG Devlab/network/DSwitch-vMotion",
|
||||||
|
"revision": 1,
|
||||||
|
"name": "DSwitch-vMotion",
|
||||||
|
"selfLink": "providers/vsphere/eaac340a-8953-479d-9463-85a1a100ab9d/networks/dvportgroup-13026",
|
||||||
|
"variant": "DvPortGroup",
|
||||||
|
"dvSwitch": {
|
||||||
|
"kind": "Network",
|
||||||
|
"id": "dvs-13021"
|
||||||
|
},
|
||||||
|
"vlanId": "",
|
||||||
|
"host": null,
|
||||||
|
"key": "dvportgroup-13026"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "dvportgroup-13027",
|
||||||
|
"parent": {
|
||||||
|
"kind": "Folder",
|
||||||
|
"id": "group-n1005"
|
||||||
|
},
|
||||||
|
"path": "/PG Devlab/network/DSwitch-vSAN",
|
||||||
|
"revision": 1,
|
||||||
|
"name": "DSwitch-vSAN",
|
||||||
|
"selfLink": "providers/vsphere/eaac340a-8953-479d-9463-85a1a100ab9d/networks/dvportgroup-13027",
|
||||||
|
"variant": "DvPortGroup",
|
||||||
|
"dvSwitch": {
|
||||||
|
"kind": "Network",
|
||||||
|
"id": "dvs-13021"
|
||||||
|
},
|
||||||
|
"vlanId": "",
|
||||||
|
"host": null,
|
||||||
|
"key": "dvportgroup-13027"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "dvportgroup-13023",
|
||||||
|
"parent": {
|
||||||
|
"kind": "Folder",
|
||||||
|
"id": "group-n1005"
|
||||||
|
},
|
||||||
|
"path": "/PG Devlab/network/DSwitch-vCenter-HA-Management",
|
||||||
|
"revision": 1,
|
||||||
|
"name": "DSwitch-vCenter-HA-Management",
|
||||||
|
"selfLink": "providers/vsphere/eaac340a-8953-479d-9463-85a1a100ab9d/networks/dvportgroup-13023",
|
||||||
|
"variant": "DvPortGroup",
|
||||||
|
"dvSwitch": {
|
||||||
|
"kind": "Network",
|
||||||
|
"id": "dvs-13021"
|
||||||
|
},
|
||||||
|
"vlanId": "",
|
||||||
|
"host": null,
|
||||||
|
"key": "dvportgroup-13023"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "dvportgroup-13025",
|
||||||
|
"parent": {
|
||||||
|
"kind": "Folder",
|
||||||
|
"id": "group-n1005"
|
||||||
|
},
|
||||||
|
"path": "/PG Devlab/network/DSwitch-vCenter-HA-VM-Network",
|
||||||
|
"revision": 1,
|
||||||
|
"name": "DSwitch-vCenter-HA-VM-Network",
|
||||||
|
"selfLink": "providers/vsphere/eaac340a-8953-479d-9463-85a1a100ab9d/networks/dvportgroup-13025",
|
||||||
|
"variant": "DvPortGroup",
|
||||||
|
"dvSwitch": {
|
||||||
|
"kind": "Network",
|
||||||
|
"id": "dvs-13021"
|
||||||
|
},
|
||||||
|
"vlanId": "",
|
||||||
|
"host": null,
|
||||||
|
"key": "dvportgroup-13025"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "dvportgroup-13024",
|
||||||
|
"parent": {
|
||||||
|
"kind": "Folder",
|
||||||
|
"id": "group-n1005"
|
||||||
|
},
|
||||||
|
"path": "/PG Devlab/network/DSwitch-vCenter-HA-Witness",
|
||||||
|
"revision": 1,
|
||||||
|
"name": "DSwitch-vCenter-HA-Witness",
|
||||||
|
"selfLink": "providers/vsphere/eaac340a-8953-479d-9463-85a1a100ab9d/networks/dvportgroup-13024",
|
||||||
|
"variant": "DvPortGroup",
|
||||||
|
"dvSwitch": {
|
||||||
|
"kind": "Network",
|
||||||
|
"id": "dvs-13021"
|
||||||
|
},
|
||||||
|
"vlanId": "",
|
||||||
|
"host": null,
|
||||||
|
"key": "dvportgroup-13024"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "dvportgroup-5002",
|
||||||
|
"parent": {
|
||||||
|
"kind": "Folder",
|
||||||
|
"id": "group-n1005"
|
||||||
|
},
|
||||||
|
"path": "/PG Devlab/network/Production-VDS-DVUplinks-5001",
|
||||||
|
"revision": 1,
|
||||||
|
"name": "Production-VDS-DVUplinks-5001",
|
||||||
|
"selfLink": "providers/vsphere/eaac340a-8953-479d-9463-85a1a100ab9d/networks/dvportgroup-5002",
|
||||||
|
"variant": "DvPortGroup",
|
||||||
|
"dvSwitch": {
|
||||||
|
"kind": "Network",
|
||||||
|
"id": "dvs-5001"
|
||||||
|
},
|
||||||
|
"vlanId": "0-4094",
|
||||||
|
"host": null,
|
||||||
|
"key": "dvportgroup-5002"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "dvportgroup-5003",
|
||||||
|
"parent": {
|
||||||
|
"kind": "Folder",
|
||||||
|
"id": "group-n1005"
|
||||||
|
},
|
||||||
|
"path": "/PG Devlab/network/VM-Network",
|
||||||
|
"revision": 1,
|
||||||
|
"name": "VM-Network",
|
||||||
|
"selfLink": "providers/vsphere/eaac340a-8953-479d-9463-85a1a100ab9d/networks/dvportgroup-5003",
|
||||||
|
"variant": "DvPortGroup",
|
||||||
|
"dvSwitch": {
|
||||||
|
"kind": "Network",
|
||||||
|
"id": "dvs-5001"
|
||||||
|
},
|
||||||
|
"vlanId": "",
|
||||||
|
"host": null,
|
||||||
|
"key": "dvportgroup-5003"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "dvportgroup-5005",
|
||||||
|
"parent": {
|
||||||
|
"kind": "Folder",
|
||||||
|
"id": "group-n1005"
|
||||||
|
},
|
||||||
|
"path": "/PG Devlab/network/mgmt",
|
||||||
|
"revision": 1,
|
||||||
|
"name": "mgmt",
|
||||||
|
"selfLink": "providers/vsphere/eaac340a-8953-479d-9463-85a1a100ab9d/networks/dvportgroup-5005",
|
||||||
|
"variant": "DvPortGroup",
|
||||||
|
"dvSwitch": {
|
||||||
|
"kind": "Network",
|
||||||
|
"id": "dvs-5001"
|
||||||
|
},
|
||||||
|
"vlanId": "",
|
||||||
|
"host": null,
|
||||||
|
"key": "dvportgroup-5005"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "dvportgroup-6001",
|
||||||
|
"parent": {
|
||||||
|
"kind": "Folder",
|
||||||
|
"id": "group-n1005"
|
||||||
|
},
|
||||||
|
"path": "/PG Devlab/network/vCenter-HA",
|
||||||
|
"revision": 1,
|
||||||
|
"name": "vCenter-HA",
|
||||||
|
"selfLink": "providers/vsphere/eaac340a-8953-479d-9463-85a1a100ab9d/networks/dvportgroup-6001",
|
||||||
|
"variant": "DvPortGroup",
|
||||||
|
"dvSwitch": {
|
||||||
|
"kind": "Network",
|
||||||
|
"id": "dvs-5001"
|
||||||
|
},
|
||||||
|
"vlanId": "",
|
||||||
|
"host": null,
|
||||||
|
"key": "dvportgroup-6001"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "dvportgroup-13022",
|
||||||
|
"parent": {
|
||||||
|
"kind": "Folder",
|
||||||
|
"id": "group-n1005"
|
||||||
|
},
|
||||||
|
"path": "/PG Devlab/network/DSwitch-DVUplinks-13021",
|
||||||
|
"revision": 1,
|
||||||
|
"name": "DSwitch-DVUplinks-13021",
|
||||||
|
"selfLink": "providers/vsphere/eaac340a-8953-479d-9463-85a1a100ab9d/networks/dvportgroup-13022",
|
||||||
|
"variant": "DvPortGroup",
|
||||||
|
"dvSwitch": {
|
||||||
|
"kind": "Network",
|
||||||
|
"id": "dvs-13021"
|
||||||
|
},
|
||||||
|
"vlanId": "0-4094",
|
||||||
|
"host": null,
|
||||||
|
"key": "dvportgroup-13022"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "network-12023",
|
||||||
|
"parent": {
|
||||||
|
"kind": "Folder",
|
||||||
|
"id": "group-n1005"
|
||||||
|
},
|
||||||
|
"path": "/PG Devlab/network/vCenter-HA-Witness",
|
||||||
|
"revision": 1,
|
||||||
|
"name": "vCenter-HA-Witness",
|
||||||
|
"selfLink": "providers/vsphere/eaac340a-8953-479d-9463-85a1a100ab9d/networks/network-12023",
|
||||||
|
"variant": "Standard",
|
||||||
|
"vlanId": "",
|
||||||
|
"host": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "network-12022",
|
||||||
|
"parent": {
|
||||||
|
"kind": "Folder",
|
||||||
|
"id": "group-n1005"
|
||||||
|
},
|
||||||
|
"path": "/PG Devlab/network/vCenter-HA-Management",
|
||||||
|
"revision": 1,
|
||||||
|
"name": "vCenter-HA-Management",
|
||||||
|
"selfLink": "providers/vsphere/eaac340a-8953-479d-9463-85a1a100ab9d/networks/network-12022",
|
||||||
|
"variant": "Standard",
|
||||||
|
"vlanId": "",
|
||||||
|
"host": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "network-12021",
|
||||||
|
"parent": {
|
||||||
|
"kind": "Folder",
|
||||||
|
"id": "group-n1005"
|
||||||
|
},
|
||||||
|
"path": "/PG Devlab/network/vCenter-HA-VM-Network",
|
||||||
|
"revision": 1,
|
||||||
|
"name": "vCenter-HA-VM-Network",
|
||||||
|
"selfLink": "providers/vsphere/eaac340a-8953-479d-9463-85a1a100ab9d/networks/network-12021",
|
||||||
|
"variant": "Standard",
|
||||||
|
"vlanId": "",
|
||||||
|
"host": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "network-1017",
|
||||||
|
"parent": {
|
||||||
|
"kind": "Folder",
|
||||||
|
"id": "group-n1005"
|
||||||
|
},
|
||||||
|
"path": "/PG Devlab/network/VM Network",
|
||||||
|
"revision": 1,
|
||||||
|
"name": "VM Network",
|
||||||
|
"selfLink": "providers/vsphere/eaac340a-8953-479d-9463-85a1a100ab9d/networks/network-1017",
|
||||||
|
"variant": "Standard",
|
||||||
|
"vlanId": "",
|
||||||
|
"host": null
|
||||||
|
}
|
||||||
|
]
|
||||||
14275
pkg/harvester/utils/vms.json
Normal file
14275
pkg/harvester/utils/vms.json
Normal file
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user