Reverse zones: other views' addresses aren't missing PTRs

A reverse zone's missing PTR count covers only its own view's A and
AAAA records. Addresses only other views' records use are counted apart
(ptr_other_views, 'Other views' in zone show, a note on the zone page)
and filled only when asked: fill-ptr --other-views, zone create
--fill --other-views, or the New reverse zone dialog's checkbox. This
keeps one view's host names out of another view's reverse zones.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
adamandClaude Opus 5.5 committed 2026-10-20 11:40:05 +01:00
1 parent d18fe18ccd
commit 6cd3f6ef4e
16 files changed
+216 -48

No files matched your search

+6 -3
View File
@@ -72,7 +72,10 @@ export async function render(el, params, query) {
h("span", { class: "mono" }, f.address), " → ", h("span", { class: "mono" }, display(f.target)),
f.from ? h("span", { class: "muted" }, " (from view " + f.from + ")") : null,
f.others && f.others.length ? h("span", { class: "muted" }, " (also " + f.others.map(display).join(", ") + ")") : null)),
zone.ptr_missing > (zone.ptr_missing_list || []).length ? h("li", { class: "muted" }, "and " + (zone.ptr_missing - zone.ptr_missing_list.length) + " more") : null)) : null;
zone.ptr_missing > (zone.ptr_missing_list || []).length ? h("li", { class: "muted" }, "and " + (zone.ptr_missing - zone.ptr_missing_list.length) + " more") : null))
: zone.ptr_other_views && can("zones", true) ? h("div", { class: "muted small" },
plural(zone.ptr_other_views, "address", "addresses") + " in " + zone.prefix + " only other views' records use, without a PTR record here. ",
h("button", { class: "small", title: "Adds PTR records naming them from the other views' records, publishing those names in view " + zone.view, onclick: () => fillPTR(zone, true) }, "Name them from there")) : null;
const tabBar = h("div", { class: "tabs" }, tabs.map(([k, label]) => h("a", { href: "#/zones/" + id + "/" + k, class: k === tab ? "active" : "" }, label)));
// The header and tabs show at once. Below them, switching tabs keeps the
// previous tab's content (faded) until the new one is loaded, rather
@@ -951,9 +954,9 @@ async function toggleZone(zone) {
}
// fillPTR adds the PTR records a reverse zone lacks (docs/reverse.md).
async function fillPTR(zone) {
async function fillPTR(zone, otherViews) {
try {
const r = await change("POST", "/zones/" + zone.id + "/fill-ptr");
const r = await change("POST", "/zones/" + zone.id + "/fill-ptr", undefined, otherViews ? { query: { other_views: "1" } } : {});
toast(describeResult(r));
(r.warnings || []).forEach((w) => toast(w, "warn", 10000));
rerender();
+9 -2
View File
@@ -103,7 +103,10 @@ export async function newZoneDialog(views, zones, settings, preset = {}) {
cloneSel.addEventListener("change", syncLink);
const comment = input();
const fill = checkbox("Add a PTR record for each address the view's A and AAAA records use in this network", true);
const fillRow = field("PTR records", fill, "Names already in your zones, so the reverse zone answers from the start. Leave it off to add them yourself.");
// Off unless asked (or a network only other views use is picked): it
// publishes one view's host names in another.
const fillOther = checkbox("Also for addresses only other views' records use, named from there", false);
const fillRow = field("PTR records", h("div", null, fill, views.length ? fillOther : null), "Names already in your zones, so the reverse zone answers from the start. Leave it off to add them yourself.");
const updateClones = () => {
// Records can only be copied into a single zone.
const n = (kind === "reverse" ? reverseZoneFor(name.value).name || "-" : name.value.trim().toLowerCase().replace(/\.$/, "")) + ".";
@@ -206,6 +209,7 @@ export async function newZoneDialog(views, zones, settings, preset = {}) {
name.value = c.prefix;
sizeFrom = c.samples.length ? c.samples[0].split(" ")[0] : null;
if (views.length && c.view) view.value = c.view;
if ((c.hidden || "").startsWith("records in ")) { fill.input.checked = true; fillOther.input.checked = true; }
changed();
} },
h("span", { class: "mono" }, c.prefix),
@@ -273,7 +277,10 @@ export async function newZoneDialog(views, zones, settings, preset = {}) {
if (view.value) data.view = view.value;
const opts = [];
const list = (ta) => lines(ta.value).map((l) => l.replace(/;$/, "") + ";").join(" ");
if (t === "primary" && kind === "reverse" && fill.input.checked) data.fill = true;
if (t === "primary" && kind === "reverse" && fill.input.checked) {
data.fill = true;
if (fillOther.input.checked) data.fill_other_views = true;
}
if (t === "primary") {
data.nameservers = lines(ns.value);
if (contact.value.trim()) data.soa = { rname: contact.value.trim() };