feat(widgets): add per-filesystem storage chart to Beszel system stats (#6209)

This commit is contained in:
Thomas authored and GitHub committed 2026-07-10 17:08:22 +02:00
1 parent 2ada1aa249
commit 0e513fffd5
4 files changed
+64

No files matched your search

+8
View File
@@ -3799,6 +3799,9 @@
},
"showDockerNetwork": {
"label": "Show Docker network chart"
},
"showStorage": {
"label": "Show storage chart"
}
},
"selectSystem": "Select system",
@@ -3834,6 +3837,11 @@
"read": "Read",
"write": "Write"
},
"storage": {
"title": "Storage",
"subtitle": "Per-filesystem disk usage",
"series": "Used"
},
"network": {
"title": "Bandwidth",
"subtitle": "Network traffic of public interfaces",
@@ -21,6 +21,7 @@ import {
CPU_Y_AXIS_DOMAIN,
useContainerNames,
useDockerChartData,
useEfsChartData,
useSystemChartData,
} from "../beszel/_shared/chart";
import {
@@ -185,11 +186,31 @@ export default function BeszelSystemStatsWidget({
timePeriod,
);
const efsPaths = useMemo(() => {
if (!systemStats) return [];
const paths = new Set<string>();
for (const record of systemStats) {
if (record.stats.efs) {
for (const path of Object.keys(record.stats.efs)) {
paths.add(path);
}
}
}
return [...paths];
}, [systemStats]);
const storageData = useEfsChartData(statsWhenShown(options.showStorage, systemStats), efsPaths, timePeriod);
const containerSeries = useMemo(
() => containerNames.map((name, i) => ({ name, color: containerColors[i % containerColors.length] as string })),
[containerNames],
);
const storageSeries = useMemo(
() => efsPaths.map((path, i) => ({ name: path, color: containerColors[i % containerColors.length] as string })),
[efsPaths],
);
const cols = gridColumns[Number(width > 600)];
if (systemsError) throw systemsError;
@@ -385,6 +406,21 @@ export default function BeszelSystemStatsWidget({
/>
)}
{options.showStorage && storageData.length > 0 && storageSeries.length > 0 && (
<BeszelChartPanel
title={t("chart.storage.title")}
subtitle={t("chart.storage.subtitle")}
chartProps={{
h: CHART_HEIGHT,
data: storageData,
type: "stacked",
series: storageSeries,
yAxisFormatter: chartAxisFormatters.bytes,
tooltipProps: tooltipBytesTotal,
}}
/>
)}
{showDocker && containerSeries.length > 0 && (
<>
{options.showDockerCpu && dockerCpuData.length > 0 && (
@@ -45,6 +45,7 @@ export const { definition, componentLoader } = createWidgetDefinition("beszelSys
showDockerCpu: factory.switch({ defaultValue: true }),
showDockerMemory: factory.switch({ defaultValue: true }),
showDockerNetwork: factory.switch({ defaultValue: true }),
showStorage: factory.switch({ defaultValue: true }),
}));
},
errors: {
@@ -174,6 +174,25 @@ const defaultContainerExtractors: Record<string, ContainerExtractor> = {
network: (c) => (c?.b ? c.b[0] + c.b[1] : (c?.ns ?? 0) + (c?.nr ?? 0)),
};
export const useEfsChartData = (
systemStats: BeszelSystemStatsRecord[] | undefined,
efsPaths: string[],
timePeriod: BeszelTimePeriod = "1h",
) =>
useMemo(() => {
if (!systemStats?.length) return [];
const { fmt, ordered } = prepareRecords(systemStats, timePeriod);
const mapped = ordered.map((record) => {
const point: Record<string, unknown> = { time: fmt(record.created), rawTime: record.created };
const efs = record.stats.efs ?? {};
for (const path of efsPaths) {
point[path] = efs[path]?.du ?? 0;
}
return point;
});
return padTimeGrid(mapped, timePeriod);
}, [systemStats, efsPaths, timePeriod]);
export const useDockerChartData = (
containerStats: BeszelContainerStatsRecord[] | undefined,
containerNames: string[],