Disable records instead of deleting them
A disabled record stays in Jarl with its ID, TTL and comment but is left out of the zone; the zone file lists it as a comment. Web UI toggle, jarl record disable|enable, record.disable|enable operations and API endpoints. Database schema 5. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
5f04ab523b
commit
295c549479
29 files changed
+815
-60
No files matched your search
@@ -4,6 +4,7 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -44,10 +45,17 @@ func (s *Server) zoneByPath(r *http.Request, st *model.State, p *auth.Principal)
|
||||
return z, nil
|
||||
}
|
||||
|
||||
func recordCounts(st *model.State) map[int64]int {
|
||||
counts := map[int64]int{}
|
||||
type recordCount struct{ all, disabled int }
|
||||
|
||||
func recordCounts(st *model.State) map[int64]recordCount {
|
||||
counts := map[int64]recordCount{}
|
||||
for _, r := range st.Records {
|
||||
counts[r.ZoneID]++
|
||||
c := counts[r.ZoneID]
|
||||
c.all++
|
||||
if r.Disabled {
|
||||
c.disabled++
|
||||
}
|
||||
counts[r.ZoneID] = c
|
||||
}
|
||||
return counts
|
||||
}
|
||||
@@ -190,10 +198,14 @@ func (s *Server) listRecords(w http.ResponseWriter, r *http.Request, p *auth.Pri
|
||||
}
|
||||
typ := strings.ToUpper(q.Get("type"))
|
||||
search := strings.ToLower(q.Get("q"))
|
||||
disabled := q.Get("disabled") // "1": only disabled records, "0": only enabled ones
|
||||
match := func(rec *model.Record) bool {
|
||||
if name != "" && rec.Name != name || typ != "" && rec.Type != typ {
|
||||
return false
|
||||
}
|
||||
if disabled != "" && rec.Disabled != (disabled == "1" || disabled == "true") {
|
||||
return false
|
||||
}
|
||||
return search == "" || strings.Contains(rec.Name, search) || strings.Contains(strings.ToLower(rec.Data), search) || strings.Contains(strings.ToLower(rec.Comment), search)
|
||||
}
|
||||
recs := st.ZoneRecords(z.ID)
|
||||
@@ -229,6 +241,13 @@ func (s *Server) listRecords(w http.ResponseWriter, r *http.Request, p *auth.Pri
|
||||
ro.Pending = "add"
|
||||
case old.Version != rec.Version:
|
||||
ro.Pending = "change"
|
||||
if only := *old; only.Disabled != rec.Disabled {
|
||||
// Just disabled or enabled: say which.
|
||||
only.Disabled, only.Meta = rec.Disabled, rec.Meta
|
||||
if reflect.DeepEqual(only, *rec) {
|
||||
ro.Pending = map[bool]string{true: "disable", false: "enable"}[rec.Disabled]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
out = append(out, ro)
|
||||
@@ -380,6 +399,56 @@ func (s *Server) deleteRecordsByMatch(w http.ResponseWriter, r *http.Request, p
|
||||
return s.mutate(w, r, p, "", []engine.Request{engine.NewRequest(engine.OpRecordDelete, 0, in)}, nil)
|
||||
}
|
||||
|
||||
// setRecordDisabled: POST /records/{id}/disable or /enable [?ptr=1]
|
||||
func (s *Server) setRecordDisabled(disabled bool) func(http.ResponseWriter, *http.Request, *auth.Principal) error {
|
||||
op := disableOp(disabled)
|
||||
return func(w http.ResponseWriter, r *http.Request, p *auth.Principal) error {
|
||||
st, err := s.state(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
rec, err := s.recordByPath(r, st, p)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req := engine.NewRequest(op, rec.ID, engine.RecordInput{PTR: queryBool(r, "ptr")})
|
||||
id := rec.ID
|
||||
return s.mutate(w, r, p, "", []engine.Request{req}, func(st *model.State, _ int64) any { return recordObject(st, id) })
|
||||
}
|
||||
}
|
||||
|
||||
// setRecordsDisabled: POST /zones/{zone}/records/disable or /enable
|
||||
// ?name=&type=[&data=][&missing_ok=1][&ptr=1]
|
||||
func (s *Server) setRecordsDisabled(disabled bool) func(http.ResponseWriter, *http.Request, *auth.Principal) error {
|
||||
op := disableOp(disabled)
|
||||
return func(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 {
|
||||
return err
|
||||
}
|
||||
q := r.URL.Query()
|
||||
if q.Get("name") == "" || q.Get("type") == "" {
|
||||
return badRequest("give name and type (and optionally data) of the records")
|
||||
}
|
||||
in := engine.RecordInput{ZoneID: z.ID, Name: model.Ptr(q.Get("name")), Type: model.Ptr(q.Get("type")), MissingOK: queryBool(r, "missing_ok"), PTR: queryBool(r, "ptr")}
|
||||
if d := q.Get("data"); d != "" {
|
||||
in.Data = &d
|
||||
}
|
||||
return s.mutate(w, r, p, "", []engine.Request{engine.NewRequest(op, 0, in)}, nil)
|
||||
}
|
||||
}
|
||||
|
||||
func disableOp(disabled bool) string {
|
||||
if disabled {
|
||||
return engine.OpRecordDisable
|
||||
}
|
||||
return engine.OpRecordEnable
|
||||
}
|
||||
|
||||
func (s *Server) putRRSet(w http.ResponseWriter, r *http.Request, p *auth.Principal) error {
|
||||
st, err := s.state(r)
|
||||
if err != nil {
|
||||
|
||||
Reference in new issue
Block a user