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>
93 lines
2.6 KiB
Go
93 lines
2.6 KiB
Go
package server
|
|
|
|
import (
|
|
"net/http"
|
|
"strings"
|
|
|
|
"jarl/internal/auth"
|
|
"jarl/internal/engine"
|
|
"jarl/internal/model"
|
|
)
|
|
|
|
// 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.
|
|
ref := r.PathValue("zone")
|
|
if ref == "root" {
|
|
ref = "."
|
|
}
|
|
name, perr := engine.ParseZoneName(ref)
|
|
zs := st.ZonesNamed(name)
|
|
if perr != nil || len(zs) < 2 {
|
|
return err
|
|
}
|
|
st.SortByView(zs)
|
|
z = zs[0]
|
|
if len(views) > 0 {
|
|
for _, o := range zs {
|
|
if strings.EqualFold(st.ViewName(o), views[0]) {
|
|
z = o
|
|
}
|
|
}
|
|
}
|
|
}
|
|
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})
|
|
}
|
|
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.ID == z.ID || !readable(o) {
|
|
continue
|
|
}
|
|
if len(with) > 0 && !containsFold(with, vn) || len(views) > 0 && !containsFold(views, vn) {
|
|
continue
|
|
}
|
|
others = append(others, o)
|
|
}
|
|
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))
|
|
}
|
|
writeJSON(w, http.StatusOK, engine.Compare(st, zones, engine.CompareFilter{Name: q.Get("name"), Type: q.Get("type"), All: queryBool(r, "all")}))
|
|
return nil
|
|
}
|
|
|
|
func containsFold(list []string, s string) bool {
|
|
for _, x := range list {
|
|
if strings.EqualFold(x, s) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|