The view shown in the zone list and on the zone page now links to that
view. The zone page and changeset page get breadcrumbs, and the zone list
keeps its filters in the address so going back restores them. Views can
be given a colour (Views > Edit, `jarl view color`, PUT /views/{id}/color)
so zones of the same view are easy to spot; the colour is UI-only, kept
outside changesets, and needs no deploy.
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, crumbs, 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" },
|
|
crumbs(["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); }
|
|
}
|