From cedf80a869feaa49fd99aa3d265a82741dcc0116 Mon Sep 17 00:00:00 2001 From: Henry Dollman Date: Thu, 10 Oct 2024 15:11:48 -0400 Subject: [PATCH] add max 1m values for cpu, bandwidth, disk io * removes unused things from chart.tsx * updates y axis width only if it grows * add generic area chart component and remove individual cpu, bandwidth, disk io charts --- beszel/internal/entities/system/system.go | 62 ++++---- beszel/internal/records/records.go | 64 ++++---- .../site/src/components/charts/area-chart.tsx | 131 ++++++++++++++++ .../src/components/charts/bandwidth-chart.tsx | 105 ------------- .../components/charts/container-cpu-chart.tsx | 14 +- .../components/charts/container-mem-chart.tsx | 6 +- .../components/charts/container-net-chart.tsx | 10 +- .../site/src/components/charts/cpu-chart.tsx | 26 +++- .../site/src/components/charts/disk-chart.tsx | 13 +- .../src/components/charts/disk-io-chart.tsx | 102 ------------- .../site/src/components/charts/mem-chart.tsx | 10 +- .../site/src/components/charts/swap-chart.tsx | 5 +- .../components/charts/temperature-chart.tsx | 13 +- beszel/site/src/components/routes/system.tsx | 144 +++++++++++++----- beszel/site/src/components/ui/chart.tsx | 114 +++++++------- beszel/site/src/components/ui/icons.tsx | 22 +++ beszel/site/src/lib/utils.ts | 7 +- beszel/site/src/types.d.ts | 18 ++- 18 files changed, 441 insertions(+), 425 deletions(-) create mode 100644 beszel/site/src/components/charts/area-chart.tsx delete mode 100644 beszel/site/src/components/charts/bandwidth-chart.tsx delete mode 100644 beszel/site/src/components/charts/disk-io-chart.tsx diff --git a/beszel/internal/entities/system/system.go b/beszel/internal/entities/system/system.go index 03aa90d..56ad70b 100644 --- a/beszel/internal/entities/system/system.go +++ b/beszel/internal/entities/system/system.go @@ -6,38 +6,42 @@ import ( ) type Stats struct { - Cpu float64 `json:"cpu"` - PeakCpu float64 `json:"pcpu,omitempty"` - Mem float64 `json:"m"` - MemUsed float64 `json:"mu"` - MemPct float64 `json:"mp"` - MemBuffCache float64 `json:"mb"` - MemZfsArc float64 `json:"mz,omitempty"` // ZFS ARC memory - Swap float64 `json:"s,omitempty"` - SwapUsed float64 `json:"su,omitempty"` - DiskTotal float64 `json:"d"` - DiskUsed float64 `json:"du"` - DiskPct float64 `json:"dp"` - DiskReadPs float64 `json:"dr"` - DiskWritePs float64 `json:"dw"` - NetworkSent float64 `json:"ns"` - PeakNetworkSent float64 `json:"pns,omitempty"` - NetworkRecv float64 `json:"nr"` - PeakNetworkRecv float64 `json:"pnr,omitempty"` - Temperatures map[string]float64 `json:"t,omitempty"` - ExtraFs map[string]*FsStats `json:"efs,omitempty"` + Cpu float64 `json:"cpu"` + MaxCpu float64 `json:"cpum,omitempty"` + Mem float64 `json:"m"` + MemUsed float64 `json:"mu"` + MemPct float64 `json:"mp"` + MemBuffCache float64 `json:"mb"` + MemZfsArc float64 `json:"mz,omitempty"` // ZFS ARC memory + Swap float64 `json:"s,omitempty"` + SwapUsed float64 `json:"su,omitempty"` + DiskTotal float64 `json:"d"` + DiskUsed float64 `json:"du"` + DiskPct float64 `json:"dp"` + DiskReadPs float64 `json:"dr"` + DiskWritePs float64 `json:"dw"` + MaxDiskReadPs float64 `json:"drm,omitempty"` + MaxDiskWritePs float64 `json:"dwm,omitempty"` + NetworkSent float64 `json:"ns"` + NetworkRecv float64 `json:"nr"` + MaxNetworkSent float64 `json:"nsm,omitempty"` + MaxNetworkRecv float64 `json:"nrm,omitempty"` + Temperatures map[string]float64 `json:"t,omitempty"` + ExtraFs map[string]*FsStats `json:"efs,omitempty"` } type FsStats struct { - Time time.Time `json:"-"` - Root bool `json:"-"` - Mountpoint string `json:"-"` - DiskTotal float64 `json:"d"` - DiskUsed float64 `json:"du"` - TotalRead uint64 `json:"-"` - TotalWrite uint64 `json:"-"` - DiskWritePs float64 `json:"w"` - DiskReadPs float64 `json:"r"` + Time time.Time `json:"-"` + Root bool `json:"-"` + Mountpoint string `json:"-"` + DiskTotal float64 `json:"d"` + DiskUsed float64 `json:"du"` + TotalRead uint64 `json:"-"` + TotalWrite uint64 `json:"-"` + DiskReadPs float64 `json:"r"` + DiskWritePs float64 `json:"w"` + MaxDiskReadPS float64 `json:"rm,omitempty"` + MaxDiskWritePS float64 `json:"wm,omitempty"` } type NetIoStats struct { diff --git a/beszel/internal/records/records.go b/beszel/internal/records/records.go index 980a8ac..e4dc57b 100644 --- a/beszel/internal/records/records.go +++ b/beszel/internal/records/records.go @@ -151,11 +151,6 @@ func (rm *RecordManager) AverageSystemStats(records []*models.Record) system.Sta // use different counter for temps in case some records don't have them tempCount := float64(0) - // peak values - peakCpu := float64(0) - peakNs := float64(0) - peakNr := float64(0) - var stats system.Stats for _, record := range records { record.UnmarshalJSONField("stats", &stats) @@ -175,9 +170,11 @@ func (rm *RecordManager) AverageSystemStats(records []*models.Record) system.Sta sum.NetworkSent += stats.NetworkSent sum.NetworkRecv += stats.NetworkRecv // set peak values - peakCpu = max(peakCpu, stats.PeakCpu, stats.Cpu) - peakNs = max(peakNs, stats.PeakNetworkSent, stats.NetworkSent) - peakNr = max(peakNr, stats.PeakNetworkRecv, stats.NetworkRecv) + sum.MaxCpu = max(sum.MaxCpu, stats.MaxCpu, stats.Cpu) + sum.MaxNetworkSent = max(sum.MaxNetworkSent, stats.MaxNetworkSent, stats.NetworkSent) + sum.MaxNetworkRecv = max(sum.MaxNetworkRecv, stats.MaxNetworkRecv, stats.NetworkRecv) + sum.MaxDiskReadPs = max(sum.MaxDiskReadPs, stats.MaxDiskReadPs, stats.DiskReadPs) + sum.MaxDiskWritePs = max(sum.MaxDiskWritePs, stats.MaxDiskWritePs, stats.DiskWritePs) // add temps to sum if stats.Temperatures != nil { tempCount++ @@ -198,29 +195,34 @@ func (rm *RecordManager) AverageSystemStats(records []*models.Record) system.Sta sum.ExtraFs[key].DiskUsed += value.DiskUsed sum.ExtraFs[key].DiskWritePs += value.DiskWritePs sum.ExtraFs[key].DiskReadPs += value.DiskReadPs + // peak values + sum.ExtraFs[key].MaxDiskReadPS = max(sum.ExtraFs[key].MaxDiskReadPS, value.MaxDiskReadPS, value.DiskReadPs) + sum.ExtraFs[key].MaxDiskWritePS = max(sum.ExtraFs[key].MaxDiskWritePS, value.MaxDiskWritePS, value.DiskWritePs) } } } stats = system.Stats{ - Cpu: twoDecimals(sum.Cpu / count), - PeakCpu: twoDecimals(peakCpu), - Mem: twoDecimals(sum.Mem / count), - MemUsed: twoDecimals(sum.MemUsed / count), - MemPct: twoDecimals(sum.MemPct / count), - MemBuffCache: twoDecimals(sum.MemBuffCache / count), - MemZfsArc: twoDecimals(sum.MemZfsArc / count), - Swap: twoDecimals(sum.Swap / count), - SwapUsed: twoDecimals(sum.SwapUsed / count), - DiskTotal: twoDecimals(sum.DiskTotal / count), - DiskUsed: twoDecimals(sum.DiskUsed / count), - DiskPct: twoDecimals(sum.DiskPct / count), - DiskReadPs: twoDecimals(sum.DiskReadPs / count), - DiskWritePs: twoDecimals(sum.DiskWritePs / count), - NetworkSent: twoDecimals(sum.NetworkSent / count), - PeakNetworkSent: twoDecimals(peakNs), - NetworkRecv: twoDecimals(sum.NetworkRecv / count), - PeakNetworkRecv: twoDecimals(peakNr), + Cpu: twoDecimals(sum.Cpu / count), + Mem: twoDecimals(sum.Mem / count), + MemUsed: twoDecimals(sum.MemUsed / count), + MemPct: twoDecimals(sum.MemPct / count), + MemBuffCache: twoDecimals(sum.MemBuffCache / count), + MemZfsArc: twoDecimals(sum.MemZfsArc / count), + Swap: twoDecimals(sum.Swap / count), + SwapUsed: twoDecimals(sum.SwapUsed / count), + DiskTotal: twoDecimals(sum.DiskTotal / count), + DiskUsed: twoDecimals(sum.DiskUsed / count), + DiskPct: twoDecimals(sum.DiskPct / count), + DiskReadPs: twoDecimals(sum.DiskReadPs / count), + DiskWritePs: twoDecimals(sum.DiskWritePs / count), + NetworkSent: twoDecimals(sum.NetworkSent / count), + NetworkRecv: twoDecimals(sum.NetworkRecv / count), + MaxCpu: sum.MaxCpu, + MaxDiskReadPs: sum.MaxDiskReadPs, + MaxDiskWritePs: sum.MaxDiskWritePs, + MaxNetworkSent: sum.MaxNetworkSent, + MaxNetworkRecv: sum.MaxNetworkRecv, } if len(sum.Temperatures) != 0 { @@ -234,10 +236,12 @@ func (rm *RecordManager) AverageSystemStats(records []*models.Record) system.Sta stats.ExtraFs = make(map[string]*system.FsStats) for key, value := range sum.ExtraFs { stats.ExtraFs[key] = &system.FsStats{ - DiskTotal: twoDecimals(value.DiskTotal / count), - DiskUsed: twoDecimals(value.DiskUsed / count), - DiskWritePs: twoDecimals(value.DiskWritePs / count), - DiskReadPs: twoDecimals(value.DiskReadPs / count), + DiskTotal: twoDecimals(value.DiskTotal / count), + DiskUsed: twoDecimals(value.DiskUsed / count), + DiskWritePs: twoDecimals(value.DiskWritePs / count), + DiskReadPs: twoDecimals(value.DiskReadPs / count), + MaxDiskReadPS: value.MaxDiskReadPS, + MaxDiskWritePS: value.MaxDiskWritePS, } } } diff --git a/beszel/site/src/components/charts/area-chart.tsx b/beszel/site/src/components/charts/area-chart.tsx new file mode 100644 index 0000000..b16ac0b --- /dev/null +++ b/beszel/site/src/components/charts/area-chart.tsx @@ -0,0 +1,131 @@ +import { Area, AreaChart, CartesianGrid, XAxis, YAxis } from 'recharts' + +import { ChartContainer, ChartTooltip, ChartTooltipContent } from '@/components/ui/chart' +import { + useYAxisWidth, + chartTimeData, + cn, + formatShortDate, + toFixedWithoutTrailingZeros, + twoDecimalString, + chartMargin, +} from '@/lib/utils' +// import Spinner from '../spinner' +import { ChartTimes, SystemStatsRecord } from '@/types' +import { useMemo } from 'react' + +/** [label, key, color, opacity] */ +type DataKeys = [string, string, number, number] + +const getNestedValue = (path: string, max = false, data: any): number | null => { + // fallback value (obj?.stats?.cpum ? 0 : null) should only come into play when viewing + // a max value which doesn't exist, or the value was zero and omitted from the stats object. + // so we check if cpum is present. if so, return 0 to make sure the zero value is displayed. + // if not, return null - there is no max data so do not display anything. + return `stats.${path}${max ? 'm' : ''}` + .split('.') + .reduce((acc: any, key: string) => acc?.[key] ?? (data.stats?.cpum ? 0 : null), data) +} + +export default function AreaChartDefault({ + ticks, + systemData, + showMax = false, + unit = ' MB/s', + chartName, + chartTime, +}: { + ticks: number[] + systemData: SystemStatsRecord[] + showMax?: boolean + unit?: string + chartName: string + chartTime: ChartTimes +}) { + const { yAxisWidth, updateYAxisWidth } = useYAxisWidth() + + const dataKeys: DataKeys[] = useMemo(() => { + // [label, key, color, opacity] + if (chartName === 'CPU Usage') { + return [[chartName, 'cpu', 1, 0.4]] + } else if (chartName === 'dio') { + return [ + ['Write', 'dw', 3, 0.3], + ['Read', 'dr', 1, 0.3], + ] + } else if (chartName === 'bw') { + return [ + ['Sent', 'ns', 5, 0.2], + ['Received', 'nr', 2, 0.2], + ] + } else if (chartName.startsWith('efs')) { + return [ + ['Write', `${chartName}.w`, 3, 0.3], + ['Read', `${chartName}.r`, 1, 0.3], + ] + } + return [] + }, []) + + return ( +
+ + + + { + const val = toFixedWithoutTrailingZeros(value, 2) + unit + return updateYAxisWidth(val) + }} + tickLine={false} + axisLine={false} + /> + + formatShortDate(data[0].payload.created)} + contentFormatter={(item) => twoDecimalString(item.value) + unit} + indicator="line" + /> + } + /> + {dataKeys.map((key, i) => { + const color = `hsl(var(--chart-${key[2]}))` + return ( + + ) + })} + {/* } /> */} + + +
+ ) +} diff --git a/beszel/site/src/components/charts/bandwidth-chart.tsx b/beszel/site/src/components/charts/bandwidth-chart.tsx deleted file mode 100644 index 1a4c6e2..0000000 --- a/beszel/site/src/components/charts/bandwidth-chart.tsx +++ /dev/null @@ -1,105 +0,0 @@ -import { Area, AreaChart, CartesianGrid, XAxis, YAxis } from 'recharts' - -import { ChartContainer, ChartTooltip, ChartTooltipContent } from '@/components/ui/chart' -import { - useYAxisWidth, - chartTimeData, - cn, - formatShortDate, - toFixedWithoutTrailingZeros, - twoDecimalString, -} from '@/lib/utils' -// import Spinner from '../spinner' -import { useStore } from '@nanostores/react' -import { $chartTime } from '@/lib/stores' -import { SystemStatsRecord } from '@/types' - -export default function BandwidthChart({ - ticks, - systemData, -}: { - ticks: number[] - systemData: SystemStatsRecord[] -}) { - const chartTime = useStore($chartTime) - const { yAxisWidth, updateYAxisWidth } = useYAxisWidth() - - return ( -
- {/* {!yAxisSet && } */} - - - - (max <= 0.4 ? 0.4 : Math.ceil(max))]} - tickFormatter={(value) => { - const val = toFixedWithoutTrailingZeros(value, 2) + ' MB/s' - return updateYAxisWidth(val) - }} - tickLine={false} - axisLine={false} - // unit={' MB/s'} - /> - - formatShortDate(data[0].payload.created)} - contentFormatter={(item) => twoDecimalString(item.value) + ' MB/s'} - indicator="line" - /> - } - /> - - - - -
- ) -} diff --git a/beszel/site/src/components/charts/container-cpu-chart.tsx b/beszel/site/src/components/charts/container-cpu-chart.tsx index 177f066..311a75b 100644 --- a/beszel/site/src/components/charts/container-cpu-chart.tsx +++ b/beszel/site/src/components/charts/container-cpu-chart.tsx @@ -6,7 +6,14 @@ import { ChartTooltipContent, } from '@/components/ui/chart' import { useMemo } from 'react' -import { useYAxisWidth, chartTimeData, cn, formatShortDate, twoDecimalString } from '@/lib/utils' +import { + useYAxisWidth, + chartTimeData, + cn, + formatShortDate, + twoDecimalString, + chartMargin, +} from '@/lib/utils' // import Spinner from '../spinner' import { useStore } from '@nanostores/react' import { $chartTime, $containerFilter } from '@/lib/stores' @@ -65,7 +72,6 @@ export default function ContainerCpuChart({
{/* {!yAxisSet && } */} diff --git a/beszel/site/src/components/charts/container-mem-chart.tsx b/beszel/site/src/components/charts/container-mem-chart.tsx index 8258adb..d345b7c 100644 --- a/beszel/site/src/components/charts/container-mem-chart.tsx +++ b/beszel/site/src/components/charts/container-mem-chart.tsx @@ -13,6 +13,7 @@ import { formatShortDate, toFixedWithoutTrailingZeros, twoDecimalString, + chartMargin, } from '@/lib/utils' // import Spinner from '../spinner' import { useStore } from '@nanostores/react' @@ -72,7 +73,6 @@ export default function ContainerMemChart({
{/* {!yAxisSet && } */} - // } - return (
{/* {!yAxisSet && } */} diff --git a/beszel/site/src/components/charts/cpu-chart.tsx b/beszel/site/src/components/charts/cpu-chart.tsx index a55f55f..766458d 100644 --- a/beszel/site/src/components/charts/cpu-chart.tsx +++ b/beszel/site/src/components/charts/cpu-chart.tsx @@ -1,11 +1,19 @@ import { Area, AreaChart, CartesianGrid, XAxis, YAxis } from 'recharts' import { ChartContainer, ChartTooltip, ChartTooltipContent } from '@/components/ui/chart' -import { useYAxisWidth, chartTimeData, cn, formatShortDate, twoDecimalString } from '@/lib/utils' +import { + useYAxisWidth, + chartTimeData, + cn, + formatShortDate, + twoDecimalString, + chartMargin, +} from '@/lib/utils' // import Spinner from '../spinner' import { useStore } from '@nanostores/react' -import { $chartTime } from '@/lib/stores' +import { $chartTime, $cpuMax } from '@/lib/stores' import { SystemStatsRecord } from '@/types' +import { useMemo } from 'react' export default function CpuChart({ ticks, @@ -16,11 +24,16 @@ export default function CpuChart({ }) { const chartTime = useStore($chartTime) const { yAxisWidth, updateYAxisWidth } = useYAxisWidth() + const showMax = useStore($cpuMax) + + const dataKey = useMemo( + () => `stats.cpu${showMax && chartTime !== '1h' ? 'm' : ''}`, + [showMax, systemData] + ) return (
@@ -63,16 +76,13 @@ export default function CpuChart({ } /> diff --git a/beszel/site/src/components/charts/disk-chart.tsx b/beszel/site/src/components/charts/disk-chart.tsx index 3b234d0..cb8bd9d 100644 --- a/beszel/site/src/components/charts/disk-chart.tsx +++ b/beszel/site/src/components/charts/disk-chart.tsx @@ -10,6 +10,7 @@ import { toFixedFloat, getSizeVal, getSizeUnit, + chartMargin, } from '@/lib/utils' // import { useMemo } from 'react' // import Spinner from '../spinner' @@ -35,21 +36,11 @@ export default function DiskChart({
{/* {!yAxisSet && } */} - + - {/* {!yAxisSet && } */} - - - - (max <= 0.4 ? 0.4 : Math.ceil(max))]} - tickFormatter={(value) => { - const val = toFixedWithoutTrailingZeros(value, 2) + ' MB/s' - return updateYAxisWidth(val) - }} - tickLine={false} - axisLine={false} - /> - - formatShortDate(data[0].payload.created)} - contentFormatter={(item) => twoDecimalString(item.value) + ' MB/s'} - indicator="line" - /> - } - /> - {dataKeys.map((dataKey, i) => { - const action = i ? 'Read' : 'Write' - const color = i ? 'hsl(var(--chart-1))' : 'hsl(var(--chart-3))' - return ( - - ) - })} - - -
- ) -} diff --git a/beszel/site/src/components/charts/mem-chart.tsx b/beszel/site/src/components/charts/mem-chart.tsx index cbb9b12..ab6bd85 100644 --- a/beszel/site/src/components/charts/mem-chart.tsx +++ b/beszel/site/src/components/charts/mem-chart.tsx @@ -8,6 +8,7 @@ import { toFixedFloat, twoDecimalString, formatShortDate, + chartMargin, } from '@/lib/utils' import { useMemo } from 'react' import { useStore } from '@nanostores/react' @@ -32,18 +33,11 @@ export default function MemChart({
{/* {!yAxisSet && } */} - + {totalMem && ( - + {/* {!yAxisSet && } */} - + import('../charts/cpu-chart')) 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 DiskIoChart = lazy(() => import('../charts/disk-io-chart')) -const BandwidthChart = lazy(() => import('../charts/bandwidth-chart')) +const AreaChartDefault = lazy(() => import('../charts/area-chart')) const ContainerNetChart = lazy(() => import('../charts/container-net-chart')) const SwapChart = lazy(() => import('../charts/swap-chart')) const TemperatureChart = lazy(() => import('../charts/temperature-chart')) @@ -29,11 +28,16 @@ const TemperatureChart = lazy(() => import('../charts/temperature-chart')) export default function SystemDetail({ name }: { name: string }) { const systems = useStore($systems) const chartTime = useStore($chartTime) + /** Max CPU toggle value */ + const cpuMaxStore = useState(false) + 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 netCardRef = useRef(null) + const [containerFilterBar, setContainerFilterBar] = useState(null as null | JSX.Element) const [dockerCpuChartData, setDockerCpuChartData] = useState[]>( [] ) @@ -43,15 +47,18 @@ export default function SystemDetail({ name }: { name: string }) { const [dockerNetChartData, setDockerNetChartData] = useState[]>( [] ) - const hasDockerStats = dockerCpuChartData.length > 0 + const isLongerChart = chartTime !== '1h' useEffect(() => { document.title = `${name} / Beszel` return () => { resetCharts() $chartTime.set($userSettings.get().chartTime) + setContainerFilterBar(null) $containerFilter.set('') - // setHasDocker(false) + cpuMaxStore[1](false) + bandwidthMaxStore[1](false) + diskIoMaxStore[1](false) } }, [name]) @@ -133,12 +140,15 @@ export default function SystemDetail({ name }: { name: string }) { getStats('container_stats'), ]).then(([systemStats, containerStats]) => { const expectedInterval = chartTimeData[chartTime].expectedInterval - if (containerStats.status === 'fulfilled' && containerStats.value.length) { - makeContainerData(addEmptyValues(containerStats.value, expectedInterval)) - } if (systemStats.status === 'fulfilled') { setSystemStats(addEmptyValues(systemStats.value, expectedInterval)) } + if (containerStats.status === 'fulfilled' && containerStats.value.length) { + !containerFilterBar && setContainerFilterBar() + makeContainerData(addEmptyValues(containerStats.value, expectedInterval)) + } else { + setContainerFilterBar(null) + } }) }, [system, chartTime]) @@ -149,7 +159,10 @@ export default function SystemDetail({ name }: { name: string }) { const now = new Date() const startTime = chartTimeData[chartTime].getOffset(now) const scale = scaleTime([startTime.getTime(), now], [0, systemStats.length]) - setTicks(scale.ticks(chartTimeData[chartTime].ticks).map((d) => d.getTime())) + 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 @@ -192,7 +205,7 @@ export default function SystemDetail({ name }: { name: string }) { let uptime: number | string = system.info.u if (system.info.u < 172800) { const hours = Math.trunc(uptime / 3600) - uptime = `${hours} hour${hours > 1 ? 's' : ''}` + uptime = `${hours} hour${hours == 1 ? '' : 's'}` } else { uptime = `${Math.trunc(system.info?.u / 86400)} days` } @@ -239,7 +252,7 @@ export default function SystemDetail({ name }: { name: string }) { return ( <> -
+
{/* system info */}
@@ -324,17 +337,27 @@ export default function SystemDetail({ name }: { name: string }) { : null} > - + - {hasDockerStats && ( + {containerFilterBar && ( @@ -348,12 +371,12 @@ export default function SystemDetail({ name }: { name: string }) { - {hasDockerStats && ( + {containerFilterBar && ( @@ -368,23 +391,37 @@ export default function SystemDetail({ name }: { name: string }) { /> - - : null} + > + : null} description="Network traffic of public interfaces" > - + - {hasDockerStats && dockerNetChartData.length > 0 && ( + {containerFilterBar && dockerNetChartData.length > 0 && (
@@ -436,11 +473,14 @@ export default function SystemDetail({ name }: { name: string }) { grid={grid} title={`${extraFsName} I/O`} description={`Throughput of ${extraFsName}`} + cornerEl={isLongerChart ? : null} > -
@@ -461,10 +501,10 @@ function ContainerFilterBar() { const handleChange = useCallback((e: React.ChangeEvent) => { $containerFilter.set(e.target.value) - }, []) // Use an empty dependency array to prevent re-creation + }, []) return ( -
+ <> )} -
+ + ) +} + +function SelectAvgMax({ + store, +}: { + store: [boolean, React.Dispatch>] +}) { + const [max, setMax] = store + const Icon = max ? ChartMax : ChartAverage + + return ( + ) } @@ -492,13 +558,13 @@ function ChartCard({ description, children, grid, - isContainerChart, + cornerEl, }: { title: string description: string children: React.ReactNode grid?: boolean - isContainerChart?: boolean + cornerEl?: JSX.Element | null }) { const { isIntersecting, ref } = useIntersectionObserver() @@ -510,12 +576,16 @@ function ChartCard({ {title} {description} - {isContainerChart && } + {cornerEl && ( +
+ {cornerEl} +
+ )}
- +
{} {isIntersecting && {children}} - +
) } diff --git a/beszel/site/src/components/ui/chart.tsx b/beszel/site/src/components/ui/chart.tsx index db94800..1e94248 100644 --- a/beszel/site/src/components/ui/chart.tsx +++ b/beszel/site/src/components/ui/chart.tsx @@ -16,77 +16,77 @@ export type ChartConfig = { ) } -type ChartContextProps = { - config: ChartConfig -} +// type ChartContextProps = { +// config: ChartConfig +// } -const ChartContext = React.createContext(null) +// const ChartContext = React.createContext(null) -function useChart() { - const context = React.useContext(ChartContext) +// function useChart() { +// const context = React.useContext(ChartContext) - if (!context) { - throw new Error('useChart must be used within a ') - } +// if (!context) { +// throw new Error('useChart must be used within a ') +// } - return context -} +// return context +// } const ChartContainer = React.forwardRef< HTMLDivElement, React.ComponentProps<'div'> & { - config: ChartConfig + // config: ChartConfig children: React.ComponentProps['children'] } ->(({ id, className, children, config, ...props }, ref) => { +>(({ id, className, children, ...props }, ref) => { const uniqueId = React.useId() const chartId = `chart-${id || uniqueId.replace(/:/g, '')}` return ( - -
- - {children} -
-
+ // +
+ {/* */} + {children} +
+ //
) }) ChartContainer.displayName = 'Chart' -const ChartStyle = ({ id, config }: { id: string; config: ChartConfig }) => { - const colorConfig = Object.entries(config).filter(([_, config]) => config.theme || config.color) +// const ChartStyle = ({ id, config }: { id: string; config: ChartConfig }) => { +// const colorConfig = Object.entries(config).filter(([_, config]) => config.theme || config.color) - if (!colorConfig.length) { - return null - } +// if (!colorConfig.length) { +// return null +// } - return ( -