refactor(host): update resource usage gauges and table column headers to fit small screen (#1031) (#1034)

* refactor(host): update resource usage gauges and table column headers

- Replace ConsumptionGauge with PercentageBar in CPU/Memory/Storage
  formatters to show the percentage next to the progress bar
- Rename the Disk State column header to Disk (via i18n)
- Display the CPU Manager column header on two lines



* refactor: add noopener noreferrer rel



---------


(cherry picked from commit 1c3b40b6316df61239ed1875db67e8a8aeb68168)

Signed-off-by: Andy Lee <andy.lee@suse.com>
Co-authored-by: Andy Lee <andy.lee@suse.com>
This commit is contained in:
mergify[bot] 2026-07-20 15:52:16 +08:00 committed by GitHub
parent bcde1744e1
commit 3cbaf97399
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 189 additions and 108 deletions

View File

@ -1,11 +1,11 @@
<script> <script>
import ConsumptionGauge from '@shell/components/ConsumptionGauge'; import PercentageBar from '@shell/components/PercentageBar';
import { METRIC, NODE } from '@shell/config/types'; import { METRIC, NODE } from '@shell/config/types';
import { parseSi } from '@shell/utils/units'; import { parseSi } from '@shell/utils/units';
export default { export default {
name: 'HarvesterCpuUsed', name: 'HarvesterCpuUsed',
components: { ConsumptionGauge }, components: { PercentageBar },
props: { props: {
value: { value: {
@ -74,54 +74,89 @@ export default {
return 0; return 0;
} }
}, },
}
reservedPercentage() {
return this.cpuTotal ? (this.reserved * 100) / this.cpuTotal : 0;
},
usedPercentage() {
return this.cpuTotal ? (this.used * 100) / this.cpuTotal : 0;
},
reservedAmountTemplateValues() {
return {
used: this.numberFormatter(this.reserved || 0),
total: this.numberFormatter(this.cpuTotal || 0),
unit: ` ${ this.cpuUnits }`,
};
},
usedAmountTemplateValues() {
return {
used: this.numberFormatter(this.used || 0),
total: this.numberFormatter(this.cpuTotal || 0),
unit: ` ${ this.cpuUnits }`,
};
},
},
methods: {
numberFormatter(value) {
return Number.isInteger(value) ? value : value.toFixed(2);
},
},
}; };
</script> </script>
<template> <template>
<div> <div>
<ConsumptionGauge <div class="consumption-gauge">
:capacity="cpuTotal" <div class="numbers">
:used="reserved" <span class="mr-5">
:units="cpuUnits"
:resource-name="resourceName"
>
<template #title="{amountTemplateValues, formattedPercentage}">
<span>
{{ t('harvester.formatters.hardwareResourceGauge.reserved') }} {{ t('harvester.formatters.hardwareResourceGauge.reserved') }}
</span> </span>
<span class="precent-data"> <span class="precent-data">
{{ t('node.detail.glance.consumptionGauge.amount', amountTemplateValues) }} {{ t('node.detail.glance.consumptionGauge.amount', reservedAmountTemplateValues) }}
<span class="ml-10 percentage">/&nbsp;{{ formattedPercentage }}
</span> </span>
</span> </div>
</template> <div class="mt-10">
</ConsumptionGauge> <PercentageBar
:model-value="reservedPercentage"
show-percentage
/>
</div>
</div>
<div <div
v-if="showUsed" v-if="showUsed"
class="mt-10" class="consumption-gauge mt-10"
> >
<ConsumptionGauge <div class="numbers">
:capacity="cpuTotal"
:used="used"
:units="cpuUnits"
>
<template #title="{amountTemplateValues, formattedPercentage}">
<span> <span>
{{ t('harvester.formatters.hardwareResourceGauge.used') }} {{ t('harvester.formatters.hardwareResourceGauge.used') }}
</span> </span>
<span class="precent-data"> <span class="precent-data">
{{ t('node.detail.glance.consumptionGauge.amount', amountTemplateValues) }} {{ t('node.detail.glance.consumptionGauge.amount', usedAmountTemplateValues) }}
<span class="ml-10 percentage">/&nbsp;{{ formattedPercentage }}
</span> </span>
</span> </div>
</template> <div class="mt-10">
</ConsumptionGauge> <PercentageBar
:model-value="usedPercentage"
show-percentage
/>
</div>
</div> </div>
</div> </div>
</template> </template>
<style lang="scss" scoped> <style lang="scss" scoped>
.consumption-gauge {
.numbers {
display: flex;
flex-direction: row;
justify-content: space-between;
}
}
.precent-data { .precent-data {
white-space: nowrap; white-space: nowrap;
} }

View File

