Hidden interfaces, last-seen pings, activity by subnet

- 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>
This commit is contained in:
abrown38andClaude Opus 5.5 committed 2026-10-18 17:50:21 +01:00
1 parent 6ad1bb9a0d
commit 5e6c61cc8e
31 files changed
+358 -39

No files matched your search

@@ -1,5 +1,5 @@
// Reservations: fixed addresses for clients.
import { h, mount, table, input, modal, errorBox, field, confirmDialog, select, debounce, badge, checkbox } from "../ui.js";
import { h, mount, table, time, input, modal, errorBox, field, confirmDialog, select, debounce, badge, checkbox } from "../ui.js";
import { get, change, can } from "../api.js";
import { optionsEditor, subnetLabel } from "../widgets.js";
@@ -12,7 +12,8 @@ export function reservationsTable(rs, done, opts = {}) {
{ label: "Host name", render: (r) => r.hostname || h("span", { class: "muted" }, "–") },
opts.subnet ? null : { label: "Subnet", render: (r) => r.subnet_id ? h("a", { href: "#/subnets/" + r.subnet_id }, r.subnet) : "–" },
{ label: "Online", render: (r) => r.lease
? h("span", { title: "lease until " + new Date(r.lease.end).toLocaleString() }, badge("online", "ok"), h("span", { class: "muted small" }, " on " + r.lease.interface))
? h("span", { title: "lease until " + new Date(r.lease.end).toLocaleString() }, badge("online", "ok"), h("span", { class: "muted small" }, " on " + r.lease.interface),
r.lease.last_seen ? h("span", { class: "muted small", title: "last answered a ping" }, ", seen ", time(r.lease.last_seen)) : null)
: badge("offline") },
{ label: "Options", render: (r) => r.options.length ? h("span", { class: "mono small" }, r.options.map((o) => o.name + "=" + o.value).join("; ")) : "" },
{ label: "Comment", render: (r) => r.comment },