mirror of
https://github.com/amir20/dozzle.git
synced 2026-06-23 04:10:12 +00:00
727b33c8cc
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
190 lines
6.2 KiB
Vue
190 lines
6.2 KiB
Vue
<template>
|
|
<div class="flex items-stretch gap-2.5">
|
|
<IOCard
|
|
:network-rx="networkRate.rx"
|
|
:network-tx="networkRate.tx"
|
|
:disk-read="diskRate.read"
|
|
:disk-write="diskRate.write"
|
|
/>
|
|
|
|
<StatCard
|
|
:icon="PhCpu"
|
|
card-class="bg-primary/10 md:min-w-56"
|
|
icon-class="text-primary"
|
|
:title="t('tooltip.cpu-usage', { cpu: totalStat.cpu.toFixed(2), cores: roundCPU(limits.cpu) })"
|
|
>
|
|
<template #value="{ hoveredValue }">
|
|
<span class="tabular-nums">
|
|
<span class="font-semibold"> {{ Math.max(0, hoveredValue ?? totalStat.cpu).toFixed(1) }}% </span>
|
|
<span class="text-base-content/60 max-md:hidden"> / {{ roundCPU(limits.cpu) }} CPU</span>
|
|
</span>
|
|
</template>
|
|
<template #chart="{ onHoverValue }">
|
|
<Sparkline :data="cpuData" bar-class="bg-primary" class="max-md:hidden" @hover-value="onHoverValue" />
|
|
</template>
|
|
</StatCard>
|
|
|
|
<StatCard
|
|
:icon="PhMemory"
|
|
card-class="bg-secondary/10 md:min-w-56"
|
|
icon-class="text-secondary"
|
|
:title="
|
|
t('tooltip.memory-usage', { used: formatBytes(totalStat.memoryUsage), total: formatBytes(limits.memory) })
|
|
"
|
|
>
|
|
<template #value="{ hoveredValue }">
|
|
<span class="tabular-nums">
|
|
<span class="font-semibold">{{
|
|
formatBytes(hoveredValue ?? totalStat.memoryUsage, { short: true, decimals: 1 })
|
|
}}</span>
|
|
<span class="text-base-content/60 max-md:hidden">
|
|
/ {{ formatBytes(limits.memory, { short: true, decimals: 1 }) }}</span
|
|
>
|
|
</span>
|
|
</template>
|
|
<template #chart="{ onHoverValue }">
|
|
<Sparkline :data="memoryData" bar-class="bg-secondary" class="max-md:hidden" @hover-value="onHoverValue" />
|
|
</template>
|
|
</StatCard>
|
|
</div>
|
|
</template>
|
|
|
|
<script lang="ts" setup>
|
|
import { Container, Stat, emptyStat } from "@/models/Container";
|
|
import StatCard from "@/components/LogViewer/StatCard.vue";
|
|
import IOCard from "@/components/LogViewer/IOCard.vue";
|
|
import Sparkline from "@/components/LogViewer/Sparkline.vue";
|
|
// @ts-ignore
|
|
import PhCpu from "~icons/ph/cpu";
|
|
// @ts-ignore
|
|
import PhMemory from "~icons/ph/memory";
|
|
|
|
const { containers } = defineProps<{
|
|
containers: Container[];
|
|
}>();
|
|
|
|
const { t } = useI18n();
|
|
|
|
const totalStat = ref<Stat>(emptyStat());
|
|
const { history, reset } = useSimpleRefHistory(totalStat, { capacity: 300 });
|
|
const { hosts } = useHosts();
|
|
const networkRate = ref({ rx: 0, tx: 0 });
|
|
const diskRate = ref({ read: 0, write: 0 });
|
|
|
|
const roundCPU = (num: number) => (Number.isInteger(num) ? num.toFixed(0) : num.toFixed(1));
|
|
|
|
function toContainerCores(container: Container): number {
|
|
if (container.cpuLimit && container.cpuLimit > 0) {
|
|
return container.cpuLimit;
|
|
}
|
|
const hostInfo = hosts.value[container.host];
|
|
return hostInfo?.nCPU ?? 1;
|
|
}
|
|
|
|
watch(
|
|
() => containers,
|
|
() => {
|
|
const initial: Stat[] = [];
|
|
for (let i = 1; i <= 300; i++) {
|
|
const stat = containers.reduce((acc, container) => {
|
|
const item = container.statsHistory.at(-i);
|
|
if (!item) {
|
|
return acc;
|
|
}
|
|
const cores = toContainerCores(container);
|
|
return {
|
|
cpu: acc.cpu + item.cpu / cores,
|
|
memory: acc.memory + item.memory,
|
|
memoryUsage: acc.memoryUsage + item.memoryUsage,
|
|
networkRxTotal: acc.networkRxTotal + item.networkRxTotal,
|
|
networkTxTotal: acc.networkTxTotal + item.networkTxTotal,
|
|
diskReadTotal: acc.diskReadTotal + item.diskReadTotal,
|
|
diskWriteTotal: acc.diskWriteTotal + item.diskWriteTotal,
|
|
};
|
|
}, emptyStat());
|
|
initial.push(stat);
|
|
}
|
|
totalStat.value = initial[0];
|
|
reset({ initial: initial.reverse() });
|
|
},
|
|
{ immediate: true },
|
|
);
|
|
|
|
const limits = computed(() => {
|
|
const containersByHost = new Map<string, Container[]>();
|
|
containers.forEach((container) => {
|
|
if (!containersByHost.has(container.host)) {
|
|
containersByHost.set(container.host, []);
|
|
}
|
|
containersByHost.get(container.host)!.push(container);
|
|
});
|
|
|
|
let totalCpu = 0;
|
|
let totalMemory = 0;
|
|
|
|
containersByHost.forEach((hostContainers, hostId) => {
|
|
const hostInfo = hosts.value[hostId];
|
|
const hostTotalMemory = hostInfo?.memTotal || 0;
|
|
const hostTotalCpu = hostInfo?.nCPU || 0;
|
|
|
|
const hasUnlimitedCpu = hostContainers.some((c) => !c.cpuLimit || c.cpuLimit <= 0);
|
|
const hasUnlimitedMemory = hostContainers.some((c) => !c.memoryLimit);
|
|
|
|
if (hasUnlimitedCpu) {
|
|
totalCpu += hostTotalCpu;
|
|
} else {
|
|
const sumCpu = hostContainers.reduce((sum, c) => sum + (c.cpuLimit || 0), 0);
|
|
totalCpu += Math.min(sumCpu, hostTotalCpu);
|
|
}
|
|
|
|
if (hasUnlimitedMemory) {
|
|
totalMemory += hostTotalMemory;
|
|
} else {
|
|
const sumMemory = hostContainers.reduce((sum, c) => sum + (c.memoryLimit || 0), 0);
|
|
totalMemory += Math.min(sumMemory, hostTotalMemory);
|
|
}
|
|
});
|
|
|
|
return { cpu: totalCpu, memory: totalMemory };
|
|
});
|
|
|
|
useIntervalFn(() => {
|
|
const previousStat = totalStat.value;
|
|
totalStat.value = containers.reduce((acc, container) => {
|
|
const cores = toContainerCores(container);
|
|
return {
|
|
cpu: acc.cpu + container.stat.cpu / cores,
|
|
memory: acc.memory + container.stat.memory,
|
|
memoryUsage: acc.memoryUsage + container.stat.memoryUsage,
|
|
networkRxTotal: acc.networkRxTotal + container.stat.networkRxTotal,
|
|
networkTxTotal: acc.networkTxTotal + container.stat.networkTxTotal,
|
|
diskReadTotal: acc.diskReadTotal + container.stat.diskReadTotal,
|
|
diskWriteTotal: acc.diskWriteTotal + container.stat.diskWriteTotal,
|
|
};
|
|
}, emptyStat());
|
|
|
|
networkRate.value = {
|
|
rx: Math.max(0, totalStat.value.networkRxTotal - previousStat.networkRxTotal),
|
|
tx: Math.max(0, totalStat.value.networkTxTotal - previousStat.networkTxTotal),
|
|
};
|
|
diskRate.value = {
|
|
read: Math.max(0, totalStat.value.diskReadTotal - previousStat.diskReadTotal),
|
|
write: Math.max(0, totalStat.value.diskWriteTotal - previousStat.diskWriteTotal),
|
|
};
|
|
}, 1000);
|
|
|
|
const cpuData = computed(() =>
|
|
history.value.map((stat) => ({
|
|
percent: Math.max(0, stat.cpu),
|
|
value: Math.max(0, stat.cpu),
|
|
})),
|
|
);
|
|
|
|
const memoryData = computed(() =>
|
|
history.value.map((stat) => ({
|
|
percent: stat.memory,
|
|
value: stat.memoryUsage,
|
|
})),
|
|
);
|
|
</script>
|