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
+5 -6
View File
@@ -1,4 +1,4 @@
import { html, PageHeader, Empty, Table, renderGuard, esc, enc, $val, apiFetch, toast, openModal, closeModal, formModal, modalVNodes, refreshModals, definePage, getModel, modelFetch, ActionCell, certStatusBadge, poll, formAction } from '/static/hoover/index.js';
import { html, PageHeader, Empty, Table, renderGuard, esc, enc, $val, apiFetch, toast, openModal, closeModal, formModal, modalVNodes, refreshModals, definePage, getModel, ActionCell, certStatusBadge, poll, formAction } from '/static/hoover/index.js';
import { isModalProcessing, setModalProcessing } from '/static/hoover/components/modal.js';
function _accountCard(account) {
@@ -56,7 +56,7 @@ function registerAccountModal() {
if (!resp.ok) throw resp.error || 'Registration failed';
toast('ACME account registered', 'success');
closeModal();
modelFetch('acme');
// No modelFetch — WS delta updates the acme model.
}),
},
],
@@ -100,7 +100,7 @@ function settingsModal(account) {
if (!resp.ok) throw resp.error || 'Failed';
toast('Email updated', 'success');
closeModal(idx);
modelFetch('acme');
// No modelFetch — WS delta updates the acme model.
}),
);
@@ -111,7 +111,7 @@ function settingsModal(account) {
if (!resp.ok) throw resp.error || 'Failed';
toast('Account deactivated', 'success');
closeModal(idx);
modelFetch('acme');
// No modelFetch — WS delta updates the acme model.
}),
);
});
@@ -262,7 +262,7 @@ async function pollCertIssue(rid) {
onErrorKey: (d) => d.status === 'failed',
onComplete: (d) => {
toast('Certificate issued for ' + (d.domain || rid), 'success');
modelFetch('acme');
// No modelFetch — WS delta updates the acme model.
},
onError: (d) => {
toast('Issuance failed: ' + (d?.error || 'unknown'), 'error');
@@ -299,7 +299,6 @@ export default definePage({
removeUrl=${'/api/certs/' + enc(c.domain)}
removeMessage=${'Remove certificate for ' + c.domain + '?'}
removeSuccess="Certificate removed"
removeRefresh="acme"
deleteKey=${c.domain} />
</tr>`;
});