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
+2 -5
View File
@@ -104,7 +104,6 @@ function addDomain(state, preselectedBackend) {
!b.backend ? 'Backend is required' : null,
successMsg: 'Domain added',
},
refresh: ['nginx', 'acme'],
postRender: (inner) => {
if (preselectedBackend) {
const backendSelect = inner.querySelector('#p-backend');
@@ -161,7 +160,6 @@ function editDomain(d, state) {
validate: (b) => !b.cert ? 'Cert is required' : null,
successMsg: 'Domain updated',
},
refresh: ['nginx', 'acme'],
postRender: (inner) => {
const certSelect = inner.querySelector('#pe-cert');
if (certSelect) certSelect.value = selectedCert;
@@ -213,7 +211,6 @@ function domainRow(domainName, domainPaths, state) {
removeUrl=${'/api/proxy/domains/' + enc(domainName)}
removeMessage=${'Remove ' + enc(domainName) + '?'}
removeSuccess="Domain removed"
removeRefresh=["nginx", "acme"]
removeLabel="Delete" />
</td>
</tr>`;
@@ -231,7 +228,7 @@ function backendSection(section, state) {
deleteKey=${backendName}
message=${'Remove backend ' + enc(backendName) + '?'}
success="Backend removed"
refresh=["backends", "nginx"]
onComplete=${() => modelFetch('backends')}
label="Delete" />`);
}
}
@@ -267,7 +264,7 @@ export default definePage({
const sectionVNodes = sections.map(s => backendSection(s, state));
const actions = ActionGroup(
html`<button class="btn btn-primary" onClick=${() => addDomain(state)}>Add Domain</button>`,
ActionButton({ url: '/api/proxy/apply', successMsg: 'Nginx applied & reloaded', label: 'Apply', refresh: ['nginx', 'acme'] }),
ActionButton({ url: '/api/proxy/apply', successMsg: 'Nginx applied & reloaded', label: 'Apply' }),
);
return [
PageHeader({ title: 'Proxy', subtitle: 'Nginx reverse proxy', actions }),