From 88965096bb5dfc04c30e708fbcee138be9170067 Mon Sep 17 00:00:00 2001 From: adam Date: Mon, 12 Oct 2026 13:24:03 +0100 Subject: [PATCH] Web UI: option values keep the editor's layout A shown option value wrapped long lines back to the left edge, losing their indentation, and had different padding and width from the editor, so the text and column moved when Edit opened. Values now don't wrap (a long line scrolls), with the editor's padding and the same width. The "{ } around the list are optional" hint only shows for options that end in a list, not rate-limit, which only has one inside. Co-Authored-By: Claude Opus 5.5 --- CHANGELOG.md | 6 ++++++ internal/web/static/assets/pages/options.js | 10 +++++++--- internal/web/static/assets/style.css | 8 ++++++++ 3 files changed, 21 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 00a4677..20ea3b4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,12 @@ All notable changes to Jarl are documented here. The format follows ### Fixed +- Option values in the web UI looked different from the editor: a long + line wrapped back to the left edge, losing its indentation, and the + column changed width when Edit opened. Values now keep their layout + (a long line scrolls), with the same width and padding as the editor. + The "{ } around the list are optional" hint only shows for options that + end in a list (not rate-limit, which only has one inside). - Query statistics skipped every query signed with a TSIG key (named writes "/key NAME" after the client's port), such as queries with a view's key: they are counted now. diff --git a/internal/web/static/assets/pages/options.js b/internal/web/static/assets/pages/options.js index c561369..8323dc0 100644 --- a/internal/web/static/assets/pages/options.js +++ b/internal/web/static/assets/pages/options.js @@ -31,9 +31,13 @@ function valueWidget(stmt, value, ctx) { const t = codeArea(value || ""); // Statements such as allow-transfer and listen-on take an address // match list after optional parameters: suggest names at the cursor. - const takesList = stmt && /address_match_element/.test(stmt.grammar || ""); + // Names are suggested wherever the grammar has an address match list + // (rate-limit's exempt-clients too); the braces are only optional for + // statements that end in the list (catalog WrapList). + const hasAML = stmt && /address_match_element/.test(stmt.grammar || ""); + const takesList = stmt && /\{ ; \.\.\. \};$/.test((stmt.grammar || "").split("\n")[0]); if (takesList && !value) t.placeholder = "homelab; key xfer-key"; - return { el: h("div", null, t, takesList && ctx ? suggestions(t, ctx, { mode: "inline" }) : null), get: () => t.value.trim(), + return { el: h("div", null, t, hasAML && ctx ? suggestions(t, ctx, { mode: "inline" }) : null), get: () => t.value.trim(), hint: takesList ? "The { } around the list are optional: 10.0.0.53; key xfer-key becomes { 10.0.0.53; key xfer-key; }." : null }; } const i = input({ value: value || "", class: "mono" }); @@ -83,7 +87,7 @@ export async function optionsEditor(el, { scope, scope_id, catalogScope, title, const draw = () => { mount(listEl, table([ { label: "Option", render: (o) => h("span", null, h("strong", { class: "mono" }, o.name), o.status ? [" ", badge(o.status, "warn")] : null), tdClass: "nowrap" }, - { label: "Value", render: (o) => editing === o.id ? editRow(o) : h("pre", { class: "code", style: { padding: "2px 6px", whiteSpace: "pre-wrap" } }, o.formatted || o.value || "(no value)") }, + { label: "Value", render: (o) => editing === o.id ? editRow(o) : h("pre", { class: "code optval" }, o.formatted || o.value || "(no value)") }, { label: "Description", render: (o) => h("span", { class: "muted small" }, o.description || ""), tdClass: "small" }, { label: "", render: (o) => editing === o.id ? "" : h("span", null, h("button", { class: "small", onclick: () => { editing = o.id; draw(); } }, "Edit"), " ", diff --git a/internal/web/static/assets/style.css b/internal/web/static/assets/style.css index 1b2d1a4..1d548ef 100644 --- a/internal/web/static/assets/style.css +++ b/internal/web/static/assets/style.css @@ -501,3 +501,11 @@ ul.plain li { padding: 3px 0; } a.card.link { color: inherit; text-decoration: none; } a.card.link:hover { border-color: var(--accent, var(--link)); } + +/* An option's value as shown: laid out like the editor (no wrapping, a + long line scrolls; same padding and an invisible border), so the text + stays put when Edit opens it. */ +pre.optval { padding: 5px 8px; border: 1px solid transparent; background: none; white-space: pre; overflow-x: auto; max-width: min(72ch, 50vw); } +/* ...and the editor in a table cell gets the same width, so the column + doesn't jump when Edit opens. */ +td textarea.code { min-width: min(72ch, 50vw); }