Files
vacuum-wall/webui/static/app.js
T
mteehan 633505e7dc refactor: modernize frontend with hoover framework components and docs
- Add quick modal, table, service status, and confirmation dialog components
- Refactor all pages (certs, dhcp, proxy, etc.) to use new component patterns
- Introduce refactor load utility and render guard for consistent UX
- Add hoover documentation and update AGENTS.md, architecture, overview
2026-06-21 04:29:27 +00:00

98 lines
3.9 KiB
JavaScript

import { h, render, Link, hComp, ToastContainer, connect, requestUpdate, reactive } from '/static/hoover/index.js?v=6';
import DashboardPage from '/static/pages/dashboard.js?v=6';
import InterfacesPage from '/static/pages/interfaces.js?v=6';
import ZonesPage from '/static/pages/zones.js?v=6';
import RulesPage from '/static/pages/rules.js?v=6';
import NatPage from '/static/pages/nat.js?v=6';
import DhcpPage from '/static/pages/dhcp.js?v=6';
import ProxyPage from '/static/pages/proxy.js?v=6';
import CertsPage from '/static/pages/certs.js?v=6';
import WireguardPage from '/static/pages/wireguard.js?v=6';
import LogsPage from '/static/pages/logs.js?v=6';
import NotFoundPage from '/static/pages/notfound.js?v=6';
/* ── Navigation items ──────────────────────────────────────── */
const Nav = [
{ path: '/dashboard', label: 'Dashboard' },
{ path: '/interfaces', label: 'Interfaces' },
{ path: '/zones', label: 'Zones' },
{ path: '/rules', label: 'Rules' },
{ path: '/nat', label: 'NAT' },
{ path: '/dhcp', label: 'DHCP' },
{ path: '/proxy', label: 'Proxy' },
{ path: '/certs', label: 'Certs' },
{ path: '/wireguard', label: 'WireGuard' },
{ path: '/logs', label: 'Logs' },
];
/* ── Page map ──────────────────────────────────────────────── */
const Pages = {
dashboard: DashboardPage,
interfaces: InterfacesPage,
zones: ZonesPage,
rules: RulesPage,
nat: NatPage,
dhcp: DhcpPage,
proxy: ProxyPage,
certs: CertsPage,
wireguard: WireguardPage,
logs: LogsPage,
};
/* ── Router ────────────────────────────────────────────────── */
const router = {
state: reactive({ path: location.hash.slice(1) || '/dashboard' }),
component() {
const name = this.state.path.replace(/^\//, '');
const page = Pages[name] || NotFoundPage;
return hComp(page, this.state.path);
},
};
window.location.hash || (window.location.hash = router.state.path);
window.addEventListener('hashchange', () => {
router.state.path = location.hash.slice(1) || '/dashboard';
});
/* ── Sidebar render root ───────────────────────────────────── */
function Sidebar() {
const current = router.state.path;
return h('div', { class: 'sidebar' },
h('div', { class: 'logo' }, 'Vacuum Wall'),
h('nav', null,
Nav.map(item =>
Link({
path: item.path,
class: current === item.path ? 'active' : '',
children: [item.label],
}),
),
),
);
}
/* ── Main content render root ──────────────────────────────── */
function MainContent() {
return [
router.component(),
ToastContainer(),
];
}
/* ── Init ──────────────────────────────────────────────────── */
export function initApp() {
const sidebarEl = document.getElementById('sidebar');
const mainEl = document.getElementById('main');
if (sidebarEl && mainEl) {
render(sidebarEl, Sidebar);
render(mainEl, MainContent);
}
// Defer connect() after the first render microtask settles to prevent
// the initial requestUpdate() from triggering a second commit while
// the vnode tree is still being finalized.
setTimeout(connect, 0);
}
document.addEventListener('DOMContentLoaded', initApp);