Layout & Navigation¶
App shell¶
src/routes/+layout.svelte decides what to render:
| State | Renders |
|---|---|
/callback, /enroll |
the page alone, no shell |
| auth loading | a loading placeholder |
| signed in, no permission at all | NoOrganization — organizations are created by a platform admin and joined by invitation |
| signed in | ConnectionBanner, Toaster, and the shell below |
The shell is a CSS grid with no top bar on desktop:
┌──────┬───────────┬────────────────────────────┐
│ │ │ [Breadcrumb] │
│ Icon │ Secondary │ │
│ side │ panel │ Content │
│ bar │ (/edge/*) │ │
│ │ │ │
└──────┴───────────┴────────────────────────────┘
grid-template-columns: var(--app-sidebar-width) var(--panel-col-width, 0px) 1fr;
grid-template-areas: 'sidebar panel content';
--app-sidebar-width is 72 px and --secondary-panel-width 260 px
(src/lib/styles/tokens.css). The panel column exists only under /edge.
Below 768 px the grid becomes a single column with a 56 px top bar holding a hamburger; the sidebar and panel slide in as a drawer, which closes on navigation.
Icon sidebar¶
src/lib/components/layout/AppSidebar.svelte
| Item | Target |
|---|---|
| Hub | / |
| Edge Gateway | /edge |
| Monsieur Wifi | VITE_MRWIFI_URL (external) |
| Admin | /admin/devices; shown only to platform admins (hasRole('admin')) |
The avatar at the bottom opens the user menu: name and email, organization
switcher (searchable; an owned org whose name equals the user's display
name is labelled Personal — a check left in the switcher; the platform
creates no personal orgs), Account (/account), Team
(/account/team), Language, light/dark theme, About, Log out (OIDC sign-out).
The Admin area (src/routes/admin/) has five pages: Devices, Enrollment,
Organizations, Profiles, Relays.
Secondary panel¶
src/lib/components/layout/SecondaryPanel.svelte
Device list (/edge)¶
Filters: All devices, Gateways. Collapsible to a thin strip.
Device pages (/edge/[deviceId]/*)¶
Grouped under foldable headings. Fold state is a per-user preference
($lib/preferences); the section holding the current page never folds. The
panel cannot be collapsed here and shows a pending-changes badge when the
draft has unapplied edits.
| Group | Entry | Route(s) |
|---|---|---|
| — | Overview | /edge/[id] |
| Connectivity | Interfaces & network | interfaces, internet, lans, dhcp (tab bar on the pages, under the (network) route group) |
| Routing | routes |
|
| VPN tunnels | vpn |
|
| Published services | published |
|
| Security | Security policy | policies (firewall and NAT) |
| Web filtering | filtering |
|
| Monitoring | detection |
|
| Building blocks | Integrations | managed |
| Network objects | objects |
|
| Detected equipment | arp |
|
| — | Governance | history, logs, diagnostics (tab bar); shown only when the caller may audit the device |
| — | Settings | settings |
Other device routes: console (root console), and aliases, firewall
and nat, which redirect to their current pages so old links keep working.
Every device sub-route needs a label in src/lib/device-sections.ts
(DEVICE_SECTION_LABEL); the breadcrumb and dashboard shortcuts read it, and
src/lib/i18n-coverage.test.ts fails on a route without one.
Breadcrumb¶
src/lib/components/layout/Breadcrumb.svelte, sticky with a frosted
background, rendered only on /edge/[deviceId]/*:
| Route | Breadcrumb |
|---|---|
/edge/gw-xxx |
Edge Gateway › Device name |
/edge/gw-xxx/interfaces |
Edge Gateway › Device name › Interfaces & network |
The device name comes from the gateway store (getDevice()), falling back to
the first eight characters of the id.
Connection monitor¶
src/lib/stores/connection.svelte.ts polls nexus GET /healthz every 15 s and
listens to the browser's online/offline events. ConnectionBanner shows an
amber banner while disconnected and a green one on recovery that fades after
4 s. The version from /healthz is shown in About.
About modal¶
src/lib/components/layout/AboutModal.svelte: app version (__APP_VERSION__
from package.json via Vite define), API version (from /healthz), and the
environment from VITE_ENV with a coloured badge.
Icons¶
Lucide-style SVGs wrapped as Svelte components in src/lib/components/icons/.
Do not inline raw SVG in components.