From bea37d62b4a264341dbd7c8f95e79a78889bee85 Mon Sep 17 00:00:00 2001 From: Henry Dollman Date: Mon, 14 Oct 2024 11:48:33 -0400 Subject: [PATCH] simplify container chart data and reduce rerenders --- .../site/src/components/charts/area-chart.tsx | 42 +++-- ...iner-cpu-chart.tsx => container-chart.tsx} | 69 ++++---- .../components/charts/container-mem-chart.tsx | 147 ---------------- .../components/charts/container-net-chart.tsx | 55 +++--- .../site/src/components/charts/cpu-chart.tsx | 91 ---------- .../site/src/components/charts/disk-chart.tsx | 41 ++--- .../site/src/components/charts/mem-chart.tsx | 45 ++--- .../site/src/components/charts/swap-chart.tsx | 42 +++-- .../components/charts/temperature-chart.tsx | 42 ++--- beszel/site/src/components/routes/system.tsx | 162 ++++++++---------- beszel/site/src/components/ui/chart.tsx | 45 +++-- 11 files changed, 281 insertions(+), 500 deletions(-) rename beszel/site/src/components/charts/{container-cpu-chart.tsx => container-chart.tsx} (73%) delete mode 100644 beszel/site/src/components/charts/container-mem-chart.tsx delete mode 100644 beszel/site/src/components/charts/cpu-chart.tsx diff --git a/beszel/site/src/components/charts/area-chart.tsx b/beszel/site/src/components/charts/area-chart.tsx index 1d9021f..6eaa14f 100644 --- a/beszel/site/src/components/charts/area-chart.tsx +++ b/beszel/site/src/components/charts/area-chart.tsx @@ -12,7 +12,7 @@ import { } from '@/lib/utils' // import Spinner from '../spinner' import { ChartTimes, SystemStatsRecord } from '@/types' -import { useMemo } from 'react' +import { memo, useMemo } from 'react' /** [label, key, color, opacity] */ type DataKeys = [string, string, number, number] @@ -27,23 +27,28 @@ const getNestedValue = (path: string, max = false, data: any): number | null => .reduce((acc: any, key: string) => acc?.[key] ?? (data.stats?.cpum ? 0 : null), data) } -export default function AreaChartDefault({ - ticks, - systemData, - showMax = false, +export default memo(function AreaChartDefault({ + maxToggled = false, unit = ' MB/s', chartName, - chartTime, + systemChartData, }: { - ticks: number[] - systemData: SystemStatsRecord[] - showMax?: boolean + maxToggled?: boolean unit?: string chartName: string - chartTime: ChartTimes + systemChartData: { + systemStats: SystemStatsRecord[] + ticks: number[] + domain: number[] + chartTime: ChartTimes + } }) { const { yAxisWidth, updateYAxisWidth } = useYAxisWidth() + const { chartTime } = systemChartData + + const showMax = chartTime !== '1h' && maxToggled + const dataKeys: DataKeys[] = useMemo(() => { // [label, key, color, opacity] if (chartName === 'CPU Usage') { @@ -67,6 +72,8 @@ export default function AreaChartDefault({ return [] }, []) + // console.log('Rendered at', new Date()) + return (
- + formatShortDate(data[0].payload.created)} contentFormatter={(item) => decimalString(item.value) + unit} - indicator="line" + // indicator="line" /> } /> @@ -128,4 +136,4 @@ export default function AreaChartDefault({
) -} +}) diff --git a/beszel/site/src/components/charts/container-cpu-chart.tsx b/beszel/site/src/components/charts/container-chart.tsx similarity index 73% rename from beszel/site/src/components/charts/container-cpu-chart.tsx rename to beszel/site/src/components/charts/container-chart.tsx index ae37bf7..21c85b3 100644 --- a/beszel/site/src/components/charts/container-cpu-chart.tsx +++ b/beszel/site/src/components/charts/container-chart.tsx @@ -5,7 +5,7 @@ import { ChartTooltip, ChartTooltipContent, } from '@/components/ui/chart' -import { useMemo } from 'react' +import { memo, useMemo } from 'react' import { useYAxisWidth, chartTimeData, @@ -13,22 +13,32 @@ import { formatShortDate, decimalString, chartMargin, + toFixedWithoutTrailingZeros, } from '@/lib/utils' // import Spinner from '../spinner' import { useStore } from '@nanostores/react' -import { $chartTime, $containerFilter } from '@/lib/stores' +import { $containerFilter } from '@/lib/stores' +import { ChartTimes, ContainerStats } from '@/types' -export default function ContainerCpuChart({ - chartData, - ticks, +export default memo(function ContainerCpuChart({ + dataKey, + unit = '%', + containerChartData, }: { - chartData: Record[] - ticks: number[] + dataKey: string + unit?: string + containerChartData: { + containerData: Record[] + ticks: number[] + domain: number[] + chartTime: ChartTimes + } }) { - const chartTime = useStore($chartTime) const filter = useStore($containerFilter) const { yAxisWidth, updateYAxisWidth } = useYAxisWidth() + const { containerData, ticks, domain, chartTime } = containerChartData + const chartConfig = useMemo(() => { let config = {} as Record< string, @@ -38,16 +48,16 @@ export default function ContainerCpuChart({ } > const totalUsage = {} as Record - for (let stats of chartData) { + for (let stats of containerData) { for (let key in stats) { - if (key === 'time') { + if (!key || typeof stats[key] === 'number') { continue } if (!(key in totalUsage)) { totalUsage[key] = 0 } // @ts-ignore - totalUsage[key] += stats[key] + totalUsage[key] += stats[key]?.[dataKey] ?? 0 } } let keys = Object.keys(totalUsage) @@ -62,15 +72,12 @@ export default function ContainerCpuChart({ } } return config satisfies ChartConfig - }, [chartData]) + }, [containerChartData]) - // if (!chartData.length || !ticks.length) { - // return - // } + // console.log('rendered at', new Date()) return (
- {/* {!yAxisSet && } */} Math.max(Math.ceil(max), 0.4)]} width={yAxisWidth} - tickLine={false} - axisLine={false} - tickFormatter={(x) => { - const val = (x % 1 === 0 ? x : x.toFixed(1)) + '%' + tickFormatter={(value) => { + const val = toFixedWithoutTrailingZeros(value, 2) + unit return updateYAxisWidth(val) }} + tickLine={false} + axisLine={false} /> formatShortDate(data[0].payload.time)} + labelFormatter={(_, data) => formatShortDate(data[0].payload.created)} // @ts-ignore itemSorter={(a, b) => b.value - a.value} content={ decimalString(item.value) + '%'} - indicator="line" + contentFormatter={(item) => decimalString(item.value) + unit} + // indicator="line" /> } /> @@ -129,7 +135,8 @@ export default function ContainerCpuChart({
) -} +}) diff --git a/beszel/site/src/components/charts/container-mem-chart.tsx b/beszel/site/src/components/charts/container-mem-chart.tsx deleted file mode 100644 index 8e89da5..0000000 --- a/beszel/site/src/components/charts/container-mem-chart.tsx +++ /dev/null @@ -1,147 +0,0 @@ -import { Area, AreaChart, CartesianGrid, XAxis, YAxis } from 'recharts' -import { - ChartConfig, - ChartContainer, - ChartTooltip, - ChartTooltipContent, -} from '@/components/ui/chart' -import { useMemo } from 'react' -import { - useYAxisWidth, - chartTimeData, - cn, - formatShortDate, - toFixedWithoutTrailingZeros, - decimalString, - chartMargin, -} from '@/lib/utils' -// import Spinner from '../spinner' -import { useStore } from '@nanostores/react' -import { $chartTime, $containerFilter } from '@/lib/stores' - -export default function ContainerMemChart({ - chartData, - ticks, -}: { - chartData: Record[] - ticks: number[] -}) { - const chartTime = useStore($chartTime) - const filter = useStore($containerFilter) - const { yAxisWidth, updateYAxisWidth } = useYAxisWidth() - - const chartConfig = useMemo(() => { - let config = {} as Record< - string, - { - label: string - color: string - } - > - const totalUsage = {} as Record - for (let stats of chartData) { - for (let key in stats) { - if (key === 'time') { - continue - } - if (!(key in totalUsage)) { - totalUsage[key] = 0 - } - // @ts-ignore - totalUsage[key] += stats[key] - } - } - let keys = Object.keys(totalUsage) - keys.sort((a, b) => (totalUsage[a] > totalUsage[b] ? -1 : 1)) - const length = keys.length - for (let i = 0; i < length; i++) { - const key = keys[i] - const hue = ((i * 360) / length) % 360 - config[key] = { - label: key, - color: `hsl(${hue}, 60%, 55%)`, - } - } - return config satisfies ChartConfig - }, [chartData]) - - // if (!chartData.length || !ticks.length) { - // return - // } - - return ( -
- {/* {!yAxisSet && } */} - - - - Math.ceil(max)]} - tickLine={false} - axisLine={false} - width={yAxisWidth} - tickFormatter={(value) => { - const val = toFixedWithoutTrailingZeros(value / 1024, 2) + ' GB' - return updateYAxisWidth(val) - }} - /> - - formatShortDate(data[0].payload.time)} - // @ts-ignore - itemSorter={(a, b) => b.value - a.value} - content={ - decimalString(item.value) + ' MB'} - indicator="line" - /> - } - /> - {Object.keys(chartConfig).map((key) => { - const filtered = filter && !key.includes(filter) - let fillOpacity = filtered ? 0.05 : 0.4 - let strokeOpacity = filtered ? 0.1 : 1 - return ( - - ) - })} - - -
- ) -} diff --git a/beszel/site/src/components/charts/container-net-chart.tsx b/beszel/site/src/components/charts/container-net-chart.tsx index 10b94e1..00ae790 100644 --- a/beszel/site/src/components/charts/container-net-chart.tsx +++ b/beszel/site/src/components/charts/container-net-chart.tsx @@ -5,7 +5,7 @@ import { ChartTooltip, ChartTooltipContent, } from '@/components/ui/chart' -import { useMemo } from 'react' +import { memo, useMemo } from 'react' import { useYAxisWidth, chartTimeData, @@ -15,22 +15,26 @@ import { decimalString, chartMargin, } from '@/lib/utils' -// import Spinner from '../spinner' import { useStore } from '@nanostores/react' -import { $chartTime, $containerFilter } from '@/lib/stores' +import { $containerFilter } from '@/lib/stores' import { Separator } from '@/components/ui/separator' +import { ChartTimes, ContainerStats } from '@/types' -export default function ContainerCpuChart({ - chartData, - ticks, +export default memo(function ContainerCpuChart({ + containerChartData, }: { - chartData: Record[] - ticks: number[] + containerChartData: { + containerData: Record[] + ticks: number[] + domain: number[] + chartTime: ChartTimes + } }) { - const chartTime = useStore($chartTime) const filter = useStore($containerFilter) const { yAxisWidth, updateYAxisWidth } = useYAxisWidth() + const { containerData, ticks, domain, chartTime } = containerChartData + const chartConfig = useMemo(() => { let config = {} as Record< string, @@ -40,15 +44,16 @@ export default function ContainerCpuChart({ } > const totalUsage = {} as Record - for (let stats of chartData) { + for (let stats of containerData) { for (let key in stats) { - if (!Array.isArray(stats[key])) { + // continue if number and not container stats + if (!key || typeof stats[key] === 'number') { continue } if (!(key in totalUsage)) { totalUsage[key] = 0 } - totalUsage[key] += stats[key][2] ?? 0 + totalUsage[key] += stats[key]?.ns ?? 0 + stats[key]?.nr ?? 0 } } let keys = Object.keys(totalUsage) @@ -63,7 +68,9 @@ export default function ContainerCpuChart({ } } return config satisfies ChartConfig - }, [chartData]) + }, [containerChartData]) + + // console.log('rendered at', new Date()) return (
@@ -75,7 +82,7 @@ export default function ContainerCpuChart({ > @@ -92,11 +99,12 @@ export default function ContainerCpuChart({ }} /> formatShortDate(data[0].payload.time)} + labelFormatter={(_, data) => formatShortDate(data[0].payload.created)} // @ts-ignore itemSorter={(a, b) => b.value - a.value} content={ { try { - const sent = item?.payload?.[key][0] ?? 0 - const received = item?.payload?.[key][1] ?? 0 + const sent = item?.payload?.[key]?.ns ?? 0 + const received = item?.payload?.[key]?.nr ?? 0 return ( {decimalString(received)} MB/s @@ -140,9 +148,8 @@ export default function ContainerCpuChart({ data?.[key]?.[2] ?? 0} + dataKey={(data) => data?.[key]?.ns ?? 0 + data?.[key]?.nr ?? 0} type="monotoneX" fill={chartConfig[key].color} fillOpacity={fillOpacity} @@ -157,4 +164,4 @@ export default function ContainerCpuChart({
) -} +}) diff --git a/beszel/site/src/components/charts/cpu-chart.tsx b/beszel/site/src/components/charts/cpu-chart.tsx deleted file mode 100644 index 7300d15..0000000 --- a/beszel/site/src/components/charts/cpu-chart.tsx +++ /dev/null @@ -1,91 +0,0 @@ -import { Area, AreaChart, CartesianGrid, XAxis, YAxis } from 'recharts' - -import { ChartContainer, ChartTooltip, ChartTooltipContent } from '@/components/ui/chart' -import { - useYAxisWidth, - chartTimeData, - cn, - formatShortDate, - decimalString, - chartMargin, -} from '@/lib/utils' -// import Spinner from '../spinner' -import { useStore } from '@nanostores/react' -import { $chartTime, $cpuMax } from '@/lib/stores' -import { SystemStatsRecord } from '@/types' -import { useMemo } from 'react' - -export default function CpuChart({ - ticks, - systemData, -}: { - ticks: number[] - systemData: SystemStatsRecord[] -}) { - const chartTime = useStore($chartTime) - const { yAxisWidth, updateYAxisWidth } = useYAxisWidth() - const showMax = useStore($cpuMax) - - const dataKey = useMemo( - () => `stats.cpu${showMax && chartTime !== '1h' ? 'm' : ''}`, - [showMax, systemData] - ) - - return ( -
- - - - Math.ceil(max)]} - width={yAxisWidth} - tickLine={false} - axisLine={false} - tickFormatter={(value) => updateYAxisWidth(value + '%')} - /> - - formatShortDate(data[0].payload.created)} - contentFormatter={(item) => decimalString(item.value) + '%'} - indicator="line" - /> - } - /> - - - -
- ) -} diff --git a/beszel/site/src/components/charts/disk-chart.tsx b/beszel/site/src/components/charts/disk-chart.tsx index d36d57d..abcd5f0 100644 --- a/beszel/site/src/components/charts/disk-chart.tsx +++ b/beszel/site/src/components/charts/disk-chart.tsx @@ -12,35 +12,35 @@ import { getSizeUnit, chartMargin, } from '@/lib/utils' -// import { useMemo } from 'react' -// import Spinner from '../spinner' -import { useStore } from '@nanostores/react' -import { $chartTime } from '@/lib/stores' -import { SystemStatsRecord } from '@/types' +import { ChartTimes, SystemStatsRecord } from '@/types' +import { memo } from 'react' -export default function DiskChart({ - ticks, - systemData, +export default memo(function DiskChart({ dataKey, diskSize, + systemChartData, }: { - ticks: number[] - systemData: SystemStatsRecord[] dataKey: string diskSize: number + systemChartData: { + systemStats: SystemStatsRecord[] + ticks: number[] + domain: number[] + chartTime: ChartTimes + } }) { - const chartTime = useStore($chartTime) const { yAxisWidth, updateYAxisWidth } = useYAxisWidth() + // console.log('rendered at', new Date()) + return (
- {/* {!yAxisSet && } */} - + decimalString(getSizeVal(value)) + getSizeUnit(value) } - indicator="line" + // indicator="line" /> } /> @@ -92,4 +93,4 @@ export default function DiskChart({
) -} +}) diff --git a/beszel/site/src/components/charts/mem-chart.tsx b/beszel/site/src/components/charts/mem-chart.tsx index 58da7d8..097de43 100644 --- a/beszel/site/src/components/charts/mem-chart.tsx +++ b/beszel/site/src/components/charts/mem-chart.tsx @@ -10,24 +10,24 @@ import { formatShortDate, chartMargin, } from '@/lib/utils' -import { useMemo } from 'react' -import { useStore } from '@nanostores/react' -import { $chartTime } from '@/lib/stores' -import { SystemStatsRecord } from '@/types' +import { memo } from 'react' +import { ChartTimes, SystemStatsRecord } from '@/types' -export default function MemChart({ - ticks, - systemData, +export default memo(function MemChart({ + systemChartData, }: { - ticks: number[] - systemData: SystemStatsRecord[] + systemChartData: { + systemStats: SystemStatsRecord[] + ticks: number[] + domain: number[] + chartTime: ChartTimes + } }) { - const chartTime = useStore($chartTime) const { yAxisWidth, updateYAxisWidth } = useYAxisWidth() - const totalMem = useMemo(() => { - return toFixedFloat(systemData.at(-1)?.stats.m ?? 0, 1) - }, [systemData]) + const totalMem = toFixedFloat(systemChartData.systemStats.at(-1)?.stats.m ?? 0, 1) + + // console.log('rendered at', new Date()) return (
@@ -37,7 +37,7 @@ export default function MemChart({ 'opacity-100': yAxisWidth, })} > - + {totalMem && ( a.order - b.order} labelFormatter={(_, data) => formatShortDate(data[0].payload.created)} contentFormatter={(item) => decimalString(item.value) + ' GB'} - indicator="line" + // indicator="line" /> } /> @@ -90,7 +91,7 @@ export default function MemChart({ stackId="1" isAnimationActive={false} /> - {systemData.at(-1)?.stats.mz && ( + {systemChartData.systemStats.at(-1)?.stats.mz && (
) -} +}) diff --git a/beszel/site/src/components/charts/swap-chart.tsx b/beszel/site/src/components/charts/swap-chart.tsx index 0c648dd..85fa0cc 100644 --- a/beszel/site/src/components/charts/swap-chart.tsx +++ b/beszel/site/src/components/charts/swap-chart.tsx @@ -10,18 +10,19 @@ import { decimalString, chartMargin, } from '@/lib/utils' -import { useStore } from '@nanostores/react' -import { $chartTime } from '@/lib/stores' -import { SystemStatsRecord } from '@/types' +import { ChartTimes, SystemStatsRecord } from '@/types' +import { memo } from 'react' -export default function SwapChart({ - ticks, - systemData, +export default memo(function SwapChart({ + systemChartData, }: { - ticks: number[] - systemData: SystemStatsRecord[] + systemChartData: { + systemStats: SystemStatsRecord[] + ticks: number[] + domain: number[] + chartTime: ChartTimes + } }) { - const chartTime = useStore($chartTime) const { yAxisWidth, updateYAxisWidth } = useYAxisWidth() return ( @@ -31,11 +32,15 @@ export default function SwapChart({ 'opacity-100': yAxisWidth, })} > - + toFixedWithoutTrailingZeros(systemData.at(-1)?.stats.s ?? 0.04, 2)]} + domain={[ + 0, + () => + toFixedWithoutTrailingZeros(systemChartData.systemStats.at(-1)?.stats.s ?? 0.04, 2), + ]} width={yAxisWidth} tickLine={false} axisLine={false} @@ -43,14 +48,15 @@ export default function SwapChart({ /> formatShortDate(data[0].payload.created)} contentFormatter={(item) => decimalString(item.value) + ' GB'} - indicator="line" + // indicator="line" /> } /> @@ -76,4 +82,4 @@ export default function SwapChart({ ) -} +}) diff --git a/beszel/site/src/components/charts/temperature-chart.tsx b/beszel/site/src/components/charts/temperature-chart.tsx index 6db26df..f31b6fd 100644 --- a/beszel/site/src/components/charts/temperature-chart.tsx +++ b/beszel/site/src/components/charts/temperature-chart.tsx @@ -16,19 +16,19 @@ import { decimalString, chartMargin, } from '@/lib/utils' -import { useStore } from '@nanostores/react' -import { $chartTime } from '@/lib/stores' -import { SystemStatsRecord } from '@/types' -import { useMemo } from 'react' +import { ChartTimes, SystemStatsRecord } from '@/types' +import { memo, useMemo } from 'react' -export default function TemperatureChart({ - ticks, - systemData, +export default memo(function TemperatureChart({ + systemChartData, }: { - ticks: number[] - systemData: SystemStatsRecord[] + systemChartData: { + systemStats: SystemStatsRecord[] + ticks: number[] + domain: number[] + chartTime: ChartTimes + } }) { - const chartTime = useStore($chartTime) const { yAxisWidth, updateYAxisWidth } = useYAxisWidth() /** Format temperature data for chart and assign colors */ @@ -38,7 +38,7 @@ export default function TemperatureChart({ colors: Record } const tempSums = {} as Record - for (let data of systemData) { + for (let data of systemChartData.systemStats) { let newData = { created: data.created } as Record let keys = Object.keys(data.stats?.t ?? {}) for (let i = 0; i < keys.length; i++) { @@ -53,13 +53,14 @@ export default function TemperatureChart({ chartData.colors[key] = `hsl(${((keys.indexOf(key) * 360) / keys.length) % 360}, 60%, 55%)` } return chartData - }, [systemData]) + }, [systemChartData]) const colors = Object.keys(newChartData.colors) + // console.log('rendered at', new Date()) + return (
- {/* {!yAxisSet && } */} formatShortDate(data[0].payload.created)} contentFormatter={(item) => decimalString(item.value) + ' °C'} - indicator="line" + // indicator="line" /> } /> @@ -119,4 +121,4 @@ export default function TemperatureChart({
) -} +}) diff --git a/beszel/site/src/components/routes/system.tsx b/beszel/site/src/components/routes/system.tsx index e8eb6dd..b5f0df7 100644 --- a/beszel/site/src/components/routes/system.tsx +++ b/beszel/site/src/components/routes/system.tsx @@ -1,5 +1,5 @@ import { $systems, pb, $chartTime, $containerFilter, $userSettings } from '@/lib/stores' -import { ContainerStatsRecord, SystemRecord, SystemStatsRecord } from '@/types' +import { ContainerStats, ContainerStatsRecord, SystemRecord, SystemStatsRecord } from '@/types' import React, { Suspense, lazy, useCallback, useEffect, useMemo, useRef, useState } from 'react' import { Card, CardHeader, CardTitle, CardDescription } from '../ui/card' import { useStore } from '@nanostores/react' @@ -16,11 +16,10 @@ import { ChartAverage, ChartMax, Rows, TuxIcon } from '../ui/icons' import { useIntersectionObserver } from '@/lib/use-intersection-observer' import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '../ui/select' -const ContainerCpuChart = lazy(() => import('../charts/container-cpu-chart')) -const MemChart = lazy(() => import('../charts/mem-chart')) -const ContainerMemChart = lazy(() => import('../charts/container-mem-chart')) -const DiskChart = lazy(() => import('../charts/disk-chart')) const AreaChartDefault = lazy(() => import('../charts/area-chart')) +const ContainerChartDefault = lazy(() => import('../charts/container-chart')) +const MemChart = lazy(() => import('../charts/mem-chart')) +const DiskChart = lazy(() => import('../charts/disk-chart')) const ContainerNetChart = lazy(() => import('../charts/container-net-chart')) const SwapChart = lazy(() => import('../charts/swap-chart')) const TemperatureChart = lazy(() => import('../charts/temperature-chart')) @@ -35,27 +34,22 @@ export default function SystemDetail({ name }: { name: string }) { const bandwidthMaxStore = useState(false) const diskIoMaxStore = useState(false) const [grid, setGrid] = useLocalStorage('grid', true) - const [ticks, setTicks] = useState([] as number[]) const [system, setSystem] = useState({} as SystemRecord) const [systemStats, setSystemStats] = useState([] as SystemStatsRecord[]) + const [containerData, setContainerData] = useState( + [] as Record[] + ) const netCardRef = useRef(null) const [containerFilterBar, setContainerFilterBar] = useState(null as null | JSX.Element) - const [dockerCpuChartData, setDockerCpuChartData] = useState[]>( - [] - ) - const [dockerMemChartData, setDockerMemChartData] = useState[]>( - [] - ) - const [dockerNetChartData, setDockerNetChartData] = useState[]>( - [] - ) const isLongerChart = chartTime !== '1h' useEffect(() => { document.title = `${name} / Beszel` return () => { - resetCharts() $chartTime.set($userSettings.get().chartTime) + // resetCharts() + setSystemStats([]) + setContainerData([]) setContainerFilterBar(null) $containerFilter.set('') cpuMaxStore[1](false) @@ -64,15 +58,14 @@ export default function SystemDetail({ name }: { name: string }) { } }, [name]) - function resetCharts() { - setSystemStats([]) - setDockerCpuChartData([]) - setDockerMemChartData([]) - setDockerNetChartData([]) - } + // function resetCharts() { + // setSystemStats([]) + // setContainerData([]) + // } - useEffect(resetCharts, [chartTime]) + // useEffect(resetCharts, [chartTime]) + // find matching system useEffect(() => { if (system.id && system.name === name) { return @@ -96,6 +89,33 @@ export default function SystemDetail({ name }: { name: string }) { } }, [system]) + function getTimeData() { + const now = new Date() + const startTime = chartTimeData[chartTime].getOffset(now) + const scale = scaleTime([startTime.getTime(), now], [0, systemStats.length]) + const ticks = scale.ticks(chartTimeData[chartTime].ticks).map((d) => d.getTime()) + + return { + ticks, + chartTime, + domain: [chartTimeData[chartTime].getOffset(now).getTime(), now.getTime()], + } + } + + const systemChartData = useMemo(() => { + return { + systemStats, + ...getTimeData(), + } + }, [systemStats]) + + const containerChartData = useMemo(() => { + return { + containerData, + ...getTimeData(), + } + }, [containerData]) + async function getStats(collection: string): Promise { const lastCached = cache.get(`${system.id}_${chartTime}_${collection}`)?.at(-1) ?.created as number @@ -174,49 +194,24 @@ export default function SystemDetail({ name }: { name: string }) { }) }, [system, chartTime]) - useEffect(() => { - if (!systemStats.length) { - return - } - const now = new Date() - const startTime = chartTimeData[chartTime].getOffset(now) - const scale = scaleTime([startTime.getTime(), now], [0, systemStats.length]) - const newTicks = scale.ticks(chartTimeData[chartTime].ticks).map((d) => d.getTime()) - if (newTicks[0] !== ticks[0]) { - setTicks(newTicks) - } - }, [chartTime, systemStats]) - // make container stats for charts const makeContainerData = useCallback((containers: ContainerStatsRecord[]) => { // console.log('containers', containers) - const dockerCpuData = [] - const dockerMemData = [] - const dockerNetData = [] + const containerData = [] as Record[] for (let { created, stats } of containers) { if (!created) { - let nullData = { time: null } as unknown - dockerCpuData.push(nullData as Record) - dockerMemData.push(nullData as Record) - dockerNetData.push(nullData as Record) + let nullData = { created: null } as unknown + containerData.push(nullData as Record) continue } - const time = new Date(created).getTime() - let cpuData = { time } as Record - let memData = { time } as Record - let netData = { time } as Record + created = new Date(created).getTime() + let containerStats = { created } as Record for (let container of stats) { - cpuData[container.n] = container.c - memData[container.n] = container.m - netData[container.n] = [container.ns, container.nr, container.ns + container.nr] // sent, received, total + containerStats[container.n] = container } - dockerCpuData.push(cpuData) - dockerMemData.push(memData) - dockerNetData.push(netData) + containerData.push(containerStats) } - setDockerCpuChartData(dockerCpuData) - setDockerMemChartData(dockerMemData) - setDockerNetChartData(dockerNetData) + setContainerData(containerData) }, []) // values for system info bar @@ -257,16 +252,16 @@ export default function SystemDetail({ name }: { name: string }) { /** Space for tooltip if more than 12 containers */ const bottomSpacing = useMemo(() => { - if (!netCardRef.current || !dockerNetChartData.length) { + if (!netCardRef.current || !containerData.length) { return 0 } - const tooltipHeight = (Object.keys(dockerNetChartData[0]).length - 11) * 17.8 - 40 + const tooltipHeight = (Object.keys(containerData[0]).length - 11) * 17.8 - 40 const wrapperEl = document.getElementById('chartwrap') as HTMLDivElement const wrapperRect = wrapperEl.getBoundingClientRect() const chartRect = netCardRef.current.getBoundingClientRect() const distanceToBottom = wrapperRect.bottom - chartRect.bottom return tooltipHeight - distanceToBottom - }, [netCardRef.current, dockerNetChartData]) + }, [netCardRef.current, containerData]) if (!system.id) { return null @@ -365,12 +360,10 @@ export default function SystemDetail({ name }: { name: string }) { cornerEl={isLongerChart ? : null} > @@ -381,7 +374,7 @@ export default function SystemDetail({ name }: { name: string }) { description="Average CPU utilization of containers" cornerEl={containerFilterBar} > - + )} @@ -390,7 +383,7 @@ export default function SystemDetail({ name }: { name: string }) { title="Total Memory Usage" description="Precise utilization at the recorded time" > - + {containerFilterBar && ( @@ -400,14 +393,17 @@ export default function SystemDetail({ name }: { name: string }) { description="Memory usage of docker containers" cornerEl={containerFilterBar} > - + )} @@ -420,11 +416,9 @@ export default function SystemDetail({ name }: { name: string }) { cornerEl={isLongerChart ? : null} > @@ -435,15 +429,13 @@ export default function SystemDetail({ name }: { name: string }) { description="Network traffic of public interfaces" > - {containerFilterBar && dockerNetChartData.length > 0 && ( + {containerFilterBar && containerData.length > 0 && (
- + {/* @ts-ignore */} +
)} {(systemStats.at(-1)?.stats.su ?? 0) > 0 && ( - + )} {systemStats.at(-1)?.stats.t && ( - + )} @@ -485,8 +478,7 @@ export default function SystemDetail({ name }: { name: string }) { description={`Disk usage of ${extraFsName}`} > @@ -498,11 +490,9 @@ export default function SystemDetail({ name }: { name: string }) { cornerEl={isLongerChart ? : null} > diff --git a/beszel/site/src/components/ui/chart.tsx b/beszel/site/src/components/ui/chart.tsx index 1e94248..334458f 100644 --- a/beszel/site/src/components/ui/chart.tsx +++ b/beszel/site/src/components/ui/chart.tsx @@ -95,7 +95,6 @@ const ChartTooltipContent = React.forwardRef< React.ComponentProps & React.ComponentProps<'div'> & { hideLabel?: boolean - hideIndicator?: boolean indicator?: 'line' | 'dot' | 'dashed' nameKey?: string labelKey?: string @@ -109,9 +108,8 @@ const ChartTooltipContent = React.forwardRef< active, payload, className, - indicator = 'dot', + indicator = 'line', hideLabel = false, - hideIndicator = false, label, labelFormatter, labelClassName, @@ -145,7 +143,7 @@ const ChartTooltipContent = React.forwardRef< } const [item] = payload - const key = `${labelKey || item.dataKey || item.name || 'value'}` + const key = `${labelKey || item.name || 'value'}` const itemConfig = getPayloadConfigFromPayload(config, item, key) const value = !labelKey && typeof label === 'string' ? label : itemConfig?.label @@ -166,7 +164,8 @@ const ChartTooltipContent = React.forwardRef< return null } - const nestLabel = payload.length === 1 && indicator !== 'dot' + // const nestLabel = payload.length === 1 && indicator !== 'dot' + const nestLabel = false return (
) : ( - !hideIndicator && ( -
- ) + )} + style={ + { + '--color-bg': indicatorColor, + '--color-border': indicatorColor, + } as React.CSSProperties + } + /> )}