Staged undo, built-ins window, machine warnings; raw and dynamic set fixes
- Undo is staged like any edit: in review mode the web UI's Undo, and hc history undo with -C or HOUSECARL_CHANGESET, add a revert operation that checks, when replayed, that nothing it touches changed since; applied, History links the entries. Imports and takeovers still can't be staged. - The rules page sums up a chain's built-ins in one line; a window lists them with what they do and their hits, and switches them. - Warnings about the machine (dashboard, hc status): nftables.service enabled (it loads /etc/nftables.conf at boot and flushes every table when stopped), and the imported table still in force beside Housecarl's. The docs say to disable nftables.service but not stop it. - A raw rule with other fields set is refused (raw_conflict) instead of silently ignoring them; the rule dialog's nftables section hides what it replaces, and the importer's raw rules carry only their text. - A dynamic set with configured entries is refused (set_dynamic_entries): they were silently ignored. The set dialog shows entries or the entry timeout, as fits. - The rule dialog's chain choices are just the chains' names. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
d25e12995e
commit
9a165cf70b
26 files changed
+408
-58
No files matched your search
@@ -795,3 +795,73 @@ func TestAutoConfirm(t *testing.T) {
|
||||
t.Errorf("manual confirmation: %+v", mr)
|
||||
}
|
||||
}
|
||||
|
||||
// TestStagedUndo: an undo staged in a changeset changes nothing until the
|
||||
// changeset is applied; then History links the two entries.
|
||||
func TestStagedUndo(t *testing.T) {
|
||||
e := newEnv(t)
|
||||
e.srv.probationFor = 5 * time.Second
|
||||
var mr api.MutationResponse
|
||||
e.call(e.token, "POST", "/zones", model.Zone{Name: "lan", Interfaces: []string{"eth1"}}, &mr)
|
||||
e.call(e.token, "POST", "/confirm", nil, nil)
|
||||
made := mr.HistoryID
|
||||
var cs model.Changeset
|
||||
e.call(e.token, "POST", "/changesets", nil, &cs)
|
||||
q := "?changeset=" + itoa(cs.ID)
|
||||
if code := e.call(e.token, "POST", "/history/"+itoa(made)+"/revert"+q, nil, &mr); code != 200 || !mr.Staged {
|
||||
t.Fatalf("staging the undo: %d %+v", code, mr)
|
||||
}
|
||||
if op := mr.Changeset.Ops[0]; op.Op != model.OpRevert || !strings.HasPrefix(op.Summary, "undo "+itoa(made)+":") {
|
||||
t.Errorf("the operation: %+v", op)
|
||||
}
|
||||
if len(e.eng.State().Zones) != 1 {
|
||||
t.Fatal("a staged undo was applied")
|
||||
}
|
||||
// Staging it again can't work: the changeset already undoes it.
|
||||
var eb api.ErrorBody
|
||||
if code := e.call(e.token, "POST", "/history/"+itoa(made)+"/revert"+q, nil, &eb); code != 409 {
|
||||
t.Errorf("a second undo: %d %+v", code, eb)
|
||||
}
|
||||
if code := e.call(e.token, "POST", "/changesets/"+itoa(cs.ID)+"/apply", nil, &mr); code != 200 || !mr.Applied {
|
||||
t.Fatalf("apply: %d %+v", code, mr)
|
||||
}
|
||||
if len(e.eng.State().Zones) != 0 {
|
||||
t.Error("the undo wasn't applied")
|
||||
}
|
||||
h, _ := e.srv.store.GetHistory(context.Background(), mr.HistoryID)
|
||||
orig, _ := e.srv.store.GetHistory(context.Background(), made)
|
||||
if h.RevertOf != made || orig.RevertedBy != h.ID || !strings.HasPrefix(h.Description, "undo ") {
|
||||
t.Errorf("history: %+v, original %+v", h, orig)
|
||||
}
|
||||
}
|
||||
|
||||
// TestHostProblems: an enabled nftables.service, and an imported table
|
||||
// still in force, are warned about.
|
||||
func TestHostProblems(t *testing.T) {
|
||||
e := newEnv(t)
|
||||
dir := t.TempDir()
|
||||
link := dir + "/nftables.service"
|
||||
e.srv.unitLinks = []string{link}
|
||||
codes := func() string {
|
||||
var st api.Status
|
||||
e.call(e.token, "GET", "/status", nil, &st)
|
||||
var out []string
|
||||
for _, p := range st.Problems {
|
||||
out = append(out, p.Code)
|
||||
}
|
||||
return strings.Join(out, " ")
|
||||
}
|
||||
if c := codes(); strings.Contains(c, "nftables_service") || strings.Contains(c, "takeover_pending") {
|
||||
t.Errorf("warned without cause: %s", c)
|
||||
}
|
||||
os.Symlink("/usr/lib/systemd/system/nftables.service", link) // dangling is fine: Lstat
|
||||
// The fake ruleset lists inet main: imported from it, it still applies.
|
||||
e.srv.store.SetMeta(context.Background(), importedTableKey, "inet main")
|
||||
if c := codes(); !strings.Contains(c, "nftables_service") || !strings.Contains(c, "takeover_pending") {
|
||||
t.Errorf("not warned: %s", c)
|
||||
}
|
||||
e.srv.store.SetMeta(context.Background(), importedTableKey, "inet gone")
|
||||
if c := codes(); strings.Contains(c, "takeover_pending") {
|
||||
t.Errorf("warned about a table that is gone: %s", c)
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user