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",
|
||||
"cache-loader": "^4.1.0",
|
||||
"color": "4.2.3",
|
||||
"elkjs": "^0.11.0",
|
||||
"ip": "2.0.1",
|
||||
"node-polyfill-webpack-plugin": "^3.0.0",
|
||||
"elkjs": "^0.11.0",
|
||||
"vue-draggable-next": "^2.2.1",
|
||||
"yaml": "^2.5.1"
|
||||
},
|
||||
@ -53,9 +53,9 @@
|
||||
"agents:generate": "./scripts/generate-agent-and-persona-mds.sh"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@commitlint/load": "^19.8.1",
|
||||
"@commitlint/cli": "^19.8.1",
|
||||
"@commitlint/config-conventional": "^19.8.1",
|
||||
"@commitlint/load": "^19.8.1",
|
||||
"eslint-plugin-node": "^11.1.0",
|
||||
"eslint-plugin-promise": "^7.1.0",
|
||||
"husky": "^9.1.7"
|
||||
|
||||
@ -18,7 +18,7 @@ defineProps({
|
||||
:key="'net-' + idx"
|
||||
class="mapping-entry"
|
||||
>
|
||||
<i class="icon icon-cluster" />
|
||||
<i class="icon icon-network" />
|
||||
<span>{{ entry }}</span>
|
||||
</div>
|
||||
<div
|
||||
@ -26,7 +26,7 @@ defineProps({
|
||||
:key="'stor-' + idx"
|
||||
class="mapping-entry"
|
||||
>
|
||||
<i class="icon icon-storage" />
|
||||
<i class="icon icon-datastore" />
|
||||
<span>{{ entry }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -1,7 +1,18 @@
|
||||
import HarvesterResource from './harvester';
|
||||
import { HCI } from '../types';
|
||||
import { PRODUCT_NAME } from '../config/harvester';
|
||||
|
||||
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() {
|
||||
const conditions = this.status?.conditions || [];
|
||||
|
||||
@ -23,11 +34,22 @@ export default class ForkliftPlan extends HarvesterResource {
|
||||
}
|
||||
|
||||
get stateDescription() {
|
||||
if (this.planCritical) {
|
||||
return this.criticalMessages.join('; ');
|
||||
const messages = [];
|
||||
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() {
|
||||
@ -103,31 +125,51 @@ export default class ForkliftPlan extends HarvesterResource {
|
||||
return 'bg-success';
|
||||
}
|
||||
|
||||
get isForkliftDashboard() {
|
||||
const route = this.currentRouter()?.currentRoute?.value;
|
||||
|
||||
return route?.name?.endsWith('-forklift');
|
||||
}
|
||||
|
||||
get _availableActions() {
|
||||
const canStop = this.isMigrating && !this.planCanceled;
|
||||
const canStart = !this.planSucceeded && (!this.isMigrating || this.planFailed || this.planCanceled || this.planCritical);
|
||||
if (this.isForkliftDashboard) {
|
||||
const canStop = this.isMigrating && !this.planCanceled;
|
||||
const canStart = !this.planSucceeded && (!this.isMigrating || this.planFailed || this.planCanceled || this.planCritical);
|
||||
const out = [];
|
||||
|
||||
const out = super._availableActions;
|
||||
if (canStart) {
|
||||
out.push({
|
||||
action: 'startMigration',
|
||||
enabled: true,
|
||||
icon: 'icon icon-play',
|
||||
label: 'Start',
|
||||
});
|
||||
}
|
||||
|
||||
if (canStop) {
|
||||
out.unshift({
|
||||
action: 'stopMigration',
|
||||
enabled: true,
|
||||
icon: 'icon icon-pause',
|
||||
label: 'Stop',
|
||||
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;
|
||||
}
|
||||
|
||||
if (canStart) {
|
||||
out.unshift({
|
||||
action: 'startMigration',
|
||||
enabled: true,
|
||||
icon: 'icon icon-play',
|
||||
label: 'Start',
|
||||
});
|
||||
}
|
||||
|
||||
return out;
|
||||
return super._availableActions;
|
||||
}
|
||||
|
||||
async stopMigration() {
|
||||
|
||||
@ -448,7 +448,7 @@ init();
|
||||
<span class="source-detail text-muted">VLAN {{ entry.vlanId || '0' }}</span>
|
||||
</div>
|
||||
<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 class="mapping-target">
|
||||
<LabeledSelect
|
||||
@ -498,7 +498,7 @@ init();
|
||||
>{{ formatStorageDetail(entry) }}</span>
|
||||
</div>
|
||||
<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 class="mapping-target">
|
||||
<LabeledSelect
|
||||
@ -546,7 +546,7 @@ init();
|
||||
|
||||
.mappings-columns {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
|
||||
gap: 64px;
|
||||
}
|
||||
|
||||
@ -602,7 +602,6 @@ init();
|
||||
.mapping-arrow {
|
||||
font-size: 22px;
|
||||
flex-shrink: 0;
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
|
||||
.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;
|
||||
|
||||
return plan;
|
||||
@ -129,7 +141,6 @@ const headers = [
|
||||
{
|
||||
...NAME_COL,
|
||||
labelKey: 'harvester.addons.forklift.dashboard.columns.plan',
|
||||
subLabel: 'VM IDs',
|
||||
},
|
||||
{ ...FORKLIFT_PLAN_VM_COUNT, width: 105 },
|
||||
{
|
||||
@ -203,9 +214,6 @@ init();
|
||||
<div class="plan-name">
|
||||
{{ row.metadata.name }}
|
||||
</div>
|
||||
<div class="plan-vms text-muted">
|
||||
{{ row.vmIdsDisplay }}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template #cell:vmCount="{ row }">
|
||||
@ -309,6 +317,7 @@ init();
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.vm-id {
|
||||
|
||||
@ -113,7 +113,7 @@ const startMigration = async(buttonCb) => {
|
||||
const inStore = store.getters['currentProduct'].inStore;
|
||||
|
||||
try {
|
||||
const planName = `${ providerName.value }-plan-${ Math.random().toString(36).substring(2, 7) }`;
|
||||
const planName = `${ providerName.value }`;
|
||||
|
||||
const planSpec = {
|
||||
provider: {
|
||||
@ -363,7 +363,7 @@ init();
|
||||
<div class="vm-card-specs">
|
||||
<span class="vm-os text-muted">{{ vm.os }}</span>
|
||||
<span class="vm-resources">
|
||||
<i class="icon icon-dock" />
|
||||
<i class="icon icon-disk" />
|
||||
{{ vm.cpus }} vCPU • {{ vm.memGB }} • {{ vm.diskDisplay }}
|
||||
</span>
|
||||
</div>
|
||||
@ -433,7 +433,7 @@ init();
|
||||
.details-grid {
|
||||
display: grid;
|
||||
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;
|
||||
width: 747px;
|
||||
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: ['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