"use client";
import { useState, useEffect, useRef } from "react";
import { IconLayoutGrid } from "@tabler/icons-react";
import { type Stats, type NetworkInterface } from "../lib/getStats";
import { type PowerData } from "../lib/getPower";
import { formatBytes, statColor } from "../lib/utils";
export type WidgetId =
| "cpu" | "memory" | "disk" | "temp"
| "power-server" | "power-desktop"
| "network" | "uptime" | "empty";
const 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: "power-server", label: "Server Power" },
{ id: "power-desktop", label: "Desktop Power" },
{ id: "network", label: "Network" },
{ id: "uptime", label: "Uptime" },
];
const DEFAULT_WIDGETS: WidgetId[] = ["power-server", "power-desktop", "cpu", "memory"];
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;
}) {
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 === "power-server") return d.name === "server") ?? null} />;
if (id === "power-desktop") return d.name === "desktop") ?? null} />;
if (id === "network") return ;
if (id === "uptime") 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] as WidgetId[];
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)}
/>
))}
);
}