Files
steward/internal/web/static/assets/pages/subnets.js
T
abrown38andClaude Opus 5.5 6ad1bb9a0d
release / release (push) Successful in 5m53s
Disable reservations; subnet enable/disable buttons; page-head CSS fix
- A disabled reservation is kept but ignored: the client gets a pool
  address like any other (steward reservation disable|enable, buttons and
  a checkbox in the web UI). Schema migration 3.
- Subnets get Disable/Enable buttons on their page and in the list.
- A rule left without its selector in style.css broke the page heading's
  layout, so the New ... buttons sat cramped under the description.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 16:02:15 +01:00

48 lines
2.5 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// The subnets, and the New subnet dialog.
import { h, mount, table, modal, errorBox, badge } from "../ui.js";
import { get, change, can } from "../api.js";
import { usageBar, subnetForm, fmtSeconds, subnetLabel } from "../widgets.js";
import { toggleSubnet } from "./subnet.js";
export async function render(el, params, query) {
const subnets = await get("/subnets");
const poolsText = (s) => s.pools.length ? s.pools.map((p) => p.start + "-" + p.end).join(", ") : "–";
mount(el,
h("div", { class: "page-head" },
h("div", { class: "grow" }, h("h1", null, "Subnets"),
h("div", { class: "muted" }, "Each subnet is served on one interface: only clients on that interface get its addresses.")),
can("config", true) ? h("div", { class: "actions" }, h("button", { class: "primary", onclick: () => newSubnet() }, "New subnet")) : null),
h("div", { class: "panel" }, table([
{ label: "Subnet", render: (s) => h("a", { href: "#/subnets/" + s.id }, subnetLabel(s)) },
{ label: "Interface", render: (s) => h("span", { class: "mono" }, s.interface) },
{ label: "Pools", render: (s) => h("span", { class: "mono small" }, poolsText(s)) },
{ label: "In use", render: (s) => usageBar(s.usage) },
{ label: "Reserved", class: "num", tdClass: "num", render: (s) => String(s.usage.reservations) },
{ label: "Lease time", render: (s) => s.lease_time ? fmtSeconds(s.lease_time) : h("span", { class: "muted" }, "default") },
{ label: "", tdClass: "actions-cell", render: (s) => [s.disabled ? [badge("disabled", "off"), " "] : null,
can("config", true) ? h("button", { class: "small reveal", onclick: () => toggleSubnet(s) }, s.disabled ? "Enable" : "Disable") : null] },
], subnets, { empty: "No subnets yet. Add one for each network Steward should serve.", rowClass: (s) => s.disabled ? "disabled" : "" })));
if (query.get("new") === "1" && can("config", true)) newSubnet();
}
export async function newSubnet() {
const settings = await get("/settings");
const form = await subnetForm(null, settings);
const err = h("div");
const res = await modal("New subnet", h("div", null, form, err), [
{ label: "Cancel", value: null },
{ label: "Add subnet", kind: "primary", onclick: async () => {
mount(err);
try {
const r = await change("POST", "/subnets", form.values());
location.hash = "#/subnets/" + (r.object ? r.object.id : "");
return true;
} catch (e) {
mount(err, errorBox(e));
return false;
}
} },
], { wide: true, sticky: true });
return res;
}