diff --git a/packages/translation/src/lang/en.json b/packages/translation/src/lang/en.json index 54ce4c7f9..50c516092 100644 --- a/packages/translation/src/lang/en.json +++ b/packages/translation/src/lang/en.json @@ -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", diff --git a/packages/widgets/src/beszel-system-stats/component.tsx b/packages/widgets/src/beszel-system-stats/component.tsx index c82196639..21b21981f 100644 --- a/packages/widgets/src/beszel-system-stats/component.tsx +++ b/packages/widgets/src/beszel-system-stats/component.tsx @@ -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(); + 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 && ( + + )} + {showDocker && containerSeries.length > 0 && ( <> {options.showDockerCpu && dockerCpuData.length > 0 && ( diff --git a/packages/widgets/src/beszel-system-stats/index.ts b/packages/widgets/src/beszel-system-stats/index.ts index a184963e7..3e48a1e4f 100644 --- a/packages/widgets/src/beszel-system-stats/index.ts +++ b/packages/widgets/src/beszel-system-stats/index.ts @@ -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: { diff --git a/packages/widgets/src/beszel/_shared/chart.tsx b/packages/widgets/src/beszel/_shared/chart.tsx index 8d8a5e319..0866dc9d8 100644 --- a/packages/widgets/src/beszel/_shared/chart.tsx +++ b/packages/widgets/src/beszel/_shared/chart.tsx @@ -174,6 +174,25 @@ const defaultContainerExtractors: Record = { 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 = { 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[],