// Zones: groups of interfaces, and the machine's interfaces. import { h, mount, table, modal, errorBox, field, input, checkbox, confirmDialog, badge } from "../ui.js"; import { get, change, can } from "../api.js"; import { rerender } from "../app.js"; import { listEditor, rowClass } from "../widgets.js"; export async function render(el) { const [zones, ifs] = await Promise.all([get("/zones"), get("/interfaces")]); const write = can("config", true); mount(el, h("div", { class: "page-head" }, h("div", { class: "grow" }, h("h1", null, "Zones"), h("div", { class: "muted" }, "Groups of interfaces that rules name. External zones face the internet: antispoofing, MSS clamping and port forwards apply there.")), write ? h("div", { class: "actions" }, h("button", { class: "primary", onclick: () => zoneDialog(null, ifs) }, "Add zone")) : null), h("div", { class: "panel" }, table([ { label: "Name", render: (z) => h("strong", null, z.name) }, { label: "Interfaces", render: (z) => h("span", { class: "mono" }, z.interfaces.join(", ")) }, { label: "", render: (z) => z.external ? badge("external", "warn") : badge("internal", "") }, { label: "Comment", render: (z) => z.comment }, write ? { label: "", tdClass: "actions-cell", render: (z) => z.pending === "deleted" ? badge("deleted", "err") : [ h("button", { class: "small reveal", onclick: () => zoneDialog(z, ifs) }, "Edit"), " ", h("button", { class: "small reveal", onclick: () => del(z) }, "Delete")] } : null, ].filter(Boolean), zones, { empty: "No zones yet: add one per network (wan, lan, guest…).", rowClass })), h("div", { class: "panel" }, h("h2", null, "This machine's interfaces"), table([ { label: "Interface", render: (i) => h("span", { class: "mono" }, i.name) }, { label: "State", render: (i) => badge(i.up ? "up" : "down", i.up ? "ok" : "") }, { label: "Zone", render: (i) => i.zone || h("span", { class: "muted" }, "none") }, { label: "Addresses", render: (i) => h("span", { class: "mono small" }, i.addresses.join(", ")) }, ], ifs))); } async function del(z) { if (!await confirmDialog("Delete zone", "Delete zone " + z.name + "?", "Delete", "danger")) return; try { await change("DELETE", "/zones/" + z.id); rerender(); } catch (e) { modal("Not deleted", errorBox(e)); } } async function zoneDialog(z, ifs) { z = z || { interfaces: [] }; const name = input({ value: z.name || "", placeholder: "wan, lan, guest" }); const list = listEditor(z.interfaces, { suggestions: ifs.map((i) => i.name), placeholder: "eth0, or incusbr* for many", addLabel: "Add interface" }); const ext = checkbox("external: faces the internet", z.external); const comment = input({ value: z.comment || "" }); const err = h("div"); await modal(z.id ? "Edit zone " + z.name : "Add zone", h("div", null, h("div", { class: "form-grid" }, field("Name", name), field("Interfaces", list, "A trailing * matches many (incusbr*)."), h("label"), ext, field("Comment", comment)), err), [ { label: "Cancel", value: null }, { label: z.id ? "Save" : "Add zone", kind: "primary", onclick: async () => { mount(err); try { await change(z.id ? "PATCH" : "POST", z.id ? "/zones/" + z.id : "/zones", { name: name.value.trim(), interfaces: list.value(), external: ext.input.checked, comment: comment.value.trim() }); rerender(); return true; } catch (e) { mount(err, errorBox(e)); return false; } } }, ], { sticky: true }); }