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:
co-authored by
Claude Fable 5
parent
db16964d5a
commit
23448294a4
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user