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
| Name |
Gateway |
Status |
RTT |
Loss |
{sys.gateways.map((gw) => (
| {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 (
| Interface |
Status |
MAC |
Adressen |
RX |
TX |
{ifaces.map((i) => (
| {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 (
| Interface |
Public Key |
Endpoint |
Transfer |
Last Handshake |
{peers.map((p, i) => (
| {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 (
| Ziel |
Gateway |
Flags |
Interface |
{routes.map((r, i) => (
| {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 (
| IP |
MAC |
Hostname |
Ablauf |
{leases.map((l, i) => (
| {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 (
| Name |
Aussteller |
Gueltig bis |
Typ |
{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 (
| {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
) : (
| ID |
Erstellt |
Groesse |
Hash |
{backups.map((b) => (
| {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`
}