let refreshMs = 5000; function fmtBytes(num) { const units = ["B", "KiB", "MiB", "GiB", "TiB"]; let i = 0; while (Math.abs(num) >= 1024 && i < units.length - 1) { num /= 1024; i++; } return num.toFixed(1) + " " + units[i]; } function fmtHandshake(iso) { if (!iso) return "never"; const seconds = Math.floor((Date.now() - new Date(iso).getTime()) / 1000); if (seconds < 60) return seconds + "s ago"; if (seconds < 3600) return Math.floor(seconds / 60) + "m ago"; return Math.floor(seconds / 3600) + "h ago"; } async function refresh() { let data; try { const res = await fetch("/api/status"); if (!res.ok) return; data = await res.json(); } catch { return; } if (data.meta && data.meta.refresh_seconds) { refreshMs = data.meta.refresh_seconds * 1000; } const tbody = document.querySelector("#peer-table tbody"); if (!tbody) return; const badgeBase = "rounded-full px-2 py-0.5 text-xs font-medium"; const esc = (s) => String(s).replace(/[&<>"]/g, (ch) => ({ "&": "&", "<": "<", ">": ">", '"': """ })[ch] ); let html = ""; for (const iface of data.interfaces) { for (const p of iface.peers) { const badge = p.online ? `online` : p.enabled ? `offline` : `disabled`; html += `` + `${esc(p.name)}` + `${esc(iface.name)}` + `${esc(p.address)}` + `${badge}` + `${fmtHandshake(p.latest_handshake)}` + `${fmtBytes(p.rx_bytes)}` + `${fmtBytes(p.tx_bytes)}` + ``; } } if (tbody.dataset.html !== html) { tbody.dataset.html = html; tbody.innerHTML = html; } } async function loop() { await refresh(); setTimeout(loop, refreshMs); } loop();