"use client";
import { useEffect, useState, useCallback } from "react";
import { toast } from "sonner";
import { fetchNetworkStatus, type NetworkSwitchStatus } from "@/lib/api";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Skeleton } from "@/components/ui/skeleton";
import {
Network, Wifi, WifiOff, AlertTriangle, CheckCircle2,
RefreshCw, Cpu, HardDrive, Thermometer, Activity,
} from "lucide-react";
import { cn } from "@/lib/utils";
const SITE_ID = "sg-01";
function formatUptime(seconds: number | null): string {
if (seconds === null) return "—";
const d = Math.floor(seconds / 86400);
const h = Math.floor((seconds % 86400) / 3600);
if (d > 0) return `${d}d ${h}h`;
const m = Math.floor((seconds % 3600) / 60);
return h > 0 ? `${h}h ${m}m` : `${m}m`;
}
function StateChip({ state }: { state: NetworkSwitchStatus["state"] }) {
const cfg = {
up: { label: "Up", icon: CheckCircle2, cls: "bg-green-500/10 text-green-400 border-green-500/20" },
degraded: { label: "Degraded", icon: AlertTriangle, cls: "bg-amber-500/10 text-amber-400 border-amber-500/20" },
down: { label: "Down", icon: WifiOff, cls: "bg-destructive/10 text-destructive border-destructive/20" },
unknown: { label: "Unknown", icon: WifiOff, cls: "bg-muted/50 text-muted-foreground border-border" },
}[state] ?? { label: state, icon: WifiOff, cls: "bg-muted/50 text-muted-foreground border-border" };
const Icon = cfg.icon;
return (
{cfg.label}
);
}
function MiniBar({ value, max, className }: { value: number; max: number; className?: string }) {
const pct = Math.min(100, (value / max) * 100);
return (
);
}
function SwitchCard({ sw }: { sw: NetworkSwitchStatus }) {
const portPct = sw.active_ports !== null ? Math.round((sw.active_ports / sw.port_count) * 100) : null;
const stateOk = sw.state === "up";
const stateDeg = sw.state === "degraded";
return (
{sw.role}
·
{sw.room_id}
·
{sw.rack_id}
{/* Ports headline */}
Ports Active
{sw.active_ports !== null ? Math.round(sw.active_ports) : "—"} / {sw.port_count}
{portPct !== null &&
{portPct}% utilised
}
= 90 ? "bg-amber-500" : "bg-primary"}
/>
{/* Bandwidth */}
Ingress
{sw.bandwidth_in_mbps !== null ? `${sw.bandwidth_in_mbps.toFixed(0)} Mbps` : "—"}
Egress
{sw.bandwidth_out_mbps !== null ? `${sw.bandwidth_out_mbps.toFixed(0)} Mbps` : "—"}
{/* CPU + Mem */}
CPU
= 80 ? "bg-destructive" :
(sw.cpu_pct ?? 0) >= 60 ? "bg-amber-500" : "bg-green-500"
}
/>
{sw.cpu_pct !== null ? `${sw.cpu_pct.toFixed(0)}%` : "—"}
Mem
= 85 ? "bg-destructive" :
(sw.mem_pct ?? 0) >= 70 ? "bg-amber-500" : "bg-blue-500"
}
/>
{sw.mem_pct !== null ? `${sw.mem_pct.toFixed(0)}%` : "—"}
{/* Footer stats */}
{sw.temperature_c !== null ? `${sw.temperature_c.toFixed(0)}°C` : "—"}
Pkt loss: 1 ? "text-destructive" :
(sw.packet_loss_pct ?? 0) > 0.1 ? "text-amber-400" : "text-green-400"
)}>
{sw.packet_loss_pct !== null ? `${sw.packet_loss_pct.toFixed(2)}%` : "—"}
Up: {formatUptime(sw.uptime_s)}
);
}
export default function NetworkPage() {
const [switches, setSwitches] = useState([]);
const [loading, setLoading] = useState(true);
const load = useCallback(async () => {
try {
const data = await fetchNetworkStatus(SITE_ID);
setSwitches(data);
} catch { toast.error("Failed to load network data"); }
finally { setLoading(false); }
}, []);
useEffect(() => {
load();
const id = setInterval(load, 30_000);
return () => clearInterval(id);
}, [load]);
const down = switches.filter((s) => s.state === "down").length;
const degraded = switches.filter((s) => s.state === "degraded").length;
const up = switches.filter((s) => s.state === "up").length;
return (
Network Infrastructure
Singapore DC01 — switch health · refreshes every 30s
{/* Summary chips */}
{!loading && switches.length > 0 && (
{up}
up
{degraded > 0 && (
{degraded}
degraded
)}
{down > 0 && (
{down}
down
)}
{switches.length} switches total
)}
{/* Switch cards */}
{loading ? (
{Array.from({ length: 3 }).map((_, i) => )}
) : switches.length === 0 ? (
No network switch data available
Ensure the simulator is running and network bots are publishing data
) : (
{switches.map((sw) => )}
)}
);
}