"use client"; import { useState, useEffect, useRef } from "react"; import { IconLayoutGrid } from "@tabler/icons-react"; import HelpTooltip from "./HelpTooltip"; import { type Stats, type NetworkInterface } from "../lib/getStats"; import { type PowerData } from "../lib/getPower"; import { formatBytes, statColor } from "../lib/utils"; export type WidgetId = string; const STATIC_WIDGET_OPTIONS: { id: WidgetId; label: string }[] = [ { id: "empty", label: "Empty" }, { id: "cpu", label: "CPU" }, { id: "memory", label: "Memory" }, { id: "disk", label: "Disk" }, { id: "temp", label: "Temp" }, { id: "network", label: "Network" }, { id: "uptime", label: "Uptime" }, ]; const DEFAULT_WIDGETS: WidgetId[] = ["cpu", "memory", "network", "uptime"]; const STORAGE_KEY = "sidenav-widgets"; const card = "flex flex-col gap-2 p-3 bg-secondary/30 border border-secondary rounded-xl"; function MiniStatWidget({ label, value, percent }: { label: string; value: string; percent?: number }) { const color = statColor(percent ?? 0); return (
{label} {value} {percent !== undefined && (
)}
); } function MiniPowerWidget({ label, device }: { label: string; device: { on: boolean; current_power_w: number } | null }) { return (
{label} {device && ( )}
{device ? `${device.current_power_w.toFixed(1)} W` : "—"} {device ? (device.on ? "On" : "Off") : "—"}
); } function MiniNetworkWidget({ speed }: { speed: { rx: number; tx: number } | null }) { return (
Network {speed ? ( <> ↓ {formatBytes(speed.rx)}/s ↑ {formatBytes(speed.tx)}/s ) : ( )}
); } function MiniUptimeWidget({ uptime }: { uptime: { days: number; hours: number; minutes: number } | null }) { return (
Uptime {uptime ? ( <> {uptime.days}d {uptime.hours}h {uptime.minutes}m ) : ( )}
); } function WidgetSlot({ id, stats, power, netSpeed, editing, onChange, }: { id: WidgetId; stats: Stats | null; power: PowerData | null; netSpeed: { rx: number; tx: number } | null; editing: boolean; onChange: (id: WidgetId) => void; }) { const powerOptions = (power?.devices ?? []).map((d) => ({ id: `power-${d.name}`, label: `${d.name} Power`, })); const allOptions = [...STATIC_WIDGET_OPTIONS, ...powerOptions]; if (editing) { return ( ); } if (id === "empty") return
; if (id === "cpu") return ; if (id === "memory") return ; if (id === "disk") return ; if (id === "temp") return ; if (id === "network") return ; if (id === "uptime") return ; if (id.startsWith("power-")) { const deviceName = id.slice("power-".length); const device = power?.devices.find((d) => d.name === deviceName) ?? null; return ; } return null; } export function SideNavWidgets() { const [widgets, setWidgets] = useState(DEFAULT_WIDGETS); const [editing, setEditing] = useState(false); const [mounted, setMounted] = useState(false); const [stats, setStats] = useState(null); const [power, setPower] = useState(null); const [netSpeed, setNetSpeed] = useState<{ rx: number; tx: number } | null>(null); const prevNetRef = useRef | null>(null); const lastFetchRef = useRef(0); useEffect(() => { setMounted(true); try { const saved = localStorage.getItem(STORAGE_KEY); if (saved) setWidgets(JSON.parse(saved)); } catch {} }, []); useEffect(() => { const fetchStats = async () => { try { const now = Date.now(); const res = await fetch("/api/stats"); if (!res.ok) return; const data: Stats = await res.json(); if (prevNetRef.current && lastFetchRef.current > 0) { const elapsed = (now - lastFetchRef.current) / 1000; const primary = Object.keys(data.network).find( (k) => !k.startsWith("docker") && !k.startsWith("br-") && data.network[k].rx > 0, ); if (primary && prevNetRef.current[primary]) { setNetSpeed({ rx: Math.max(0, (data.network[primary].rx - prevNetRef.current[primary].rx) / elapsed), tx: Math.max(0, (data.network[primary].tx - prevNetRef.current[primary].tx) / elapsed), }); } } prevNetRef.current = data.network; lastFetchRef.current = now; setStats(data); } catch {} }; fetchStats(); const id = setInterval(fetchStats, 4000); return () => clearInterval(id); }, []); useEffect(() => { const fetchPower = async () => { try { const res = await fetch("/api/power"); if (!res.ok) return; setPower(await res.json()); } catch {} }; fetchPower(); const id = setInterval(fetchPower, 3000); return () => clearInterval(id); }, []); function updateWidget(index: number, id: WidgetId) { setWidgets((prev) => { const next = [...prev]; next[index] = id; try { localStorage.setItem(STORAGE_KEY, JSON.stringify(next)); } catch {} return next; }); } if (!mounted) return null; return (
Widgets
{widgets.map((id, i) => ( updateWidget(i, newId)} /> ))}
); }