refactor: introduce model layer for centralized data synchronization

Add hoover model.js as a central reactive store per subsystem, replacing
per-component data fetching with a single source of truth.

- Add hoover/model.js with modelRegister, modelFetch, and WS invalidation
- Refactor websocket.js to route messages to model refresh (drop per-component
  subscribe/unsubscribe)
- Simplify component.js by removing WS subscription management
- Add refresh option to apiSubmit, deprecate refactorLoad and checkAbort
- Rewrite all pages to use getModel() instead of inline data fetching
- Bootstrap model registrations in app.js
- Add GET /api/firewall/state endpoint
- Fix restart-services.sh restart order and add service health verification
- Update hoover.md docs with model layer architecture
This commit is contained in:
2026-06-22 22:54:29 +00:00
parent 633505e7dc
commit b673e87c9b
27 changed files with 952 additions and 838 deletions
+7 -7
View File
@@ -6,9 +6,9 @@
* avoid fighting with the main render cycle.
*/
import { esc } from '../helpers.js?v=6';
import { att_esc } from '../helpers.js?v=6';
import { apiSubmit } from '../api.js?v=6';
import { esc } from '../helpers.js?v=7';
import { att_esc } from '../helpers.js?v=7';
import { apiSubmit } from '../api.js?v=7';
const _modalQueue = [];
@@ -114,7 +114,7 @@ export function formModal(inner, title, fields, actions) {
* @param {string[]} props.selected - Currently selected values
* @param {string} props.fieldKey - JSON key for the field
* @param {string} [props.successMsg] - Success toast message
* @param {function} [props.reload] - () => Promise, called on success
* @param {string|string[]} [props.refresh] - Model name(s) to refresh via modelFetch
* @returns {function} () => void, calls openModal
*/
export function MultiSelectModal(props = {}) {
@@ -138,7 +138,7 @@ export function MultiSelectModal(props = {}) {
.map(o => o.value),
}),
successMsg: props.successMsg || 'Updated',
reload: props.reload,
refresh: props.refresh,
closeModal: () => closeModal(),
}),
],
@@ -160,7 +160,7 @@ export function MultiSelectModal(props = {}) {
* @param {function} [props.submit.body] - (data) => object
* @param {function} [props.submit.validate] - (body) => string|null
* @param {string|function} [props.submit.successMsg] - Toast message or (data) => string
* @param {function} [props.reload] - (data) => Promise, called on success with the data argument
* @param {string|string[]} [props.refresh] - Model name(s) to refresh via modelFetch
* @param {function} [props.handler] - Custom submit handler override (bypasses apiSubmit)
* @param {string} [props.submitLabel] - Submit button label (default: 'Submit')
* @returns {function} (data) => void, calls openModal
@@ -194,7 +194,7 @@ export function QuickModal(props = {}) {
successMsg: typeof props.submit.successMsg === 'function'
? props.submit.successMsg(data)
: (props.submit.successMsg || 'Done'),
reload: props.reload ? () => props.reload(data) : undefined,
refresh: props.refresh || undefined,
closeModal: () => closeModal(),
}),
];