From 23448294a4767526f0bb07cb715f9dcdefd9c662 Mon Sep 17 00:00:00 2001 From: Andreas Wrede Date: Mon, 6 Jul 2026 14:08:03 -0400 Subject: [PATCH] cockpit: show IP + hostname for AP clients Join station MACs against dnsmasq leases (IP + hostname), with the kernel's br0 neighbor table as a fallback IP source for static-IP clients. Co-Authored-By: Claude Fable 5 --- cockpit/vanrouter/vanrouter.js | 35 ++++++++++++++++++++++++++++------ 1 file changed, 29 insertions(+), 6 deletions(-) diff --git a/cockpit/vanrouter/vanrouter.js b/cockpit/vanrouter/vanrouter.js index 02de422..e28e94b 100644 --- a/cockpit/vanrouter/vanrouter.js +++ b/cockpit/vanrouter/vanrouter.js @@ -51,7 +51,28 @@ async function readAP(a) { return { band: a.band, unit: a.unit, active, ssid, chan, width, stations }; } -function renderAPs(aps) { +async function readClientDir() { + // MAC -> { ip, host } for AP clients. Kernel neighbor table first (covers + // static-IP clients), then dnsmasq leases on top (authoritative + hostname). + const dir = {}; + try { + JSON.parse(await run(["ip", "-j", "neigh", "show", "dev", "br0"])).forEach(n => { + if (n.lladdr && n.dst && !n.dst.includes(":")) // IPv4 only + dir[n.lladdr.toLowerCase()] = { ip: n.dst, host: null }; + }); + } catch (e) { /* ignore */ } + try { + // lease line: + (await run(["cat", "/var/lib/misc/dnsmasq.leases"])).trim().split("\n").forEach(l => { + const f = l.split(" "); + if (f.length >= 4) + dir[f[1].toLowerCase()] = { ip: f[2], host: f[3] === "*" ? null : f[3] }; + }); + } catch (e) { /* ignore */ } + return dir; +} + +function renderAPs(aps, dir) { const el = document.getElementById("ap"); el.innerHTML = ""; aps.forEach(ap => { @@ -68,9 +89,11 @@ function renderAPs(aps) { `

`; if (ap.stations.length) { - html += ``; + html += `
MACSignalTX rate
`; ap.stations.forEach(s => { - html += ``; + const c = dir[s.mac.toLowerCase()] || {}; + html += `` + + ``; }); html += `
MACIPHostnameSignalTX rate
${esc(s.mac)}${esc(s.sig || "?")} dBm${esc(s.tx || "?")}
${esc(s.mac)}${esc(c.ip || "—")}${esc(c.host || "—")}${esc(s.sig || "?")} dBm${esc(s.tx || "?")}
`; } @@ -279,9 +302,9 @@ async function restartAP(unit) { async function refresh() { try { - const [aps, th, bat, fo, wan] = await Promise.all([ - Promise.all(APS.map(readAP)), readThermal(), readBattery(), readFailover(), readWAN()]); - renderAPs(aps); + const [aps, dir, th, bat, fo, wan] = await Promise.all([ + Promise.all(APS.map(readAP)), readClientDir(), readThermal(), readBattery(), readFailover(), readWAN()]); + renderAPs(aps, dir); renderThermal(th); renderBattery(bat); renderFailover(fo);