import React, { useEffect, useState } from 'react'; import api from '../utils/api'; import './Dashboard.css'; import './Swarm.css'; import { HardDrive, RefreshCw, Trash2, Wifi, WifiOff } from 'lucide-react'; interface SwarmHost { uuid: string; name: string; address: string; agent_port: number; status: string; last_heartbeat: string | null; client_cert_fingerprint: string; updater_cert_fingerprint: string | null; enrolled_at: string; notes: string | null; } const shortFp = (fp: string): string => (fp ? fp.slice(0, 16) + '…' : '—'); const SwarmHosts: React.FC = () => { const [hosts, setHosts] = useState([]); const [loading, setLoading] = useState(true); const [decommissioning, setDecommissioning] = useState(null); const [error, setError] = useState(null); const fetchHosts = async () => { try { const res = await api.get('/swarm/hosts'); setHosts(res.data); setError(null); } catch (err: any) { setError(err?.response?.data?.detail || 'Failed to fetch swarm hosts'); } finally { setLoading(false); } }; useEffect(() => { fetchHosts(); const t = setInterval(fetchHosts, 10000); return () => clearInterval(t); }, []); const handleDecommission = async (host: SwarmHost) => { if (!window.confirm(`Decommission ${host.name} (${host.address})? This removes certs and decky mappings.`)) return; setDecommissioning(host.uuid); try { await api.delete(`/swarm/hosts/${host.uuid}`); await fetchHosts(); } catch (err: any) { alert(err?.response?.data?.detail || 'Decommission failed'); } finally { setDecommissioning(null); } }; return (

SWARM Hosts

{error &&
{error}
}
{loading ? (

Loading hosts…

) : hosts.length === 0 ? (

No swarm hosts enrolled yet. Head to SWARM → Agent Enrollment to onboard one.

) : ( {hosts.map((h) => ( ))}
Status Name Address Last heartbeat Client cert Enrolled
{h.status === 'active' ? : } {h.status} {h.name} {h.address}:{h.agent_port} {h.last_heartbeat ? new Date(h.last_heartbeat).toLocaleString() : '—'} {shortFp(h.client_cert_fingerprint)} {new Date(h.enrolled_at).toLocaleString()}
)}
); }; export default SwarmHosts;