Files
steward/internal/web/static/assets/pages/subnets.js
T
abrown38andClaude Opus 5.5 8ee430276c Address order: sequential, reverse or random, per subnet and pool
- Which never-used pool address a new client gets: sequential (the
  lowest, as before), reverse or random, in the settings, a subnet or
  a single pool (FIRST-LAST:ORDER). When no never-used address is left,
  the one free the longest, whatever the order; returning clients keep
  theirs.
- A subnet's pools are used in the order listed, or one at random
  weighted by its never-used addresses. Pools now keep the order they
  are given in (no more sorting by address).
- CLI --address-order and --pool-order; --add-pool of an existing range
  changes its order in place. Web UI: per-pool order and a move-up
  button, pool and address order on the subnet and settings pages.
- Kea's allocator: random is imported. Schema migration 5.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 18:13:25 +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, poolText } 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(poolText).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;
}