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
+3 -5
View File
@@ -1,4 +1,4 @@
import { html, PageHeader, Empty, Card, ConfirmDelete, Table, renderGuard, esc, enc, $val, apiFetch, toast, definePage, getModel, modelFetch, MonoText, QuickModal } from '/static/hoover/index.js';
import { html, PageHeader, Empty, Card, ConfirmDelete, Table, renderGuard, esc, enc, $val, apiFetch, toast, definePage, getModel, MonoText, QuickModal } from '/static/hoover/index.js';
const addRule = QuickModal({
title: 'Add Rich Rule',
@@ -12,7 +12,6 @@ const addRule = QuickModal({
validate: (b) => !b.zone || !b.rule ? 'Zone and rule are required' : null,
successMsg: 'Rule added',
},
refresh: 'firewall',
});
export default definePage({
@@ -26,7 +25,7 @@ export default definePage({
if (guard) return guard;
const cfg = state.firewall.data?.config || {};
const zones = state.firewall.data?.zones?.available || [];
const zones = Object.keys(state.firewall.data?.zones || {});
const zoneData = cfg.zones || {};
const zoneRules = {};
Object.entries(zoneData).forEach(([zname, zcfg]) => {
@@ -46,8 +45,7 @@ export default definePage({
url=${'/api/firewall/rich-rules/' + enc(zone) + '/' + enc(ruleId || '')}
deleteKey=${zone + '-' + (ruleId || i)}
message=${'Remove rule: ' + ruleText.substring(0, 40) + '...?'}
success="Rule removed"
refresh="firewall" />
success="Rule removed" />
</td>
</tr>`;
});