Files
beszel/site/src/components/routes/server.tsx
Henry Dollman 0af3138ef7 updates
2024-07-13 23:51:22 -04:00

243 lines
8.0 KiB
TypeScript

import { $servers, pb } from '@/lib/stores'
import { ContainerStatsRecord, SystemRecord, SystemStatsRecord } from '@/types'
import { Suspense, lazy, useEffect, useState } from 'react'
import { Card, CardHeader, CardTitle, CardDescription, CardContent } from '../ui/card'
import { useStore } from '@nanostores/react'
import Spinner from '../spinner'
// import CpuChart from '../charts/cpu-chart'
// import MemChart from '../charts/mem-chart'
// import DiskChart from '../charts/disk-chart'
// import ContainerCpuChart from '../charts/container-cpu-chart'
// import ContainerMemChart from '../charts/container-mem-chart'
import { CpuIcon, MemoryStickIcon } from 'lucide-react'
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'))
function timestampToBrowserTime(timestamp: string) {
const date = new Date(timestamp)
return date.toLocaleString()
}
// function addColors(objects: Record<string, any>[]) {
// objects.forEach((obj, index) => {
// const hue = ((index * 360) / objects.length) % 360 // Distribute hues evenly
// obj.fill = `hsl(${hue}, 100%, 50%)` // Set fill to HSL color with full saturation and 50% lightness
// })
// }
export default function ServerDetail({ name }: { name: string }) {
const servers = useStore($servers)
const [server, setServer] = useState({} as SystemRecord)
const [containers, setContainers] = useState([] as ContainerStatsRecord[])
const [serverStats, setServerStats] = useState([] as SystemStatsRecord[])
const [cpuChartData, setCpuChartData] = useState([] as { time: string; cpu: number }[])
const [memChartData, setMemChartData] = useState(
[] as { time: string; mem: number; memUsed: number; memCache: number }[]
)
const [diskChartData, setDiskChartData] = useState(
[] as { time: string; disk: number; diskUsed: number }[]
)
const [containerCpuChartData, setContainerCpuChartData] = useState(
[] as Record<string, number | string>[]
)
const [containerMemChartData, setContainerMemChartData] = useState(
[] as Record<string, number | string>[]
)
useEffect(() => {
document.title = `${name} / Qoma`
return () => {
setContainerCpuChartData([])
setCpuChartData([])
setMemChartData([])
setDiskChartData([])
}
}, [name])
// get stats
useEffect(() => {
if (!('name' in server)) {
return
}
pb.collection<SystemStatsRecord>('system_stats')
.getList(1, 60, {
filter: `system="${server.id}"`,
fields: 'created,stats',
sort: '-created',
})
.then((records) => {
// console.log('sctats', records)
setServerStats(records.items)
})
}, [server, servers])
// get cpu data
useEffect(() => {
if (!serverStats.length) {
return
}
// let maxCpu = 0
const cpuData = [] as { time: string; cpu: number }[]
const memData = [] as { time: string; mem: number; memUsed: number; memCache: number }[]
const diskData = [] as { time: string; disk: number; diskUsed: number }[]
for (let { created, stats } of serverStats) {
cpuData.push({ time: created, cpu: stats.c })
// maxCpu = Math.max(maxCpu, stats.c)
memData.push({ time: created, mem: stats.m, memUsed: stats.mu, memCache: stats.mb })
diskData.push({ time: created, disk: stats.d, diskUsed: stats.du })
}
setCpuChartData(cpuData.reverse())
setMemChartData(memData.reverse())
setDiskChartData(diskData.reverse())
}, [serverStats])
useEffect(() => {
if ($servers.get().length === 0) {
// console.log('skipping')
return
}
// console.log('running')
const matchingServer = servers.find((s) => s.name === name) as SystemRecord
// console.log('found server', matchingServer)
setServer(matchingServer)
pb.collection<ContainerStatsRecord>('container_stats')
.getList(1, 60, {
filter: `system="${matchingServer.id}"`,
fields: 'created,stats',
sort: '-created',
})
.then((records) => {
// console.log('records', records)
setContainers(records.items)
})
}, [servers, name])
// container stats for charts
useEffect(() => {
// console.log('containers', containers)
const containerCpuData = [] as Record<string, number | string>[]
const containerMemData = [] as Record<string, number | string>[]
for (let { created, stats } of containers) {
let cpuData = { time: created } as Record<string, number | string>
let memData = { time: created } as Record<string, number | string>
for (let container of stats) {
cpuData[container.n] = container.c
memData[container.n] = container.m
}
containerCpuData.push(cpuData)
containerMemData.push(memData)
}
setContainerCpuChartData(containerCpuData.reverse())
setContainerMemChartData(containerMemData.reverse())
}, [containers])
return (
<>
<div className="grid gap-6 mb-10">
<div className="p-6">
<h1 className="text-3xl font-semibold">{name}</h1>
</div>
<Card className="pb-2">
<CardHeader>
<CardTitle className="flex gap-2 justify-between">
<span>Total CPU Usage</span>
<CpuIcon className="opacity-70" />
</CardTitle>
<CardDescription>
System-wide CPU utilization of the preceding one minute as a percentage
</CardDescription>
</CardHeader>
<CardContent className={'pl-1 w-[calc(100%-2em)] h-52 relative'}>
<Suspense fallback={<Spinner />}>
<CpuChart chartData={cpuChartData} />
</Suspense>
</CardContent>
</Card>
{containerCpuChartData.length > 0 && (
<Card className="pb-2">
<CardHeader>
<CardTitle className="flex gap-2 justify-between">
<span>Docker CPU Usage</span>
<CpuIcon className="opacity-70" />
</CardTitle>{' '}
<CardDescription>CPU utilization of docker containers</CardDescription>
</CardHeader>
<CardContent className={'pl-1 w-[calc(100%-2em)] h-52 relative'}>
<Suspense fallback={<Spinner />}>
<ContainerCpuChart chartData={containerCpuChartData} />
</Suspense>
</CardContent>
</Card>
)}
<Card className="pb-2">
<CardHeader>
<CardTitle>Total Memory Usage</CardTitle>
<CardDescription>Precise utilization at the recorded time</CardDescription>
</CardHeader>
<CardContent className={'pl-1 w-[calc(100%-2em)] h-52 relative'}>
<Suspense fallback={<Spinner />}>
<MemChart chartData={memChartData} />
</Suspense>
</CardContent>
</Card>
{containerMemChartData.length > 0 && (
<Card className="pb-2">
<CardHeader>
<CardTitle className="flex gap-2 justify-between">
<span>Docker Memory Usage</span>
<MemoryStickIcon className="opacity-70" />
</CardTitle>{' '}
<CardDescription>Memory usage of docker containers</CardDescription>
</CardHeader>
<CardContent className={'pl-1 w-[calc(100%-2em)] h-52 relative'}>
<Suspense fallback={<Spinner />}>
{server?.stats?.m && <ContainerMemChart chartData={containerMemChartData} />}
</Suspense>
</CardContent>
</Card>
)}
<Card className="pb-2">
<CardHeader>
<CardTitle>Disk Usage</CardTitle>
<CardDescription>Precise usage at the recorded time</CardDescription>
</CardHeader>
<CardContent className={'pl-1 w-[calc(100%-2em)] h-52 relative'}>
<Suspense fallback={<Spinner />}>
<DiskChart chartData={diskChartData} />
</Suspense>
</CardContent>
</Card>
</div>
<Card>
<CardHeader>
<CardTitle className={'mb-3'}>{server.name}</CardTitle>
<CardDescription>
{server.ip} - last updated: {timestampToBrowserTime(server.updated)}
</CardDescription>
</CardHeader>
<CardContent>
<pre>{JSON.stringify(server, null, 2)}</pre>
</CardContent>
</Card>
{/* <Card>
<CardHeader>
<CardTitle className={'mb-3'}>Containers</CardTitle>
</CardHeader>
<CardContent>
<pre>{JSON.stringify(containers, null, 2)}</pre>
</CardContent>
</Card> */}
</>
)
}