state: applied-config snapshots + per-field pending diffs
- lib/common: stamp_applied() now records a _last_applied_config snapshot alongside the hash; strip_apply_meta() centralizes bookkeeping-key stripping; deep_diff() reports field-level changes - state collectors (dnsmasq/nginx/wireguard/networkd) expose pending_diff so the dashboard can show exactly which fields changed since the last apply (wireguard diff excludes private_key paths) - dashboard pending-changes card renders per-change lines with a generic fallback when no snapshot is recorded - firewall: firewalld built-in zones no longer flagged as unmanaged; public-zone masquerade skipped in pending changes since apply drives it via nftables propagation - schema: PendingChange TypedDict; pending_diff on DnsmasqStatus / WgStatus; tests in test_common.py, test_firewall.py, test_state.py
This commit is contained in:
@@ -134,6 +134,23 @@ class DnsmasqDhcpLease(TypedDict):
|
||||
interface: str
|
||||
|
||||
|
||||
class PendingChange(TypedDict):
|
||||
"""One field-level difference between the applied config and the current
|
||||
saved config (see `lib.common.deep_diff`).
|
||||
|
||||
Attributes:
|
||||
path: Dotted (or indexed) path to the changed field.
|
||||
action: "added", "removed", or "changed".
|
||||
old: Value in the last applied config (None when added).
|
||||
new: Value in the current config (None when removed).
|
||||
"""
|
||||
|
||||
path: str
|
||||
action: str
|
||||
old: Any
|
||||
new: Any
|
||||
|
||||
|
||||
class DnsmasqStatus(TypedDict):
|
||||
"""Dnsmasq service status snapshot.
|
||||
|
||||
@@ -142,12 +159,15 @@ class DnsmasqStatus(TypedDict):
|
||||
config_file_exists: Whether the rendered .conf is on disk.
|
||||
active_leases: Count of currently active leases.
|
||||
pending_changes: Whether the config is dirty vs the applied state.
|
||||
pending_diff: Field-level changes since the last apply (empty when
|
||||
up to date or when no applied snapshot is recorded).
|
||||
"""
|
||||
|
||||
service_active: bool
|
||||
config_file_exists: bool
|
||||
active_leases: int
|
||||
pending_changes: bool
|
||||
pending_diff: list[PendingChange]
|
||||
|
||||
|
||||
class DnsmasqState(TypedDict):
|
||||
@@ -316,6 +336,8 @@ class WgStatus(TypedDict):
|
||||
peers: Legacy single-interface runtime peers.
|
||||
classes: Per-access-class runtime status (keyed by class name).
|
||||
pending_changes: Whether the config is dirty vs the applied state.
|
||||
pending_diff: Field-level changes since the last apply (empty when
|
||||
up to date or when no applied snapshot is recorded).
|
||||
"""
|
||||
|
||||
up: bool
|
||||
@@ -323,6 +345,7 @@ class WgStatus(TypedDict):
|
||||
peers: list[WgStatusPeer]
|
||||
classes: dict[str, WgClassStatus]
|
||||
pending_changes: bool
|
||||
pending_diff: list[PendingChange]
|
||||
|
||||
|
||||
class WgPeer(TypedDict, total=False):
|
||||
|
||||
Reference in New Issue
Block a user