import { useEffect, useState } from 'react'
import { useParams, useNavigate } from 'react-router-dom'
import api from '../api/client'
import StatusBadge from '../components/StatusBadge'
import {
Cpu,
HardDrive,
MemoryStick,
Network,
Clock,
Shield,
ArrowLeft,
Terminal,
Download,
} from 'lucide-react'
export default function AgentDetail() {
const { id } = useParams()
const navigate = useNavigate()
const [data, setData] = useState(null)
const [loading, setLoading] = useState(true)
const reload = () => {
api.getAgent(id).then(setData).finally(() => setLoading(false))
}
useEffect(() => {
reload()
const interval = setInterval(reload, 30000)
return () => clearInterval(interval)
}, [id])
if (loading) return
Laden...
if (!data) return Agent nicht gefunden
const { agent, system_data: sys, status } = data
return (
{/* Header */}
{agent.name}
{agent.ip} — {agent.opnsense_version || 'Version unbekannt'}
api.triggerBackup(id)} />
{}} />
{!sys ? (
Noch keine Systemdaten vorhanden
) : (
<>
{/* Quick Stats */}
{/* Network Interfaces */}
{sys.network_interfaces && sys.network_interfaces.length > 0 && (
{sys.network_interfaces
.filter((i) => i.addresses && i.addresses.length > 0)
.map((iface) => (
{iface.name}
{iface.status}
{iface.addresses?.filter((a) => !a.includes(':')).join(', ')}
RX: {formatBytes(iface.rx_bytes)}
TX: {formatBytes(iface.tx_bytes)}
))}
)}
{/* Services */}
{sys.services && sys.services.length > 0 && (
{sys.services.map((svc) => (
{svc.name}
))}
)}
{/* Gateways */}
{sys.gateways && sys.gateways.length > 0 && (
| Name |
Gateway |
Status |
RTT |
Loss |
{sys.gateways.map((gw) => (
| {gw.name} |
{gw.gateway} |
{gw.status === 'none' ? 'OK' : gw.status}
|
{gw.delay || '—'} |
{gw.loss || '—'} |
))}
)}
{/* Disks */}
{sys.disks && sys.disks.length > 0 && (
| Dataset |
Mountpoint |
Belegt |
Gesamt |
% |
{sys.disks.map((d) => {
const pct = d.total_bytes > 0 ? (d.used_bytes / d.total_bytes * 100) : 0
return (
| {d.filesystem} |
{d.mount_point} |
{formatBytes(d.used_bytes)} |
{formatBytes(d.total_bytes)} |
90 ? 'bg-red-500' : pct > 70 ? 'bg-yellow-500' : 'bg-green-500'}`}
style={{ width: `${pct}%` }}
/>
{pct.toFixed(0)}%
|
)
})}
)}
>
)}
)
}
function Section({ title, icon: Icon, children }) {
return (
{Icon && }
{title}
{children}
)
}
function ActionButton({ icon: Icon, label, onClick }) {
return (
)
}
function QuickStat({ icon: Icon, label, value, sub }) {
return (
{label}
{value}
{sub &&
{sub}
}
)
}
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)
if (d > 0) return `${d}d ${h}h`
const m = Math.floor((seconds % 3600) / 60)
return `${h}h ${m}m`
}