diff --git a/hbd/server/templates/plugins.html b/hbd/server/templates/plugins.html index 94f3659..ab6d577 100644 --- a/hbd/server/templates/plugins.html +++ b/hbd/server/templates/plugins.html @@ -445,8 +445,12 @@ `; if (data.connections && data.connections.length) { - html += `
Connectivity
- + html += `
Connectivity
`; + for (const c of data.connections) { + html += `
${escHtml(c.family)} · ${escHtml(c.addr || '—')} RTT
+
`; + } + html += `
`; @@ -495,6 +499,15 @@ } el.innerHTML = html; + + if (data.connections && data.connections.length) { + const rttThresholds = (data.thresholds || []).find(t => t.metric === 'rtt') || null; + for (const c of data.connections) { + fetchRttHistory(hostname, c.family) + .then(samples => renderRttChart(hostname, c.family, samples, rttThresholds)) + .catch(() => {}); + } + } } async function fetchHostGlance(hostname) { @@ -899,6 +912,35 @@ }); } + async function fetchRttHistory(hostname, family) { + const plugin = `rtt_${family.toLowerCase()}`; + const r = await fetch(`/api/0/hosts/${encodeURIComponent(hostname)}/plugins/${plugin}?limit=100`); + if (!r.ok) return []; + const json = await r.json(); + return json.samples || []; + } + + function renderRttChart(hostname, family, samples, rttThresholds) { + const pts = samples + .filter(s => s.data.rtt != null && s.data.rtt > 0) + .map(s => ({ t: s.timestamp, v: s.data.rtt })); + + renderTimeSeriesChart(`rtt-chart-${hostname}-${family}`, pts, { + yDomain: 'auto', + clipId: `rtt-clip-${hostname}-${family}`, + unitSuffix: ' ms', + colorFor: (latest) => { + if (rttThresholds?.critical != null && latest > rttThresholds.critical) { + return { stroke: '#e53935', fill: '#ffcdd2' }; + } + if (rttThresholds?.warning != null && latest > rttThresholds.warning) { + return { stroke: '#fb8c00', fill: '#ffe0b2' }; + } + return { stroke: '#1976d2', fill: '#bbdefb' }; + }, + }); + } + function renderCpuTable(hostname, d) { const KEYS = [ ['cpu_percent', 'CPU Usage', 'bar'],
FamilyAddressState RTTLast ChangeLast Packet