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, 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';
|
||||
import { html, 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',
|
||||
@@ -66,33 +66,30 @@ export default definePage({
|
||||
|
||||
const peerRows = (state.wireguard.data?.peers || []).map(p => {
|
||||
const hasHandshake = !!p.latest_handshake;
|
||||
return h('tr', { key: p.name },
|
||||
h('td', null,
|
||||
StatusDot({ status: hasHandshake ? 'success' : 'danger' }),
|
||||
h('strong', null, esc(p.name || 'unnamed')),
|
||||
),
|
||||
h('td', null, MonoText({ text: p.public_key || 'N/A', maxLength: 20 })),
|
||||
h('td', { class: 'text-sm' }, esc(p.allowed_ips || '-')),
|
||||
h('td', { class: 'text-sm' }, esc(p.endpoint || '-')),
|
||||
h('td', { class: 'text-sm' }, esc(p.latest_handshake || 'Never')),
|
||||
h('td', { class: 'text-sm' },
|
||||
'Recv: ' + esc(p.transfer_recv || '0'),
|
||||
h('br'),
|
||||
'Sent: ' + esc(p.transfer_sent || '0'),
|
||||
),
|
||||
ActionCell({
|
||||
editLabel: 'Config',
|
||||
editClick: () => downloadConfigModal(p.name, state.wireguard.data?.config, state),
|
||||
removeUrl: '/api/wireguard/peers/' + enc(p.name),
|
||||
removeMessage: 'Remove peer ' + p.name + '?',
|
||||
removeSuccess: 'Peer removed',
|
||||
removeRefresh: 'wireguard',
|
||||
}),
|
||||
);
|
||||
return html`<tr key=${p.name}>
|
||||
<td>
|
||||
<${StatusDot} status=${hasHandshake ? 'success' : 'danger'} />
|
||||
<strong>${esc(p.name || 'unnamed')}</strong>
|
||||
</td>
|
||||
<td><${MonoText} text=${p.public_key || 'N/A'} maxLength=20 /></td>
|
||||
<td class="text-sm">${esc(p.allowed_ips || '-')}</td>
|
||||
<td class="text-sm">${esc(p.endpoint || '-')}</td>
|
||||
<td class="text-sm">${esc(p.latest_handshake || 'Never')}</td>
|
||||
<td class="text-sm">
|
||||
Recv: ${esc(p.transfer_recv || '0')}<br/>
|
||||
Sent: ${esc(p.transfer_sent || '0')}
|
||||
</td>
|
||||
<${ActionCell}
|
||||
editLabel="Config" editClick=${() => downloadConfigModal(p.name, state.wireguard.data?.config, state)}
|
||||
removeUrl=${'/api/wireguard/peers/' + enc(p.name)}
|
||||
removeMessage=${'Remove peer ' + p.name + '?'}
|
||||
removeSuccess="Peer removed"
|
||||
removeRefresh="wireguard" />
|
||||
</tr>`;
|
||||
});
|
||||
|
||||
const actions = ActionGroup(
|
||||
h('button', { class: 'btn btn-primary', 'on:click': () => addPeer(state) }, 'Add Peer'),
|
||||
html`<button class="btn btn-primary" onClick=${() => addPeer(state)}>Add Peer</button>`,
|
||||
ActionButton({
|
||||
url: '/api/wireguard/' + (isUp ? 'down' : 'up'),
|
||||
labelOn: 'Stop', labelOff: 'Start', condition: isUp,
|
||||
@@ -122,4 +119,4 @@ export default definePage({
|
||||
: Empty({ text: 'No peers configured. Add a peer above.' }),
|
||||
];
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user