diff --git a/packages/widgets/src/beszel-system-stats/component.tsx b/packages/widgets/src/beszel-system-stats/component.tsx index f502c5598..67adf9302 100644 --- a/packages/widgets/src/beszel-system-stats/component.tsx +++ b/packages/widgets/src/beszel-system-stats/component.tsx @@ -2,7 +2,7 @@ import { useCallback, useMemo } from "react"; import { ActionIcon, Center, Menu, ScrollArea, Select, SimpleGrid, Stack, Text } from "@mantine/core"; -import { IconServer, IconServerQuestion } from "@tabler/icons-react"; +import { IconServer, IconQuestionMark } from "@tabler/icons-react"; import { clientApi } from "@homarr/api/client"; import { useScopedI18n } from "@homarr/translation/client"; @@ -26,6 +26,7 @@ import { formatStorageBytes, } from "../beszel/_shared/format"; import { makeTooltipProps } from "../beszel/_shared/tooltip"; +import { useLiveStats } from "../beszel/_shared/use-live-stats"; const tooltipPercent = makeTooltipProps(formatPercent); const tooltipGB = makeTooltipProps(formatGB, true); @@ -63,6 +64,7 @@ export default function BeszelSystemStatsWidget({ ); const showDocker = options.showDockerCpu || options.showDockerMemory || options.showDockerNetwork; + const isLive = options.timePeriod === "1m"; const { data: statsResult } = clientApi.widget.beszel.getSystemStats.useQuery( { @@ -71,9 +73,13 @@ export default function BeszelSystemStatsWidget({ timePeriod: options.timePeriod as "1h" | "12h" | "24h" | "1w" | "30d", includeDocker: showDocker, }, - { staleTime: 30 * 1000, enabled: systemExists && selectedSystem !== "" }, + { staleTime: 30 * 1000, enabled: !isLive && systemExists && selectedSystem !== "" }, ); + const liveData = useLiveStats(integrationIds, selectedSystem, showDocker, isLive && systemExists); + + const activeStats = isLive ? liveData : statsResult; + const cpuMap = useCallback((s: { cpu: number }) => ({ [t("chart.cpu.series")]: s.cpu }), [t]); const memoryMap = useCallback( (s: { mu: number; mb: number }) => ({ @@ -98,16 +104,16 @@ export default function BeszelSystemStatsWidget({ [t], ); - const cpuData = useSystemChartData(statsResult?.systemStats, cpuMap); - const memoryData = useSystemChartData(statsResult?.systemStats, memoryMap); - const diskData = useSystemChartData(statsResult?.systemStats, diskMap); - const diskIOData = useSystemChartData(statsResult?.systemStats, diskIOMap); - const networkData = useSystemChartData(statsResult?.systemStats, networkMap); + const cpuData = useSystemChartData(activeStats?.systemStats, cpuMap, isLive); + const memoryData = useSystemChartData(activeStats?.systemStats, memoryMap, isLive); + const diskData = useSystemChartData(activeStats?.systemStats, diskMap, isLive); + const diskIOData = useSystemChartData(activeStats?.systemStats, diskIOMap, isLive); + const networkData = useSystemChartData(activeStats?.systemStats, networkMap, isLive); - const containerNames = useContainerNames(statsResult?.containerStats); - const dockerCpuData = useDockerChartData(statsResult?.containerStats, containerNames, "cpu"); - const dockerMemData = useDockerChartData(statsResult?.containerStats, containerNames, "memory"); - const dockerNetData = useDockerChartData(statsResult?.containerStats, containerNames, "network"); + const containerNames = useContainerNames(activeStats?.containerStats); + const dockerCpuData = useDockerChartData(activeStats?.containerStats, containerNames, "cpu", isLive); + const dockerMemData = useDockerChartData(activeStats?.containerStats, containerNames, "memory", isLive); + const dockerNetData = useDockerChartData(activeStats?.containerStats, containerNames, "network", isLive); const containerSeries = containerNames.map((name, i) => ({ name, @@ -120,7 +126,7 @@ export default function BeszelSystemStatsWidget({ return (
- + {t("error.systemNotFound")} @@ -179,7 +185,7 @@ export default function BeszelSystemStatsWidget({ )} - {statsResult && ( + {activeStats && ( {options.showCpu && cpuData.length > 0 && ( diff --git a/packages/widgets/src/beszel-system-stats/index.ts b/packages/widgets/src/beszel-system-stats/index.ts index 674c41d4c..e7111a586 100644 --- a/packages/widgets/src/beszel-system-stats/index.ts +++ b/packages/widgets/src/beszel-system-stats/index.ts @@ -4,6 +4,7 @@ import { createWidgetDefinition } from "../definition"; import { optionsBuilder } from "../options"; const timePeriodOptions = [ + { value: "1m", label: "Live" }, { value: "1h", label: "1 Hour" }, { value: "12h", label: "12 Hours" }, { value: "24h", label: "24 Hours" }, diff --git a/packages/widgets/src/beszel/_shared/use-live-stats.ts b/packages/widgets/src/beszel/_shared/use-live-stats.ts new file mode 100644 index 000000000..4d5f5fd69 --- /dev/null +++ b/packages/widgets/src/beszel/_shared/use-live-stats.ts @@ -0,0 +1,42 @@ +"use client"; + +import { useCallback, useRef, useState } from "react"; + +import { clientApi } from "@homarr/api/client"; +import type { BeszelContainerStatsRecord, BeszelSystemStatsRecord } from "@homarr/integrations/types"; + +const MAX_BUFFER = 60; + +interface LiveStatsData { + systemStats: BeszelSystemStatsRecord[]; + containerStats: BeszelContainerStatsRecord[]; +} + +export const useLiveStats = ( + integrationIds: string[], + systemId: string, + includeDocker: boolean, + enabled: boolean, +) => { + const bufferRef = useRef({ systemStats: [], containerStats: [] }); + const [data, setData] = useState(null); + + const onData = useCallback( + (incoming: { stats: BeszelSystemStatsRecord; containerStats: BeszelContainerStatsRecord | null }) => { + const buf = bufferRef.current; + buf.systemStats = [...buf.systemStats, incoming.stats].slice(-MAX_BUFFER); + if (incoming.containerStats) { + buf.containerStats = [...buf.containerStats, incoming.containerStats].slice(-MAX_BUFFER); + } + setData({ ...buf }); + }, + [], + ); + + clientApi.widget.beszel.subscribeSystemStats.useSubscription( + { integrationIds, systemId, includeDocker }, + { enabled: enabled && systemId !== "", onData }, + ); + + return data; +};