Compare views rebuilt: from the zone's view, in sections, one line per record

The Compare views tab and jarl zone diff compare a zone with the same zone in other views from the view you're on: a summary per view, what is answered differently (Use this everywhere), the names only some views have (Add to VIEW), and how many are the same. One name can be compared (jarl zone diff ZONE NAME [TYPE], a Compare button per record). The CLI prints one line per record, cut to the terminal (--wide shows it whole); the web UI cuts long values with the whole one on hover. GET /zones/{zone}/compare returns these sections, with ?with, ?name and ?type.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
adamandClaude Opus 5.5 committed 2026-10-13 10:55:57 +01:00
1 parent ec16de875d
commit b1719ce362
12 files changed
+656 -146

No files matched your search

+43 -40
View File
@@ -9,15 +9,29 @@ import (
"jarl/internal/model"
)
// zoneCompare: GET /zones/{zone}/compare lines up a zone's records across
// the views that have it (docs/views.md#linked-zones). ?views=a,b picks the
// views (default: every view with a primary zone of that name), ?all=1
// includes the names and types that are the same everywhere.
// zoneCompare: GET /zones/{zone}/compare compares a zone with the same
// zone in other views (docs/views.md#comparing-views), from the zone's own
// view: ?with=a,b picks the other views (default: every view with a
// primary zone of that name), ?name=www[&type=A] narrows it to one name,
// ?all=1 includes what is the same. A zone named without its view is
// compared from the first view that has it. (?views=a,b, the views to
// compare with the first as the base, works too.)
func (s *Server) zoneCompare(w http.ResponseWriter, r *http.Request, p *auth.Principal) error {
st, err := s.state(r)
if err != nil {
return err
}
q := r.URL.Query()
list := func(k string) []string {
var out []string
for _, v := range strings.Split(q.Get(k), ",") {
if v = strings.TrimSpace(v); v != "" {
out = append(out, v)
}
}
return out
}
with, views := list("with"), list("views")
z, err := s.zoneByPath(r, st, p)
if err != nil {
// A name in several views is what there is to compare.
@@ -26,56 +40,45 @@ func (s *Server) zoneCompare(w http.ResponseWriter, r *http.Request, p *auth.Pri
ref = "."
}
name, perr := engine.ParseZoneName(ref)
if zs := st.ZonesNamed(name); perr != nil || len(zs) < 2 {
zs := st.ZonesNamed(name)
if perr != nil || len(zs) < 2 {
return err
} else {
z = zs[0]
}
st.SortByView(zs)
z = zs[0]
if len(views) > 0 {
for _, o := range zs {
if strings.EqualFold(st.ViewName(o), views[0]) {
z = o
}
}
}
}
var want []string
for _, v := range strings.Split(r.URL.Query().Get("views"), ",") {
if v = strings.TrimSpace(v); v != "" {
want = append(want, v)
}
readable := func(o *model.Zone) bool {
vn := st.ViewName(o)
return o.Type == model.ZonePrimary && p.CanSeeZone(o.Name, vn) && p.Can(auth.Action{Resource: auth.ResRecords, Zone: o.Name, View: vn})
}
var zones []*model.Zone
if !readable(z) {
return badRequest("zone %s isn't a primary zone whose records you may read", engine.Display(z.Name))
}
zones := []*model.Zone{z}
var others []*model.Zone
for _, o := range st.ZonesNamed(z.Name) {
vn := st.ViewName(o)
if o.Type != model.ZonePrimary || !p.CanSeeZone(o.Name, vn) || !p.Can(auth.Action{Resource: auth.ResRecords, Zone: o.Name, View: vn}) {
if o.ID == z.ID || !readable(o) {
continue
}
if len(want) > 0 && !containsFold(want, vn) {
if len(with) > 0 && !containsFold(with, vn) || len(views) > 0 && !containsFold(views, vn) {
continue
}
zones = append(zones, o)
others = append(others, o)
}
st.SortByView(zones)
st.SortByView(others)
zones = append(zones, others...)
if len(zones) < 2 {
return badRequest("zone %s is in only one view (that you may read): there is nothing to compare", engine.Display(z.Name))
}
rows := engine.CompareZones(st, zones)
all := queryBool(r, "all")
out := struct {
Zone string `json:"zone"`
Views []string `json:"views"`
Linked bool `json:"linked"` // all of them in one link
Same int `json:"same"` // names and types the same everywhere
Rows []engine.CompareRow `json:"rows"`
}{Zone: engine.Display(z.Name), Rows: []engine.CompareRow{}, Linked: true}
for _, o := range zones {
out.Views = append(out.Views, st.ViewName(o))
out.Linked = out.Linked && o.LinkGroup != 0 && o.LinkGroup == zones[0].LinkGroup
}
for _, row := range rows {
if row.Same {
out.Same++
if !all {
continue
}
}
out.Rows = append(out.Rows, row)
}
writeJSON(w, http.StatusOK, out)
writeJSON(w, http.StatusOK, engine.Compare(st, zones, engine.CompareFilter{Name: q.Get("name"), Type: q.Get("type"), All: queryBool(r, "all")}))
return nil
}