refactor: replace status strings with systemstatus enum

- also small style changes after tailwind update
This commit is contained in:
henrygd
2025-08-23 20:35:18 -04:00
parent 3c4ae46f50
commit 0f5b1b5157
8 changed files with 50 additions and 72 deletions

View File

@@ -20,6 +20,7 @@ import { ChevronDownIcon, ExternalLinkIcon, PlusIcon } from "lucide-react"
import { memo, useEffect, useRef, useState } from "react" import { memo, useEffect, useRef, useState } from "react"
import { $router, basePath, Link, navigate } from "./router" import { $router, basePath, Link, navigate } from "./router"
import { SystemRecord } from "@/types" import { SystemRecord } from "@/types"
import { SystemStatus } from "@/lib/enums"
import { AppleIcon, DockerIcon, TuxIcon, WindowsIcon } from "./ui/icons" import { AppleIcon, DockerIcon, TuxIcon, WindowsIcon } from "./ui/icons"
import { InputCopy } from "./ui/input-copy" import { InputCopy } from "./ui/input-copy"
import { getPagePath } from "@nanostores/router" import { getPagePath } from "@nanostores/router"
@@ -105,7 +106,7 @@ export const SystemDialog = ({ setOpen, system }: { setOpen: (open: boolean) =>
try { try {
setOpen(false) setOpen(false)
if (system) { if (system) {
await pb.collection("systems").update(system.id, { ...data, status: "pending" }) await pb.collection("systems").update(system.id, { ...data, status: SystemStatus.Pending })
} else { } else {
const createdSystem = await pb.collection("systems").create(data) const createdSystem = await pb.collection("systems").create(data)
await pb.collection("fingerprints").create({ await pb.collection("fingerprints").create({

View File

@@ -11,7 +11,7 @@ import {
$temperatureFilter, $temperatureFilter,
} from "@/lib/stores" } from "@/lib/stores"
import { ChartData, ChartTimes, ContainerStatsRecord, GPUData, SystemRecord, SystemStatsRecord } from "@/types" import { ChartData, ChartTimes, ContainerStatsRecord, GPUData, SystemRecord, SystemStatsRecord } from "@/types"
import { ChartType, Unit, Os } from "@/lib/enums" import { ChartType, Unit, Os, SystemStatus } from "@/lib/enums"
import React, { lazy, memo, useCallback, useEffect, useMemo, useRef, useState, type JSX } from "react" import React, { lazy, memo, useCallback, useEffect, useMemo, useRef, useState, type JSX } from "react"
import { Card, CardHeader, CardTitle, CardDescription } from "../ui/card" import { Card, CardHeader, CardTitle, CardDescription } from "../ui/card"
import { useStore } from "@nanostores/react" import { useStore } from "@nanostores/react"
@@ -382,9 +382,9 @@ export default function SystemDetail({ name }: { name: string }) {
const hasGpuPowerData = lastGpuVals.some((gpu) => gpu.p !== undefined) const hasGpuPowerData = lastGpuVals.some((gpu) => gpu.p !== undefined)
let translatedStatus: string = system.status let translatedStatus: string = system.status
if (system.status === "up") { if (system.status === SystemStatus.Up) {
translatedStatus = t({ message: "Up", comment: "Context: System is up" }) translatedStatus = t({ message: "Up", comment: "Context: System is up" })
} else if (system.status === "down") { } else if (system.status === SystemStatus.Down) {
translatedStatus = t({ message: "Down", comment: "Context: System is down" }) translatedStatus = t({ message: "Down", comment: "Context: System is down" })
} }
@@ -399,7 +399,7 @@ export default function SystemDetail({ name }: { name: string }) {
<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")}>
{system.status === "up" && ( {system.status === SystemStatus.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" }}
@@ -407,10 +407,10 @@ export default function SystemDetail({ 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": system.status === "up", "bg-green-500": system.status === SystemStatus.Up,
"bg-red-500": system.status === "down", "bg-red-500": system.status === SystemStatus.Down,
"bg-primary/40": system.status === "paused", "bg-primary/40": system.status === SystemStatus.Paused,
"bg-yellow-500": system.status === "pending", "bg-yellow-500": system.status === SystemStatus.Pending,
})} })}
></span> ></span>
</span> </span>

View File

@@ -54,15 +54,15 @@ import {
} from "../ui/alert-dialog" } from "../ui/alert-dialog"
import { buttonVariants } from "../ui/button" import { buttonVariants } from "../ui/button"
import { t } from "@lingui/core/macro" import { t } from "@lingui/core/macro"
import { MeterState } from "@/lib/enums" import { MeterState, SystemStatus } from "@/lib/enums"
import { $router, Link } from "../router" import { $router, Link } from "../router"
import { getPagePath } from "@nanostores/router" import { getPagePath } from "@nanostores/router"
const STATUS_COLORS = { const STATUS_COLORS = {
up: "bg-green-500", [SystemStatus.Up]: "bg-green-500",
down: "bg-red-500", [SystemStatus.Down]: "bg-red-500",
paused: "bg-primary/40", [SystemStatus.Paused]: "bg-primary/40",
pending: "bg-yellow-500", [SystemStatus.Pending]: "bg-yellow-500",
} as const } as const
/** /**
@@ -82,9 +82,9 @@ export default function SystemsTableColumns(viewMode: "table" | "grid"): ColumnD
let filterInputLower = "" let filterInputLower = ""
const nameCache = new Map<string, string>() const nameCache = new Map<string, string>()
const statusTranslations = { const statusTranslations = {
up: t`Up`.toLowerCase(), [SystemStatus.Up]: t`Up`.toLowerCase(),
down: t`Down`.toLowerCase(), [SystemStatus.Down]: t`Down`.toLowerCase(),
paused: t`Paused`.toLowerCase(), [SystemStatus.Paused]: t`Paused`.toLowerCase(),
} as const } as const
// match filter value against name or translated status // match filter value against name or translated status
@@ -186,7 +186,7 @@ export default function SystemsTableColumns(viewMode: "table" | "grid"): ColumnD
} }
const max = Math.max(...loadAverages) const max = Math.max(...loadAverages)
if (max === 0 && (status === "paused" || minor < 12)) { if (max === 0 && (status === SystemStatus.Paused || minor < 12)) {
return null return null
} }
@@ -197,10 +197,10 @@ export default function SystemsTableColumns(viewMode: "table" | "grid"): ColumnD
<div className="flex items-center gap-[.35em] w-full tabular-nums tracking-tight"> <div className="flex items-center gap-[.35em] w-full tabular-nums tracking-tight">
<span <span
className={cn("inline-block size-2 rounded-full me-0.5", { className={cn("inline-block size-2 rounded-full me-0.5", {
[STATUS_COLORS.up]: threshold === MeterState.Good, [STATUS_COLORS[SystemStatus.Up]]: threshold === MeterState.Good,
[STATUS_COLORS.pending]: threshold === MeterState.Warn, [STATUS_COLORS[SystemStatus.Pending]]: threshold === MeterState.Warn,
[STATUS_COLORS.down]: threshold === MeterState.Crit, [STATUS_COLORS[SystemStatus.Down]]: threshold === MeterState.Crit,
[STATUS_COLORS.paused]: status !== "up", [STATUS_COLORS[SystemStatus.Paused]]: status !== SystemStatus.Up,
})} })}
/> />
{loadAverages?.map((la, i) => ( {loadAverages?.map((la, i) => (
@@ -220,7 +220,7 @@ export default function SystemsTableColumns(viewMode: "table" | "grid"): ColumnD
cell(info) { cell(info) {
const sys = info.row.original const sys = info.row.original
const userSettings = useStore($userSettings, { keys: ["unitNet"] }) const userSettings = useStore($userSettings, { keys: ["unitNet"] })
if (sys.status === "paused") { if (sys.status === SystemStatus.Paused) {
return null return null
} }
const { value, unit } = formatBytes(info.getValue() as number, true, userSettings.unitNet, false) const { value, unit } = formatBytes(info.getValue() as number, true, userSettings.unitNet, false)
@@ -273,9 +273,9 @@ export default function SystemsTableColumns(viewMode: "table" | "grid"): ColumnD
<IndicatorDot <IndicatorDot
system={system} system={system}
className={ className={
(system.status !== "up" && STATUS_COLORS.paused) || (system.status !== SystemStatus.Up && STATUS_COLORS[SystemStatus.Paused]) ||
(version === globalThis.BESZEL.HUB_VERSION && STATUS_COLORS.up) || (version === globalThis.BESZEL.HUB_VERSION && STATUS_COLORS[SystemStatus.Up]) ||
STATUS_COLORS.pending STATUS_COLORS[SystemStatus.Pending]
} }
/> />
<span className="truncate max-w-14">{info.getValue() as string}</span> <span className="truncate max-w-14">{info.getValue() as string}</span>
@@ -325,7 +325,7 @@ function TableCellWithMeter(info: CellContext<SystemRecord, unknown>) {
<span <span
className={cn( className={cn(
"absolute inset-0 w-full h-full origin-left", "absolute inset-0 w-full h-full origin-left",
(info.row.original.status !== "up" && STATUS_COLORS.paused) || (info.row.original.status !== SystemStatus.Up && STATUS_COLORS.paused) ||
(threshold === MeterState.Good && STATUS_COLORS.up) || (threshold === MeterState.Good && STATUS_COLORS.up) ||
(threshold === MeterState.Warn && STATUS_COLORS.pending) || (threshold === MeterState.Warn && STATUS_COLORS.pending) ||
STATUS_COLORS.down STATUS_COLORS.down
@@ -384,11 +384,11 @@ export const ActionsButton = memo(({ system }: { system: SystemRecord }) => {
className={cn(isReadOnlyUser() && "hidden")} className={cn(isReadOnlyUser() && "hidden")}
onClick={() => { onClick={() => {
pb.collection("systems").update(id, { pb.collection("systems").update(id, {
status: status === "paused" ? "pending" : "paused", status: status === SystemStatus.Paused ? SystemStatus.Pending : SystemStatus.Paused,
}) })
}} }}
> >
{status === "paused" ? ( {status === SystemStatus.Paused ? (
<> <>
<PlayCircleIcon className="me-2.5 size-4" /> <PlayCircleIcon className="me-2.5 size-4" />
<Trans>Resume</Trans> <Trans>Resume</Trans>

View File

@@ -48,6 +48,7 @@ import { Input } from "../ui/input"
import { getPagePath } from "@nanostores/router" import { getPagePath } from "@nanostores/router"
import SystemsTableColumns, { ActionsButton, IndicatorDot } from "./systems-table-columns" import SystemsTableColumns, { ActionsButton, IndicatorDot } from "./systems-table-columns"
import AlertButton from "../alerts/alert-button" import AlertButton from "../alerts/alert-button"
import { SystemStatus } from "@/lib/enums"
type ViewMode = "table" | "grid" type ViewMode = "table" | "grid"
@@ -292,7 +293,7 @@ const SystemTableRow = memo(
<TableRow <TableRow
// data-state={row.getIsSelected() && "selected"} // data-state={row.getIsSelected() && "selected"}
className={cn("cursor-pointer transition-opacity relative", { className={cn("cursor-pointer transition-opacity relative", {
"opacity-50": system.status === "paused", "opacity-50": system.status === SystemStatus.Paused,
})} })}
> >
{row.getVisibleCells().map((cell) => ( {row.getVisibleCells().map((cell) => (
@@ -324,7 +325,7 @@ const SystemCard = memo(
className={cn( className={cn(
"cursor-pointer hover:shadow-md transition-all bg-transparent w-full dark:border-border duration-200 relative", "cursor-pointer hover:shadow-md transition-all bg-transparent w-full dark:border-border duration-200 relative",
{ {
"opacity-50": system.status === "paused", "opacity-50": system.status === SystemStatus.Paused,
} }
)} )}
> >

View File

@@ -25,7 +25,7 @@ const DropdownMenuSubTrigger = React.forwardRef<
<DropdownMenuPrimitive.SubTrigger <DropdownMenuPrimitive.SubTrigger
ref={ref} ref={ref}
className={cn( className={cn(
"flex cursor-default select-none items-center rounded-sm px-2.5 py-1.5 text-sm outline-hidden focus:bg-accent/70 data-[state=open]:bg-accent/70", "flex select-none items-center rounded-sm px-2.5 py-1.5 text-sm outline-hidden focus:bg-accent/70 data-[state=open]:bg-accent/70",
inset && "ps-8", inset && "ps-8",
className className
)} )}
@@ -79,7 +79,7 @@ const DropdownMenuItem = React.forwardRef<
<DropdownMenuPrimitive.Item <DropdownMenuPrimitive.Item
ref={ref} ref={ref}
className={cn( className={cn(
"relative flex cursor-default select-none items-center rounded-sm px-2.5 py-1.5 text-sm outline-hidden focus:bg-accent/70 focus:text-accent-foreground data-disabled:pointer-events-none data-disabled:opacity-50", "relative flex select-none items-center rounded-sm px-2.5 py-1.5 text-sm outline-hidden focus:bg-accent/70 focus:text-accent-foreground data-disabled:pointer-events-none data-disabled:opacity-50",
inset && "ps-8", inset && "ps-8",
className className
)} )}
@@ -95,7 +95,7 @@ const DropdownMenuCheckboxItem = React.forwardRef<
<DropdownMenuPrimitive.CheckboxItem <DropdownMenuPrimitive.CheckboxItem
ref={ref} ref={ref}
className={cn( className={cn(
"relative flex cursor-default select-none items-center rounded-sm py-1.5 ps-8 pe-2 text-sm outline-hidden focus:bg-accent/70 focus:text-accent-foreground data-disabled:pointer-events-none data-disabled:opacity-50", "relative flex select-none items-center rounded-sm py-1.5 ps-8 pe-2 text-sm outline-hidden focus:bg-accent/70 focus:text-accent-foreground data-disabled:pointer-events-none data-disabled:opacity-50",
className className
)} )}
checked={checked} checked={checked}

View File

@@ -3,24 +3,6 @@
@config '../tailwind.config.js'; @config '../tailwind.config.js';
/*
The default border color has changed to `currentcolor` in Tailwind CSS v4,
so we've added these compatibility styles to make sure everything still
looks the same as it did with Tailwind CSS v3.
If we ever want to remove these styles, we need to add an explicit border
color utility to any element that depends on these defaults.
*/
@layer base {
*,
::after,
::before,
::backdrop,
::file-selector-button {
border-color: var(--color-gray-200, currentcolor);
}
}
@utility link { @utility link {
@apply text-primary font-medium underline-offset-4 hover:underline; @apply text-primary font-medium underline-offset-4 hover:underline;
} }
@@ -33,24 +15,6 @@
} }
} }
/*
The default border color has changed to `currentcolor` in Tailwind CSS v4,
so we've added these compatibility styles to make sure everything still
looks the same as it did with Tailwind CSS v3.
If we ever want to remove these styles, we need to add an explicit border
color utility to any element that depends on these defaults.
*/
@layer base {
*,
::after,
::before,
::backdrop,
::file-selector-button {
border-color: var(--color-gray-200, currentcolor);
}
}
@layer base { @layer base {
:root { :root {
--background: 30 8% 98%; --background: 30 8% 98%;
@@ -130,6 +94,9 @@
body { body {
@apply bg-background text-foreground; @apply bg-background text-foreground;
} }
button {
cursor: pointer;
}
} }
.recharts-tooltip-wrapper { .recharts-tooltip-wrapper {

View File

@@ -28,3 +28,11 @@ export enum MeterState {
Warn, Warn,
Crit, Crit,
} }
/** System status states */
export enum SystemStatus {
Up = "up",
Down = "down",
Pending = "pending",
Paused = "paused",
}

View File

@@ -15,6 +15,7 @@ import Navbar from "./components/navbar.tsx"
import { I18nProvider } from "@lingui/react" import { I18nProvider } from "@lingui/react"
import { i18n } from "@lingui/core" import { i18n } from "@lingui/core"
import { getLocale, dynamicActivate } from "./lib/i18n.ts" import { getLocale, dynamicActivate } from "./lib/i18n.ts"
import { SystemStatus } from "./lib/enums.ts"
// const ServerDetail = lazy(() => import('./components/routes/system.tsx')) // const ServerDetail = lazy(() => import('./components/routes/system.tsx'))
const LoginPage = lazy(() => import("./components/login/login.tsx")) const LoginPage = lazy(() => import("./components/login/login.tsx"))
@@ -50,10 +51,10 @@ const App = memo(() => {
} else { } else {
let up = false let up = false
for (const system of systems) { for (const system of systems) {
if (system.status === "down") { if (system.status === SystemStatus.Down) {
updateFavicon("favicon-red.svg") updateFavicon("favicon-red.svg")
return return
} else if (system.status === "up") { } else if (system.status === SystemStatus.Up) {
up = true up = true
} }
} }