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:
1 parent
ec16de875d
commit
b1719ce362
12 files changed
+656
-146
No files matched your search
@@ -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
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user