release / release (push) Successful in 5m53s
- 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>
48 lines
2.5 KiB
JavaScript
48 lines
2.5 KiB
JavaScript
// 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;
|
||
}
|