The zone page's breadcrumb (Zones › view) linked the view to a filtered zone list while the view link below it opened the view. It is now a single "← Zones" link back to the list with its filters, and a changeset has "← History". View links carry where they came from, so the views page offers "← example.org (external)" or "← Zones" (only the UI's own #/ addresses are accepted). Switching a zone's tabs no longer blanks the page: the header and tabs show at once and the previous tab stays, faded, until the next has loaded. The router shows "Loading…" only for pages that take a while. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
60 lines
3.5 KiB
JavaScript
60 lines
3.5 KiB
JavaScript
import { h, mount, table, toast, confirmDialog, diffView, time, fmtTime, badge, plural, backLink, lastList } from "../ui.js";
|
|
import { api, get, useDraft } from "../api.js";
|
|
|
|
export async function render(el, params) {
|
|
if (params.id) return detail(el, params.id);
|
|
const list = h("div");
|
|
let rows = [];
|
|
const load = async (before) => {
|
|
const page = await get("/changesets", { status: "applied", limit: 50, before });
|
|
rows = rows.concat(page);
|
|
mount(list, table([
|
|
{ label: "#", render: (c) => h("a", { href: "#/history/" + c.id }, String(c.id)) },
|
|
{ label: "Applied", render: (c) => time(c.applied_at) },
|
|
{ label: "By", render: (c) => c.applied_by || c.username },
|
|
{ label: "Via", render: (c) => badge(c.via) },
|
|
{ label: "Description", render: (c) => h("a", { href: "#/history/" + c.id }, c.description || "(no description)") },
|
|
{ label: "Operations", render: (c) => String((c.ops || []).length), tdClass: "right" },
|
|
], rows, { empty: "Nothing has been applied yet." }),
|
|
page.length === 50 ? h("div", { style: { marginTop: "8px" } }, h("button", { onclick: () => load(rows[rows.length - 1].id) }, "Load older")) : null);
|
|
};
|
|
mount(el, h("div", { class: "page-head" }, h("div", { class: "grow" }, h("h1", null, "History"),
|
|
h("div", { class: "muted" }, "Every applied changeset, with the exact changes made to BIND's files. Any of them can be reverted."))),
|
|
h("div", { class: "panel" }, list));
|
|
await load();
|
|
}
|
|
|
|
async function detail(el, id) {
|
|
const c = await get("/changesets/" + id);
|
|
const res = c.result || {};
|
|
mount(el,
|
|
h("div", { class: "page-head" },
|
|
h("div", { class: "grow" },
|
|
backLink("History", lastList.history || "#/history"),
|
|
h("h1", null, "Changeset #" + c.id),
|
|
h("div", { class: "muted" }, c.description || "(no description)")),
|
|
h("div", { class: "actions" },
|
|
c.status === "applied" && c.undoable ? h("button", { onclick: () => revert(c) }, "Revert…") : null)),
|
|
h("div", { class: "panel" }, h("dl", { class: "kv" },
|
|
h("dt", null, "Status"), h("dd", null, badge(c.status, c.status === "applied" ? "ok" : "")),
|
|
h("dt", null, "Created"), h("dd", null, fmtTime(c.created_at) + " by " + c.username + " via " + c.via),
|
|
c.applied_at ? [h("dt", null, "Applied"), h("dd", null, fmtTime(c.applied_at) + " by " + c.applied_by)] : null,
|
|
(res.serials || []).length ? [h("dt", null, "Serials"), h("dd", { class: "mono" }, res.serials.map((s) => s.zone.replace(/\.$/, "") + (s.view ? " [" + s.view + "]" : "") + ": " + s.old + " → " + s.new).join(", "))] : null,
|
|
(res.warnings || []).length ? [h("dt", null, "Warnings"), h("dd", null, res.warnings.join("; "))] : null)),
|
|
h("div", { class: "panel" }, h("h2", null, plural((c.ops || []).length, "operation")), table([
|
|
{ label: "#", render: (o) => String(o.seq) },
|
|
{ label: "Change", render: (o) => o.summary || o.op, tdClass: "mono small" },
|
|
], c.ops || [])),
|
|
h("div", { class: "panel" }, h("h2", null, "File changes"), diffView(c.diff)));
|
|
}
|
|
|
|
async function revert(c) {
|
|
if (!(await confirmDialog("Revert changeset #" + c.id, "This creates a new changeset that undoes these changes. You can review it before applying.", "Create revert", "primary"))) return;
|
|
try {
|
|
const rev = await api("POST", "/changesets/" + c.id + "/revert");
|
|
useDraft(rev);
|
|
toast("Created changeset #" + rev.id + "; review and apply it");
|
|
location.hash = "#/changes";
|
|
} catch (e) { toast(e.message, "err", 8000); }
|
|
}
|