diff --git a/hub/site/bun.lockb b/hub/site/bun.lockb
index 0111c6b..6588bda 100755
Binary files a/hub/site/bun.lockb and b/hub/site/bun.lockb differ
diff --git a/hub/site/package.json b/hub/site/package.json
index 2b6defb..0c354ca 100644
--- a/hub/site/package.json
+++ b/hub/site/package.json
@@ -37,6 +37,7 @@
"recharts": "^2.13.0-alpha.4",
"tailwind-merge": "^2.4.0",
"tailwindcss-animate": "^1.0.7",
+ "use-is-in-viewport": "^1.0.9",
"valibot": "^0.36.0"
},
"devDependencies": {
diff --git a/hub/site/src/components/charts/bandwidth-chart.tsx b/hub/site/src/components/charts/bandwidth-chart.tsx
index bcf430f..9eda231 100644
--- a/hub/site/src/components/charts/bandwidth-chart.tsx
+++ b/hub/site/src/components/charts/bandwidth-chart.tsx
@@ -1,12 +1,12 @@
import { Area, AreaChart, CartesianGrid, XAxis, YAxis } from 'recharts'
import { ChartContainer, ChartTooltip, ChartTooltipContent } from '@/components/ui/chart'
-import { chartTimeData, formatShortDate, useYaxisWidth } from '@/lib/utils'
-import Spinner from '../spinner'
+import { chartTimeData, cn, formatShortDate, useYaxisWidth } from '@/lib/utils'
+// import Spinner from '../spinner'
import { useStore } from '@nanostores/react'
import { $chartTime } from '@/lib/stores'
import { SystemStatsRecord } from '@/types'
-import { useRef } from 'react'
+import { useMemo, useRef } from 'react'
export default function BandwidthChart({
ticks,
@@ -19,13 +19,17 @@ export default function BandwidthChart({
const yAxisWidth = useYaxisWidth(chartRef)
const chartTime = useStore($chartTime)
- if (!systemData.length || !ticks.length) {
- return
- }
+ const yAxisSet = useMemo(() => yAxisWidth !== 180, [yAxisWidth])
return (
-
+ {/* {!yAxisSet && } */}
+
diff --git a/hub/site/src/components/charts/container-cpu-chart.tsx b/hub/site/src/components/charts/container-cpu-chart.tsx
index b162145..5e15c09 100644
--- a/hub/site/src/components/charts/container-cpu-chart.tsx
+++ b/hub/site/src/components/charts/container-cpu-chart.tsx
@@ -6,8 +6,8 @@ import {
ChartTooltipContent,
} from '@/components/ui/chart'
import { useMemo, useRef } from 'react'
-import { chartTimeData, formatShortDate, useYaxisWidth } from '@/lib/utils'
-import Spinner from '../spinner'
+import { chartTimeData, cn, formatShortDate, useYaxisWidth } from '@/lib/utils'
+// import Spinner from '../spinner'
import { useStore } from '@nanostores/react'
import { $chartTime } from '@/lib/stores'
@@ -22,6 +22,8 @@ export default function ContainerCpuChart({
const yAxisWidth = useYaxisWidth(chartRef)
const chartTime = useStore($chartTime)
+ const yAxisSet = useMemo(() => yAxisWidth !== 180, [yAxisWidth])
+
const chartConfig = useMemo(() => {
let config = {} as Record<
string,
@@ -57,13 +59,19 @@ export default function ContainerCpuChart({
return config satisfies ChartConfig
}, [chartData])
- if (!chartData.length || !ticks.length) {
- return
- }
+ // if (!chartData.length || !ticks.length) {
+ // return
+ // }
return (
-
+ {/* {!yAxisSet && } */}
+
[]
ticks: number[]
}) {
+ const chartTime = useStore($chartTime)
const chartRef = useRef(null)
const yAxisWidth = useYaxisWidth(chartRef)
- const chartTime = useStore($chartTime)
+
+ const yAxisSet = useMemo(() => yAxisWidth !== 180, [yAxisWidth])
const chartConfig = useMemo(() => {
let config = {} as Record<
@@ -57,13 +59,19 @@ export default function ContainerMemChart({
return config satisfies ChartConfig
}, [chartData])
- if (!chartData.length || !ticks.length) {
- return
- }
+ // if (!chartData.length || !ticks.length) {
+ // return
+ // }
return (
-
+ {/* {!yAxisSet && } */}
+
(
[]
ticks: number[]
}) {
+ const chartTime = useStore($chartTime)
const chartRef = useRef(null)
const yAxisWidth = useYaxisWidth(chartRef)
- const chartTime = useStore($chartTime)
+
+ const yAxisSet = useMemo(() => yAxisWidth !== 180, [yAxisWidth])
const chartConfig = useMemo(() => {
let config = {} as Record<
@@ -57,13 +59,19 @@ export default function ContainerCpuChart({
return config satisfies ChartConfig
}, [chartData])
- if (!chartData.length || !ticks.length) {
- return
- }
+ // if (!chartData.length || !ticks.length) {
+ // return
+ // }
return (
-
+ {/* {!yAxisSet && } */}
+
data?.[key]?.[2] ?? 0}
type="monotoneX"
fill={chartConfig[key].color}
diff --git a/hub/site/src/components/charts/cpu-chart.tsx b/hub/site/src/components/charts/cpu-chart.tsx
index 0d1f1bb..5edaf40 100644
--- a/hub/site/src/components/charts/cpu-chart.tsx
+++ b/hub/site/src/components/charts/cpu-chart.tsx
@@ -1,12 +1,12 @@
import { Area, AreaChart, CartesianGrid, XAxis, YAxis } from 'recharts'
import { ChartContainer, ChartTooltip, ChartTooltipContent } from '@/components/ui/chart'
-import { chartTimeData, formatShortDate, useYaxisWidth } from '@/lib/utils'
-import Spinner from '../spinner'
+import { chartTimeData, cn, formatShortDate, useYaxisWidth } from '@/lib/utils'
+// import Spinner from '../spinner'
import { useStore } from '@nanostores/react'
import { $chartTime } from '@/lib/stores'
import { SystemStatsRecord } from '@/types'
-import { useRef } from 'react'
+import { useMemo, useRef } from 'react'
export default function CpuChart({
ticks,
@@ -15,17 +15,24 @@ export default function CpuChart({
ticks: number[]
systemData: SystemStatsRecord[]
}) {
+ const chartTime = useStore($chartTime)
const chartRef = useRef(null)
const yAxisWidth = useYaxisWidth(chartRef)
- const chartTime = useStore($chartTime)
- if (!systemData.length || !ticks.length) {
- return
- }
+ const yAxisSet = useMemo(() => yAxisWidth !== 180, [yAxisWidth])
+
+ // if (!systemData.length || !ticks.length) {
+ // return
+ // }
return (
-
+
diff --git a/hub/site/src/components/charts/disk-chart.tsx b/hub/site/src/components/charts/disk-chart.tsx
index b488a02..430cff1 100644
--- a/hub/site/src/components/charts/disk-chart.tsx
+++ b/hub/site/src/components/charts/disk-chart.tsx
@@ -1,9 +1,9 @@
import { Area, AreaChart, CartesianGrid, XAxis, YAxis } from 'recharts'
import { ChartContainer, ChartTooltip, ChartTooltipContent } from '@/components/ui/chart'
-import { chartTimeData, formatShortDate, useYaxisWidth } from '@/lib/utils'
+import { chartTimeData, cn, formatShortDate, useYaxisWidth } from '@/lib/utils'
import { useMemo, useRef } from 'react'
-import Spinner from '../spinner'
+// import Spinner from '../spinner'
import { useStore } from '@nanostores/react'
import { $chartTime } from '@/lib/stores'
import { SystemStatsRecord } from '@/types'
@@ -15,9 +15,11 @@ export default function DiskChart({
ticks: number[]
systemData: SystemStatsRecord[]
}) {
+ const chartTime = useStore($chartTime)
const chartRef = useRef(null)
const yAxisWidth = useYaxisWidth(chartRef)
- const chartTime = useStore($chartTime)
+
+ const yAxisSet = useMemo(() => yAxisWidth !== 180, [yAxisWidth])
const diskSize = useMemo(() => {
return Math.round(systemData[0]?.stats.d)
@@ -32,13 +34,19 @@ export default function DiskChart({
// return ticks
// }, [diskSize])
- if (!systemData.length || !ticks.length) {
- return
- }
+ // if (!systemData.length || !ticks.length) {
+ // return
+ // }
return (
-
+ {/* {!yAxisSet && } */}
+
diff --git a/hub/site/src/components/charts/disk-io-chart.tsx b/hub/site/src/components/charts/disk-io-chart.tsx
index 813f0f6..d13bb25 100644
--- a/hub/site/src/components/charts/disk-io-chart.tsx
+++ b/hub/site/src/components/charts/disk-io-chart.tsx
@@ -1,12 +1,12 @@
import { Area, AreaChart, CartesianGrid, XAxis, YAxis } from 'recharts'
import { ChartContainer, ChartTooltip, ChartTooltipContent } from '@/components/ui/chart'
-import { chartTimeData, formatShortDate, useYaxisWidth } from '@/lib/utils'
-import Spinner from '../spinner'
+import { chartTimeData, cn, formatShortDate, useYaxisWidth } from '@/lib/utils'
+// import Spinner from '../spinner'
import { useStore } from '@nanostores/react'
import { $chartTime } from '@/lib/stores'
import { SystemStatsRecord } from '@/types'
-import { useRef } from 'react'
+import { useMemo, useRef } from 'react'
export default function DiskIoChart({
ticks,
@@ -15,17 +15,25 @@ export default function DiskIoChart({
ticks: number[]
systemData: SystemStatsRecord[]
}) {
+ const chartTime = useStore($chartTime)
const chartRef = useRef(null)
const yAxisWidth = useYaxisWidth(chartRef)
- const chartTime = useStore($chartTime)
- if (!systemData.length || !ticks.length) {
- return
- }
+ const yAxisSet = useMemo(() => yAxisWidth !== 180, [yAxisWidth])
+
+ // if (!systemData.length || !ticks.length) {
+ // return
+ // }
return (
-
+ {/* {!yAxisSet && } */}
+
diff --git a/hub/site/src/components/charts/mem-chart.tsx b/hub/site/src/components/charts/mem-chart.tsx
index 0dbc477..f9c229a 100644
--- a/hub/site/src/components/charts/mem-chart.tsx
+++ b/hub/site/src/components/charts/mem-chart.tsx
@@ -1,9 +1,9 @@
import { Area, AreaChart, CartesianGrid, XAxis, YAxis } from 'recharts'
import { ChartContainer, ChartTooltip, ChartTooltipContent } from '@/components/ui/chart'
-import { chartTimeData, formatShortDate, useYaxisWidth } from '@/lib/utils'
+import { chartTimeData, cn, formatShortDate, useYaxisWidth } from '@/lib/utils'
import { useMemo, useRef } from 'react'
-import Spinner from '../spinner'
+// import Spinner from '../spinner'
import { useStore } from '@nanostores/react'
import { $chartTime } from '@/lib/stores'
import { SystemStatsRecord } from '@/types'
@@ -19,18 +19,26 @@ export default function MemChart({
const chartRef = useRef(null)
const yAxisWidth = useYaxisWidth(chartRef)
+ const yAxisSet = useMemo(() => yAxisWidth !== 180, [yAxisWidth])
+
const totalMem = useMemo(() => {
const maxMem = Math.ceil(systemData[0]?.stats.m)
return maxMem > 2 && maxMem % 2 !== 0 ? maxMem + 1 : maxMem
}, [systemData])
- if (!systemData.length || !ticks.length) {
- return
- }
+ // if (!systemData.length || !ticks.length) {
+ // return
+ // }
return (
-
+ {/* {!yAxisSet && } */}
+
diff --git a/hub/site/src/components/routes/system.tsx b/hub/site/src/components/routes/system.tsx
index d5a84dc..77bae84 100644
--- a/hub/site/src/components/routes/system.tsx
+++ b/hub/site/src/components/routes/system.tsx
@@ -1,12 +1,12 @@
import { $updatedSystem, $systems, pb, $chartTime } from '@/lib/stores'
import { ContainerStatsRecord, SystemRecord, SystemStatsRecord } from '@/types'
-import { Suspense, lazy, useCallback, useEffect, useMemo, useState } from 'react'
+import { Suspense, lazy, useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { Card, CardHeader, CardTitle, CardDescription, CardContent } from '../ui/card'
import { useStore } from '@nanostores/react'
import Spinner from '../spinner'
import { ClockArrowUp, CpuIcon, GlobeIcon } from 'lucide-react'
import ChartTimeSelect from '../charts/chart-time-select'
-import { chartTimeData, cn, getPbTimestamp } from '@/lib/utils'
+import { chartTimeData, cn, getPbTimestamp, useClampedIsInViewport } from '@/lib/utils'
import { Separator } from '../ui/separator'
import { scaleTime } from 'd3-scale'
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '../ui/tooltip'
@@ -27,15 +27,10 @@ export default function ServerDetail({ name }: { name: string }) {
const [ticks, setTicks] = useState([] as number[])
const [server, setServer] = useState({} as SystemRecord)
const [systemStats, setSystemStats] = useState([] as SystemStatsRecord[])
- const [dockerCpuChartData, setDockerCpuChartData] = useState(
- [] as Record[]
- )
- const [dockerMemChartData, setDockerMemChartData] = useState(
- [] as Record[]
- )
- const [dockerNetChartData, setDockerNetChartData] = useState(
- [] as Record[]
- )
+ const [dockerCpuChartData, setDockerCpuChartData] = useState[]>()
+ const [dockerMemChartData, setDockerMemChartData] = useState[]>()
+ const [dockerNetChartData, setDockerNetChartData] =
+ useState[]>()
useEffect(() => {
document.title = `${name} / Beszel`
@@ -47,9 +42,9 @@ export default function ServerDetail({ name }: { name: string }) {
const resetCharts = useCallback(() => {
setSystemStats([])
- setDockerCpuChartData([])
- setDockerMemChartData([])
- setDockerNetChartData([])
+ setDockerCpuChartData(undefined)
+ setDockerMemChartData(undefined)
+ setDockerNetChartData(undefined)
}, [])
useEffect(resetCharts, [chartTime])
@@ -121,7 +116,9 @@ export default function ServerDetail({ name }: { name: string }) {
sort: 'created',
})
.then((records) => {
- makeContainerData(records)
+ if (records.length) {
+ makeContainerData(records)
+ }
// setContainers(records)
})
}, [server, chartTime])
@@ -129,15 +126,14 @@ export default function ServerDetail({ name }: { name: string }) {
// container stats for charts
const makeContainerData = useCallback((containers: ContainerStatsRecord[]) => {
// console.log('containers', containers)
- const dockerCpuData = [] as typeof dockerCpuChartData
- const dockerMemData = [] as typeof dockerMemChartData
- const dockerNetData = [] as typeof dockerNetChartData
-
+ const dockerCpuData = []
+ const dockerMemData = []
+ const dockerNetData = []
for (let { created, stats } of containers) {
const time = new Date(created).getTime()
- let cpuData = { time } as (typeof dockerCpuChartData)[0]
- let memData = { time } as (typeof dockerMemChartData)[0]
- let netData = { time } as (typeof dockerNetChartData)[0]
+ let cpuData = { time } as Record
+ let memData = { time } as Record
+ let netData = { time } as Record
for (let container of stats) {
cpuData[container.n] = container.c
memData[container.n] = container.m
@@ -225,7 +221,7 @@ export default function ServerDetail({ name }: { name: string }) {
- {dockerCpuChartData.length > 0 && (
+ {dockerCpuChartData && (
@@ -235,7 +231,7 @@ export default function ServerDetail({ name }: { name: string }) {
- {dockerMemChartData.length > 0 && (
+ {dockerMemChartData && (
@@ -256,7 +252,7 @@ export default function ServerDetail({ name }: { name: string }) {
- {dockerNetChartData.length > 0 && (
+ {dockerNetChartData && (
(null)
+ const [isInViewport, wrappedTargetRef] = useClampedIsInViewport({ target: target })
return (
-
+
{title}
{description}
@@ -287,7 +285,8 @@ function ChartCard({
- }>{children}
+ {}
+ {isInViewport && {children}}
)
diff --git a/hub/site/src/lib/utils.ts b/hub/site/src/lib/utils.ts
index 50e728e..6187cb4 100644
--- a/hub/site/src/lib/utils.ts
+++ b/hub/site/src/lib/utils.ts
@@ -7,6 +7,7 @@ import { RecordModel, RecordSubscription } from 'pocketbase'
import { WritableAtom } from 'nanostores'
import { timeDay, timeHour } from 'd3-time'
import { useEffect, useState } from 'react'
+import useIsInViewport, { CallbackRef, HookOptions } from 'use-is-in-viewport'
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs))
@@ -183,17 +184,36 @@ export const chartTimeData: ChartTimeData = {
/** Hacky solution to set the correct width of the yAxis in recharts */
export function useYaxisWidth(chartRef: React.RefObject) {
- const [yAxisWidth, setYAxisWidth] = useState(90)
+ const [yAxisWidth, setYAxisWidth] = useState(180)
useEffect(() => {
let interval = setInterval(() => {
// console.log('chartRef', chartRef.current)
const yAxisElement = chartRef?.current?.querySelector('.yAxis')
if (yAxisElement) {
- console.log('yAxisElement', yAxisElement)
+ // console.log('yAxisElement', yAxisElement)
setYAxisWidth(yAxisElement.getBoundingClientRect().width + 22)
clearInterval(interval)
}
- }, 16)
+ }, 0)
+ return () => clearInterval(interval)
}, [])
return yAxisWidth
}
+
+export function useClampedIsInViewport(options: HookOptions): [boolean | null, CallbackRef] {
+ const [isInViewport, wrappedTargetRef] = useIsInViewport(options)
+ const [wasInViewportAtleastOnce, setWasInViewportAtleastOnce] = useState(isInViewport)
+
+ useEffect(() => {
+ setWasInViewportAtleastOnce((prev) => {
+ // this will clamp it to the first true
+ // received from useIsInViewport
+ if (!prev) {
+ return isInViewport
+ }
+ return prev
+ })
+ }, [isInViewport])
+
+ return [wasInViewportAtleastOnce, wrappedTargetRef]
+}