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:
adamandClaude Opus 5.5 committed 2026-10-09 22:33:02 +00:00
1 parent 5f04ab523b
commit 295c549479
29 files changed
+815 -60

No files matched your search

+72 -3
View File
@@ -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 {