@ -1,11 +1,11 @@
<script> <script>
import ConsumptionGauge from '@shell/components/ConsumptionGauge'; import PercentageBar from '@shell/components/PercentageBar';
import { METRIC, NODE } from '@shell/config/types'; import { METRIC, NODE } from '@shell/config/types';
import { formatSi, exponentNeeded, UNITS, parseSi } from '@shell/utils/units'; import { formatSi, exponentNeeded, UNITS, parseSi } from '@shell/utils/units';
import { UNIT_SUFFIX } from '../utils/unit'; import { UNIT_SUFFIX } from '../utils/unit';
export default { export default {
name: 'HarvesterMemoryUsed', name: 'HarvesterMemoryUsed',
components: { ConsumptionGauge }, components: { PercentageBar },
props: { props: {
value: { value: {
@ -76,6 +76,30 @@ export default {
return 0; return 0;
} }
}, },
reservedPercentage() {
return this.memoryTotal ? (this.reserved * 100) / this.memoryTotal : 0;
},
usedPercentage() {
return this.memoryTotal ? (this.used * 100) / this.memoryTotal : 0;
},
reservedAmountTemplateValues() {
return {
used: this.memoryFormatter(this.reserved || 0),
total: this.memoryFormatter(this.memoryTotal || 0),
unit: ` ${ this.memoryUnits }`,
};
},
usedAmountTemplateValues() {
return {
used: this.memoryFormatter(this.used || 0),
total: this.memoryFormatter(this.memoryTotal || 0),
unit: ` ${ this.memoryUnits }`,
};
},
}, },
methods: { methods: {
@ -96,50 +120,53 @@ export default {
<template> <template>
<div> <div>
<ConsumptionGauge <div class="consumption-gauge">
:capacity="memoryTotal" <div class="numbers">
:used="reserved" <span class="mr-10">
:units="memoryUnits"
:number-formatter="memoryFormatter"
:resource-name="resourceName"
>
<template #title="{amountTemplateValues, formattedPercentage}">
<span>
{{ t('harvester.formatters.hardwareResourceGauge.reserved') }} {{ t('harvester.formatters.hardwareResourceGauge.reserved') }}
</span> </span>
<span class="precent-data"> <span class="precent-data">
{{ t('node.detail.glance.consumptionGauge.amount', amountTemplateValues) }} {{ t('node.detail.glance.consumptionGauge.amount', reservedAmountTemplateValues) }}
<span class="ml-10 percentage">/&nbsp;{{ formattedPercentage }}
</span> </span>
</span> </div>
</template> <div class="mt-10">
</ConsumptionGauge> <PercentageBar
:model-value="reservedPercentage"
show-percentage
/>
</div>
</div>
<div <div
v-if="showUsed" v-if="showUsed"
class="mt-10" class="consumption-gauge mt-10"
> >
<ConsumptionGauge <div class="numbers">
:capacity="memoryTotal"
:used="used"
:units="memoryUnits"
:number-formatter="memoryFormatter"
>
<template #title="{amountTemplateValues, formattedPercentage}">
<span> <span>
{{ t('harvester.formatters.hardwareResourceGauge.used') }} {{ t('harvester.formatters.hardwareResourceGauge.used') }}
</span> </span>
<span class="precent-data"> <span class="precent-data">
{{ t('node.detail.glance.consumptionGauge.amount', amountTemplateValues) }} {{ t('node.detail.glance.consumptionGauge.amount', usedAmountTemplateValues) }}
<span class="ml-10 percentage">/&nbsp;{{ formattedPercentage }}
</span> </span>
</span> </div>
</template> <div class="mt-10">
</ConsumptionGauge> <PercentageBar
:model-value="usedPercentage"
show-percentage
/>
</div>
</div> </div>
</div> </div>
</template> </template>
<style lang="scss" scoped> <style lang="scss" scoped>
.consumption-gauge {
.numbers {
display: flex;
flex-direction: row;
justify-content: space-between;
}
}
.precent-data { .precent-data {
white-space: nowrap; white-space: nowrap;
} }

View File

@ -1,12 +1,12 @@
<script> <script>
import ConsumptionGauge from '@shell/components/ConsumptionGauge'; import PercentageBar from '@shell/components/PercentageBar';
import { LONGHORN } from '@shell/config/types'; import { LONGHORN } from '@shell/config/types';
import { formatSi, exponentNeeded, UNITS } from '@shell/utils/units'; import { formatSi, exponentNeeded, UNITS } from '@shell/utils/units';
import { UNIT_SUFFIX } from '../utils/unit'; import { UNIT_SUFFIX } from '../utils/unit';
export default { export default {
name: 'HarvesterStorageUsed', name: 'HarvesterStorageUsed',
components: { ConsumptionGauge }, components: { PercentageBar },
props: { props: {
value: { value: {
@ -120,6 +120,14 @@ export default {
unit: this.allocatedUnits, unit: this.allocatedUnits,
}; };
}, },
usedPercentage() {
return this.storageStats.maximum ? (this.storageStats.used * 100) / this.storageStats.maximum : 0;
},
allocatedPercentage() {
return this.storageStats.total ? (this.storageStats.scheduled * 100) / this.storageStats.total : 0;
},
}, },
methods: { methods: {
@ -144,53 +152,54 @@ export default {
<div> <div>
<div <div
v-if="showAllocated" v-if="showAllocated"
class="consumption-gauge"
> >
<ConsumptionGauge <div class="numbers">
:capacity="storageStats.total" <span class="mr-10">
:used="storageStats.scheduled"
:units="allocatedUnits"
:number-formatter="formatter"
:resource-name="resourceName"
>
<template #title="{formattedPercentage}">
<span>
{{ t('harvester.dashboard.hardwareResourceGauge.allocated') }} {{ t('harvester.dashboard.hardwareResourceGauge.allocated') }}
</span> </span>
<span class="precent-data"> <span class="precent-data">
{{ t('node.detail.glance.consumptionGauge.amount', allocatedAmountTemplateValues) }} {{ t('node.detail.glance.consumptionGauge.amount', allocatedAmountTemplateValues) }}
<span class="ml-10 percentage">
/&nbsp;{{ formattedPercentage }}
</span> </span>
</span>
</template>
</ConsumptionGauge>
</div> </div>
<ConsumptionGauge <div class="mt-10">
:capacity="storageStats.maximum" <PercentageBar
:used="storageStats.used" :model-value="allocatedPercentage"
:units="usedUnits" show-percentage
:number-formatter="formatter" />
:resource-name="showAllocated ? '' : resourceName" </div>
:class="{ </div>
'mt-10': showAllocated, <div
}" class="consumption-gauge"
:class="{ 'mt-10': showAllocated }"
> >
<template #title="{formattedPercentage}"> <div class="numbers">
<span> <span>
{{ t('node.detail.glance.consumptionGauge.used') }} {{ t('node.detail.glance.consumptionGauge.used') }}
</span> </span>
<span class="precent-data"> <span class="precent-data">
{{ t('node.detail.glance.consumptionGauge.amount', usedAmountTemplateValues) }} {{ t('node.detail.glance.consumptionGauge.amount', usedAmountTemplateValues) }}
<span class="ml-10 percentage">
/&nbsp;{{ formattedPercentage }}
</span> </span>
</span> </div>
</template> <div class="mt-10">
</ConsumptionGauge> <PercentageBar
:model-value="usedPercentage"
show-percentage
/>
</div>
</div>
</div> </div>
</template> </template>
<style lang="scss" scoped> <style lang="scss" scoped>
.consumption-gauge {
.numbers {
display: flex;
flex-direction: row;
justify-content: space-between;
}
}
.precent-data { .precent-data {
white-space: nowrap; white-space: nowrap;
} }

View File

@ -331,7 +331,8 @@ harvester:
message: Message message: Message
phase: Phase phase: Phase
attachedVM: Attached Virtual Machine attachedVM: Attached Virtual Machine
cpuManager: CPU Manager cpuManager: CPU<br>Manager
diskState: Disk
routeConnectivityTooltip: Connectivity between the VM network and the management network, which the Harvester nodes are connected to. routeConnectivityTooltip: Connectivity between the VM network and the management network, which the Harvester nodes are connected to.
fingerprint: Fingerprint fingerprint: Fingerprint
value: Value value: Value

View File

@ -84,7 +84,7 @@ export default {
}, },
{ {
...NAME, ...NAME,
width: 130, width: 150,
}, },
{ {
name: 'host-ip', name: 'host-ip',
@ -136,14 +136,14 @@ export default {
value: 'id', value: 'id',
formatter: 'HarvesterCPUPinning', formatter: 'HarvesterCPUPinning',
formatterOpts: { rows: this.rows }, formatterOpts: { rows: this.rows },
width: 150, width: 75,
align: 'center', align: 'center',
}); });
} }
if (this.hasLonghornSchema) { if (this.hasLonghornSchema) {
out.push({ out.push({
name: 'diskState', name: 'diskState',
labelKey: 'tableHeaders.diskState', labelKey: 'harvester.tableHeaders.diskState',
value: 'diskState', value: 'diskState',
formatter: 'HarvesterDiskState', formatter: 'HarvesterDiskState',
width: 130, width: 130,
@ -156,7 +156,6 @@ export default {
name: 'console', name: 'console',
label: ' ', label: ' ',
align: 'right', align: 'right',
width: 80,
}); });
return out; return out;
@ -239,6 +238,8 @@ export default {
v-if="!row.consoleUrl" v-if="!row.consoleUrl"
:href="consoleDocLink" :href="consoleDocLink"
target="_blank" target="_blank"
rel="noopener noreferrer"
class="console-info"
><i class="icon icon-info" /></a> ><i class="icon icon-info" /></a>
</div> </div>
</template> </template>
@ -249,5 +250,13 @@ export default {
<style lang="scss" scoped> <style lang="scss" scoped>
.console-button { .console-button {
display: flex; display: flex;
position: relative;
justify-content: flex-end;
}
.console-info {
position: absolute;
top: -8px;
right: -3px;
} }
</style> </style>