feat(lite): display storage usage (#6421)
This commit is contained in:
parent
4b3728e8d8
commit
7f3d25964f
@ -0,0 +1,103 @@
|
|||||||
|
<template>
|
||||||
|
<UiCard>
|
||||||
|
<UiTitle type="h4">{{ $t("storage-usage") }}</UiTitle>
|
||||||
|
<UsageBar :data="data.result" :nItems="5">
|
||||||
|
<template #header>
|
||||||
|
<span>{{ $t("storage") }}</span>
|
||||||
|
<span>{{ $t("top-#", { n: 5 }) }}</span>
|
||||||
|
</template>
|
||||||
|
<template #footer v-if="showFooter">
|
||||||
|
<div class="footer-card">
|
||||||
|
<p>{{ $t("total-used") }}:</p>
|
||||||
|
<div class="footer-value">
|
||||||
|
<p>{{ percentUsed }}%</p>
|
||||||
|
<p>
|
||||||
|
{{ formatSize(data.usedSize) }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="footer-card">
|
||||||
|
<p>{{ $t("total-free") }}:</p>
|
||||||
|
<div class="footer-value">
|
||||||
|
<p>{{ percentFree }}%</p>
|
||||||
|
<p>
|
||||||
|
{{ formatSize(data.maxSize) }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</UsageBar>
|
||||||
|
</UiCard>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { computed } from "vue";
|
||||||
|
import UsageBar from "@/components/UsageBar.vue";
|
||||||
|
import UiCard from "@/components/ui/UiCard.vue";
|
||||||
|
import UiTitle from "@/components/ui/UiTitle.vue";
|
||||||
|
import { formatSize, percent } from "@/libs/utils";
|
||||||
|
import { useSrStore } from "@/stores/storage.store";
|
||||||
|
|
||||||
|
const srStore = useSrStore();
|
||||||
|
|
||||||
|
const percentUsed = computed(() =>
|
||||||
|
percent(data.value.usedSize, data.value.maxSize, 1)
|
||||||
|
);
|
||||||
|
|
||||||
|
const percentFree = computed(() =>
|
||||||
|
percent(data.value.maxSize - data.value.usedSize, data.value.maxSize, 1)
|
||||||
|
);
|
||||||
|
|
||||||
|
const showFooter = computed(() => !isNaN(percentUsed.value));
|
||||||
|
|
||||||
|
const data = computed<{
|
||||||
|
result: { label: string; value: number }[];
|
||||||
|
maxSize: number;
|
||||||
|
usedSize: number;
|
||||||
|
}>(() => {
|
||||||
|
const result: { label: string; value: number }[] = [];
|
||||||
|
let maxSize = 0;
|
||||||
|
let usedSize = 0;
|
||||||
|
|
||||||
|
srStore.allRecords.forEach(
|
||||||
|
({ name_label, physical_size, physical_utilisation }) => {
|
||||||
|
if (physical_size < 0 || physical_utilisation < 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
maxSize += physical_size;
|
||||||
|
usedSize += physical_utilisation;
|
||||||
|
|
||||||
|
const percent = (physical_utilisation / physical_size) * 100;
|
||||||
|
|
||||||
|
if (isNaN(percent)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
result.push({
|
||||||
|
label: name_label,
|
||||||
|
value: percent,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
||||||
|
return { result, maxSize, usedSize };
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="postcss" scoped>
|
||||||
|
.footer-card {
|
||||||
|
color: var(--color-blue-scale-200);
|
||||||
|
display: flex;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-card p {
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-value {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
</style>
|
@ -1,4 +1,5 @@
|
|||||||
import { utcParse } from "d3-time-format";
|
import { utcParse } from "d3-time-format";
|
||||||
|
import humanFormat from "human-format";
|
||||||
import { round } from "lodash-es";
|
import { round } from "lodash-es";
|
||||||
import type { Filter } from "@/types/filter";
|
import type { Filter } from "@/types/filter";
|
||||||
import { faSquareCheck } from "@fortawesome/free-regular-svg-icons";
|
import { faSquareCheck } from "@fortawesome/free-regular-svg-icons";
|
||||||
@ -32,6 +33,12 @@ const iconsByType = {
|
|||||||
enum: faList,
|
enum: faList,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export function formatSize(bytes: number) {
|
||||||
|
return bytes != null
|
||||||
|
? humanFormat(bytes, { scale: "binary", unit: "B" })
|
||||||
|
: "N/D";
|
||||||
|
}
|
||||||
|
|
||||||
export function getFilterIcon(filter: Filter | undefined) {
|
export function getFilterIcon(filter: Filter | undefined) {
|
||||||
if (!filter) {
|
if (!filter) {
|
||||||
return;
|
return;
|
||||||
|
@ -79,6 +79,12 @@ export interface XenApiHost extends XenApiRecord {
|
|||||||
resident_VMs: string[];
|
resident_VMs: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface XenApiSr extends XenApiRecord {
|
||||||
|
name_label: string;
|
||||||
|
physical_size: number;
|
||||||
|
physical_utilisation: number;
|
||||||
|
}
|
||||||
|
|
||||||
export interface XenApiVm extends XenApiRecord {
|
export interface XenApiVm extends XenApiRecord {
|
||||||
name_label: string;
|
name_label: string;
|
||||||
name_description: string;
|
name_description: string;
|
||||||
|
@ -33,9 +33,11 @@
|
|||||||
"stats": "Stats",
|
"stats": "Stats",
|
||||||
"status": "Status",
|
"status": "Status",
|
||||||
"storage": "Storage",
|
"storage": "Storage",
|
||||||
|
"storage-usage": "Storage usage",
|
||||||
"switch-theme": "Switch theme",
|
"switch-theme": "Switch theme",
|
||||||
"system": "System",
|
"system": "System",
|
||||||
"tasks": "Tasks",
|
"tasks": "Tasks",
|
||||||
|
"top-#": "Top {n}",
|
||||||
"total-free": "Total free",
|
"total-free": "Total free",
|
||||||
"total-used": "Total used",
|
"total-used": "Total used",
|
||||||
"vms": "VMs"
|
"vms": "VMs"
|
||||||
|
@ -33,9 +33,11 @@
|
|||||||
"stats": "Stats",
|
"stats": "Stats",
|
||||||
"status": "Statut",
|
"status": "Statut",
|
||||||
"storage": "Stockage",
|
"storage": "Stockage",
|
||||||
|
"storage-usage": "Utilisation du stockage",
|
||||||
"switch-theme": "Changer de thème",
|
"switch-theme": "Changer de thème",
|
||||||
"system": "Système",
|
"system": "Système",
|
||||||
"tasks": "Tâches",
|
"tasks": "Tâches",
|
||||||
|
"top-#": "Top {n}",
|
||||||
"total-free": "Total libre",
|
"total-free": "Total libre",
|
||||||
"total-used": "Total utilisé",
|
"total-used": "Total utilisé",
|
||||||
"vms": "VMs"
|
"vms": "VMs"
|
||||||
|
7
@xen-orchestra/lite/src/stores/storage.store.ts
Normal file
7
@xen-orchestra/lite/src/stores/storage.store.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import { defineStore } from "pinia";
|
||||||
|
import type { XenApiSr } from "@/libs/xen-api";
|
||||||
|
import { createRecordContext } from "@/stores/index";
|
||||||
|
|
||||||
|
export const useSrStore = defineStore("SR", () =>
|
||||||
|
createRecordContext<XenApiSr>("SR")
|
||||||
|
);
|
@ -9,6 +9,7 @@ import { useHostMetricsStore } from "@/stores/host-metrics.store";
|
|||||||
import { useHostStore } from "@/stores/host.store";
|
import { useHostStore } from "@/stores/host.store";
|
||||||
import { usePoolStore } from "@/stores/pool.store";
|
import { usePoolStore } from "@/stores/pool.store";
|
||||||
import { useRecordsStore } from "@/stores/records.store";
|
import { useRecordsStore } from "@/stores/records.store";
|
||||||
|
import { useSrStore } from "@/stores/storage.store";
|
||||||
import { useVmGuestMetricsStore } from "@/stores/vm-guest-metrics.store";
|
import { useVmGuestMetricsStore } from "@/stores/vm-guest-metrics.store";
|
||||||
import { useVmMetricsStore } from "@/stores/vm-metrics.store";
|
import { useVmMetricsStore } from "@/stores/vm-metrics.store";
|
||||||
import { useVmStore } from "@/stores/vm.store";
|
import { useVmStore } from "@/stores/vm.store";
|
||||||
@ -76,11 +77,13 @@ export const useXenApiStore = defineStore("xen-api", () => {
|
|||||||
const hostMetricsStore = useHostMetricsStore();
|
const hostMetricsStore = useHostMetricsStore();
|
||||||
const vmMetricsStore = useVmMetricsStore();
|
const vmMetricsStore = useVmMetricsStore();
|
||||||
const vmGuestMetricsStore = useVmGuestMetricsStore();
|
const vmGuestMetricsStore = useVmGuestMetricsStore();
|
||||||
|
const srStore = useSrStore();
|
||||||
|
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
hostMetricsStore.init(),
|
hostMetricsStore.init(),
|
||||||
vmMetricsStore.init(),
|
vmMetricsStore.init(),
|
||||||
vmGuestMetricsStore.init(),
|
vmGuestMetricsStore.init(),
|
||||||
|
srStore.init(),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const consoleStore = useConsoleStore();
|
const consoleStore = useConsoleStore();
|
||||||
|
15
@xen-orchestra/lite/src/types/human-format.d.ts
vendored
15
@xen-orchestra/lite/src/types/human-format.d.ts
vendored
@ -1,3 +1,16 @@
|
|||||||
declare module "human-format" {
|
declare module "human-format" {
|
||||||
function bytes(value: number): string;
|
type Options = {
|
||||||
|
decimals?: number;
|
||||||
|
maxDecimals?: number;
|
||||||
|
prefix?: string;
|
||||||
|
scale?: string;
|
||||||
|
separator?: string;
|
||||||
|
unit?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
function humanFormat(value: number, opts?: Options): number;
|
||||||
|
function bytes(value: number): number;
|
||||||
|
|
||||||
|
humanFormat.bytes = bytes;
|
||||||
|
export default humanFormat;
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,13 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="pool-dashboard-view">
|
<div class="pool-dashboard-view">
|
||||||
<PoolDashboardStatus class="item" />
|
<PoolDashboardStatus class="item" />
|
||||||
|
<PoolDashboardStorageUsage class="item" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import PoolDashboardStatus from "@/components/pool/dashboard/PoolDashboardStatus.vue";
|
import PoolDashboardStatus from "@/components/pool/dashboard/PoolDashboardStatus.vue";
|
||||||
|
import PoolDashboardStorageUsage from "@/components/pool/dashboard/PoolDashboardStorageUsage.vue";
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="postcss" scoped>
|
<style lang="postcss" scoped>
|
||||||
|
Loading…
Reference in New Issue
Block a user