status: cancel-all reverts pending changes to last applied config

- lib.common.revert_to_applied(): restore a config file from its
  _last_applied_config snapshot (stamped hash); no baseline -> skip with
  reason, file untouched
- firewall config_apply now stamps the applied baseline like the other
  subsystems; GET /firewall/config and the state collector strip the
  internal _last_applied_* keys
- POST /status/cancel-all + /api/status/cancel-all: revert pending
  subsystems, {cancelled, skipped, errors}, partial-failure safe
- dashboard: "Cancel All Changes" button with confirm modal
  (CancelConfirm, reuses the pending-changes modal rows); the pending
  changes card is hidden entirely when nothing is pending
- tests: revert_to_applied, status_cancel_all, firewall stamping/meta
  stripping, /api/status/cancel-all route, node tests for CancelConfirm;
  firewall _config_apply tests no longer write the real repo config
- docs: api.md, state-model.md, config.md, hoover.md
This commit is contained in:
2026-08-21 02:10:05 +00:00
parent 30b51ad7d3
commit 55309cfd86
18 changed files with 791 additions and 19 deletions
+24
View File
@@ -55,6 +55,29 @@ def stamp_applied(cfg: dict[str, Any]) -> dict[str, Any]:
return cfg
def revert_to_applied(path: Path) -> tuple[bool, str]:
"""Restore the config file at *path* to its last-applied snapshot.
Reads the raw file, and when it records a ``_last_applied_config``
snapshot, rewrites the file from that snapshot (stamped with a fresh
hash so the pending check reports the config as up to date).
Args:
path: Path to the config JSON file to revert.
Returns:
Tuple ``(True, "")`` when the file was restored, or
``(False, reason)`` when it could not be (missing file or no
recorded baseline — i.e. the config was never applied).
"""
cfg = load_json(path)
snap = cfg.get(_LAST_APPLIED_CONFIG_KEY)
if not isinstance(snap, dict):
return False, "No baseline recorded (never applied)"
save_json(path, stamp_applied(deepcopy(snap)))
return True, ""
def deep_diff(old: Any, new: Any, prefix: str = "") -> list[dict[str, Any]]:
"""Return a list of field-level changes between two configurations.
@@ -284,6 +307,7 @@ __all__ = [
"ensure_dirs",
"get_interface_ip",
"load_json",
"revert_to_applied",
"run",
"run_proc",
"save_json",