import { $updatedSystem, $systems, pb, $chartTime } from '@/lib/stores' import { ContainerStatsRecord, SystemRecord, SystemStatsRecord } from '@/types' import { Suspense, lazy, useCallback, useEffect, useMemo, 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 { Separator } from '../ui/separator' import { scaleTime } from 'd3-scale' import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '../ui/tooltip' const CpuChart = lazy(() => 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')) export default function ServerDetail({ name }: { name: string }) { const servers = useStore($systems) const updatedSystem = useStore($updatedSystem) const chartTime = useStore($chartTime) const [ticks, setTicks] = useState([] as number[]) const [server, setServer] = useState({} as SystemRecord) const [containers, setContainers] = useState([] as ContainerStatsRecord[]) const [serverStats, setServerStats] = useState([] as SystemStatsRecord[]) const [cpuChartData, setCpuChartData] = useState([] as { time: number; cpu: number }[]) const [memChartData, setMemChartData] = useState( [] as { time: number; mem: number; memUsed: number; memCache: number }[] ) const [diskChartData, setDiskChartData] = useState( [] as { time: number; disk: number; diskUsed: number }[] ) const [diskIoChartData, setDiskIoChartData] = useState( [] as { time: number; read: number; write: number }[] ) const [bandwidthChartData, setBandwidthChartData] = useState( [] as { time: number; sent: number; recv: number }[] ) const [dockerCpuChartData, setDockerCpuChartData] = useState( [] as Record[] ) const [dockerMemChartData, setDockerMemChartData] = useState( [] as Record[] ) useEffect(() => { document.title = `${name} / Beszel` return () => { resetCharts() $chartTime.set('1h') } }, [name]) const resetCharts = useCallback(() => { setServerStats([]) setCpuChartData([]) setMemChartData([]) setDiskChartData([]) setBandwidthChartData([]) setDockerCpuChartData([]) setDockerMemChartData([]) }, []) useEffect(resetCharts, [chartTime]) useEffect(() => { if (server.id && server.name === name) { return } const matchingServer = servers.find((s) => s.name === name) as SystemRecord if (matchingServer) { setServer(matchingServer) } }, [name, server, servers]) // get stats useEffect(() => { if (!server.id || !chartTime) { return } pb.collection('system_stats') .getFullList({ filter: pb.filter('system={:id} && created > {:created} && type={:type}', { id: server.id, created: getPbTimestamp(chartTime), type: chartTimeData[chartTime].type, }), fields: 'created,stats', sort: 'created', }) .then((records) => { // console.log('sctats', records) setServerStats(records) }) }, [server, chartTime]) useEffect(() => { if (updatedSystem.id === server.id) { setServer(updatedSystem) } }, [updatedSystem]) // create cpu / mem / disk data for charts useEffect(() => { if (!serverStats.length) { return } const cpuData = [] as typeof cpuChartData const memData = [] as typeof memChartData const diskData = [] as typeof diskChartData const diskIoData = [] as typeof diskIoChartData const networkData = [] as typeof bandwidthChartData for (let { created, stats } of serverStats) { const time = new Date(created).getTime() cpuData.push({ time, cpu: stats.cpu }) memData.push({ time, mem: stats.m, memUsed: stats.mu, memCache: stats.mb, }) diskData.push({ time, disk: stats.d, diskUsed: stats.du }) diskIoData.push({ time, read: stats.dr, write: stats.dw }) networkData.push({ time, sent: stats.ns, recv: stats.nr }) } setCpuChartData(cpuData) setMemChartData(memData) setDiskChartData(diskData) setDiskIoChartData(diskIoData) setBandwidthChartData(networkData) }, [serverStats]) useEffect(() => { if (!serverStats.length) { return } const now = new Date() const startTime = chartTimeData[chartTime].getOffset(now) const scale = scaleTime([startTime.getTime(), now], [0, cpuChartData.length]) setTicks(scale.ticks().map((d) => d.getTime())) }, [chartTime, serverStats]) // get container stats useEffect(() => { if (!server.id || !chartTime) { return } pb.collection('container_stats') .getFullList({ filter: pb.filter('system={:id} && created > {:created} && type={:type}', { id: server.id, created: getPbTimestamp(chartTime), type: chartTimeData[chartTime].type, }), fields: 'created,stats', sort: 'created', }) .then((records) => { setContainers(records) }) }, [server, chartTime]) // container stats for charts useEffect(() => { // console.log('containers', containers) const dockerCpuData = [] as Record[] const dockerMemData = [] as Record[] 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] for (let container of stats) { cpuData[container.n] = container.c memData[container.n] = container.m } dockerCpuData.push(cpuData) dockerMemData.push(memData) } // console.log('containerMemData', containerMemData) setDockerCpuChartData(dockerCpuData) setDockerMemChartData(dockerMemData) }, [containers]) const uptime = useMemo(() => { let uptime = server.info?.u || 0 if (uptime < 172800) { return `${Math.trunc(uptime / 3600)} hours` } return `${Math.trunc(server.info?.u / 86400)} days` }, [server.info?.u]) if (!server.id) { return null } return (

{server.name}

{server.status === 'up' && ( )} {server.status}
{server.host}
{server.info?.u && (
{uptime}
Uptime
)} {server.info?.m && ( <>
{server.info.m} ({server.info.c}c / {server.info.t}t)
)}
{dockerCpuChartData.length > 0 && ( )} {dockerMemChartData.length > 0 && ( )}
) } function ChartCard({ title, description, children, }: { title: string description: string children: React.ReactNode }) { return ( {title} {description} }>{children} ) }