import { useEffect, useRef, useState } from 'react'
import { api, type HostMetrics as HostMetricsData, type Integration } from '../lib/api'
const TEXT_SECONDARY = '#7A7D85'
const TEXT_PRIMARY = '#E8E6E0'
const GOLD = '#C8A434'
const cardBase: React.CSSProperties = {
backgroundColor: 'rgba(10, 10, 12, 0.92)',
border: '1px solid rgba(200, 164, 52, 0.08)',
borderRadius: '12px',
padding: '16px',
boxShadow: '0 0 20px rgba(200, 164, 52, 0.03)',
}
const sectionTitle: React.CSSProperties = {
fontSize: '11px',
textTransform: 'uppercase',
letterSpacing: '1.5px',
color: TEXT_SECONDARY,
fontWeight: 500,
marginBottom: '12px',
}
function percentColor(p: number | null): string {
if (p === null) return TEXT_SECONDARY
if (p >= 90) return '#E74C3C'
if (p >= 75) return '#E67E22'
return '#2ECC71'
}
function Gauge({ label, percent, sub }: { label: string; percent: number | null; sub?: string }) {
return (
{label}
{percent !== null ? `${percent}%` : '—'}
{sub &&
{sub}
}
)
}
export default function HostMetrics() {
const [hosts, setHosts] = useState([])
const [hostId, setHostId] = useState(null)
const [metrics, setMetrics] = useState(null)
const [error, setError] = useState(null)
const [loading, setLoading] = useState(false)
const pollRef = useRef | null>(null)
useEffect(() => {
api.listIntegrations().then(({ integrations }) => {
setHosts(integrations.filter((i) => i.type === 'ssh'))
})
}, [])
useEffect(() => {
return () => {
if (pollRef.current) clearInterval(pollRef.current)
}
}, [])
function fetchMetrics(id: number) {
setLoading(true)
api
.getHostMetrics(id)
.then((data) => {
setMetrics(data)
setError(null)
})
.catch((err) => setError(err instanceof Error ? err.message : 'Failed to load metrics'))
.finally(() => setLoading(false))
}
function handleSelect(id: number) {
setHostId(id)
setMetrics(null)
setError(null)
if (pollRef.current) clearInterval(pollRef.current)
fetchMetrics(id)
pollRef.current = setInterval(() => fetchMetrics(id), 5000)
}
const host = hosts.find((h) => h.id === hostId)
return (
SSH Hosts
{hosts.length === 0 && (
No SSH integrations configured. Add one in Settings → Integrations.
)}
{hosts.map((h) => (
))}
{host ? host.name : 'Select a host to view live metrics'}
{error ?? (loading ? 'Refreshing…' : metrics ? 'Live · updates every 5s' : '')}
{metrics && (
<>
l.toFixed(2)).join(' / ') ?? '—'}` : undefined} />
Uptime
{metrics.uptime.formatted ?? '—'}
{metrics.system.hostname ?? ''} {metrics.system.os ? `· ${metrics.system.os}` : ''}
Network Interfaces
{metrics.network.interfaces.length === 0 ? (
No interfaces reported.
) : (
{metrics.network.interfaces.map((iface) => (
{iface.name}
{iface.ip || '—'}
{iface.state}
))}
)}
Listening Ports ({metrics.ports.ports.length})
{metrics.ports.ports.length === 0 ? (
None detected.
) : (
{metrics.ports.ports.slice(0, 12).map((p, i) => (
{p.protocol}/{p.localPort}
{p.process ?? '—'}
))}
)}
Top Processes
{metrics.processes.top.length === 0 ? (
No process data.
) : (
| PID |
User |
CPU% |
Mem% |
Command |
{metrics.processes.top.map((p) => (
| {p.pid} |
{p.user} |
{p.cpu} |
{p.mem} |
{p.command} |
))}
)}
{metrics.processes.total !== null && (
{metrics.processes.total} total · {metrics.processes.running ?? '—'} running
)}
Firewall ({metrics.firewall.type})
{metrics.firewall.type === 'none' ? (
No firewall data available (requires root, or none configured).
) : (
{metrics.firewall.chains.map((c) => (
{c.name}
{c.policy} · {c.rules.length} rules
))}
)}
Login Activity
{metrics.loginStats.recentLogins.length === 0 && metrics.loginStats.failedLogins.length === 0 ? (
No login history readable.
) : (
{metrics.loginStats.totalLogins} recent logins · {metrics.loginStats.failedLogins.length} failed attempts · {metrics.loginStats.uniqueIPs} unique IPs
)}
>
)}
{!metrics && !error && host &&
Loading metrics…
}
)
}