Diffs name their chain; history shows the ruleset's diff
The ruleset's diff names the chain each hunk is in on its @@ line, as
git names the function. A history entry shows what it did to the
ruleset (web page, hc history diff, GET /history/{id}/diff), rebuilt
from the current configuration by stepping back through the later
entries. A changed setting is listed by its own fields
(builtins.drop_invalid) instead of the whole object.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
4e35236dba
commit
92147c8681
12 files changed
+289
-11
No files matched your search
@@ -1,5 +1,5 @@
|
||||
// History: every configuration change, with undo.
|
||||
import { h, mount, table, time, confirmDialog, modal, errorBox, badge } from "../ui.js";
|
||||
import { h, mount, table, time, confirmDialog, modal, errorBox, badge, diffView } from "../ui.js";
|
||||
import { get, change, can, state } from "../api.js";
|
||||
import { rerender } from "../app.js";
|
||||
|
||||
@@ -29,7 +29,7 @@ async function undo(e) {
|
||||
}
|
||||
|
||||
async function entry(el, id) {
|
||||
const e = await get("/history/" + id);
|
||||
const [e, d] = await Promise.all([get("/history/" + id), get("/history/" + id + "/diff").catch(() => null)]);
|
||||
mount(el,
|
||||
h("nav", { class: "back-link" }, h("a", { href: "#/history" }, "← History")),
|
||||
h("div", { class: "page-head" },
|
||||
@@ -40,7 +40,11 @@ async function entry(el, id) {
|
||||
can("config", true) && !e.reverted_by ? h("div", { class: "actions" }, h("button", { onclick: () => undo(e) }, "Undo")) : null),
|
||||
e.changes.map((c) => h("div", { class: "panel" },
|
||||
h("h3", null, action(c) + " " + c.kind + " ", h("span", { class: "mono" }, c.label)),
|
||||
fieldsTable(c))));
|
||||
fieldsTable(c))),
|
||||
d ? h("div", { class: "panel" },
|
||||
h("h3", null, "Ruleset"),
|
||||
h("p", { class: "muted small" }, "What this change did to the ruleset, rendered by this version of Housecarl."),
|
||||
diffView(d.diff)) : null);
|
||||
}
|
||||
|
||||
function action(c) {
|
||||
@@ -50,9 +54,10 @@ function action(c) {
|
||||
}
|
||||
|
||||
// fieldsTable lists the fields that differ (all of them for a create or
|
||||
// delete).
|
||||
// delete). An object within (settings' builtins) is listed by its own
|
||||
// fields, "builtins.drop_invalid", so only what changed shows.
|
||||
function fieldsTable(c) {
|
||||
const a = c.before || {}, b = c.after || {};
|
||||
const a = flatten(c.before || {}), b = flatten(c.after || {});
|
||||
const keys = [...new Set([...Object.keys(a), ...Object.keys(b)])].sort();
|
||||
const show = (v, k) => {
|
||||
if (v === undefined || v === "") return "–";
|
||||
@@ -68,3 +73,14 @@ function fieldsTable(c) {
|
||||
c.after ? { label: c.before ? "After" : "Value", render: (k) => h("span", { class: "mono small" }, show(b[k], k)) } : null,
|
||||
].filter(Boolean), rows);
|
||||
}
|
||||
|
||||
// flatten lifts the fields of objects within an object to dotted keys
|
||||
// ({builtins: {loopback: true}} → {"builtins.loopback": true}); lists
|
||||
// stay whole.
|
||||
function flatten(o, prefix = "", out = {}) {
|
||||
for (const [k, v] of Object.entries(o)) {
|
||||
if (v && typeof v === "object" && !Array.isArray(v) && Object.keys(v).length) flatten(v, prefix + k + ".", out);
|
||||
else out[prefix + k] = v;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
Reference in new issue
Block a user