dashboard: list record types in the README's order (closes #226)
check / check (push) Waiting to run
check / check (push) Waiting to run
Each row of the Hostnames table listed a nameserver's record types in the order Go happens to walk the record map, which changed from row to row and on every page load, so two nameservers with the same records looked different. formatRecords now sorts the types by their place in the README's list (A, AAAA, CNAME, MX, TXT, SRV, CAA, NS); any other type comes after them in alphabetical order. Values within a type were already sorted by the resolver. Model: opus-5-5
This commit is contained in:
@@ -19,6 +19,8 @@ trial run of the finished image: https://git.eeqj.de/sneak/dnswatcher/issues/149
|
||||
|
||||
# Completed Steps
|
||||
|
||||
- 2026-10-02: the dashboard lists each nameserver's record types in one fixed
|
||||
order, the README's, then any other type, not a random one (closes #226).
|
||||
- 2026-10-02: the dashboard and `/api/v1/status` show why a nameserver query or
|
||||
a certificate check failed, which only the state file showed (closes #225).
|
||||
- 2026-10-02: a name's CNAME is stored once per nameserver, not once per record
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"cmp"
|
||||
"embed"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"maps"
|
||||
"math"
|
||||
"net/http"
|
||||
"slices"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -122,16 +125,37 @@ func joinStrings(items []string, sep string) string {
|
||||
}
|
||||
|
||||
// formatRecords formats a map of record type → values into a
|
||||
// compact display string.
|
||||
// compact display string. Record types are listed in the order the
|
||||
// README lists them, any other type after them in alphabetical order,
|
||||
// so rows of nameservers with the same records read the same.
|
||||
func formatRecords(records map[string][]string) string {
|
||||
if len(records) == 0 {
|
||||
return "-"
|
||||
}
|
||||
|
||||
order := []string{"A", "AAAA", "CNAME", "MX", "TXT", "SRV", "CAA", "NS"}
|
||||
|
||||
position := func(rtype string) int {
|
||||
i := slices.Index(order, rtype)
|
||||
if i < 0 {
|
||||
return len(order)
|
||||
}
|
||||
|
||||
return i
|
||||
}
|
||||
|
||||
rtypes := slices.Collect(maps.Keys(records))
|
||||
slices.SortFunc(rtypes, func(a, b string) int {
|
||||
return cmp.Or(
|
||||
cmp.Compare(position(a), position(b)),
|
||||
strings.Compare(a, b),
|
||||
)
|
||||
})
|
||||
|
||||
var parts []string
|
||||
|
||||
for rtype, values := range records {
|
||||
for _, v := range values {
|
||||
for _, rtype := range rtypes {
|
||||
for _, v := range records[rtype] {
|
||||
parts = append(parts, rtype+": "+v)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,6 +80,45 @@ func TestFormatRecords(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestFormatRecordsTypeOrder checks that record types are listed in
|
||||
// the README's order (A, AAAA, CNAME, MX, TXT, SRV, CAA, NS), with
|
||||
// any other type after them in alphabetical order.
|
||||
func TestFormatRecordsTypeOrder(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
got := handlers.FormatRecords(map[string][]string{
|
||||
"SOA": {"ns1.example.com. hostmaster.example.com. 1 2 3 4 5"},
|
||||
"NS": {"ns1.example.com.", "ns2.example.com."},
|
||||
"CAA": {`0 issue "letsencrypt.org"`},
|
||||
"DNAME": {"example.net."},
|
||||
"TXT": {"v=spf1 -all"},
|
||||
"SRV": {"10 5 443 www.example.com."},
|
||||
"MX": {"10 mail.example.com."},
|
||||
"CNAME": {"www.example.com."},
|
||||
"AAAA": {"2001:db8::1"},
|
||||
"A": {"192.0.2.1"},
|
||||
})
|
||||
|
||||
want := strings.Join([]string{
|
||||
"A: 192.0.2.1",
|
||||
"AAAA: 2001:db8::1",
|
||||
"CNAME: www.example.com.",
|
||||
"MX: 10 mail.example.com.",
|
||||
"TXT: v=spf1 -all",
|
||||
"SRV: 10 5 443 www.example.com.",
|
||||
`CAA: 0 issue "letsencrypt.org"`,
|
||||
"NS: ns1.example.com.",
|
||||
"NS: ns2.example.com.",
|
||||
"DNAME: example.net.",
|
||||
"SOA: ns1.example.com. hostmaster.example.com. 1 2 3 4 5",
|
||||
}, ", ")
|
||||
|
||||
if got != want {
|
||||
t.Errorf("FormatRecords lists types out of order:\n got %q\nwant %q",
|
||||
got, want)
|
||||
}
|
||||
}
|
||||
|
||||
// dashboardRow returns the table row of page that contains name.
|
||||
func dashboardRow(t *testing.T, page string, name string) string {
|
||||
t.Helper()
|
||||
|
||||
Reference in New Issue
Block a user