Skip to content

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.

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.