import { useEffect, useState } from 'react' import api from '../api/client' import StatusBadge from './StatusBadge' import { X, Cpu, MemoryStick, HardDrive, Clock, Network, Shield, Route, Globe, Key, Download, Terminal, Wifi, } from 'lucide-react' const tabs = [ { id: 'overview', label: 'Uebersicht' }, { id: 'interfaces', label: 'Interfaces' }, { id: 'services', label: 'Dienste' }, { id: 'vpn', label: 'VPN' }, { id: 'routes', label: 'Routen' }, { id: 'dhcp', label: 'DHCP' }, { id: 'certs', label: 'Zertifikate' }, { id: 'backups', label: 'Backups' }, ] export default function AgentPanel({ agentId, customers, onClose, onReload }) { const [data, setData] = useState(null) const [tab, setTab] = useState('overview') const [loading, setLoading] = useState(true) useEffect(() => { setLoading(true) setTab('overview') api.getAgent(agentId).then(setData).finally(() => setLoading(false)) }, [agentId]) useEffect(() => { if (!data) return const iv = setInterval(() => { api.getAgent(agentId).then(setData) }, 30000) return () => clearInterval(iv) }, [agentId, data]) if (loading) return
Laden...
if (!data) return
Nicht gefunden
const { agent, system_data: sys, status } = data const cust = agent.customer_id ? customers.find((c) => c.id === agent.customer_id) : null return ( {/* Header */}

{agent.name}

{cust && (
{cust.number} — {cust.name}
)}
{agent.hostname} · IP: {agent.ip} · {sys?.opnsense_version || agent.opnsense_version || '—'}
{/* Tabs */}
{tabs.map((t) => ( ))}
{/* Content */}
{!sys ? (
Keine Systemdaten
) : tab === 'overview' ? ( ) : tab === 'interfaces' ? ( ) : tab === 'services' ? ( ) : tab === 'vpn' ? ( ) : tab === 'routes' ? ( ) : tab === 'dhcp' ? ( ) : tab === 'certs' ? ( ) : tab === 'backups' ? ( ) : null}
) } function Panel({ onClose, children }) { return ( <>
{children}
) } function MetricCard({ icon: Icon, label, value, sub, bar, barColor }) { return (
{Icon && } {label}
{value}
{sub &&
{sub}
} {bar !== undefined && bar !== null && (
)}
) } function OverviewTab({ sys }) { const rootDisk = sys.disks?.find((d) => d.mount_point === '/') const diskPct = rootDisk && rootDisk.total_bytes > 0 ? (rootDisk.used_bytes / rootDisk.total_bytes * 100) : null const ramPct = sys.memory?.total_bytes > 0 ? (sys.memory.used_bytes / sys.memory.total_bytes * 100) : null const cpuPct = sys.cpu?.usage_percent const barColor = (v) => v > 90 ? 'bg-red-500' : v > 70 ? 'bg-yellow-500' : v > 50 ? 'bg-orange-500' : 'bg-green-500' const ifaceCount = sys.network_interfaces?.filter((i) => i.status === 'active' || i.status === 'up').length || 0 const gwCount = sys.gateways?.length || 0 const wgCount = sys.wireguard_peers?.length || 0 const routeCount = sys.routes?.length || 0 const certCount = sys.certificates?.length || 0 const svcRunning = sys.services?.filter((s) => s.running).length || 0 const svcTotal = sys.services?.length || 0 return ( <>

System-Metriken

Firewall Status

{/* Gateways */} {sys.gateways && sys.gateways.length > 0 && ( <>

Gateways

{sys.gateways.map((gw) => ( ))}
Name Gateway Status RTT Loss
{gw.name} {gw.gateway} {gw.status === 'none' ? 'OK' : gw.status} {gw.delay || '—'} {gw.loss || '—'}
)} ) } function StatusCard({ icon: Icon, value, label }) { return (
{Icon && }
{value}
{label}
) } function InterfacesTab({ sys }) { const ifaces = sys.network_interfaces || [] return (
{ifaces.map((i) => ( ))}
Interface Status MAC Adressen RX TX
{i.name} {i.status || '—'} {i.mac || '—'} {i.addresses && i.addresses.length > 0 ? i.addresses.join(', ') : '—'} {formatBytes(i.rx_bytes)} {formatBytes(i.tx_bytes)}
) } function ServicesTab({ sys }) { const services = sys.services || [] const running = services.filter((s) => s.running) const stopped = services.filter((s) => !s.running) return (
{running.length} aktiv / {stopped.length} inaktiv
{services.map((svc) => ( {svc.name} ))}
) } function VPNTab({ sys }) { const peers = sys.wireguard_peers || [] if (peers.length === 0) return
Keine WireGuard-Peers
return (
{peers.map((p, i) => ( ))}
Interface Public Key Endpoint Transfer Last Handshake
{p.interface || '—'} {(p.public_key || '—').substring(0, 16)}... {p.endpoint || '—'} {p.transfer_rx ? `RX: ${formatBytes(p.transfer_rx)} / TX: ${formatBytes(p.transfer_tx)}` : '—'} {p.latest_handshake || '—'}
) } function RoutesTab({ sys }) { const routes = sys.routes || [] if (routes.length === 0) return
Keine Routen
return (
{routes.map((r, i) => ( ))}
Ziel Gateway Flags Interface
{r.destination} {r.gateway} {r.flags} {r.interface_name || r.iface}
) } function DHCPTab({ sys }) { const leases = sys.dhcp_leases || [] if (leases.length === 0) return
Keine DHCP Leases
return (
{leases.map((l, i) => ( ))}
IP MAC Hostname Ablauf
{l.ip || l.address} {l.mac || l.hwaddr} {l.hostname || '—'} {l.expires || l.expire || '—'}
) } function CertsTab({ sys }) { const certs = sys.certificates || [] if (certs.length === 0) return
Keine Zertifikate
return (
{certs.map((c, i) => { const expires = c.not_after ? new Date(c.not_after) : null const isExpired = expires && expires < new Date() const soonExpires = expires && !isExpired && (expires - new Date()) < 30 * 86400000 return ( ) })}
Name Aussteller Gueltig bis Typ
{c.name || c.subject || '—'} {c.issuer || '—'} {expires ? expires.toLocaleDateString('de-DE') : '—'} {c.type || (c.is_ca ? 'CA' : 'Cert')}
) } function BackupsTab({ agentId }) { const [backups, setBackups] = useState([]) const [loading, setLoading] = useState(true) useEffect(() => { api.getBackups(agentId).then(setBackups).catch(() => {}).finally(() => setLoading(false)) }, [agentId]) const triggerBackup = async () => { await api.triggerBackup(agentId) setTimeout(() => { api.getBackups(agentId).then(setBackups) }, 3000) } if (loading) return
Laden...
return (
{backups.length === 0 ? (
Keine Backups vorhanden
) : (
{backups.map((b) => ( ))}
ID Erstellt Groesse Hash
{b.id} {new Date(b.created_at).toLocaleString('de-DE')} {formatBytes(b.size)} {(b.hash || '').substring(0, 16)}...
)}
) } function formatBytes(bytes) { if (!bytes || bytes === 0) return '0 B' const k = 1024 const sizes = ['B', 'KB', 'MB', 'GB', 'TB'] const i = Math.floor(Math.log(bytes) / Math.log(k)) return `${(bytes / Math.pow(k, i)).toFixed(1)} ${sizes[i]}` } function formatUptime(seconds) { const d = Math.floor(seconds / 86400) const h = Math.floor((seconds % 86400) / 3600) const m = Math.floor((seconds % 3600) / 60) if (d > 0) return `${d}d ${h}h ${m}m` return `${h}h ${m}m` }