server-dash/app/page.tsx

147 lines
4.2 KiB
TypeScript

"use client";
import { useEffect, useState, useRef } from "react";
import { getStats, type Stats, type NetworkInterface } from "./lib/getStats";
import NavBar from "./components/NavBar";
import Hero from "./components/Hero";
import StatsGrid from "./components/StatsGrid";
import ServicesCard from "./components/ServicesCard";
import UptimeCard from "./components/UptimeCard";
import NetworkCard from "./components/NetworkCard";
import PowerGrid from "./components/PowerGrid";
import LinksGrid from "./components/LinksGrid";
import { useCheckAuth } from "@/hooks/useCheckAuth";
import { useRouter } from "next/navigation";
import { getPower, type PowerData } from "./lib/getPower";
export default function Home() {
const router = useRouter();
useCheckAuth();
const [stats, setStats] = useState<Stats | null>(null);
const [power, setPower] = useState<PowerData | null>(null);
const [netSpeed, setNetSpeed] = useState<
Record<string, { rx: number; tx: number }>
>({});
// We use Refs for these because changing them shouldn't trigger a "refresh"
// but we need them to calculate the delta (speed) between fetches.
const prevNetRef = useRef<Record<string, NetworkInterface> | null>(null);
const lastFetchRef = useRef<number>(0);
useEffect(() => {
const fetchData = async () => {
try {
const now = Date.now();
const data = await getStats();
// 1. Calculate speeds if we have previous data
if (prevNetRef.current && lastFetchRef.current > 0) {
const elapsed = (now - lastFetchRef.current) / 1000;
const speeds: Record<string, { rx: number; tx: number }> = {};
for (const iface of Object.keys(data.network)) {
const prev = prevNetRef.current[iface];
if (prev) {
speeds[iface] = {
rx: Math.max(0, (data.network[iface].rx - prev.rx) / elapsed),
tx: Math.max(0, (data.network[iface].tx - prev.tx) / elapsed),
};
}
}
setNetSpeed(speeds);
}
// 2. Update our "silent" trackers
prevNetRef.current = data.network;
lastFetchRef.current = now;
// 3. Update the UI state with new data
// React's Virtual DOM will only update the changed text/numbers.
setStats(data);
} catch (e) {
if (e instanceof Error && e.message === "UNAUTHORIZED") {
router.push(
"/auth?callbackUrl=" + encodeURIComponent(window.location.pathname),
);
return;
}
console.error("Dashboard fetch failed:", e);
}
};
// Initial fetch
fetchData();
// Start the 4s loop
const id = setInterval(fetchData, 4000);
// Clean up on unmount so we don't have multiple intervals running
return () => clearInterval(id);
}, []);
useEffect(() => {
const fetchPower = async () => {
try {
setPower(await getPower());
} catch (e) {
if (e instanceof Error && e.message === "UNAUTHORIZED") return;
console.error("Power fetch failed:", e);
}
};
fetchPower();
const id = setInterval(fetchPower, 10000);
return () => clearInterval(id);
}, []); // Empty array means this setup only happens ONCE.
// Derived values for the UI
const primaryIface = stats
? Object.keys(stats.network).find(
(k) =>
!k.startsWith("docker") &&
!k.startsWith("br-") &&
stats.network[k].rx > 0,
)
: null;
const primarySpeed = primaryIface ? netSpeed[primaryIface] || null : null;
return (
<div className="max-w-5xl mx-auto px-6 pb-20">
<NavBar online={!!stats} />
<Hero lastUpdated={stats?.timestamp ?? null} />
<div className="flex items-baseline justify-between mb-5">
<h2 className="text-lg font-medium tracking-tight text-gray-900">
System Stats
</h2>
</div>
<StatsGrid stats={stats} />
<div className="grid grid-cols-1 md:grid-cols-2 gap-3.5 mb-11">
<ServicesCard services={stats?.services ?? null} delay={200} />
<div className="flex flex-col gap-3.5">
<UptimeCard uptime={stats?.uptime ?? null} delay={250} />
<NetworkCard
iface={primaryIface ?? null}
speed={primarySpeed}
delay={300}
/>
</div>
</div>
<div className="flex items-baseline justify-between mb-5">
<h2 className="text-lg font-medium tracking-tight text-gray-900">
Power Consumption
</h2>
</div>
<PowerGrid power={power} />
<LinksGrid />
</div>
);
}