mirror of
https://github.com/fankes/beszel.git
synced 2025-10-19 01:39:34 +08:00
updates
This commit is contained in:
2
main.go
2
main.go
@@ -169,7 +169,7 @@ func main() {
|
||||
}
|
||||
|
||||
func serverUpdateTicker() {
|
||||
ticker := time.NewTicker(10 * time.Second)
|
||||
ticker := time.NewTicker(60 * time.Second)
|
||||
for range ticker.C {
|
||||
updateServers()
|
||||
}
|
||||
|
35
site/src/components/charts/chart-time-select.tsx
Normal file
35
site/src/components/charts/chart-time-select.tsx
Normal file
@@ -0,0 +1,35 @@
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from '@/components/ui/select'
|
||||
import { $chartTime } from '@/lib/stores'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { useStore } from '@nanostores/react'
|
||||
import { useEffect } from 'react'
|
||||
|
||||
export default function ChartTimeSelect({ className }: { className?: string }) {
|
||||
const chartTime = useStore($chartTime)
|
||||
|
||||
useEffect(() => {
|
||||
// todo make sure this doesn't cause multiple fetches on load
|
||||
return () => $chartTime.set('1h')
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<Select defaultValue="1h" value={chartTime} onValueChange={(value) => $chartTime.set(value)}>
|
||||
<SelectTrigger className={cn(className, 'w-40 px-5')}>
|
||||
<SelectValue placeholder="1h" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="1h">1 hour</SelectItem>
|
||||
<SelectItem value="12h">12 hours</SelectItem>
|
||||
<SelectItem value="24h">24 hours</SelectItem>
|
||||
<SelectItem value="1w">1 week</SelectItem>
|
||||
<SelectItem value="30d">30 days</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
)
|
||||
}
|
@@ -8,9 +8,6 @@ import {
|
||||
} from '@/components/ui/chart'
|
||||
import { formatShortDate, formatShortTime } from '@/lib/utils'
|
||||
import Spinner from '../spinner'
|
||||
// for (const data of chartData) {
|
||||
// data.month = formatDateShort(data.month)
|
||||
// }
|
||||
|
||||
const chartConfig = {
|
||||
cpu: {
|
||||
|
@@ -1,93 +0,0 @@
|
||||
import { TrendingUp } from 'lucide-react'
|
||||
import { Label, PolarRadiusAxis, RadialBar, RadialBarChart } from 'recharts'
|
||||
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardDescription,
|
||||
CardFooter,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from '@/components/ui/card'
|
||||
import {
|
||||
ChartConfig,
|
||||
ChartContainer,
|
||||
ChartTooltip,
|
||||
ChartTooltipContent,
|
||||
} from '@/components/ui/chart'
|
||||
const chartData = [{ month: 'january', desktop: 1260, mobile: 570 }]
|
||||
|
||||
const chartConfig = {
|
||||
mobile: {
|
||||
label: 'Mobile',
|
||||
color: 'hsl(var(--chart-2))',
|
||||
},
|
||||
} satisfies ChartConfig
|
||||
|
||||
export function RadialChart() {
|
||||
const totalVisitors = chartData[0].desktop + chartData[0].mobile
|
||||
|
||||
return (
|
||||
<Card className="flex flex-col">
|
||||
<CardHeader className="items-center pb-0">
|
||||
<CardTitle>Radial Chart - Stacked</CardTitle>
|
||||
<CardDescription>January - June 2024</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="flex flex-1 items-center pb-0">
|
||||
<ChartContainer config={chartConfig} className="mx-auto aspect-square w-full max-w-[250px]">
|
||||
<RadialBarChart data={chartData} endAngle={180} innerRadius={80} outerRadius={130}>
|
||||
<ChartTooltip cursor={false} content={<ChartTooltipContent hideLabel />} />
|
||||
<PolarRadiusAxis tick={false} tickLine={false} axisLine={false}>
|
||||
<Label
|
||||
content={({ viewBox }) => {
|
||||
if (viewBox && 'cx' in viewBox && 'cy' in viewBox) {
|
||||
return (
|
||||
<text x={viewBox.cx} y={viewBox.cy} textAnchor="middle">
|
||||
<tspan
|
||||
x={viewBox.cx}
|
||||
y={(viewBox.cy || 0) - 16}
|
||||
className="fill-foreground text-2xl font-bold"
|
||||
>
|
||||
{totalVisitors.toLocaleString()}
|
||||
</tspan>
|
||||
<tspan
|
||||
x={viewBox.cx}
|
||||
y={(viewBox.cy || 0) + 4}
|
||||
className="fill-muted-foreground"
|
||||
>
|
||||
Visitors
|
||||
</tspan>
|
||||
</text>
|
||||
)
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</PolarRadiusAxis>
|
||||
<RadialBar
|
||||
dataKey="desktop"
|
||||
stackId="a"
|
||||
cornerRadius={5}
|
||||
fill="var(--color-desktop)"
|
||||
className="stroke-transparent stroke-2"
|
||||
/>
|
||||
<RadialBar
|
||||
dataKey="mobile"
|
||||
fill="var(--color-mobile)"
|
||||
stackId="a"
|
||||
cornerRadius={5}
|
||||
className="stroke-transparent stroke-2"
|
||||
/>
|
||||
</RadialBarChart>
|
||||
</ChartContainer>
|
||||
</CardContent>
|
||||
<CardFooter className="flex-col gap-2 text-sm">
|
||||
<div className="flex items-center gap-2 font-medium leading-none">
|
||||
Trending up by 5.2% this month <TrendingUp className="h-4 w-4" />
|
||||
</div>
|
||||
<div className="leading-none text-muted-foreground">
|
||||
Showing total visitors for the last 6 months
|
||||
</div>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
)
|
||||
}
|
@@ -1,24 +1,13 @@
|
||||
import { $systems, pb } from '@/lib/stores'
|
||||
import { $updatedSystem, $systems, pb } from '@/lib/stores'
|
||||
import { ContainerStatsRecord, SystemRecord, SystemStatsRecord } from '@/types'
|
||||
import { Suspense, lazy, useEffect, useState } from 'react'
|
||||
import { Suspense, lazy, useEffect, useMemo, 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, ServerIcon } from 'lucide-react'
|
||||
import { RadialChart } from '../charts/radial'
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from '@/components/ui/select'
|
||||
import { Separator } from '@/components/ui/separator'
|
||||
import { ClockArrowUp, CpuIcon, GlobeIcon } from 'lucide-react'
|
||||
import ChartTimeSelect from '../charts/chart-time-select'
|
||||
import { cn, getPbTimestamp } from '@/lib/utils'
|
||||
import { Separator } from '../ui/separator'
|
||||
|
||||
const CpuChart = lazy(() => import('../charts/cpu-chart'))
|
||||
const ContainerCpuChart = lazy(() => import('../charts/container-cpu-chart'))
|
||||
@@ -31,15 +20,9 @@ function timestampToBrowserTime(timestamp: string) {
|
||||
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($systems)
|
||||
const updatedSystem = useStore($updatedSystem)
|
||||
const [server, setServer] = useState({} as SystemRecord)
|
||||
const [containers, setContainers] = useState([] as ContainerStatsRecord[])
|
||||
|
||||
@@ -59,38 +42,60 @@ export default function ServerDetail({ name }: { name: string }) {
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
document.title = `${name} / Qoma`
|
||||
return () => {
|
||||
setContainerCpuChartData([])
|
||||
setCpuChartData([])
|
||||
setMemChartData([])
|
||||
setDiskChartData([])
|
||||
document.title = `${name} / Beszel`
|
||||
if (server?.id && server.name === name) {
|
||||
return
|
||||
}
|
||||
}, [name])
|
||||
const matchingServer = servers.find((s) => s.name === name) as SystemRecord
|
||||
if (matchingServer) {
|
||||
setServer(matchingServer)
|
||||
}
|
||||
}, [name, server])
|
||||
|
||||
// if visiting directly, make sure server gets set when servers are loaded
|
||||
// useEffect(() => {
|
||||
// if (!('id' in server)) {
|
||||
// const matchingServer = servers.find((s) => s.name === name) as SystemRecord
|
||||
// if (matchingServer) {
|
||||
// console.log('setting server')
|
||||
// setServer(matchingServer)
|
||||
// }
|
||||
// }
|
||||
// }, [servers])
|
||||
|
||||
// get stats
|
||||
useEffect(() => {
|
||||
if (!('name' in server)) {
|
||||
if (!('id' in server)) {
|
||||
console.log('no id in server')
|
||||
return
|
||||
} else {
|
||||
console.log('id in server')
|
||||
}
|
||||
|
||||
pb.collection<SystemStatsRecord>('system_stats')
|
||||
.getList(1, 60, {
|
||||
filter: `system="${server.id}"`,
|
||||
.getFullList({
|
||||
filter: `system="${server.id}" && created > "${getPbTimestamp('1h')}"`,
|
||||
fields: 'created,stats',
|
||||
sort: '-created',
|
||||
})
|
||||
.then((records) => {
|
||||
// console.log('sctats', records)
|
||||
setServerStats(records.items)
|
||||
setServerStats(records)
|
||||
})
|
||||
}, [server, servers])
|
||||
}, [server])
|
||||
|
||||
useEffect(() => {
|
||||
if (updatedSystem.id === server.id) {
|
||||
setServer(updatedSystem)
|
||||
}
|
||||
}, [updatedSystem])
|
||||
|
||||
// get cpu data
|
||||
useEffect(() => {
|
||||
if (!serverStats.length) {
|
||||
return
|
||||
}
|
||||
|
||||
console.log('stats', serverStats)
|
||||
// let maxCpu = 0
|
||||
const cpuData = [] as { time: string; cpu: number }[]
|
||||
const memData = [] as { time: string; mem: number; memUsed: number; memCache: number }[]
|
||||
@@ -107,26 +112,17 @@ export default function ServerDetail({ name }: { name: string }) {
|
||||
}, [serverStats])
|
||||
|
||||
useEffect(() => {
|
||||
if ($systems.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}"`,
|
||||
.getFullList({
|
||||
filter: `system="${server.id}" && created > "${getPbTimestamp('1h')}"`,
|
||||
fields: 'created,stats',
|
||||
sort: '-created',
|
||||
})
|
||||
.then((records) => {
|
||||
// console.log('records', records)
|
||||
setContainers(records.items)
|
||||
// console.log('containers', records)
|
||||
setContainers(records)
|
||||
})
|
||||
}, [servers, name])
|
||||
}, [server])
|
||||
|
||||
// container stats for charts
|
||||
useEffect(() => {
|
||||
@@ -148,98 +144,85 @@ export default function ServerDetail({ name }: { name: string }) {
|
||||
setContainerCpuChartData(containerCpuData.reverse())
|
||||
setContainerMemChartData(containerMemData.reverse())
|
||||
}, [containers])
|
||||
const uptime = useMemo(() => {
|
||||
console.log('making uptime')
|
||||
let uptime = server.info?.u || 0
|
||||
if (uptime < 172800) {
|
||||
return `${Math.floor(uptime / 3600)} hours`
|
||||
}
|
||||
return `${Math.floor(server.info?.u / 86400)} days`
|
||||
}, [server.info?.u])
|
||||
|
||||
if (!('id' in server)) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="grid gap-6 mb-10 grid-cols-3">
|
||||
<Card className="col-span-full">
|
||||
<CardHeader>
|
||||
<CardTitle className="flex gap-2 items-center text-3xl">{name}</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="flex items-center justify-between gap-6">
|
||||
<p>{server.status}</p>
|
||||
<p>Uptime {(server.info?.u / 86400).toLocaleString()} days</p>
|
||||
<p>
|
||||
{server.info?.m} ({server.info?.c} cores / {server.info?.t} threads)
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<RadialChart />
|
||||
<RadialChart />
|
||||
<RadialChart />
|
||||
<div className="grid gap-5 mb-10">
|
||||
<Card>
|
||||
<div className="grid gap-1.5 px-6 pt-4 pb-5">
|
||||
<h1 className="text-[1.6rem] font-semibold">{server.name}</h1>
|
||||
<div className="flex flex-wrap items-center gap-3 text-sm opacity-90">
|
||||
<div className="capitalize flex gap-2 items-center">
|
||||
<span className={cn('relative flex h-3 w-3')}>
|
||||
{server.status === 'up' && (
|
||||
<span
|
||||
className="animate-ping absolute inline-flex h-full w-full rounded-full bg-green-400 opacity-75"
|
||||
style={{ animationDuration: '1.5s' }}
|
||||
></span>
|
||||
)}
|
||||
<span
|
||||
className={cn('relative inline-flex rounded-full h-3 w-3', {
|
||||
'bg-green-500': server.status === 'up',
|
||||
'bg-red-500': server.status === 'down',
|
||||
'bg-primary/40': server.status === 'paused',
|
||||
'bg-yellow-500': server.status === 'pending',
|
||||
})}
|
||||
></span>
|
||||
</span>
|
||||
{server.status}
|
||||
</div>
|
||||
<Separator orientation="vertical" className="h-4 bg-primary/30" />
|
||||
<div className="flex gap-1.5 items-center">
|
||||
<GlobeIcon className="h-4 w-4" /> {server.host}
|
||||
</div>
|
||||
<Separator orientation="vertical" className="h-4 bg-primary/30" />
|
||||
<div className="flex gap-1.5 items-center">
|
||||
<ClockArrowUp className="h-4 w-4" /> {uptime}
|
||||
</div>
|
||||
<Separator orientation="vertical" className="h-4 bg-primary/30" />
|
||||
<div className="flex gap-1.5 items-center">
|
||||
<CpuIcon className="h-4 w-4" />
|
||||
{server.info?.m} ({server.info?.c}c / {server.info.t}t)
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
<Card className="pb-3 col-span-full">
|
||||
<CardHeader className="pb-5">
|
||||
<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-3 col-span-full">
|
||||
<CardHeader className="pb-5">
|
||||
<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-3 col-span-full">
|
||||
<CardHeader className="pb-5">
|
||||
<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-3 col-span-full">
|
||||
<CardHeader className="pb-5">
|
||||
<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 />}>
|
||||
<ContainerMemChart chartData={containerMemChartData} />
|
||||
</Suspense>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)}
|
||||
<Card className="pb-3 col-span-full">
|
||||
<CardHeader className="pb-5">
|
||||
<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>
|
||||
<ChartCard
|
||||
title="Total CPU Usage"
|
||||
description="Average system-wide CPU utilization as a percentage"
|
||||
>
|
||||
<CpuChart chartData={cpuChartData} />
|
||||
</ChartCard>
|
||||
|
||||
{containerCpuChartData.length > 0 && (
|
||||
<ChartCard title="Docker CPU Usage" description="CPU utilization of docker containers">
|
||||
<ContainerCpuChart chartData={containerCpuChartData} />
|
||||
</ChartCard>
|
||||
)}
|
||||
<ChartCard title="Total Memory Usage" description="Precise utilization at the recorded time">
|
||||
<MemChart chartData={memChartData} />
|
||||
</ChartCard>
|
||||
|
||||
{containerMemChartData.length > 0 && (
|
||||
<ChartCard title="Docker Memory Usage" description="Memory usage of docker containers">
|
||||
<ContainerMemChart chartData={containerMemChartData} />
|
||||
</ChartCard>
|
||||
)}
|
||||
<ChartCard title="Disk Usage" description="Precise usage at the recorded time">
|
||||
<DiskChart chartData={diskChartData} />
|
||||
</ChartCard>
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className={'mb-3'}>{server.name}</CardTitle>
|
||||
@@ -251,15 +234,31 @@ export default function ServerDetail({ name }: { name: string }) {
|
||||
<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> */}
|
||||
</>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function ChartCard({
|
||||
title,
|
||||
description,
|
||||
children,
|
||||
}: {
|
||||
title: string
|
||||
description: string
|
||||
children: React.ReactNode
|
||||
}) {
|
||||
return (
|
||||
<Card className="pb-4 col-span-full">
|
||||
<CardHeader className="pb-5 pt-4">
|
||||
<CardTitle className="flex gap-2 items-center justify-between -mb-1.5">
|
||||
{title}
|
||||
<ChartTimeSelect className="translate-y-1" />
|
||||
</CardTitle>
|
||||
<CardDescription>{description}</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className={'pl-1 w-[calc(100%-1.6em)] h-52 relative'}>
|
||||
<Suspense fallback={<Spinner />}>{children}</Suspense>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
|
@@ -66,7 +66,8 @@ import AlertsButton from '../table-alerts'
|
||||
function CellFormatter(info: CellContext<SystemRecord, unknown>) {
|
||||
const val = info.getValue() as number
|
||||
return (
|
||||
<div className="flex gap-2 items-center tabular-nums tracking-tight">
|
||||
<div className="flex gap-1 items-center tabular-nums tracking-tight">
|
||||
<span className="w-16">{val.toFixed(2)}%</span>
|
||||
<span className="grow min-w-10 block bg-muted h-[1em] relative rounded-sm overflow-hidden">
|
||||
<span
|
||||
className={cn(
|
||||
@@ -76,7 +77,6 @@ function CellFormatter(info: CellContext<SystemRecord, unknown>) {
|
||||
style={{ transform: `scalex(${val}%)` }}
|
||||
></span>
|
||||
</span>
|
||||
<span className="w-16">{val.toFixed(2)}%</span>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
@@ -3,7 +3,7 @@
|
||||
@tailwind utilities;
|
||||
@layer base {
|
||||
:root {
|
||||
--background: 30 8% 97.45%;
|
||||
--background: 30 8% 98.5%;
|
||||
--foreground: 30 0% 0%;
|
||||
--card: 30 0% 100%;
|
||||
--card-foreground: 240 6.67% 2.94%;
|
||||
|
@@ -25,8 +25,14 @@ export const $authenticated = atom(pb.authStore.isValid)
|
||||
/** List of system records */
|
||||
export const $systems = atom([] as SystemRecord[])
|
||||
|
||||
/** Last updated system record (realtime) */
|
||||
export const $updatedSystem = atom({} as SystemRecord)
|
||||
|
||||
/** List of alert records */
|
||||
export const $alerts = atom([] as AlertRecord[])
|
||||
|
||||
/** SSH public key */
|
||||
export const $publicKey = atom('')
|
||||
|
||||
/** Chart time period */
|
||||
export const $chartTime = atom('1h')
|
||||
|
@@ -96,3 +96,25 @@ export function updateRecordList<T extends RecordModel>(
|
||||
}
|
||||
$store.set(newRecords)
|
||||
}
|
||||
|
||||
export function getPbTimestamp(timeString: string) {
|
||||
const now = new Date()
|
||||
let timeValue = parseInt(timeString.slice(0, -1))
|
||||
let unit = timeString.slice(-1)
|
||||
|
||||
if (unit === 'h') {
|
||||
now.setUTCHours(now.getUTCHours() - timeValue)
|
||||
} else {
|
||||
// d
|
||||
now.setUTCDate(now.getUTCDate() - timeValue)
|
||||
}
|
||||
|
||||
const year = now.getUTCFullYear()
|
||||
const month = String(now.getUTCMonth() + 1).padStart(2, '0')
|
||||
const day = String(now.getUTCDate()).padStart(2, '0')
|
||||
const hours = String(now.getUTCHours()).padStart(2, '0')
|
||||
const minutes = String(now.getUTCMinutes()).padStart(2, '0')
|
||||
const seconds = String(now.getUTCSeconds()).padStart(2, '0')
|
||||
|
||||
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`
|
||||
}
|
||||
|
@@ -3,7 +3,15 @@ import React, { Suspense, lazy, useEffect } from 'react'
|
||||
import ReactDOM from 'react-dom/client'
|
||||
import Home from './components/routes/home.tsx'
|
||||
import { ThemeProvider } from './components/theme-provider.tsx'
|
||||
import { $alerts, $authenticated, $router, $systems, navigate, pb } from './lib/stores.ts'
|
||||
import {
|
||||
$alerts,
|
||||
$authenticated,
|
||||
$updatedSystem,
|
||||
$router,
|
||||
$systems,
|
||||
navigate,
|
||||
pb,
|
||||
} from './lib/stores.ts'
|
||||
import { ModeToggle } from './components/mode-toggle.tsx'
|
||||
import {
|
||||
cn,
|
||||
@@ -53,6 +61,7 @@ const App = () => {
|
||||
// subscribe to real time updates for systems / alerts
|
||||
pb.collection<SystemRecord>('systems').subscribe('*', (e) => {
|
||||
updateRecordList(e, $systems)
|
||||
$updatedSystem.set(e.record)
|
||||
})
|
||||
pb.collection<AlertRecord>('alerts').subscribe('*', (e) => {
|
||||
updateRecordList(e, $alerts)
|
||||
|
16
types.go
16
types.go
@@ -18,14 +18,14 @@ type SystemData struct {
|
||||
}
|
||||
|
||||
type SystemInfo struct {
|
||||
Cores int `json:"c"`
|
||||
Threads int `json:"t"`
|
||||
CpuModel string `json:"m"`
|
||||
Os string `json:"o"`
|
||||
Uptime uint64 `json:"u"`
|
||||
Cpu float64 `json:"cpu"`
|
||||
MemPct float64 `json:"mp"`
|
||||
DiskPct float64 `json:"dp"`
|
||||
Cores int `json:"c"`
|
||||
Threads int `json:"t"`
|
||||
CpuModel string `json:"m"`
|
||||
// Os string `json:"o"`
|
||||
Uptime uint64 `json:"u"`
|
||||
Cpu float64 `json:"cpu"`
|
||||
MemPct float64 `json:"mp"`
|
||||
DiskPct float64 `json:"dp"`
|
||||
}
|
||||
|
||||
type SystemStats struct {
|
||||
|
Reference in New Issue
Block a user