Reverse zone suggestions: yours and public, dismiss, per-view switch; child zones take their records

Suggestions split into networks that are yours (private IPv4 /24, ULA /48, an IPv6 /48 this server has an address in), which Create all creates, and public addresses (/32 or /128) to create only if they are yours or dismiss. A view can turn suggestions off, which also keeps it out of choosing a reverse zone's view (database version 13). A zone created inside another in the same view takes the parent's records under it; a delegation and its glue stay, a linked parent keeps them with a warning.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
adamandClaude Opus 5.5 committed 2026-10-12 22:01:18 +01:00
1 parent aa4b004a14
commit b24c730b97
21 files changed
+543 -107

No files matched your search

+24 -9
View File
@@ -281,7 +281,9 @@ export function pendingBadge(kind) {
}
// suggestPanel lists the reverse zones the private views lack for their
// records' addresses, to create filled with PTR records (docs/reverse.md).
// records' addresses, to create filled with PTR records (docs/reverse.md):
// networks that are yours, and public addresses, which may be a web
// host's, to create one by one or dismiss.
function suggestPanel(sugg, views) {
const create = async (list) => {
const reqs = list.map((s) => ({ op: "zone.create", data: { name: s.prefix, view_id: s.view_id || undefined, fill: true } }));
@@ -292,15 +294,28 @@ function suggestPanel(sugg, views) {
rerender();
} catch (e) { toast(e.message, "err", 8000); }
};
const dismiss = async (s) => {
try {
const r = await change("POST", "/reverse-zones/dismiss", { view_id: s.view_id || undefined, prefix: s.prefix });
toast(describeResult(r));
rerender();
} catch (e) { toast(e.message, "err", 8000); }
};
const cols = (pub) => [
views.length ? { label: "View", render: (s) => s.view } : null,
{ label: pub ? "Address" : "Network", render: (s) => [h("span", { class: "mono" }, pub ? s.prefix.replace(/\/(32|128)$/, "") : s.prefix), pub ? null : h("div", { class: "muted small" }, s.why)] },
{ label: "Zone", render: (s) => h("span", { class: "mono small" }, s.zone) },
{ label: pub ? "Name" : "PTR records", render: (s) => pub ? h("span", { class: "mono small" }, s.samples.map((x) => x.split(" ")[1]).join(", ")) : [String(s.fill), s.samples.length ? h("div", { class: "muted small mono" }, s.samples.join(", ") + (s.fill > s.samples.length ? ", …" : "")) : null] },
{ label: "", render: (s) => [h("button", { class: "small", onclick: () => create([s]) }, "Create"), pub ? [" ", h("button", { class: "small", title: "Not yours (a web host's, say): don't suggest it again", onclick: () => dismiss(s) }, "Dismiss")] : null], tdClass: "actions-cell" },
].filter(Boolean);
const yours = sugg.filter((s) => s.group === "yours");
const pub = sugg.filter((s) => s.group !== "yours");
return h("div", { class: "panel" },
h("div", { class: "page-head" }, h("h2", { class: "grow" }, "Missing reverse zones"),
sugg.length > 1 ? h("div", { class: "actions" }, h("button", { onclick: () => create(sugg) }, "Create all " + sugg.length)) : null),
yours.length > 1 ? h("div", { class: "actions" }, h("button", { onclick: () => create(yours) }, "Create all " + yours.length)) : null),
h("p", { class: "muted small" }, "Your records use these addresses, but no reverse zone answers \"which host has this address?\" for them" + (views.length ? " in their view" : "") + ". Jarl creates each with a PTR record for every address there. Only your own clients ask this server: the internet asks whoever owns the addresses."),
table([
views.length ? { label: "View", render: (s) => s.view } : null,
{ label: "Network", render: (s) => [h("span", { class: "mono" }, s.prefix), h("div", { class: "muted small" }, s.why)] },
{ label: "Zone", render: (s) => h("span", { class: "mono small" }, s.zone) },
{ label: "PTR records", render: (s) => [String(s.fill), s.samples.length ? h("div", { class: "muted small mono" }, s.samples.join(", ") + (s.fill > s.samples.length ? ", …" : "")) : null] },
{ label: "", render: (s) => h("button", { class: "small", onclick: () => create([s]) }, "Create"), tdClass: "actions-cell" },
].filter(Boolean), sugg));
yours.length ? table(cols(false), yours) : null,
pub.length ? [h("h3", null, "Public addresses: only if they're yours"),
h("p", { class: "muted small" }, "Jarl can't tell your own servers from someone else's (a web host's). Create a zone for an address that is yours; dismiss the others. Each gets a zone of its own: the rest of its network belongs to others."),
table(cols(true), pub)] : null);
}