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
+13 -7
View File
@@ -268,8 +268,11 @@ export function formAction(fn) {
* @param {string} [opts.method] - HTTP method (default: 'POST')
* @param {function} [opts.body] - () => object, body builder
* @param {function} [opts.validate] - (body) => string|null, validation function
* @param {function} [opts.confirm] - (body) => string|null; if a message is
* returned, a native confirm() dialog gates
* the submit; on approval the body gains
* force=true (server-side guard override)
* @param {string} [opts.successMsg] - Success toast message
* @param {string|string[]} [opts.refresh] - Model name(s) to refresh via modelFetch
* @param {string} [opts.submitText] - Submit button text (default: 'Submit')
* @returns {object[]} Array of action descriptors
*/
@@ -279,8 +282,8 @@ export function apiSubmit(opts) {
method = 'POST',
body,
validate,
confirm,
successMsg = 'Saved',
refresh,
submitText = 'Submit',
closeModal,
} = opts;
@@ -300,6 +303,13 @@ export function apiSubmit(opts) {
const err = validate(b);
if (err) { toast(err, 'error'); return; }
}
if (confirm) {
const msg = confirm(b);
if (msg) {
if (!window.confirm(msg)) return;
b.force = true;
}
}
refreshModals();
const res = await apiFetch(url, { method, body: b });
if (res.ok) {
@@ -307,14 +317,10 @@ export function apiSubmit(opts) {
let msg = successMsg;
if (synced && synced.length) {
msg += ' (auto-synced: ' + synced.join(', ') + ')';
synced.forEach(s => modelFetch(s));
}
toast(msg, 'success');
if (closeModal) closeModal();
if (refresh) {
const models = Array.isArray(refresh) ? refresh : [refresh];
await Promise.all(models.map(m => modelFetch(m)));
}
// No modelFetch — WS delta updates all affected subsystems.
} else {
toast(res.error || 'Failed', 'error');
}