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:
+15
-30
@@ -1,4 +1,4 @@
|
||||
import { h, PageHeader, Badge, Empty, Table, renderGuard, esc, enc, $val, apiFetch, toast, definePage, refactorLoad, ActionButton, ActionCell, certStatusBadge, ActionGroup, QuickModal } from '/static/hoover/index.js?v=6';
|
||||
import { h, PageHeader, Badge, Empty, Table, renderGuardMulti, esc, enc, $val, apiFetch, toast, definePage, getModel, modelFetch, ActionButton, ActionCell, certStatusBadge, ActionGroup, QuickModal } from '/static/hoover/index.js?v=7';
|
||||
|
||||
const addDomain = QuickModal({
|
||||
title: 'Add Proxy Domain',
|
||||
@@ -21,7 +21,7 @@ const addDomain = QuickModal({
|
||||
validate: (b) => !b.domain || !b.backend_host || !b.backend_port ? 'Domain, host, and port are required' : null,
|
||||
successMsg: 'Domain added',
|
||||
},
|
||||
reload: (s) => load(s),
|
||||
refresh: ['nginx', 'acme'],
|
||||
});
|
||||
|
||||
const editDomain = QuickModal({
|
||||
@@ -35,7 +35,7 @@ const editDomain = QuickModal({
|
||||
submit: {
|
||||
url: (d) => '/api/proxy/domains/' + enc(d.domain),
|
||||
method: 'PUT',
|
||||
body: (d) => ({
|
||||
body: () => ({
|
||||
backend_host: ($val('pe-host') || '').trim(),
|
||||
backend_port: parseInt($val('pe-port')),
|
||||
backend_proto: ($val('pe-proto') || 'http').trim(),
|
||||
@@ -44,37 +44,22 @@ const editDomain = QuickModal({
|
||||
validate: (b) => !b.backend_host || !b.backend_port ? 'Host and port are required' : null,
|
||||
successMsg: 'Domain updated',
|
||||
},
|
||||
reload: (s) => load(s._s),
|
||||
refresh: ['nginx', 'acme'],
|
||||
});
|
||||
|
||||
async function load(state, abortController, entry) {
|
||||
await refactorLoad(state,
|
||||
s => s.domains?.length,
|
||||
async (s, sig, isAborted) => {
|
||||
const domainsR = await apiFetch('/api/proxy/domains', { signal: sig });
|
||||
if (isAborted()) return;
|
||||
if (domainsR.ok) s.domains = domainsR.data || [];
|
||||
else s.error = domainsR.error;
|
||||
const certsR = await apiFetch('/api/certs/list', { signal: sig });
|
||||
if (isAborted()) return;
|
||||
if (certsR.ok) s.certs = certsR.data || [];
|
||||
else if (!s.error) s.error = certsR.error;
|
||||
},
|
||||
{ entry, abortController },
|
||||
);
|
||||
}
|
||||
|
||||
export default definePage({
|
||||
init() {
|
||||
return { domains: [], certs: [] };
|
||||
return {
|
||||
nginx: getModel('nginx'),
|
||||
acme: getModel('acme'),
|
||||
};
|
||||
},
|
||||
subscribe: ['nginx', 'acme'],
|
||||
load,
|
||||
render(state) {
|
||||
const guard = renderGuard(state, 'Proxy', 'Nginx reverse proxy', state.domains);
|
||||
const guard = renderGuardMulti('Proxy', 'Nginx reverse proxy', state.nginx, state.acme);
|
||||
if (guard) return guard;
|
||||
|
||||
const rows = state.domains.map(d => {
|
||||
const domains = state.nginx.data || [];
|
||||
const rows = domains.map(d => {
|
||||
const certBadge = certStatusBadge({
|
||||
certStatus: d.cert_status,
|
||||
daysRemaining: d.days_remaining,
|
||||
@@ -89,11 +74,11 @@ export default definePage({
|
||||
h('td', null, certBadge),
|
||||
ActionCell({
|
||||
editLabel: 'Edit',
|
||||
editClick: () => editDomain({ ...d, _s: state }),
|
||||
editClick: () => editDomain(d),
|
||||
removeUrl: '/api/proxy/domains/' + enc(d.domain),
|
||||
removeMessage: 'Remove proxy for ' + d.domain + '?',
|
||||
removeSuccess: 'Domain removed',
|
||||
removeReload: () => load(state),
|
||||
removeRefresh: ['nginx', 'acme'],
|
||||
removeLabel: 'Delete',
|
||||
}),
|
||||
);
|
||||
@@ -105,7 +90,7 @@ export default definePage({
|
||||
url: '/api/proxy/apply',
|
||||
successMsg: 'Nginx applied & reloaded',
|
||||
label: 'Apply',
|
||||
reload: () => load(state),
|
||||
refresh: ['nginx', 'acme'],
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -119,4 +104,4 @@ export default definePage({
|
||||
: Empty({ text: 'No proxy domains configured. Add a domain to start terminating SSL.' }),
|
||||
];
|
||||
},
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user