"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.name}

{sw.model}

{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) => )}
)}
); }