ws: migrate push stream to data streaming

- daemon: send full snapshot on connect; versions/tick now carry the
  full state of one subsystem (subsystem + data); no legacy
  updated/subsystems payloads; refresh_state and POST /status/refresh
  broadcast per-subsystem versions with data
- client: modelSet() patches models in place; onMessage/topic refresh
  retired; 3s initial-load fallback via new POST /api/status/refresh
- schema: lib/schema.py TypedDicts + hoover/schema.js defaults +
  docs/state-model.md as single source of truth for state shapes
- system: poll at 1s, volatile metrics registered, dashboard uses a
  dedicated system model (status model removed)
- firewall: refuse to strip both https and ssh from the default zone
  (409, force override via UI confirm); set_zone_services persists
  services to the declarative config; collector exposes default_zone
- UI: pages migrate to flat state shapes; post-mutation modelFetch
  refreshes removed (WS delta covers it)
- tests: ws snapshot/delta/broadcast, refresh-state, schema types,
  model-set/js ws handler and reconnect fallback
This commit is contained in:
2026-08-20 01:38:00 +00:00
parent 9c9f92ad04
commit 332d14e37d
45 changed files with 2819 additions and 496 deletions
+25
View File
@@ -28,6 +28,25 @@ class TestState:
assert state is not None
assert isinstance(state, State)
def test_get_snapshot_empty(self):
"""Fresh store: snapshot lists every subsystem, all None."""
s = State()
snap = s.get_snapshot()
assert set(snap) == set(s.SUBSYSTEMS)
assert all(v is None for v in snap.values())
def test_get_snapshot_reflects_set_and_none(self):
"""Snapshot carries set data; failed collections stay None."""
s = State()
s.set("firewall", {"zones": {}})
s.set("system", {"load": {"load1": 0.0}})
s.set("dnsmasq", None)
snap = s.get_snapshot()
assert snap["firewall"] == {"zones": {}}
assert snap["system"] == {"load": {"load1": 0.0}}
assert snap["dnsmasq"] is None
assert snap["acme"] is None
class TestCollectAll:
@patch("lib.state.run")
@@ -37,6 +56,8 @@ class TestCollectAll:
def run_side(args, **kwargs):
if "--get-active-zones" in args:
return "public\n eth0"
if "--get-default-zone" in args:
return "public\n"
if "--get-services" in args:
return "ssh http"
if "ip" in args[0]:
@@ -60,6 +81,8 @@ class TestCollectAll:
result = _collect_firewall()
assert isinstance(result, dict)
assert "active_zones" in result
assert "default_zone" in result
assert result["default_zone"] == "public"
assert "interfaces" in result
assert "timestamp" in result
@@ -71,6 +94,8 @@ class TestCollectAll:
def run_side(args, **kwargs):
if "--get-active-zones" in args:
return "public\n eth0\ninternal\n eth0.100"
if "--get-default-zone" in args:
return "public\n"
if "--get-services" in args:
return "ssh http"
if "ip" in args[0]: