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:
@@ -1,4 +1,4 @@
|
||||
import { h, PageHeader, Badge, StatusDot, Empty, Table, ServiceStatus, renderGuard, esc, enc, $val, apiFetch, toast, openModal, closeModal, formModal, definePage, apiSubmit, refactorLoad, ActionButton, ActionCell, MonoText, ActionGroup, QuickModal, downloadBlob } from '/static/hoover/index.js?v=6';
|
||||
import { h, PageHeader, Badge, StatusDot, Empty, Table, ServiceStatus, renderGuard, esc, enc, $val, apiFetch, toast, openModal, closeModal, formModal, definePage, getModel, modelFetch, ActionButton, ActionCell, MonoText, ActionGroup, QuickModal, downloadBlob } from '/static/hoover/index.js?v=7';
|
||||
|
||||
const addPeer = QuickModal({
|
||||
title: 'Add WireGuard Peer',
|
||||
@@ -19,7 +19,7 @@ const addPeer = QuickModal({
|
||||
validate: (b) => !b.name ? 'Name is required' : null,
|
||||
successMsg: 'Peer added',
|
||||
},
|
||||
reload: (s) => load(s),
|
||||
refresh: 'wireguard',
|
||||
});
|
||||
|
||||
function downloadConfigModal(peerName, config, state) {
|
||||
@@ -50,42 +50,21 @@ function downloadConfigModal(peerName, config, state) {
|
||||
});
|
||||
}
|
||||
|
||||
async function load(state, abortController, entry) {
|
||||
await refactorLoad(state,
|
||||
s => s.peers?.length,
|
||||
async (s, sig, isAborted) => {
|
||||
const stR = await apiFetch('/api/wireguard/status', { signal: sig });
|
||||
if (isAborted()) return;
|
||||
if (stR.ok) s.status = stR.data || {};
|
||||
else s.error = stR.error;
|
||||
const pR = await apiFetch('/api/wireguard/peers', { signal: sig });
|
||||
if (isAborted()) return;
|
||||
if (pR.ok) s.peers = pR.data || [];
|
||||
else if (!s.error) s.error = pR.error;
|
||||
const cfgR = await apiFetch('/api/wireguard/config', { signal: sig });
|
||||
if (isAborted()) return;
|
||||
if (cfgR.ok) s.config = cfgR.data || {};
|
||||
else if (!s.error) s.error = cfgR.error;
|
||||
},
|
||||
{ entry, abortController },
|
||||
);
|
||||
}
|
||||
|
||||
export default definePage({
|
||||
init() {
|
||||
return { status: {}, peers: [], config: {} };
|
||||
return {
|
||||
wireguard: getModel('wireguard'),
|
||||
};
|
||||
},
|
||||
subscribe: ['wireguard'],
|
||||
load,
|
||||
render(state) {
|
||||
const guard = renderGuard(state, 'WireGuard', 'Tunnel: ' + ((state.status || {}).state || 'unknown') + ', Listen: ' + ((state.config?.interface || {}).listen_port || '-'), state.peers);
|
||||
const guard = renderGuard(state.wireguard, 'WireGuard', null, state.wireguard.data?.peers);
|
||||
if (guard) return guard;
|
||||
|
||||
const st = state.status || {};
|
||||
const st = state.wireguard.data?.status || {};
|
||||
const isUp = st.state === 'up';
|
||||
const listenPort = (state.config?.interface || {}).listen_port || '-';
|
||||
const listenPort = (state.wireguard.data?.config?.interface || {}).listen_port || '-';
|
||||
|
||||
const peerRows = state.peers.map(p => {
|
||||
const peerRows = (state.wireguard.data?.peers || []).map(p => {
|
||||
const hasHandshake = !!p.latest_handshake;
|
||||
return h('tr', { key: p.name },
|
||||
h('td', null,
|
||||
@@ -103,11 +82,11 @@ export default definePage({
|
||||
),
|
||||
ActionCell({
|
||||
editLabel: 'Config',
|
||||
editClick: () => downloadConfigModal(p.name, state.config, state),
|
||||
editClick: () => downloadConfigModal(p.name, state.wireguard.data?.config, state),
|
||||
removeUrl: '/api/wireguard/peers/' + enc(p.name),
|
||||
removeMessage: 'Remove peer ' + p.name + '?',
|
||||
removeSuccess: 'Peer removed',
|
||||
removeReload: () => load(state),
|
||||
removeRefresh: 'wireguard',
|
||||
}),
|
||||
);
|
||||
});
|
||||
@@ -118,13 +97,13 @@ export default definePage({
|
||||
url: '/api/wireguard/' + (isUp ? 'down' : 'up'),
|
||||
labelOn: 'Stop', labelOff: 'Start', condition: isUp,
|
||||
successMsg: 'Tunnel ' + (isUp ? 'stopped' : 'started'),
|
||||
reload: () => load(state),
|
||||
refresh: 'wireguard',
|
||||
}),
|
||||
ActionButton({
|
||||
url: '/api/wireguard/apply',
|
||||
successMsg: 'Config applied',
|
||||
label: 'Apply',
|
||||
reload: () => load(state),
|
||||
refresh: 'wireguard',
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -143,4 +122,4 @@ export default definePage({
|
||||
: Empty({ text: 'No peers configured. Add a peer above.' }),
|
||||
];
|
||||
},
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user