'use client'; import { useState, useEffect } from 'react'; import { Cpu, Globe, Wifi, WifiOff, Activity, Zap, Server, ArrowRight } from 'lucide-react'; import Link from 'next/link'; interface Node { name: string; ip: string; role: string; capacity: number; model: string; status: string; latencyMs: number; services: string[]; } interface TunnelRoute { hostname: string; service: string; pi: string; group: string; } interface FleetData { nodes: Node[]; summary: { total_nodes: number; online_nodes: number; total_capacity: number; tunnel_routes: number }; } interface TunnelData { routes: TunnelRoute[]; by_pi: Record; tunnel_id: string; } const ROLE_COLORS: Record = { PRIMARY: '#FF1D6C', SECONDARY: '#2979FF', TERTIARY: '#F5A623', IDENTITY: '#9C27B0', }; const ROLE_GLOW: Record = { PRIMARY: 'shadow-[0_0_20px_rgba(255,29,108,0.3)]', SECONDARY: 'shadow-[0_0_20px_rgba(41,121,255,0.3)]', TERTIARY: 'shadow-[0_0_20px_rgba(245,166,35,0.3)]', IDENTITY: 'shadow-[0_0_20px_rgba(156,39,176,0.3)]', }; export default function FleetPage() { const [fleet, setFleet] = useState(null); const [tunnel, setTunnel] = useState(null); const [loading, setLoading] = useState(true); const [lastChecked, setLastChecked] = useState(''); const load = async () => { try { const [f, t] = await Promise.all([fetch('/api/fleet'), fetch('/api/tunnel')]); if (f.ok) setFleet(await f.json()); if (t.ok) setTunnel(await t.json()); setLastChecked(new Date().toLocaleTimeString()); } catch {} setLoading(false); }; useEffect(() => { load(); const iv = setInterval(load, 30000); return () => clearInterval(iv); }, []); const onlineCount = fleet?.summary.online_nodes ?? 0; const totalCapacity = fleet?.summary.total_capacity ?? 30000; const tunnelRoutes = fleet?.summary.tunnel_routes ?? 14; return (
{/* Header */}

Pi Fleet

Cloudflare Tunnel → Raspberry Pi Network

{/* Stats bar */}
{[ { label: 'Nodes', value: `${onlineCount}/${fleet?.summary.total_nodes ?? 4}`, icon: Server, color: '#22c55e' }, { label: 'Agent Slots', value: totalCapacity.toLocaleString(), icon: Cpu, color: '#FF1D6C' }, { label: 'Tunnel Routes', value: tunnelRoutes, icon: Globe, color: '#2979FF' }, { label: 'Tunnel ID', value: '8ae67ab0', icon: Zap, color: '#F5A623' }, ].map(s => (
{s.label}

{s.value}

))}
{/* Node cards */}
{(fleet?.nodes ?? []).map(node => { const routes = tunnel?.by_pi[node.name] ?? []; const color = ROLE_COLORS[node.role] ?? '#fff'; const glow = ROLE_GLOW[node.role] ?? ''; return (
{/* Node header */}
{node.status === 'online' ? : }

{node.name}

{node.ip} · {node.model}

{node.role} {node.capacity > 0 && ( {node.capacity.toLocaleString()} slots )}
{/* Latency */}
{node.status === 'online' ? node.latencyMs > 0 ? `${node.latencyMs}ms` : 'online' : 'offline / unreachable'}
{/* Routes */} {routes.length > 0 && (

Tunnel Routes

{routes.map(r => (
{r.hostname} {r.service.split(':').pop()}
))}
)}
); })}
{/* All routes table */} {tunnel && (

All Tunnel Routes ({tunnel.routes.length})

{tunnel.routes.map(r => (
{r.hostname} {r.service} {r.pi}
))}
)}
); }