Add state management, WebSocket polling, html.js templating, and refactor pages
- lib/state.py: per-subsystem collectors with versioned state store - daemon/server.py: state refresh on request, batch routing updates - webui/static/hoover/html.js: new html tag template helper via htm.js - webui/static/hoover/websocket.js: real-time state change notifications - webui/static/hoover/vdom.js: VDOM improvements for keyed diff - All frontend pages refactored to use html templates - Add tests for state management and polling - Update docs and AGENTS.md
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { h, PageHeader, Table, renderGuardMulti, enc, $val, apiFetch, toast, definePage, getModel, modelFetch, StatusText, QuickModal, ZoneSelect } from '/static/hoover/index.js?v=7';
|
||||
import { html, PageHeader, Table, renderGuardMulti, enc, $val, apiFetch, toast, definePage, getModel, modelFetch, StatusText, QuickModal, ZoneSelect } from '/static/hoover/index.js?v=7';
|
||||
|
||||
async function changeZone(name, zone, state) {
|
||||
const r = await apiFetch('/api/firewall/zones/' + enc(zone) + '/interfaces', {
|
||||
@@ -54,8 +54,8 @@ export default definePage({
|
||||
|
||||
const ifaces = Object.entries(netData).map(([name, entry]) => {
|
||||
let zone = null;
|
||||
for (const [zoneName, ifaces] of Object.entries(activeZones)) {
|
||||
if ((ifaces || []).includes(name)) {
|
||||
for (const [zoneName, zIfaces] of Object.entries(activeZones)) {
|
||||
if ((zIfaces || []).includes(name)) {
|
||||
zone = zoneName;
|
||||
break;
|
||||
}
|
||||
@@ -70,26 +70,20 @@ export default definePage({
|
||||
};
|
||||
});
|
||||
|
||||
const rows = ifaces.map(iface => {
|
||||
return h('tr', { key: iface.name },
|
||||
h('td', null, h('strong', null, iface.name)),
|
||||
h('td', { class: 'text-muted' }, String(iface.mac || 'N/A')),
|
||||
h('td', null, (iface.ips || []).join(', ') || 'N/A'),
|
||||
h('td', null, StatusText({ status: iface.state })),
|
||||
h('td', null,
|
||||
ZoneSelect({
|
||||
zones,
|
||||
value: iface.zone,
|
||||
onChange: (z) => changeZone(iface.name, z, state),
|
||||
}),
|
||||
h('button', {
|
||||
class: 'btn btn-sm btn-outline',
|
||||
style: 'margin-left:8px',
|
||||
'on:click': () => cfgModalFn(iface),
|
||||
}, 'Config'),
|
||||
),
|
||||
);
|
||||
});
|
||||
const rows = ifaces.map(iface =>
|
||||
html`<tr key=${iface.name}>
|
||||
<td><strong>${iface.name}</strong></td>
|
||||
<td class="text-muted">${String(iface.mac || 'N/A')}</td>
|
||||
<td>${(iface.ips || []).join(', ') || 'N/A'}</td>
|
||||
<td><${StatusText} status=${iface.state} /></td>
|
||||
<td>
|
||||
<${ZoneSelect} zones=${zones} value=${iface.zone}
|
||||
onChange=${(z) => changeZone(iface.name, z, state)} />
|
||||
<button class="btn btn-sm btn-outline" style="margin-left:8px"
|
||||
onClick=${() => cfgModalFn(iface)}>Config</button>
|
||||
</td>
|
||||
</tr>`
|
||||
);
|
||||
|
||||
return [
|
||||
PageHeader({ title: 'Interfaces', subtitle: 'Network interface management' }),
|
||||
@@ -100,4 +94,4 @@ export default definePage({
|
||||
}),
|
||||
];
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user