Feature: PF States, HAProxy + Caddy collectors and dashboard
This commit is contained in:
@@ -73,7 +73,17 @@ export default function AgentPanel({ agentId, customers, onClose, onReload }) {
|
||||
// Filter sub-items by platform and determine visible main categories
|
||||
const getVisibleSubs = (catId) => {
|
||||
const items = subItems[catId] || []
|
||||
return items.filter(s => !s.platforms || s.platforms.includes(platform))
|
||||
let filtered = items.filter(s => !s.platforms || s.platforms.includes(platform))
|
||||
// Dynamic sub-tabs based on data availability
|
||||
if (catId === 'netzwerk') {
|
||||
if (sys?.haproxy) {
|
||||
filtered = [...filtered, { id: 'haproxy', label: 'HAProxy', icon: Server }]
|
||||
}
|
||||
if (sys?.caddy) {
|
||||
filtered = [...filtered, { id: 'caddy', label: 'Caddy', icon: Globe }]
|
||||
}
|
||||
}
|
||||
return filtered
|
||||
}
|
||||
|
||||
const visibleMainTabs = mainCategories.filter(cat => {
|
||||
@@ -111,6 +121,8 @@ export default function AgentPanel({ agentId, customers, onClose, onReload }) {
|
||||
if (tab === 'tasks') return <TasksTab agentId={agentId} />
|
||||
if (tab === 'backups') return platform === 'linux' ? <PVEBackupsTab sys={sys} /> : <BackupsTab agentId={agentId} />
|
||||
if (tab === 'agent') return <AgentTab agent={data?.agent} status={data?.status} platform={platform} />
|
||||
if (tab === 'haproxy') return <HAProxyTab sys={sys} />
|
||||
if (tab === 'caddy') return <CaddyTab sys={sys} />
|
||||
return null
|
||||
}
|
||||
|
||||
@@ -363,6 +375,14 @@ function OverviewTab({ sys }) {
|
||||
sub={rootDisk ? `${formatBytes(rootDisk.used_bytes)} / ${formatBytes(rootDisk.total_bytes)}` : ''}
|
||||
bar={diskPct} barColor={barColor(diskPct || 0)} />
|
||||
<MetricCard icon={Clock} label="Uptime" value={sys.uptime_seconds ? formatUptime(sys.uptime_seconds) : '—'} />
|
||||
{sys.pf_states && (
|
||||
<MetricCard icon={Shield} label="PF States"
|
||||
value={sys.pf_states.hard_limit > 0 ? `${sys.pf_states.current_entries.toLocaleString('de-DE')} / ${sys.pf_states.hard_limit.toLocaleString('de-DE')}` : `${sys.pf_states.current_entries.toLocaleString('de-DE')}`}
|
||||
sub={`Search: ${sys.pf_states.search_rate?.toFixed(1)}/s · Insert: ${sys.pf_states.insert_rate?.toFixed(1)}/s`}
|
||||
bar={sys.pf_states.hard_limit > 0 ? (sys.pf_states.current_entries / sys.pf_states.hard_limit * 100) : null}
|
||||
barColor={barColor(sys.pf_states.hard_limit > 0 ? (sys.pf_states.current_entries / sys.pf_states.hard_limit * 100) : 0)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<h3 className="text-sm font-medium text-gray-300 mt-4">System Status</h3>
|
||||
@@ -2289,6 +2309,103 @@ function AgentTab({ agent, status, platform }) {
|
||||
)
|
||||
}
|
||||
|
||||
function HAProxyTab({ sys }) {
|
||||
const ha = sys?.haproxy
|
||||
if (!ha) return <div className="text-gray-500">Keine HAProxy-Daten</div>
|
||||
const info = ha.info || {}
|
||||
const entries = ha.entries || []
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<h3 className="text-sm font-medium text-gray-300">HAProxy Info</h3>
|
||||
<div className="grid grid-cols-4 gap-3">
|
||||
<StatusCard icon={Clock} value={info.uptime || '—'} label="Uptime" />
|
||||
<StatusCard icon={Network} value={info.current_conns} label="Aktive Verb." />
|
||||
<StatusCard icon={Shield} value={info.current_ssl} label="SSL Verb." />
|
||||
<StatusCard value={info.total_requests?.toLocaleString('de-DE') || '0'} label="Requests Total" />
|
||||
</div>
|
||||
|
||||
<h3 className="text-sm font-medium text-gray-300">Proxies</h3>
|
||||
<div className="bg-gray-800/50 rounded-lg border border-gray-700/50 overflow-hidden">
|
||||
<table className="w-full text-sm">
|
||||
<thead>
|
||||
<tr className="text-left text-gray-500 text-xs border-b border-gray-700">
|
||||
<th className="px-3 py-2">Proxy</th>
|
||||
<th className="px-3 py-2">Server</th>
|
||||
<th className="px-3 py-2">Typ</th>
|
||||
<th className="px-3 py-2">Status</th>
|
||||
<th className="px-3 py-2">Sessions</th>
|
||||
<th className="px-3 py-2">Requests</th>
|
||||
<th className="px-3 py-2">Traffic In</th>
|
||||
<th className="px-3 py-2">Traffic Out</th>
|
||||
<th className="px-3 py-2">Fehler</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="divide-y divide-gray-700/50">
|
||||
{entries.map((e, i) => (
|
||||
<tr key={i}>
|
||||
<td className="px-3 py-2 text-white">{e.proxy_name}</td>
|
||||
<td className="px-3 py-2 text-gray-400">{e.server_name}</td>
|
||||
<td className="px-3 py-2"><span className="text-xs px-1.5 py-0.5 rounded bg-gray-700 text-gray-300">{e.type}</span></td>
|
||||
<td className="px-3 py-2">
|
||||
<span className={`text-xs px-1.5 py-0.5 rounded ${
|
||||
['OPEN', 'UP'].includes(e.status) ? 'bg-green-900/50 text-green-400' :
|
||||
e.status === 'DOWN' ? 'bg-red-900/50 text-red-400' :
|
||||
'bg-gray-700 text-gray-400'
|
||||
}`}>{e.status}</span>
|
||||
</td>
|
||||
<td className="px-3 py-2 text-gray-400">{e.current_sessions} / {e.total_sessions?.toLocaleString('de-DE')}</td>
|
||||
<td className="px-3 py-2 text-gray-400">{e.requests_total?.toLocaleString('de-DE') || '—'}</td>
|
||||
<td className="px-3 py-2 text-gray-400">{formatBytes(e.bytes_in)}</td>
|
||||
<td className="px-3 py-2 text-gray-400">{formatBytes(e.bytes_out)}</td>
|
||||
<td className="px-3 py-2 text-gray-400">{(e.errors_req || 0) + (e.errors_conn || 0) + (e.errors_resp || 0)}</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function CaddyTab({ sys }) {
|
||||
const caddy = sys?.caddy
|
||||
if (!caddy) return <div className="text-gray-500">Keine Caddy-Daten</div>
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<h3 className="text-sm font-medium text-gray-300">Caddy Reverse Proxy</h3>
|
||||
{caddy.version && (
|
||||
<div className="text-xs text-gray-400">Version: <span className="text-white font-mono">{caddy.version}</span></div>
|
||||
)}
|
||||
<div className="bg-gray-800/50 rounded-lg border border-gray-700/50 overflow-hidden">
|
||||
<table className="w-full text-sm">
|
||||
<thead>
|
||||
<tr className="text-left text-gray-500 text-xs border-b border-gray-700">
|
||||
<th className="px-3 py-2">Domain</th>
|
||||
<th className="px-3 py-2">Upstream</th>
|
||||
<th className="px-3 py-2">TLS</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="divide-y divide-gray-700/50">
|
||||
{(caddy.sites || []).map((s, i) => (
|
||||
<tr key={i}>
|
||||
<td className="px-3 py-2 text-white font-mono">{s.domain}</td>
|
||||
<td className="px-3 py-2 text-gray-400 font-mono">{s.upstream}</td>
|
||||
<td className="px-3 py-2">
|
||||
<span className={`text-xs px-1.5 py-0.5 rounded ${
|
||||
s.tls ? 'bg-green-900/50 text-green-400' : 'bg-red-900/50 text-red-400'
|
||||
}`}>{s.tls ? 'Aktiv' : 'Aus'}</span>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function gwColor(status) {
|
||||
const s = (status || '').toLowerCase()
|
||||
if (s === 'online' || s === 'none') return 'text-green-400'
|
||||
|
||||
Reference in New Issue
Block a user