Files
Dash/apps/frontend/src/widgets/system.tsx
T
Tomas DvorakandDevin e0ef60eca6 feat(ui): phase 5 — graphs, widgets, theming, PWA, boards, i18n
P5a: 24h uptime bar strip + latency chart on monitor detail, iframe
embed widget, generic JSON-path widget, accent/wallpaper/custom CSS
settings, PWA manifest + service worker, demo GIF.

P5b: transport picker + per-transport fields + test button in
settings; per-monitor alert-rule fields in dialogs.

P5c: container cpu/mem columns, SMART/ZFS/GPU sections on system
detail, subdomains section on domain detail.

P5d: login/setup page + auth middleware client handling, board
switcher + /b/:slug routes, full string extraction to typed t() keys
with {param} interpolation, complete cs locale, language select.

Authored By: TDvorak <[email protected]>

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-09-22 12:49:51 +02:00

62 lines
2.3 KiB
TypeScript

import type { WidgetData } from "@/widgets";
import type { Item } from "@/types";
import { cn } from "@/lib/utils";
import { t } from "@/i18n";
// Board tile for a monitored system: status dot + cpu/mem/disk mini bars.
// config.systemId binds it; data comes from the system fetcher.
export function SystemWidget({ item, data, error }: {
item: Item;
data: WidgetData | undefined;
error: string | undefined;
}) {
const status = typeof data?.status === "string" ? data.status : undefined;
const sysId = typeof item.config?.systemId === "string" ? item.config.systemId : "";
const cpu = typeof data?.cpu === "number" ? data.cpu : undefined;
const memTotal = typeof data?.memTotal === "number" ? data.memTotal : 0;
const memUsed = typeof data?.memUsed === "number" ? data.memUsed : 0;
const diskTotal = typeof data?.diskTotal === "number" ? data.diskTotal : 0;
const diskUsed = typeof data?.diskUsed === "number" ? data.diskUsed : 0;
const memPct = memTotal > 0 ? (memUsed / memTotal) * 100 : undefined;
const diskPct = diskTotal > 0 ? (diskUsed / diskTotal) * 100 : undefined;
const bar = (pct: number | undefined, label: string) => (
<span className="flex items-center gap-1" title={`${label}: ${pct !== undefined ? pct.toFixed(0) + "%" : "—"}`}>
<span className="font-mono text-[9px] uppercase text-text-faint">{label}</span>
<span className="h-1 w-8 overflow-hidden rounded-full bg-surface-hover">
<span
className={cn("block h-full rounded-full", pct !== undefined && pct > 90 ? "bg-down" : "bg-up")}
style={{ width: `${Math.min(pct ?? 0, 100)}%` }}
/>
</span>
</span>
);
return (
<a
href={sysId ? `/systems/${sysId}` : "/systems"}
className="flex w-full items-center gap-2.5 text-left"
onClick={(e) => e.stopPropagation()}
>
<span
className={cn(
"size-1.5 shrink-0 rounded-full",
error ? "bg-down" : status === "up" ? "bg-up" : status === "down" ? "bg-down" : "bg-text-faint/40",
)}
/>
<span className="min-w-0 flex-1 truncate text-[12.5px] font-medium">
{typeof data?.name === "string" ? data.name : item.name}
</span>
{error ? (
<span className="shrink-0 font-mono text-[10.5px] text-text-faint">{t("widget.err")}</span>
) : (
<span className="flex shrink-0 items-center gap-2">
{bar(cpu, "cpu")}
{bar(memPct, "mem")}
{bar(diskPct, "dsk")}
</span>
)}
</a>
);
}