diff --git a/packages/widgets/src/beszel-system-stats/component.tsx b/packages/widgets/src/beszel-system-stats/component.tsx index 96fbc9945..50a83a0e0 100644 --- a/packages/widgets/src/beszel-system-stats/component.tsx +++ b/packages/widgets/src/beszel-system-stats/component.tsx @@ -129,16 +129,19 @@ export default function BeszelSystemStatsWidget({ [t], ); - 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 systemStats = activeStats?.systemStats; + const containerStatsRaw = activeStats?.containerStats; - 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 cpuData = useSystemChartData(options.showCpu && systemStats || undefined, cpuMap, isLive); + const memoryData = useSystemChartData(options.showMemory && systemStats || undefined, memoryMap, isLive); + const diskData = useSystemChartData(options.showDisk && systemStats || undefined, diskMap, isLive); + const diskIOData = useSystemChartData(options.showDiskIO && systemStats || undefined, diskIOMap, isLive); + const networkData = useSystemChartData(options.showNetwork && systemStats || undefined, networkMap, isLive); + + const containerNames = useContainerNames(showDocker && containerStatsRaw || undefined); + const dockerCpuData = useDockerChartData(options.showDockerCpu && containerStatsRaw || undefined, containerNames, "cpu", isLive); + const dockerMemData = useDockerChartData(options.showDockerMemory && containerStatsRaw || undefined, containerNames, "memory", isLive); + const dockerNetData = useDockerChartData(options.showDockerNetwork && containerStatsRaw || undefined, containerNames, "network", isLive); const containerSeries = useMemo( () => containerNames.map((name, i) => ({ name, color: containerColors[i % containerColors.length] as string })), diff --git a/packages/widgets/src/beszel/_shared/use-live-stats.ts b/packages/widgets/src/beszel/_shared/use-live-stats.ts index 2ee0a82f0..d8e7d5142 100644 --- a/packages/widgets/src/beszel/_shared/use-live-stats.ts +++ b/packages/widgets/src/beszel/_shared/use-live-stats.ts @@ -1,48 +1,79 @@ "use client"; -import { useCallback, useEffect, useMemo, useState } from "react"; +import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { clientApi } from "@homarr/api/client"; import type { BeszelContainerStatsRecord, BeszelSystemStatsRecord } from "@homarr/integrations/types"; -const MAX_BUFFER = 60; +const MAX_BUFFER = 30; +const FLUSH_INTERVAL_MS = 3000; interface LiveStatsData { systemStats: BeszelSystemStatsRecord[]; containerStats: BeszelContainerStatsRecord[]; } +function appendToBuffer( + prev: LiveStatsData | null, + incoming: { stats: BeszelSystemStatsRecord; containerStats: BeszelContainerStatsRecord | null }, +): LiveStatsData { + const systemStats = [...(prev?.systemStats ?? []), incoming.stats].slice(-MAX_BUFFER); + const prevContainer = prev?.containerStats ?? []; + if (!incoming.containerStats) { + return { systemStats, containerStats: prevContainer }; + } + return { systemStats, containerStats: [...prevContainer, incoming.containerStats].slice(-MAX_BUFFER) }; +} + export const useLiveStats = (integrationIds: string[], systemId: string, enabled: boolean) => { const [data, setData] = useState(null); const [error, setError] = useState(null); + const bufferRef = useRef(null); + const dirtyRef = useRef(false); const integrationKey = integrationIds.join(","); + useEffect(() => { setData(null); setError(null); + bufferRef.current = null; + dirtyRef.current = false; }, [systemId, enabled, integrationKey]); const onData = useCallback( (incoming: { stats: BeszelSystemStatsRecord; containerStats: BeszelContainerStatsRecord | null }) => { setError(null); - setData((prev) => { - const systemStats = [...(prev?.systemStats ?? []), incoming.stats].slice(-MAX_BUFFER); - const containerStats = incoming.containerStats - ? [...(prev?.containerStats ?? []), incoming.containerStats].slice(-MAX_BUFFER) - : (prev?.containerStats ?? []); - return { systemStats, containerStats }; - }); + const isFirst = !bufferRef.current; + bufferRef.current = appendToBuffer(bufferRef.current, incoming); + dirtyRef.current = true; + if (isFirst) { + setData(bufferRef.current); + } }, [], ); const onError = useCallback((err: unknown) => { - setError(err instanceof Error ? err : new Error(String(err))); + if (err instanceof Error) { + setError(err); + return; + } + setError(new Error(String(err))); }, []); const subscriptionInput = useMemo(() => ({ integrationIds, systemId }), [integrationKey, systemId]); const subscriptionEnabled = enabled && systemId !== ""; + useEffect(() => { + if (!subscriptionEnabled) return; + const id = setInterval(() => { + if (!dirtyRef.current || !bufferRef.current) return; + dirtyRef.current = false; + setData(bufferRef.current); + }, FLUSH_INTERVAL_MS); + return () => clearInterval(id); + }, [subscriptionEnabled]); + clientApi.widget.beszel.subscribeSystemStats.useSubscription(subscriptionInput, { enabled: subscriptionEnabled, onData,