- interfaces.exclude in steward.yaml (names or shell patterns) leaves interfaces out of GET /interfaces, the web UI's picker and 'steward interface list'. Subnets on them are still served. - With the ping check on, active leases are pinged every minute; a client that answers gets a last-seen time (leases.last_seen, migration 4). Not answering never takes a client offline. - The activity log filters by subnet (--subnet, the Activity page, GET /events?subnet=). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
45 lines
2.9 KiB
JavaScript
45 lines
2.9 KiB
JavaScript
// Activity: the latest DHCP messages, refreshed every few seconds.
|
|
import { h, mount, input, select, debounce } from "../ui.js";
|
|
import { get } from "../api.js";
|
|
import { onLeave } from "../app.js";
|
|
import { subnetLabel } from "../widgets.js";
|
|
|
|
export async function render(el, params, query) {
|
|
const [ifaces, subnets] = await Promise.all([get("/interfaces"), get("/subnets")]);
|
|
const subnetSel = select([["", "all subnets"], ...subnets.map((s) => [String(s.id), subnetLabel(s)])], query.get("subnet") || "");
|
|
const mac = input({ placeholder: "MAC address", value: query.get("mac") || "", class: "mono" });
|
|
const iface = select([["", "all interfaces"], ...ifaces.filter((i) => i.subnets.length).map((i) => [i.name, i.name])], query.get("interface") || "");
|
|
const out = h("div");
|
|
let live = true;
|
|
const pause = h("button", { onclick: () => { live = !live; pause.textContent = live ? "Pause" : "Resume"; } }, "Pause");
|
|
const draw = async () => {
|
|
const evs = await get("/events", { mac: mac.value.trim(), subnet: subnetSel.value, interface: iface.value, limit: 300 });
|
|
mount(out, evs.length ? h("div", { class: "table-wrap" }, h("table", { class: "activity" },
|
|
h("thead", null, h("tr", null, ["Time", "Interface", "Message", "Client", "Address", "Host name", ""].map((t) => h("th", null, t)))),
|
|
h("tbody", null, evs.map((e) => {
|
|
const t = e.type.replace(/^DHCP/, "");
|
|
return h("tr", null,
|
|
h("td", { class: "mono small" }, new Date(e.time).toLocaleTimeString(undefined, { hourCycle: "h23" })),
|
|
h("td", { class: "mono" }, e.interface),
|
|
h("td", null, h("strong", { class: "type-" + t.split(" ")[0] }, t)),
|
|
h("td", { class: "mono nowrap" }, e.mac ? [
|
|
h("a", { href: "#/leases?q=" + encodeURIComponent(e.mac), title: "This client's leases" }, e.mac), " ",
|
|
mac.value.trim() ? null : h("button", { class: "small reveal", title: "Show only this client's messages", onclick: () => { mac.value = e.mac; draw(); } }, "Only this")] : ""),
|
|
h("td", { class: "mono" }, e.ip || ""),
|
|
h("td", null, e.hostname || ""),
|
|
h("td", { class: "detail" }, e.detail || ""));
|
|
})))) : h("div", { class: "empty" }, "No DHCP messages yet" + (mac.value || subnetSel.value || iface.value ? " that match." : ".")));
|
|
};
|
|
mac.addEventListener("input", debounce(draw, 300));
|
|
subnetSel.addEventListener("change", draw);
|
|
iface.addEventListener("change", draw);
|
|
mount(el,
|
|
h("div", { class: "page-head" }, h("div", { class: "grow" }, h("h1", null, "Activity"),
|
|
h("div", { class: "muted" }, "The latest DHCP messages Steward received and sent, newest first. A MAC address leads to that client's leases; Only this follows just that client."))),
|
|
h("div", { class: "filter-row" }, mac, subnetSel, iface, pause),
|
|
h("div", { class: "panel" }, out));
|
|
await draw();
|
|
const t = setInterval(() => { if (live) draw().catch(() => {}); }, 3000);
|
|
onLeave(() => clearInterval(t));
|
|
}
|