Zone settings: no empty changes, keep an outside name server absolute

- A zone update that changes nothing stages nothing: saving a zone's
  settings unchanged says "Nothing changed" instead of staging an empty
  change. Staging operations that change nothing is a 400 "nothing to
  change".
- The settings form showed the SOA MNAME without its final dot, which the
  server reads as relative to the zone, so saving wg.adbraun.lol turned
  ns1.adbraun.lol. into ns1.adbraun.lol.wg.adbraun.lol. Names outside the
  zone keep the dot, and MNAME is only sent when edited.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
adamandClaude Opus 5.5 committed 2026-10-18 15:13:01 +01:00
1 parent 0e663ab36c
commit e38390df7d
6 files changed
+94 -6

No files matched your search

+17 -4
View File
@@ -804,12 +804,21 @@ async function recordsTab(el, zone, query) {
// ---- settings
// inZone reports whether an absolute name is the zone or inside it.
function inZone(name, zoneName) {
if (!name) return true;
return name === zoneName || name.endsWith("." + zoneName);
}
async function settingsTab(el, zone) {
const views = await get("/views");
const err = h("div");
const rows = [];
const s = zone.soa || {};
const mname = input({ value: display(s.mname), class: "mono" });
// The server reads a name without a final dot as relative to the zone,
// so a name outside it keeps its dot.
const mnameText = inZone(s.mname, zone.name) ? display(s.mname) : (s.mname || "");
const mname = input({ value: mnameText, class: "mono" });
const rname = input({ value: zone.rname_email || emailOf(s.rname) });
const refresh = input({ type: "number", value: s.refresh });
const retry = input({ type: "number", value: s.retry });
@@ -823,7 +832,7 @@ async function settingsTab(el, zone) {
const autoPTR = checkbox("Keep PTR records in step with the A and AAAA records", !!zone.auto_ptr);
if (zone.type === "primary") {
rows.push(
field("Primary name server", mname, "SOA MNAME."),
field("Primary name server", mname, "SOA MNAME. A name outside " + zone.display_name + " ends with '.'; without it, the name is taken as relative to the zone."),
field("Contact", rname, "SOA RNAME, as an email address."),
field("Refresh", refresh, "Seconds between secondaries checking for updates."),
field("Retry", retry, "Seconds before a failed refresh is retried."),
@@ -841,7 +850,8 @@ async function settingsTab(el, zone) {
mount(err);
const body = { comment: comment.value };
if (zone.type === "primary") {
body.soa = { mname: mname.value.trim(), rname: rname.value.trim(), refresh: +refresh.value, retry: +retry.value, expire: +expire.value, minimum: +minimum.value };
body.soa = { rname: rname.value.trim(), refresh: +refresh.value, retry: +retry.value, expire: +expire.value, minimum: +minimum.value };
if (mname.value.trim() !== mnameText) body.soa.mname = mname.value.trim();
body.default_ttl = +ttl.value;
body.serial_policy = policy.value;
if (!!zone.auto_ptr !== autoPTR.input.checked) body.auto_ptr = autoPTR.input.checked;
@@ -852,7 +862,10 @@ async function settingsTab(el, zone) {
const r = await change("PATCH", "/zones/" + zone.id, body);
toast(describeResult(r));
rerender();
} catch (e) { mount(err, errorBox(e)); }
} catch (e) {
if (e.status === 400 && /^nothing to (change|do)/.test(e.message)) toast("Nothing changed");
else mount(err, errorBox(e));
}
};
mount(el, h("div", { class: "panel" }, h("div", { class: "form-grid" }, rows), err,
h("div", { class: "actions", style: { marginTop: "12px" } }, h("button", { class: "primary", onclick: save }, "Save settings"))));