Web UI: zone naming and reverse zones, ACL suggestions, record form

- New zone dialog: choose Domain or Reverse zone. Reverse zones are
  created from a network and their name is generated; both kinds show the
  exact zone name and flag likely mistakes (addresses or URLs as names,
  host-like names, duplicates in the view, parent/child zones, networks
  off the octet/nibble boundaries) before anything is submitted.
- Record form: clears every field but the type after an add; shows the
  record exactly as it will be stored and warns about unqualified names.
  In reverse zones it takes an address, defaults to PTR, suggests host
  names of A/AAAA records with that address and warns about a second PTR;
  the record list shows addresses.
- Address match lists (options, view match lists, ACL elements) offer the
  existing ACLs, TSIG keys and built-ins as one-click suggestions and
  flag unknown names.
- Server: in reverse zones a record can be named by its address, and a
  PTR target like nas.example.com without the final dot is read as the
  full host name (a bare label is refused). Warnings noticed while parsing
  record data reach the result again.
- Fixes: brand link hover underline, the dashboard's "New zone" link
  opening the dialog twice.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
abrown38andClaude Opus 5.5 committed 2026-10-07 20:49:43 +00:00
1 parent 3b7bee67f8
commit bf7f99874b
19 files changed
+577 -35

No files matched your search

+6
View File
@@ -116,6 +116,12 @@ func Owner(input, zone string) (string, error) {
if in == "" || in == "@" {
return zone, nil
}
// In reverse zones an address stands for its PTR name.
if IsReverse(zone) {
if addr, err := netip.ParseAddr(strings.TrimSuffix(in, ".")); err == nil {
return ReverseName(addr), nil
}
}
a, err := ToASCII(in)
if err != nil {
return "", err
+13
View File
@@ -172,3 +172,16 @@ func TestMisc(t *testing.T) {
t.Error("IsSubdomain")
}
}
func TestOwnerAddressInReverseZone(t *testing.T) {
for _, tc := range []struct{ in, zone, want string }{
{"10.10.0.20", "0.10.10.in-addr.arpa.", "20.0.10.10.in-addr.arpa."},
{"20", "0.10.10.in-addr.arpa.", "20.0.10.10.in-addr.arpa."},
{"2001:db8::1", "8.b.d.0.1.0.0.2.ip6.arpa.", "1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.8.b.d.0.1.0.0.2.ip6.arpa."},
{"10.10.0.20", "adbraun.lol.", "10.10.0.20.adbraun.lol."}, // not special in forward zones
} {
if got, err := Owner(tc.in, tc.zone); err != nil || got != tc.want {
t.Errorf("Owner(%q, %q) = %q, %v; want %q", tc.in, tc.zone, got, err, tc.want)
}
}
}