get rid of updatedSystem store

This commit is contained in:
Henry Dollman
2024-08-21 12:50:12 -04:00
parent 0590facc89
commit 55863f849c
4 changed files with 60 additions and 71 deletions

View File

@@ -1,9 +1,11 @@
import { Suspense, lazy, useEffect } from 'react' import { Suspense, lazy, useEffect } from 'react'
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '../ui/card' import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '../ui/card'
import { $hubVersion } from '@/lib/stores' import { $alerts, $hubVersion, $systems, pb } from '@/lib/stores'
import { useStore } from '@nanostores/react' import { useStore } from '@nanostores/react'
import { GithubIcon } from 'lucide-react' import { GithubIcon } from 'lucide-react'
import { Separator } from '../ui/separator' import { Separator } from '../ui/separator'
import { updateRecordList } from '@/lib/utils'
import { AlertRecord, SystemRecord } from '@/types'
const SystemsTable = lazy(() => import('../systems-table/systems-table')) const SystemsTable = lazy(() => import('../systems-table/systems-table'))
@@ -12,6 +14,18 @@ export default function () {
useEffect(() => { useEffect(() => {
document.title = 'Dashboard / Beszel' document.title = 'Dashboard / Beszel'
// subscribe to real time updates for systems / alerts
pb.collection<SystemRecord>('systems').subscribe('*', (e) => {
updateRecordList(e, $systems)
})
pb.collection<AlertRecord>('alerts').subscribe('*', (e) => {
updateRecordList(e, $alerts)
})
return () => {
pb.collection('systems').unsubscribe('*')
pb.collection('alerts').unsubscribe('*')
}
}, []) }, [])
return ( return (
@@ -34,7 +48,7 @@ export default function () {
</CardContent> </CardContent>
</Card> </Card>
{hubVersion && ( {hubVersion && (
<div className="flex gap-1.5 justify-end items-center pr-3 sm:pr-7 mt-3.5 text-xs opacity-80"> <div className="flex gap-1.5 justify-end items-center pr-3 sm:pr-6 mt-3.5 text-xs opacity-80">
<a <a
href="https://github.com/henrygd/beszel" href="https://github.com/henrygd/beszel"
target="_blank" target="_blank"
@@ -42,7 +56,7 @@ export default function () {
> >
<GithubIcon className="h-3 w-3" /> GitHub <GithubIcon className="h-3 w-3" /> GitHub
</a> </a>
<Separator orientation="vertical" className="h-3 bg-muted-foreground" /> <Separator orientation="vertical" className="h-2.5 bg-muted-foreground opacity-70" />
<a <a
href="https://github.com/henrygd/beszel/releases" href="https://github.com/henrygd/beszel/releases"
target="_blank" target="_blank"

View File

@@ -1,4 +1,4 @@
import { $updatedSystem, $systems, pb, $chartTime } from '@/lib/stores' import { $systems, pb, $chartTime } from '@/lib/stores'
import { ContainerStatsRecord, SystemRecord, SystemStatsRecord } from '@/types' import { ContainerStatsRecord, SystemRecord, SystemStatsRecord } from '@/types'
import { Suspense, lazy, useCallback, useEffect, useMemo, useRef, useState } from 'react' import { Suspense, lazy, useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { Card, CardHeader, CardTitle, CardDescription, CardContent } from '../ui/card' import { Card, CardHeader, CardTitle, CardDescription, CardContent } from '../ui/card'
@@ -21,12 +21,11 @@ const BandwidthChart = lazy(() => import('../charts/bandwidth-chart'))
const ContainerNetChart = lazy(() => import('../charts/container-net-chart')) const ContainerNetChart = lazy(() => import('../charts/container-net-chart'))
const SwapChart = lazy(() => import('../charts/swap-chart')) const SwapChart = lazy(() => import('../charts/swap-chart'))
export default function ServerDetail({ name }: { name: string }) { export default function SystemDetail({ name }: { name: string }) {
const systems = useStore($systems) const systems = useStore($systems)
const updatedSystem = useStore($updatedSystem)
const chartTime = useStore($chartTime) const chartTime = useStore($chartTime)
const [ticks, setTicks] = useState([] as number[]) const [ticks, setTicks] = useState([] as number[])
const [server, setServer] = useState({} as SystemRecord) const [system, setSystem] = useState({} as SystemRecord)
const [systemStats, setSystemStats] = useState([] as SystemStatsRecord[]) const [systemStats, setSystemStats] = useState([] as SystemStatsRecord[])
const [hasDockerStats, setHasDocker] = useState(false) const [hasDockerStats, setHasDocker] = useState(false)
const [dockerCpuChartData, setDockerCpuChartData] = useState<Record<string, number | string>[]>( const [dockerCpuChartData, setDockerCpuChartData] = useState<Record<string, number | string>[]>(
@@ -58,26 +57,32 @@ export default function ServerDetail({ name }: { name: string }) {
useEffect(resetCharts, [chartTime]) useEffect(resetCharts, [chartTime])
useEffect(() => { useEffect(() => {
if (server.id && server.name === name) { if (system.id && system.name === name) {
return return
} }
const matchingServer = systems.find((s) => s.name === name) as SystemRecord const matchingSystem = systems.find((s) => s.name === name) as SystemRecord
if (matchingServer) { if (matchingSystem) {
setServer(matchingServer) setSystem(matchingSystem)
} }
}, [name, server, systems]) }, [name, system, systems])
// update server when new data is available // update system when new data is available
useEffect(() => { useEffect(() => {
if (updatedSystem.id === server.id) { if (!system.id) {
setServer(updatedSystem) return
} }
}, [updatedSystem]) pb.collection<SystemRecord>('systems').subscribe(system.id, (e) => {
setSystem(e.record)
})
return () => {
pb.collection('systems').unsubscribe(system.id)
}
}, [system])
async function getStats<T>(collection: string): Promise<T[]> { async function getStats<T>(collection: string): Promise<T[]> {
return await pb.collection<T>(collection).getFullList({ return await pb.collection<T>(collection).getFullList({
filter: pb.filter('system={:id} && created > {:created} && type={:type}', { filter: pb.filter('system={:id} && created > {:created} && type={:type}', {
id: server.id, id: system.id,
created: getPbTimestamp(chartTime), created: getPbTimestamp(chartTime),
type: chartTimeData[chartTime].type, type: chartTimeData[chartTime].type,
}), }),
@@ -112,7 +117,7 @@ export default function ServerDetail({ name }: { name: string }) {
// get stats // get stats
useEffect(() => { useEffect(() => {
if (!server.id || !chartTime) { if (!system.id || !chartTime) {
return return
} }
Promise.allSettled([ Promise.allSettled([
@@ -128,7 +133,7 @@ export default function ServerDetail({ name }: { name: string }) {
setSystemStats(addEmptyValues(systemStats.value, expectedInterval)) setSystemStats(addEmptyValues(systemStats.value, expectedInterval))
} }
}) })
}, [server, chartTime]) }, [system, chartTime])
useEffect(() => { useEffect(() => {
if (!systemStats.length) { if (!systemStats.length) {
@@ -173,14 +178,14 @@ export default function ServerDetail({ name }: { name: string }) {
}, []) }, [])
const uptime = useMemo(() => { const uptime = useMemo(() => {
let uptime = server.info?.u || 0 let uptime = system.info?.u || 0
if (uptime < 172800) { if (uptime < 172800) {
return `${Math.trunc(uptime / 3600)} hours` return `${Math.trunc(uptime / 3600)} hours`
} }
return `${Math.trunc(server.info?.u / 86400)} days` return `${Math.trunc(system.info?.u / 86400)} days`
}, [server.info?.u]) }, [system.info?.u])
if (!server.id) { if (!system.id) {
return null return null
} }
@@ -188,11 +193,11 @@ export default function ServerDetail({ name }: { name: string }) {
<div className="grid gap-4 mb-10"> <div className="grid gap-4 mb-10">
<Card> <Card>
<div className="grid gap-2 px-4 sm:px-6 pt-3 sm:pt-4 pb-5"> <div className="grid gap-2 px-4 sm:px-6 pt-3 sm:pt-4 pb-5">
<h1 className="text-[1.6rem] font-semibold">{server.name}</h1> <h1 className="text-[1.6rem] font-semibold">{system.name}</h1>
<div className="flex flex-wrap items-center gap-3 gap-y-2 text-sm opacity-90"> <div className="flex flex-wrap items-center gap-3 gap-y-2 text-sm opacity-90">
<div className="capitalize flex gap-2 items-center"> <div className="capitalize flex gap-2 items-center">
<span className={cn('relative flex h-3 w-3')}> <span className={cn('relative flex h-3 w-3')}>
{server.status === 'up' && ( {system.status === 'up' && (
<span <span
className="animate-ping absolute inline-flex h-full w-full rounded-full bg-green-400 opacity-75" className="animate-ping absolute inline-flex h-full w-full rounded-full bg-green-400 opacity-75"
style={{ animationDuration: '1.5s' }} style={{ animationDuration: '1.5s' }}
@@ -200,20 +205,20 @@ export default function ServerDetail({ name }: { name: string }) {
)} )}
<span <span
className={cn('relative inline-flex rounded-full h-3 w-3', { className={cn('relative inline-flex rounded-full h-3 w-3', {
'bg-green-500': server.status === 'up', 'bg-green-500': system.status === 'up',
'bg-red-500': server.status === 'down', 'bg-red-500': system.status === 'down',
'bg-primary/40': server.status === 'paused', 'bg-primary/40': system.status === 'paused',
'bg-yellow-500': server.status === 'pending', 'bg-yellow-500': system.status === 'pending',
})} })}
></span> ></span>
</span> </span>
{server.status} {system.status}
</div> </div>
<Separator orientation="vertical" className="h-4 bg-primary/30" /> <Separator orientation="vertical" className="h-4 bg-primary/30" />
<div className="flex gap-1.5"> <div className="flex gap-1.5">
<GlobeIcon className="h-4 w-4 mt-[1px]" /> {server.host} <GlobeIcon className="h-4 w-4 mt-[1px]" /> {system.host}
</div> </div>
{server.info?.u && ( {system.info?.u && (
<TooltipProvider> <TooltipProvider>
<Tooltip> <Tooltip>
<Separator orientation="vertical" className="h-4 bg-primary/30" /> <Separator orientation="vertical" className="h-4 bg-primary/30" />
@@ -226,12 +231,12 @@ export default function ServerDetail({ name }: { name: string }) {
</Tooltip> </Tooltip>
</TooltipProvider> </TooltipProvider>
)} )}
{server.info?.m && ( {system.info?.m && (
<> <>
<Separator orientation="vertical" className="h-4 bg-primary/30" /> <Separator orientation="vertical" className="h-4 bg-primary/30" />
<div className="flex gap-1.5"> <div className="flex gap-1.5">
<CpuIcon className="h-4 w-4 mt-[1px]" /> <CpuIcon className="h-4 w-4 mt-[1px]" />
{server.info.m} ({server.info.c}c / {server.info.t}t) {system.info.m} ({system.info.c}c / {system.info.t}t)
</div> </div>
</> </>
)} )}

View File

@@ -11,9 +11,6 @@ export const $authenticated = atom(pb.authStore.isValid)
/** List of system records */ /** List of system records */
export const $systems = atom([] as SystemRecord[]) export const $systems = atom([] as SystemRecord[])
/** Last updated system record (realtime) */
export const $updatedSystem = atom({} as SystemRecord)
/** List of alert records */ /** List of alert records */
export const $alerts = atom([] as AlertRecord[]) export const $alerts = atom([] as AlertRecord[])

View File

@@ -3,24 +3,9 @@ import React, { Suspense, lazy, useEffect } from 'react'
import ReactDOM from 'react-dom/client' import ReactDOM from 'react-dom/client'
import Home from './components/routes/home.tsx' import Home from './components/routes/home.tsx'
import { ThemeProvider } from './components/theme-provider.tsx' import { ThemeProvider } from './components/theme-provider.tsx'
import { import { $authenticated, $systems, pb, $publicKey, $hubVersion } from './lib/stores.ts'
$alerts,
$authenticated,
$updatedSystem,
$systems,
pb,
$publicKey,
$hubVersion,
} from './lib/stores.ts'
import { ModeToggle } from './components/mode-toggle.tsx' import { ModeToggle } from './components/mode-toggle.tsx'
import { import { cn, isAdmin, updateAlerts, updateFavicon, updateSystemList } from './lib/utils.ts'
cn,
isAdmin,
updateAlerts,
updateFavicon,
updateRecordList,
updateSystemList,
} from './lib/utils.ts'
import { buttonVariants } from './components/ui/button.tsx' import { buttonVariants } from './components/ui/button.tsx'
import { import {
DatabaseBackupIcon, DatabaseBackupIcon,
@@ -45,7 +30,7 @@ import {
} from './components/ui/dropdown-menu.tsx' } from './components/ui/dropdown-menu.tsx'
import { AlertRecord, SystemRecord } from './types' import { AlertRecord, SystemRecord } from './types'
import { $router, Link, navigate } from './components/router.tsx' import { $router, Link, navigate } from './components/router.tsx'
import ServerDetail from './components/routes/system.tsx' import SystemDetail from './components/routes/system.tsx'
// const ServerDetail = lazy(() => import('./components/routes/system.tsx')) // const ServerDetail = lazy(() => import('./components/routes/system.tsx'))
const CommandPalette = lazy(() => import('./components/command-palette.tsx')) const CommandPalette = lazy(() => import('./components/command-palette.tsx'))
@@ -62,25 +47,13 @@ const App = () => {
$authenticated.set(pb.authStore.isValid) $authenticated.set(pb.authStore.isValid)
}) })
// get version / public key // get version / public key
pb.send('/api/beszel/getkey', {}).then(({ key, v }) => { pb.send('/api/beszel/getkey', {}).then((data) => {
$publicKey.set(key) $publicKey.set(data.key)
$hubVersion.set(v) $hubVersion.set(data.v)
}) })
// get servers / alerts // get servers / alerts
updateSystemList() updateSystemList()
updateAlerts() updateAlerts()
// 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)
})
return () => {
pb.collection('systems').unsubscribe('*')
pb.collection('alerts').unsubscribe('*')
}
}, []) }, [])
// update favicon // update favicon
@@ -110,7 +83,7 @@ const App = () => {
} else if (page.path === '/') { } else if (page.path === '/') {
return <Home /> return <Home />
} else if (page.route === 'server') { } else if (page.route === 'server') {
return <ServerDetail name={page.params.name} /> return <SystemDetail name={page.params.name} />
} }
} }