refactor (hub): add systemsManager module

- Removed the `updateSystemList` function and replaced it with a more efficient system management approach using `systemsManager`.
- Updated the `App` component to initialize and subscribe to system updates through the new `systemsManager`.
- Refactored the `SystemsTable` and `SystemDetail` components to utilize the new state management for systems, improving performance and maintainability.
- Enhanced the `ActiveAlerts` component to fetch system names directly from the new state structure.
This commit is contained in:
henrygd
2025-09-01 17:29:33 -04:00
parent d2aed0dc72
commit 9c458885f1
8 changed files with 245 additions and 186 deletions

View File

@@ -1,12 +1,10 @@
import { Suspense, memo, useEffect, useMemo } from "react"
import { Card, CardContent, CardHeader, CardTitle } from "../ui/card"
import { $alerts, $systems } from "@/lib/stores"
import { $alerts, $allSystemsById } from "@/lib/stores"
import { useStore } from "@nanostores/react"
import { GithubIcon } from "lucide-react"
import { Separator } from "../ui/separator"
import { getSystemNameFromId } from "@/lib/utils"
import { pb, updateRecordList, updateSystemList } from "@/lib/api"
import { AlertRecord, SystemRecord } from "@/types"
import { AlertRecord } from "@/types"
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"
import { $router, Link } from "../router"
import { Plural, Trans, useLingui } from "@lingui/react/macro"
@@ -14,8 +12,6 @@ import { getPagePath } from "@nanostores/router"
import { alertInfo } from "@/lib/alerts"
import SystemsTable from "@/components/systems-table/systems-table"
// const SystemsTable = lazy(() => import("../systems-table/systems-table"))
export default memo(function () {
const { t } = useLingui()
@@ -23,19 +19,6 @@ export default memo(function () {
document.title = t`Dashboard` + " / Beszel"
}, [t])
useEffect(() => {
// make sure we have the latest list of systems
updateSystemList()
// subscribe to real time updates for systems / alerts
pb.collection<SystemRecord>("systems").subscribe("*", (e) => {
updateRecordList(e, $systems)
})
return () => {
pb.collection("systems").unsubscribe("*")
}
}, [])
return useMemo(
() => (
<>
@@ -69,6 +52,7 @@ export default memo(function () {
const ActiveAlerts = () => {
const alerts = useStore($alerts)
const systems = useStore($allSystemsById)
const { activeAlerts, alertsKey } = useMemo(() => {
const activeAlerts: AlertRecord[] = []
@@ -112,7 +96,7 @@ const ActiveAlerts = () => {
>
<info.icon className="h-4 w-4" />
<AlertTitle>
{getSystemNameFromId(alert.system)} {info.name().toLowerCase().replace("cpu", "CPU")}
{systems[alert.system]?.name} {info.name().toLowerCase().replace("cpu", "CPU")}
</AlertTitle>
<AlertDescription>
{alert.name === "Status" ? (
@@ -125,7 +109,7 @@ const ActiveAlerts = () => {
)}
</AlertDescription>
<Link
href={getPagePath($router, "system", { name: getSystemNameFromId(alert.system) })}
href={getPagePath($router, "system", { name: systems[alert.system]?.name })}
className="absolute inset-0 w-full h-full"
aria-label="View system"
></Link>

View File

@@ -8,6 +8,7 @@ import {
$direction,
$maxValues,
$temperatureFilter,
$allSystemsByName,
} from "@/lib/stores"
import { ChartData, ChartTimes, ContainerStatsRecord, GPUData, SystemRecord, SystemStatsRecord } from "@/types"
import { ChartType, Unit, Os, SystemStatus } from "@/lib/enums"
@@ -49,6 +50,7 @@ import SwapChart from "@/components/charts/swap-chart"
import TemperatureChart from "@/components/charts/temperature-chart"
import GpuPowerChart from "@/components/charts/gpu-power-chart"
import LoadAverageChart from "@/components/charts/load-average-chart"
import { subscribeKeys } from "nanostores"
const cache = new Map<string, any>()
@@ -117,7 +119,7 @@ function dockerOrPodman(str: string, system: SystemRecord) {
return str
}
export default function SystemDetail({ name }: { name: string }) {
export default memo(function SystemDetail({ name }: { name: string }) {
const direction = useStore($direction)
const { t } = useLingui()
const systems = useStore($systems)
@@ -149,36 +151,13 @@ export default function SystemDetail({ name }: { name: string }) {
}
}, [name])
// function resetCharts() {
// setSystemStats([])
// setContainerData([])
// }
// useEffect(resetCharts, [chartTime])
// find matching system
// find matching system and update when it changes
useEffect(() => {
if (system.id && system.name === name) {
return
}
const matchingSystem = systems.find((s) => s.name === name) as SystemRecord
if (matchingSystem) {
setSystem(matchingSystem)
}
}, [name, system, systems])
// update system when new data is available
useEffect(() => {
if (!system.id) {
return
}
pb.collection<SystemRecord>("systems").subscribe(system.id, (e) => {
setSystem(e.record)
return subscribeKeys($allSystemsByName, [name], (newSystems) => {
const sys = newSystems[name]
sys?.id && setSystem(sys)
})
return () => {
pb.collection("systems").unsubscribe(system.id)
}
}, [system.id])
}, [name])
const chartData: ChartData = useMemo(() => {
const lastCreated = Math.max(
@@ -835,7 +814,7 @@ export default function SystemDetail({ name }: { name: string }) {
{bottomSpacing > 0 && <span className="block" style={{ height: bottomSpacing }} />}
</>
)
}
})
function FilterBar({ store = $containerFilter }: { store?: typeof $containerFilter }) {
const containerFilter = useStore(store)

View File

@@ -36,9 +36,9 @@ import {
FilterIcon,
} from "lucide-react"
import { memo, useEffect, useMemo, useRef, useState } from "react"
import { $systems } from "@/lib/stores"
import { $pausedSystems, $downSystems, $upSystems, $systems } from "@/lib/stores"
import { useStore } from "@nanostores/react"
import { cn, runOnce, useLocalStorage } from "@/lib/utils"
import { cn, runOnce, useBrowserStorage } from "@/lib/utils"
import { $router, Link } from "../router"
import { useLingui, Trans } from "@lingui/react/macro"
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "../ui/card"
@@ -50,12 +50,15 @@ import { SystemStatus } from "@/lib/enums"
import { useVirtualizer, VirtualItem } from "@tanstack/react-virtual"
type ViewMode = "table" | "grid"
type StatusFilter = "all" | "up" | "down" | "paused"
type StatusFilter = "all" | SystemRecord["status"]
const preloadSystemDetail = runOnce(() => import("@/components/routes/system.tsx"))
export default function SystemsTable() {
const data = useStore($systems)
const downSystems = $downSystems.get()
const upSystems = $upSystems.get()
const pausedSystems = $pausedSystems.get()
const { i18n, t } = useLingui()
const [filter, setFilter] = useState<string>()
const [statusFilter, setStatusFilter] = useState<StatusFilter>("all")
@@ -74,7 +77,13 @@ export default function SystemsTable() {
if (statusFilter === "all") {
return data
}
return data.filter((system) => system.status === statusFilter)
if (statusFilter === SystemStatus.Up) {
return Object.values(upSystems) ?? []
}
if (statusFilter === SystemStatus.Down) {
return Object.values(downSystems) ?? []
}
return Object.values(pausedSystems) ?? []
}, [data, statusFilter])
const [viewMode, setViewMode] = useBrowserStorage<ViewMode>(
@@ -106,7 +115,6 @@ export default function SystemsTable() {
columnVisibility,
},
defaultColumn: {
// sortDescFirst: true,
invertSorting: true,
sortUndefined: "last",
minSize: 0,
@@ -118,18 +126,22 @@ export default function SystemsTable() {
const rows = table.getRowModel().rows
const columns = table.getAllColumns()
const visibleColumns = table.getVisibleLeafColumns()
const [upSystemsLength, downSystemsLength, pausedSystemsLength] = useMemo(() => {
return [Object.values(upSystems).length, Object.values(downSystems).length, Object.values(pausedSystems).length]
}, [upSystems, downSystems, pausedSystems])
// TODO: hiding temp then gpu messes up table headers
const CardHead = useMemo(() => {
return (
<CardHeader className="pb-5 px-2 sm:px-6 max-sm:pt-5 max-sm:pb-1">
<CardHeader className="pb-4.5 px-2 sm:px-6 max-sm:pt-5 max-sm:pb-1">
<div className="grid md:flex gap-5 w-full items-end">
<div className="px-2 sm:px-1">
<CardTitle className="mb-2.5">
<CardTitle className="mb-2">
<Trans>All Systems</Trans>
</CardTitle>
</CardTitle>
<CardDescription className="flex">
<Trans>Click on a system to view information - {runningRecords} / {totalRecords}</Trans>
<p className={"ml-2 text-" + (runningRecords === totalRecords ? "emerald" : "red") + "-600"}>Online</p>
<Trans>Click on a system to view more information.</Trans>
</CardDescription>
</div>
@@ -181,13 +193,13 @@ export default function SystemsTable() {
<Trans>All Systems</Trans>
</DropdownMenuRadioItem>
<DropdownMenuRadioItem value="up" onSelect={(e) => e.preventDefault()}>
<Trans>Up</Trans>
<Trans>Up ({upSystemsLength})</Trans>
</DropdownMenuRadioItem>
<DropdownMenuRadioItem value="down" onSelect={(e) => e.preventDefault()}>
<Trans>Down</Trans>
<Trans>Down ({downSystemsLength})</Trans>
</DropdownMenuRadioItem>
<DropdownMenuRadioItem value="paused" onSelect={(e) => e.preventDefault()}>
<Trans>Paused</Trans>
<Trans>Paused ({pausedSystemsLength})</Trans>
</DropdownMenuRadioItem>
</DropdownMenuRadioGroup>
</div>
@@ -258,7 +270,16 @@ export default function SystemsTable() {
</div>
</CardHeader>
)
}, [visibleColumns.length, sorting, viewMode, locale, statusFilter, runningRecords, totalRecords])
}, [
visibleColumns.length,
sorting,
viewMode,
locale,
statusFilter,
upSystemsLength,
downSystemsLength,
pausedSystemsLength,
])
return (
<Card>