Multi-server CLI, more reverse zones, and a fix for new view directories
- CLI: name several servers and groups in the config file; select them with -s/--server NAME|GROUP|all|URL or JARL_SERVER. A command aimed at several servers is dry-run on all of them first and only runs if every one accepts it. Named servers always use their own token. - Reverse zones: /32 and /128 get single-address zones; networks between octet or nibble boundaries (e.g. a /22) are covered by several zones created together, from the CLI and the web UI. - Fix: under jarl.service's RestrictSUIDSGID, creating a view directory failed on chmod with the setgid bit; fall back to the permission bits. - Web UI: the review/live switch is a small header button, and the pending-changes bar only shows when there are pending changes. - TSIG key wording; the keys page points to restricted API tokens. - Docs: several servers (cli.md), a secondary server (operations.md), tokens vs TSIG keys (permissions.md); CHANGELOG. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
a7d389be27
commit
785b0006f9
22 files changed
+1375
-185
No files matched your search
@@ -85,6 +85,7 @@ function renderLogin(message) {
|
||||
|
||||
function layout() {
|
||||
navEl = h("nav", { class: "main" });
|
||||
let modeEl;
|
||||
barEl = h("div");
|
||||
mainEl = h("main");
|
||||
const q = h("input", { type: "search", placeholder: "Search names, data…", "aria-label": "Search" });
|
||||
@@ -96,38 +97,42 @@ function layout() {
|
||||
h("span", { class: "brand-version" }, me.version)),
|
||||
navEl,
|
||||
h("div", { class: "spacer" }),
|
||||
modeEl = h("span"),
|
||||
h("form", { class: "search", onsubmit: (e) => { e.preventDefault(); if (q.value.trim()) location.hash = "#/search?q=" + encodeURIComponent(q.value.trim()); } }, q),
|
||||
h("a", { href: "#/account", class: "user", title: (me.permissions || []).join("\n") }, me.user.username),
|
||||
me.admin ? h("a", { href: "#/users" }, "Users") : null,
|
||||
h("button", { class: "small", onclick: logout }, "Log out"));
|
||||
mount(app, header, barEl, mainEl);
|
||||
modeHolder = modeEl;
|
||||
renderNav();
|
||||
renderBar();
|
||||
}
|
||||
|
||||
let modeHolder;
|
||||
|
||||
function renderNav() {
|
||||
mount(navEl, navItems.map(([key, href, label]) => h("a", { href, class: current.nav === key ? "active" : "" }, label)));
|
||||
}
|
||||
|
||||
// renderBar shows the pending-changes bar (only while there are pending
|
||||
// changes) and the small review/live switch in the header.
|
||||
function renderBar() {
|
||||
if (!barEl) return;
|
||||
const d = state.draft;
|
||||
const n = d ? (d.ops || []).length : 0;
|
||||
const mode = h("label", { class: "check small", title: "When on, every edit is applied to named right away instead of being collected for review." },
|
||||
h("input", { type: "checkbox", checked: state.immediate, onchange: (e) => setImmediate(e.target.checked) }), "Apply edits immediately");
|
||||
if (n > 0) {
|
||||
mount(barEl, h("div", { class: "pending-bar" },
|
||||
h("strong", null, plural(n, "pending change")),
|
||||
h("span", { class: "muted" }, "Your edits are collected in changeset #" + d.id + " and are not live yet."),
|
||||
h("a", { class: "button primary small", href: "#/changes" }, "Review & apply"),
|
||||
h("div", { class: "spacer", style: { flex: 1 } }),
|
||||
mode));
|
||||
} else {
|
||||
mount(barEl, h("div", { class: "pending-bar", style: { background: "var(--panel)" } },
|
||||
h("span", { class: "muted small" }, state.immediate ? "Edits are applied to named immediately." : "Edits are collected as pending changes; review and apply them under Changes."),
|
||||
h("div", { style: { flex: 1 } }),
|
||||
mode));
|
||||
if (modeHolder) {
|
||||
const live = state.immediate;
|
||||
mount(modeHolder, h("button", {
|
||||
type: "button", class: "mode-toggle" + (live ? " live" : ""), "aria-pressed": live ? "true" : "false",
|
||||
title: live ? "Edits are applied to named right away. Click to collect them for review first."
|
||||
: "Edits are collected as pending changes and applied from Changes. Click to apply edits right away.",
|
||||
onclick: () => setImmediate(!live),
|
||||
}, h("span", { class: "dot" }), live ? "Live edits" : "Review edits"));
|
||||
}
|
||||
mount(barEl, n > 0 ? h("div", { class: "pending-bar" },
|
||||
h("strong", null, plural(n, "pending change")),
|
||||
h("span", { class: "muted" }, "Your edits are collected in changeset #" + d.id + " and are not live yet."),
|
||||
h("a", { class: "button primary small", href: "#/changes" }, "Review & apply")) : null);
|
||||
}
|
||||
|
||||
async function logout() {
|
||||
|
||||
Reference in new issue
Block a user