b673e87c9b
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
61 lines
2.4 KiB
JavaScript
61 lines
2.4 KiB
JavaScript
import { h, PageHeader, definePage, getModel, renderGuard, ServiceStatus, StatCard } from '/static/hoover/index.js?v=7';
|
|
|
|
export default definePage({
|
|
init() {
|
|
return {
|
|
status: getModel('status'),
|
|
};
|
|
},
|
|
render(state) {
|
|
const guard = renderGuard(state.status, 'Dashboard', 'System overview', state.status.data);
|
|
if (guard) return guard;
|
|
|
|
const d = state.status.data;
|
|
const fwZones = (d.firewall?.zones) || {};
|
|
const net = d.net || {};
|
|
const nCount = Object.keys(net).length;
|
|
const upI = Object.values(net).filter(i => i.state === 'up');
|
|
const upC = upI.length;
|
|
const certs = d.certs || [];
|
|
const certW = certs.filter(c => c.expired || c.days_remaining <= 30);
|
|
const dmsk = d.dnsmasq?.status || {};
|
|
const wP = (d.wg || {}).peers || [];
|
|
|
|
return [
|
|
PageHeader({ title: 'Dashboard', subtitle: 'System overview' }),
|
|
h('div', { class: 'grid grid-4' },
|
|
StatCard({
|
|
label: 'Active Zones',
|
|
value: Object.keys(fwZones).length,
|
|
meta: Object.keys(fwZones).join(', ') || 'None',
|
|
}),
|
|
StatCard({
|
|
label: 'Interfaces Up',
|
|
value: upC + '/' + nCount,
|
|
meta: upI.map(i => i.name).join(', ') || 'None up',
|
|
}),
|
|
StatCard({
|
|
label: 'WireGuard',
|
|
value: String(d.wg?.state || 'unknown'),
|
|
meta: wP.length + ' peers',
|
|
}),
|
|
StatCard({
|
|
label: 'Certificates',
|
|
value: certs.length,
|
|
meta: certW.length + ' expiring/expired',
|
|
}),
|
|
),
|
|
h('div', { class: 'grid grid-2' },
|
|
h('div', { class: 'card' },
|
|
h('div', { class: 'card-header' }, 'Services'),
|
|
h('div', { class: 'card-body' },
|
|
h('ul', { class: 'service-list' },
|
|
h('li', null, ServiceStatus({ state: dmsk.state || 'down', label: 'Dnsmasq' })),
|
|
h('li', null, ServiceStatus({ state: d.wg?.state || 'down', label: 'WireGuard' })),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
];
|
|
},
|
|
}); |