From 8122c8d7dc23b4bb9dbb07c3d34a6840b9fa79c9 Mon Sep 17 00:00:00 2001 From: Andreas Wrede Date: Mon, 21 Sep 2026 09:36:25 -0400 Subject: [PATCH] feat(ui): show host status, simplex and absent protocols Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01Uc9V1v7iXruubpoLiSE24M --- hbd/server/templates/live.html | 33 +++++++++++++++++-------------- hbd/server/templates/plugins.html | 20 ++++++++++++++++++- 2 files changed, 37 insertions(+), 16 deletions(-) diff --git a/hbd/server/templates/live.html b/hbd/server/templates/live.html index 8d5474d..e3c3a68 100644 --- a/hbd/server/templates/live.html +++ b/hbd/server/templates/live.html @@ -73,6 +73,7 @@ .tipbox .up { color: var(--st-ok); font-weight: 700; } .tipbox .dn { color: var(--st-crit); font-weight: 700; } .tipbox .ov { color: var(--st-warn); font-weight: 700; } + .tipbox .ab { color: var(--st-faint); font-weight: 700; } .tipbox .meta { margin-top: 6px; padding-top: 6px; border-top: 1px solid var(--st-line-soft); font-size: 11px; color: var(--st-faint); } @media (max-width: 760px) { @@ -134,18 +135,18 @@ function worstConnection(data) { var worst = null; (data.connections || []).forEach(function (c) { - if (!c.state || c.state === 'unknown') return; + if (!c.state || c.state === 'unknown' || c.state === 'absent') return; if (!worst || (STATE_RANK[c.state] || 0) > (STATE_RANK[worst.state] || 0)) worst = c; }); return worst; } function severity(data) { - var w = worstConnection(data); + var st = data.status; if ((data.alert_critical_unacked || 0) > 0 || (data.alert_critical_acked || 0) > 0) return 'crit'; - if (w && w.state === 'down') return 'crit'; + if (st === 'down') return 'crit'; if ((data.alert_warning_unacked || 0) > 0 || (data.alert_warning_acked || 0) > 0) return 'warn'; - if (w && w.state === 'overdue') return 'warn'; + if (st === 'overdue') return 'warn'; return 'ok'; } var SEV_ORDER = { crit: 0, warn: 1, ok: 2 }; @@ -171,17 +172,17 @@ // ---- host row rendering --------------------------------------------- function statusChip(data) { - var w = worstConnection(data); - if (!w) return '—'; - var cls = w.state === 'up' ? 'ok' : (w.state === 'down' ? 'crit' : 'warn'); - return '' + escHtml(w.state) + ''; + var st = data.status; + if (!st || st === 'unknown') return '—'; + var cls = (st === 'up' || st === 'simplex') ? 'ok' : (st === 'down' ? 'crit' : 'warn'); + return '' + escHtml(st) + ''; } function tipCard(data) { var rows = ''; (data.connections || []).forEach(function (c) { if (!c.state || c.state === 'unknown') return; - var cls = c.state === 'up' ? 'up' : (c.state === 'down' ? 'dn' : 'ov'); + var cls = c.state === 'up' ? 'up' : (c.state === 'down' ? 'dn' : (c.state === 'absent' ? 'ab' : 'ov')); var fam = (c.afam || '').replace('IPv', 'v'); var rtt = (c.state === 'up' && c.rtts && c.rtts[0]) ? Math.round(c.rtts[0]) + ' ms' : '—'; rows += '' + escHtml(fam) + '' + escHtml(c.addr || '—') + '' @@ -247,7 +248,9 @@ else if (data.hbc_version !== HBD_VERSION) { staleTag = 'v' + escHtml(String(data.hbc_version)) + ''; } - var dotCls = w ? (w.state === 'up' ? sev : (w.state === 'down' ? 'down' : 'overdue')) : 'off'; + var st = data.status; + var dotCls = (!st || st === 'unknown') ? 'off' + : ((st === 'up' || st === 'simplex') ? sev : (st === 'down' ? 'down' : 'overdue')); var flapTag = ''; var flapping = data.flapping || []; if (flapping.length) { @@ -286,11 +289,11 @@ var total = 0, up = 0, overdue = 0, down = 0; Object.values(hostData).forEach(function (d) { total++; - var w = worstConnection(d); - if (!w) return; - if (w.state === 'up') up++; - else if (w.state === 'overdue') overdue++; - else if (w.state === 'down') down++; + var st = d.status; + if (!st || st === 'unknown') return; + if (st === 'up' || st === 'simplex') up++; + else if (st === 'overdue') overdue++; + else if (st === 'down') down++; }); var parts = [total + ' hosts', up + ' up']; if (overdue) parts.push(overdue + ' overdue'); diff --git a/hbd/server/templates/plugins.html b/hbd/server/templates/plugins.html index 7244d02..bbb2ec3 100644 --- a/hbd/server/templates/plugins.html +++ b/hbd/server/templates/plugins.html @@ -456,7 +456,7 @@ `; for (const c of data.connections) { const st = escHtml(c.state || '—'); - const stCls = c.state === 'up' ? 'status-up' : (c.state ? 'status-down' : ''); + const stCls = c.state === 'up' ? 'status-up' : (c.state === 'absent' ? '' : (c.state ? 'status-down' : '')); const rtt = (c.state === 'up' && c.rtt) ? Math.round(c.rtt) + ' ms' : '—'; const chg = c.statetime ? new Date(c.statetime * 1000).toLocaleString() : '—'; const seen = c.lastbeat ? new Date(c.lastbeat * 1000).toLocaleString() : '—'; @@ -1143,6 +1143,24 @@ function renderNetworkTables(d) { let html = ''; + if (d.connectivity) { + let t = '' + + ''; + for (const fam of ['ipv4', 'ipv6']) { + const c = d.connectivity[fam]; + if (!c) continue; + const known = c.present === true || c.present === false; + const cls = c.present === true ? 'status-up' : ''; + const txt = c.present === true ? '✓ present' : (c.present === false ? 'absent' : '?'); + t += ` + + + `; + } + t += '
ProtocolInternetAddressGatewayRTT
${fam === 'ipv4' ? 'IPv4' : 'IPv6'}${txt}${escHtml(c.addr || '—')}${escHtml(c.gateway || '—')}${known && c.rtt != null ? Math.round(c.rtt) + ' ms' : '—'}
'; + html += `${t}`; + } + if (d.interfaces && Object.keys(d.interfaces).length) { const sample = Object.values(d.interfaces)[0]; const hasDelta = 'bytes_sent_delta' in sample;