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 (
-