import { h, render, Link, hComp, ToastContainer, connect, requestUpdate, reactive } from '/static/hoover/index.js?v=4'; import DashboardPage from '/static/pages/dashboard.js?v=4'; import InterfacesPage from '/static/pages/interfaces.js?v=4'; import ZonesPage from '/static/pages/zones.js?v=4'; import RulesPage from '/static/pages/rules.js?v=4'; import NatPage from '/static/pages/nat.js?v=4'; import DhcpPage from '/static/pages/dhcp.js?v=4'; import ProxyPage from '/static/pages/proxy.js?v=4'; import CertsPage from '/static/pages/certs.js?v=4'; import WireguardPage from '/static/pages/wireguard.js?v=4'; import LogsPage from '/static/pages/logs.js?v=4'; import NotFoundPage from '/static/pages/notfound.js?v=4'; /* ── 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);