/*
* SysDeck - Network Monitor Panel (v0.0.43)
* Author: Jeremy Anderson (https://dcos.net)
*
* v0.0.43 REWRITE — IPTRAF-NG STYLE.
*
* The v0.0.10-v0.0.43 panel used `ss -tulpn` for a static socket list.
* v0.0.43 recreates the iptraf-ng UI by reading the same kernel sources
* iptraf-ng reads from directly — no iptraf-ng binary dependency, no
* ncurses parsing.
*
* The panel has three sections (matching iptraf-ng's views):
* 1. Interface Overview — live RX/TX rate cards (bytes/s, packets/s)
* per interface. Auto-refreshes every 3s. The traffic subcommand
* samples /proc/net/dev twice (1s apart) to compute live rates.
* 2. IP Traffic Monitor — active TCP/UDP connections table (proto,
* state, local addr:port, remote addr:port, TX/RX queue). Reads
* /proc/net/tcp + /proc/net/udp directly.
* 3. Protocol Statistics — IP/TCP/UDP/ICMP counters from
* /proc/net/snmp (InReceives, OutRequests, InDiscards, etc.).
*
* Data sources (same as iptraf-ng):
* /proc/net/dev per-interface RX/TX byte + packet counters
* /proc/net/snmp IP/TCP/UDP/ICMP protocol counters
* /proc/net/tcp active TCP connections (state, local, remote)
* /proc/net/udp active UDP sockets
*
* The traffic subcommand takes 1 second (samples /proc/net/dev twice,
* 1s apart) — the panel shows a loading indicator during that window.
*/
export async function mount(panel, { bridge, EventBus }) {
panel.innerHTML = renderSkeleton();
// Load summary + connections + protocols in parallel (traffic is
// slow — 1s — so we load it separately and show it when ready).
const [summary, connections, protocols] = await Promise.all([
safe(bridge.netsec.summary(), { total_interfaces: 0, total_connections: 0 }),
safe(bridge.netsec.connections(), []),
safe(bridge.netsec.protocols(), {}),
]);
panel.innerHTML = `
${summary.total_interfaces || 0} interfaces
(${summary.interfaces_up || 0} active)
· ${summary.total_connections || 0} connections
· ESTABLISHED: ${summary.tcp_established || 0}
· LISTEN: ${summary.tcp_listen || 0}
·
Network Monitor
Sampling /proc/net/dev (1s window)…
| Proto | State | Local | Remote | TxQ | RxQ |
|---|---|---|---|---|---|
| ${escapeHtml(c.proto)} | ${escapeHtml(c.state)} | ${escapeHtml(c.local_ip)}:${c.local_port} | ${escapeHtml(c.remote_ip)}:${c.remote_port} | ${c.tx_queue} | ${c.rx_queue} |
| No active connections. | |||||
Showing first 100 of ${connections.length} connections.
` : ''}Traffic sample failed: ${escapeHtml(err.message || err)}
`; } } function renderTrafficCards(traffic) { if (!traffic || !traffic.length) { return 'No network interfaces found.
'; } return `Protocol statistics unavailable.
'; } const protos = ['ip', 'tcp', 'udp', 'icmp', 'icmp6']; const cards = protos.filter((p) => protocols[p]).map((p) => { const stats = protocols[p]; const fields = Object.entries(stats).slice(0, 12).map(([k, v]) => `No protocol data.
'; } // ── Event wiring ──────────────────────────────────────────────────── function wireEvents(panel, { bridge, EventBus }) { panel.querySelector('#btn-netsec-refresh')?.addEventListener('click', () => { mount(panel, { bridge, EventBus }); }); // Auto-refresh traffic every 5 seconds (the traffic subcommand // itself takes 1s to sample, so 5s gives a good balance between // freshness and load). if (panel._netsecTrafficInterval) clearInterval(panel._netsecTrafficInterval); panel._netsecTrafficInterval = setInterval(() => { loadTrafficData(panel, bridge); }, 5000); // Clean up interval when the panel is unmounted. // Cockpit doesn't have a native unmount hook, but we can detect // when the panel's DOM is removed via MutationObserver. const observer = new MutationObserver(() => { if (!document.body.contains(panel)) { clearInterval(panel._netsecTrafficInterval); observer.disconnect(); } }); observer.observe(document.body, { childList: true, subtree: true }); } // ── Utilities ─────────────────────────────────────────────────────── async function safe(p, fallback) { try { const v = await p; return v ?? fallback; } catch { return fallback; } } function escapeHtml(s) { return String(s == null ? '' : s) .replace(/&/g, '&') .replace(//g, '>') .replace(/"/g, '"'); } function renderSkeleton() { return `