UI: activity client links, online badges, ended-leases toggle, All activity
- Activity: a client's MAC address opens its leases; "Only this" filters the list to that client. - Reservations: Online is a green online / grey offline badge. - A subnet's leases: "Include ended leases" is a checkbox. - Dashboard: "All activity" is a button beside the heading. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
8a69038ab5
commit
d0ee2c8844
5 files changed
+16
-10
No files matched your search
@@ -20,7 +20,9 @@ export async function render(el, params, query) {
|
||||
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" }, e.mac ? h("a", { href: "#", onclick: (ev) => { ev.preventDefault(); mac.value = e.mac; draw(); } }, e.mac) : ""),
|
||||
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 || ""));
|
||||
@@ -30,7 +32,7 @@ export async function render(el, params, query) {
|
||||
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. Click a MAC address to follow one client."))),
|
||||
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, iface, pause),
|
||||
h("div", { class: "panel" }, out));
|
||||
await draw();
|
||||
|
||||
@@ -50,7 +50,7 @@ export async function render(el) {
|
||||
problemsList(st.problems),
|
||||
st.ping_problem ? h("div", { class: "msg warn" }, st.ping_problem) : null) : null,
|
||||
events.length ? h("div", { class: "panel" },
|
||||
h("h2", null, "Latest DHCP messages ", h("a", { href: "#/activity", class: "small" }, "all")),
|
||||
h("div", { class: "panel-head" }, h("h2", null, "Latest DHCP messages"), h("a", { class: "button small", href: "#/activity" }, "All activity")),
|
||||
table([
|
||||
{ label: "When", render: (e) => time(e.time) },
|
||||
{ label: "Interface", render: (e) => e.interface },
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// Reservations: fixed addresses for clients.
|
||||
import { h, mount, table, input, modal, errorBox, field, confirmDialog, select, debounce } from "../ui.js";
|
||||
import { h, mount, table, input, modal, errorBox, field, confirmDialog, select, debounce, badge } from "../ui.js";
|
||||
import { get, change, can } from "../api.js";
|
||||
import { optionsEditor, subnetLabel } from "../widgets.js";
|
||||
|
||||
@@ -11,7 +11,9 @@ export function reservationsTable(rs, done, opts = {}) {
|
||||
{ label: "Client", render: (r) => h("span", { class: "mono" }, r.client_id ? "id " + r.client_id : r.mac) },
|
||||
{ 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() }, "yes, on " + r.lease.interface) : h("span", { class: "muted" }, "no") },
|
||||
{ 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))
|
||||
: 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 },
|
||||
write ? { label: "", tdClass: "actions-cell", render: (r) => [
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// A subnet's page: its leases, its reservations and its settings.
|
||||
import { h, mount, badge, errorBox, confirmDialog, modal, input, debounce, readOnly } from "../ui.js";
|
||||
import { h, mount, badge, errorBox, confirmDialog, modal, input, debounce, readOnly, checkbox } from "../ui.js";
|
||||
import { get, change, can } from "../api.js";
|
||||
import { usageBar, subnetForm, fmtSeconds, subnetLabel } from "../widgets.js";
|
||||
import { leasesTable } from "./leases.js";
|
||||
@@ -32,12 +32,12 @@ export async function render(el, params) {
|
||||
async function leasesTab(el, s) {
|
||||
const search = input({ type: "search", placeholder: "address, MAC or host name" });
|
||||
const out = h("div");
|
||||
let all = false;
|
||||
const toggle = h("a", { href: "#", class: "small", onclick: (e) => { e.preventDefault(); all = !all; toggle.textContent = all ? "Only current leases" : "Also expired and released"; draw(); } }, "Also expired and released");
|
||||
const toggle = checkbox("Include ended leases (expired and released)", false);
|
||||
toggle.input.addEventListener("change", () => draw());
|
||||
const draw = async () => {
|
||||
let ls = await get("/leases", { subnet: s.id, q: search.value.trim() });
|
||||
if (!all) ls = ls.filter((l) => l.state === "active" || l.state === "declined");
|
||||
mount(out, leasesTable(ls, rerender, { subnet: true, empty: all ? "No leases." : "No current leases." }));
|
||||
if (!toggle.input.checked) ls = ls.filter((l) => l.state === "active" || l.state === "declined");
|
||||
mount(out, leasesTable(ls, rerender, { subnet: true, empty: toggle.input.checked ? "No leases." : "No current leases." }));
|
||||
};
|
||||
search.addEventListener("input", debounce(draw, 300));
|
||||
mount(el, h("div", { class: "filter-row" }, search, toggle), h("div", { class: "panel" }, out));
|
||||
|
||||
@@ -353,3 +353,5 @@ table.activity td.detail { white-space: normal; color: var(--muted); }
|
||||
td.mono, span.mono { font-family: var(--mono); font-size: 13px; }
|
||||
.mt { margin-top: 10px; }
|
||||
input:disabled, select:disabled, textarea:disabled { background: var(--info-soft); color: var(--muted); cursor: not-allowed; opacity: 1; }
|
||||
.panel-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin-bottom: 8px; }
|
||||
.panel-head h2 { margin: 0; }
|
||||
Reference in new issue
Block a user