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 <noreply@anthropic.com>
This commit is contained in:
Andreas Wrede
2026-07-06 14:08:03 -04:00
co-authored by Claude Fable 5
parent db16964d5a
commit 23448294a4
+29 -6
View File
@@ -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: <expiry-epoch> <mac> <ip> <hostname|*> <client-id>
(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) {
` <button class="btn ap-restart">Restart</button></p>`;
if (ap.stations.length) {
html += `<table><thead><tr><th>MAC</th><th>Signal</th><th>TX rate</th></tr></thead><tbody>`;
html += `<table><thead><tr><th>MAC</th><th>IP</th><th>Hostname</th><th>Signal</th><th>TX rate</th></tr></thead><tbody>`;
ap.stations.forEach(s => {
html += `<tr><td>${esc(s.mac)}</td><td>${esc(s.sig || "?")} dBm</td><td>${esc(s.tx || "?")}</td></tr>`;
const c = dir[s.mac.toLowerCase()] || {};
html += `<tr><td>${esc(s.mac)}</td><td>${esc(c.ip || "—")}</td><td>${esc(c.host || "—")}</td>` +
`<td>${esc(s.sig || "?")} dBm</td><td>${esc(s.tx || "?")}</td></tr>`;
});
html += `</tbody></table>`;
}
@@ -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);