jarld checks the DoT and DoH listeners every 10 minutes as a client would (page, dashboard, jarl encrypted-dns health, jarl_encrypted_dns_up). The Compare views tab and jarl zone diff show what differs between a zone's views, with Use this version. --local works on unlinked zones. A changed certificates.dir is followed by encrypted DNS's tls statements. Sharing an already shared version replaces other views' overrides. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
90 lines
2.4 KiB
Go
90 lines
2.4 KiB
Go
package server
|
|
|
|
import (
|
|
"net/http"
|
|
"strings"
|
|
|
|
"jarl/internal/auth"
|
|
"jarl/internal/engine"
|
|
"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.
|
|
func (s *Server) zoneCompare(w http.ResponseWriter, r *http.Request, p *auth.Principal) error {
|
|
st, err := s.state(r)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
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)
|
|
if zs := st.ZonesNamed(name); perr != nil || len(zs) < 2 {
|
|
return err
|
|
} else {
|
|
z = zs[0]
|
|
}
|
|
}
|
|
var want []string
|
|
for _, v := range strings.Split(r.URL.Query().Get("views"), ",") {
|
|
if v = strings.TrimSpace(v); v != "" {
|
|
want = append(want, v)
|
|
}
|
|
}
|
|
var zones []*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}) {
|
|
continue
|
|
}
|
|
if len(want) > 0 && !containsFold(want, vn) {
|
|
continue
|
|
}
|
|
zones = append(zones, o)
|
|
}
|
|
st.SortByView(zones)
|
|
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)
|
|
return nil
|
|
}
|
|
|
|
func containsFold(list []string, s string) bool {
|
|
for _, x := range list {
|
|
if strings.EqualFold(x, s) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|