mirror of
https://github.com/harvester/harvester-ui-extension.git
synced 2026-08-16 20:59:16 +00:00
feat(forklift): Added icons, critical state, fix grids, action button
Signed-off-by: Marcelo Fukumoto <marcelo.fukumoto@suse.com>
This commit is contained in:
parent
740ca94841
commit
a0d145ee60
@ -14,9 +14,9 @@
|
|||||||
"@vue-flow/minimap": "^1.4.0",
|
"@vue-flow/minimap": "^1.4.0",
|
||||||
"cache-loader": "^4.1.0",
|
"cache-loader": "^4.1.0",
|
||||||
"color": "4.2.3",
|
"color": "4.2.3",
|
||||||
|
"elkjs": "^0.11.0",
|
||||||
"ip": "2.0.1",
|
"ip": "2.0.1",
|
||||||
"node-polyfill-webpack-plugin": "^3.0.0",
|
"node-polyfill-webpack-plugin": "^3.0.0",
|
||||||
"elkjs": "^0.11.0",
|
|
||||||
"vue-draggable-next": "^2.2.1",
|
"vue-draggable-next": "^2.2.1",
|
||||||
"yaml": "^2.5.1"
|
"yaml": "^2.5.1"
|
||||||
},
|
},
|
||||||
@ -53,9 +53,9 @@
|
|||||||
"agents:generate": "./scripts/generate-agent-and-persona-mds.sh"
|
"agents:generate": "./scripts/generate-agent-and-persona-mds.sh"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@commitlint/load": "^19.8.1",
|
|
||||||
"@commitlint/cli": "^19.8.1",
|
"@commitlint/cli": "^19.8.1",
|
||||||
"@commitlint/config-conventional": "^19.8.1",
|
"@commitlint/config-conventional": "^19.8.1",
|
||||||
|
"@commitlint/load": "^19.8.1",
|
||||||
"eslint-plugin-node": "^11.1.0",
|
"eslint-plugin-node": "^11.1.0",
|
||||||
"eslint-plugin-promise": "^7.1.0",
|
"eslint-plugin-promise": "^7.1.0",
|
||||||
"husky": "^9.1.7"
|
"husky": "^9.1.7"
|
||||||
|
|||||||
@ -18,7 +18,7 @@ defineProps({
|
|||||||
:key="'net-' + idx"
|
:key="'net-' + idx"
|
||||||
class="mapping-entry"
|
class="mapping-entry"
|
||||||
>
|
>
|
||||||
<i class="icon icon-cluster" />
|
<i class="icon icon-network" />
|
||||||
<span>{{ entry }}</span>
|
<span>{{ entry }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
@ -26,7 +26,7 @@ defineProps({
|
|||||||
:key="'stor-' + idx"
|
:key="'stor-' + idx"
|
||||||
class="mapping-entry"
|
class="mapping-entry"
|
||||||
>
|
>
|
||||||
<i class="icon icon-storage" />
|
<i class="icon icon-datastore" />
|
||||||
<span>{{ entry }}</span>
|
<span>{{ entry }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -1,7 +1,18 @@
|
|||||||
import HarvesterResource from './harvester';
|
import HarvesterResource from './harvester';
|
||||||
import { HCI } from '../types';
|
import { HCI } from '../types';
|
||||||
|
import { PRODUCT_NAME } from '../config/harvester';
|
||||||
|
|
||||||
export default class ForkliftPlan extends HarvesterResource {
|
export default class ForkliftPlan extends HarvesterResource {
|
||||||
|
get listLocation() {
|
||||||
|
return {
|
||||||
|
name: `${ PRODUCT_NAME }-c-cluster-forklift`,
|
||||||
|
params: {
|
||||||
|
product: this.$rootGetters['productId'],
|
||||||
|
cluster: this.$rootGetters['clusterId'],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
get planFailed() {
|
get planFailed() {
|
||||||
const conditions = this.status?.conditions || [];
|
const conditions = this.status?.conditions || [];
|
||||||
|
|
||||||
@ -23,11 +34,22 @@ export default class ForkliftPlan extends HarvesterResource {
|
|||||||
}
|
}
|
||||||
|
|
||||||
get stateDescription() {
|
get stateDescription() {
|
||||||
if (this.planCritical) {
|
const messages = [];
|
||||||
return this.criticalMessages.join('; ');
|
const conditions = this.status?.conditions || [];
|
||||||
|
|
||||||
|
if (this.planFailed) {
|
||||||
|
const failedMsg = conditions.find((c) => c.type === 'Failed' && c.status === 'True')?.message;
|
||||||
|
|
||||||
|
if (failedMsg) {
|
||||||
|
messages.push(failedMsg);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
if (this.planCritical) {
|
||||||
|
messages.push(...this.criticalMessages);
|
||||||
|
}
|
||||||
|
|
||||||
|
return messages.length > 0 ? messages.join(' ') : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
get stateObj() {
|
get stateObj() {
|
||||||
@ -103,23 +125,20 @@ export default class ForkliftPlan extends HarvesterResource {
|
|||||||
return 'bg-success';
|
return 'bg-success';
|
||||||
}
|
}
|
||||||
|
|
||||||
get _availableActions() {
|
get isForkliftDashboard() {
|
||||||
const canStop = this.isMigrating && !this.planCanceled;
|
const route = this.currentRouter()?.currentRoute?.value;
|
||||||
const canStart = !this.planSucceeded && (!this.isMigrating || this.planFailed || this.planCanceled || this.planCritical);
|
|
||||||
|
|
||||||
const out = super._availableActions;
|
return route?.name?.endsWith('-forklift');
|
||||||
|
|
||||||
if (canStop) {
|
|
||||||
out.unshift({
|
|
||||||
action: 'stopMigration',
|
|
||||||
enabled: true,
|
|
||||||
icon: 'icon icon-pause',
|
|
||||||
label: 'Stop',
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get _availableActions() {
|
||||||
|
if (this.isForkliftDashboard) {
|
||||||
|
const canStop = this.isMigrating && !this.planCanceled;
|
||||||
|
const canStart = !this.planSucceeded && (!this.isMigrating || this.planFailed || this.planCanceled || this.planCritical);
|
||||||
|
const out = [];
|
||||||
|
|
||||||
if (canStart) {
|
if (canStart) {
|
||||||
out.unshift({
|
out.push({
|
||||||
action: 'startMigration',
|
action: 'startMigration',
|
||||||
enabled: true,
|
enabled: true,
|
||||||
icon: 'icon icon-play',
|
icon: 'icon icon-play',
|
||||||
@ -127,9 +146,32 @@ export default class ForkliftPlan extends HarvesterResource {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (canStop) {
|
||||||
|
out.push({
|
||||||
|
action: 'stopMigration',
|
||||||
|
enabled: true,
|
||||||
|
icon: 'icon icon-pause',
|
||||||
|
label: 'Stop',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
out.push({
|
||||||
|
action: 'promptRemove',
|
||||||
|
altAction: 'remove',
|
||||||
|
label: this.t('action.remove'),
|
||||||
|
icon: 'icon icon-trash',
|
||||||
|
bulkable: true,
|
||||||
|
enabled: this.canDelete,
|
||||||
|
bulkAction: 'promptRemove',
|
||||||
|
weight: -10,
|
||||||
|
});
|
||||||
|
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return super._availableActions;
|
||||||
|
}
|
||||||
|
|
||||||
async stopMigration() {
|
async stopMigration() {
|
||||||
const history = this.status?.migration?.history || [];
|
const history = this.status?.migration?.history || [];
|
||||||
|
|
||||||
|
|||||||
@ -448,7 +448,7 @@ init();
|
|||||||
<span class="source-detail text-muted">VLAN {{ entry.vlanId || '0' }}</span>
|
<span class="source-detail text-muted">VLAN {{ entry.vlanId || '0' }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div :class="['mapping-arrow', entry.target ? 'text-success' : 'text-muted']">
|
<div :class="['mapping-arrow', entry.target ? 'text-success' : 'text-muted']">
|
||||||
<i class="icon icon-upgrade-alt" />
|
<i class="icon icon-right-arrow-alt" />
|
||||||
</div>
|
</div>
|
||||||
<div class="mapping-target">
|
<div class="mapping-target">
|
||||||
<LabeledSelect
|
<LabeledSelect
|
||||||
@ -498,7 +498,7 @@ init();
|
|||||||
>{{ formatStorageDetail(entry) }}</span>
|
>{{ formatStorageDetail(entry) }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div :class="['mapping-arrow', entry.target ? 'text-success' : 'text-muted']">
|
<div :class="['mapping-arrow', entry.target ? 'text-success' : 'text-muted']">
|
||||||
<i class="icon icon-upgrade-alt" />
|
<i class="icon icon-right-arrow-alt" />
|
||||||
</div>
|
</div>
|
||||||
<div class="mapping-target">
|
<div class="mapping-target">
|
||||||
<LabeledSelect
|
<LabeledSelect
|
||||||
@ -546,7 +546,7 @@ init();
|
|||||||
|
|
||||||
.mappings-columns {
|
.mappings-columns {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 1fr 1fr;
|
grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
|
||||||
gap: 64px;
|
gap: 64px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -602,7 +602,6 @@ init();
|
|||||||
.mapping-arrow {
|
.mapping-arrow {
|
||||||
font-size: 22px;
|
font-size: 22px;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
transform: rotate(90deg);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.mapping-target {
|
.mapping-target {
|
||||||
|
|||||||
@ -110,6 +110,18 @@ const rows = computed(() => {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// If no migration progress but we have VMs in the spec, show them at 0%
|
||||||
|
if (plan.vmProgress.length === 0 && plan.spec?.vms?.length > 0) {
|
||||||
|
plan.vmProgress = plan.spec.vms.map((vm) => ({
|
||||||
|
vmName: vm.name || vm.id || 'Unknown',
|
||||||
|
vmId: vm.id || '',
|
||||||
|
progress: 0,
|
||||||
|
currentStep: 'Initializing migration',
|
||||||
|
errorMsg: '',
|
||||||
|
canceled: false,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
plan.progress = plan.vmProgress.length > 0 ? Math.round(plan.vmProgress.reduce((sum, vm) => sum + vm.progress, 0) / plan.vmProgress.length * 10) / 10 : 0;
|
plan.progress = plan.vmProgress.length > 0 ? Math.round(plan.vmProgress.reduce((sum, vm) => sum + vm.progress, 0) / plan.vmProgress.length * 10) / 10 : 0;
|
||||||
|
|
||||||
return plan;
|
return plan;
|
||||||
@ -129,7 +141,6 @@ const headers = [
|
|||||||
{
|
{
|
||||||
...NAME_COL,
|
...NAME_COL,
|
||||||
labelKey: 'harvester.addons.forklift.dashboard.columns.plan',
|
labelKey: 'harvester.addons.forklift.dashboard.columns.plan',
|
||||||
subLabel: 'VM IDs',
|
|
||||||
},
|
},
|
||||||
{ ...FORKLIFT_PLAN_VM_COUNT, width: 105 },
|
{ ...FORKLIFT_PLAN_VM_COUNT, width: 105 },
|
||||||
{
|
{
|
||||||
@ -203,9 +214,6 @@ init();
|
|||||||
<div class="plan-name">
|
<div class="plan-name">
|
||||||
{{ row.metadata.name }}
|
{{ row.metadata.name }}
|
||||||
</div>
|
</div>
|
||||||
<div class="plan-vms text-muted">
|
|
||||||
{{ row.vmIdsDisplay }}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<template #cell:vmCount="{ row }">
|
<template #cell:vmCount="{ row }">
|
||||||
@ -309,6 +317,7 @@ init();
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.vm-id {
|
.vm-id {
|
||||||
|
|||||||
@ -113,7 +113,7 @@ const startMigration = async(buttonCb) => {
|
|||||||
const inStore = store.getters['currentProduct'].inStore;
|
const inStore = store.getters['currentProduct'].inStore;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const planName = `${ providerName.value }-plan-${ Math.random().toString(36).substring(2, 7) }`;
|
const planName = `${ providerName.value }`;
|
||||||
|
|
||||||
const planSpec = {
|
const planSpec = {
|
||||||
provider: {
|
provider: {
|
||||||
@ -363,7 +363,7 @@ init();
|
|||||||
<div class="vm-card-specs">
|
<div class="vm-card-specs">
|
||||||
<span class="vm-os text-muted">{{ vm.os }}</span>
|
<span class="vm-os text-muted">{{ vm.os }}</span>
|
||||||
<span class="vm-resources">
|
<span class="vm-resources">
|
||||||
<i class="icon icon-dock" />
|
<i class="icon icon-disk" />
|
||||||
{{ vm.cpus }} vCPU • {{ vm.memGB }} • {{ vm.diskDisplay }}
|
{{ vm.cpus }} vCPU • {{ vm.memGB }} • {{ vm.diskDisplay }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@ -433,7 +433,7 @@ init();
|
|||||||
.details-grid {
|
.details-grid {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: minmax(230px, 1fr) 1fr 1fr;
|
grid-template-columns: minmax(230px, 1fr) 1fr 1fr;
|
||||||
grid-template-rows: 1fr 1fr;
|
grid-template-rows: minmax(0, 1fr) minmax(0, 1fr);
|
||||||
gap: 12px 128px;
|
gap: 12px 128px;
|
||||||
width: 747px;
|
width: 747px;
|
||||||
line-height: 20px;
|
line-height: 20px;
|
||||||
|
|||||||
@ -1,6 +1,21 @@
|
|||||||
const config = require('@rancher/shell/vue.config');
|
const path = require('path');
|
||||||
|
const baseConfig = require('@rancher/shell/vue.config');
|
||||||
|
|
||||||
module.exports = config(__dirname, {
|
const shellConfig = baseConfig(__dirname, {
|
||||||
excludes: [],
|
excludes: [],
|
||||||
// excludes: ['harvester']
|
// excludes: ['harvester']
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const originalConfigureWebpack = shellConfig.configureWebpack;
|
||||||
|
|
||||||
|
shellConfig.configureWebpack = function(config) {
|
||||||
|
if (originalConfigureWebpack) {
|
||||||
|
originalConfigureWebpack(config);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Force all @rancher/icons imports (including from @rancher/shell) to use
|
||||||
|
// the top-level 2.0.60 version so new icons are available everywhere.
|
||||||
|
config.resolve.alias['@rancher/icons'] = path.resolve(__dirname, 'node_modules', '@rancher/icons');
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = shellConfig;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user