Something went wrong. Try again.
The IoT platform Cloudflare didn't build. Telemetry, realtime dashboards, and automations on Workers + D1 + R2 + Durable Objects — in your… nodrix.live
Something went wrong. Try again.
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788import { createRouter, createWebHistory, type RouteRecordRaw } from 'vue-router';import { useSessionStore } from './stores/session';
const routes: RouteRecordRaw[] = [ // Login lives outside the app shell. { path: '/login', name: 'login', component: () => import('./pages/Login.vue'), meta: { title: 'Sign in' } }, // Invite acceptance — public, outside the app shell. { path: '/invite/:token', name: 'invite', component: () => import('./pages/Invite.vue'), meta: { title: 'Accept invite' } }, // Back-compat redirect for any bookmarks that still point at /setup. { path: '/setup', redirect: '/login' },
// Public dashboard viewer — no auth, outside the app shell. /share is a // standalone page; /embed is the chrome-stripped, iframe-able variant. { path: '/share/:token', name: 'public-share', component: () => import('./pages/public/PublicDashboard.vue'), meta: { title: 'Dashboard' } }, { path: '/embed/:token', name: 'public-embed', component: () => import('./pages/public/PublicDashboard.vue'), meta: { title: 'Dashboard' } },
// Everything else is wrapped in the authenticated app shell. { path: '/', component: () => import('./layouts/AppShell.vue'), children: [ { path: '', name: 'home', component: () => import('./pages/Home.vue'), meta: { title: 'Home' } }, { path: 'projects', name: 'projects', component: () => import('./pages/Projects.vue'), meta: { title: 'Projects' } }, { path: 'users', name: 'users', component: () => import('./pages/account/Users.vue'), meta: { title: 'Users', roles: ['owner', 'admin'] } }, { path: 'tokens', name: 'tokens', component: () => import('./pages/account/Tokens.vue'), meta: { title: 'Tokens', roles: ['owner', 'admin'] } }, { path: 'audit-log', name: 'audit-log', component: () => import('./pages/account/AuditLog.vue'), meta: { title: 'Audit log', roles: ['owner'] } }, { path: 'settings', name: 'settings', component: () => import('./pages/account/Settings.vue'), meta: { title: 'Settings' } },
// Project-scoped routes. { path: 'p/:proj', children: [ { path: '', redirect: (to) => `/p/${to.params['proj'] as string}/variables` }, { path: 'dashboards', name: 'dashboards', component: () => import('./pages/project/Dashboards.vue'), meta: { title: 'Dashboards' } }, { path: 'd/:dash', name: 'dashboard-view', component: () => import('./pages/project/DashboardView.vue'), meta: { title: 'Dashboard' } }, { path: 'd/:dash/edit', name: 'dashboard-edit', component: () => import('./pages/project/DashboardEdit.vue'), meta: { title: 'Edit dashboard' } }, // Variables + Connection tokens live under one hub with two tabs. { path: 'variables', component: () => import('./pages/project/variables/VariablesHub.vue'), children: [ { path: '', name: 'variables', component: () => import('./pages/project/variables/VariablesList.vue'), meta: { title: 'Variables' } }, { path: 'tokens', name: 'variable-tokens', component: () => import('./pages/project/variables/ConnectionTokens.vue'), meta: { title: 'Connection tokens' } }, ], }, // Automations + Integrations live under one hub with two tabs. { path: 'automations', component: () => import('./pages/project/automations/AutomationsHub.vue'), children: [ { path: '', name: 'automations', component: () => import('./pages/project/automations/AutomationsList.vue'), meta: { title: 'Automations' } }, { path: 'integrations', name: 'integrations', component: () => import('./pages/project/automations/Integrations.vue'), meta: { title: 'Integrations' } }, // Back-compat for the old Connections tab URL. { path: 'connections', redirect: (to) => `/p/${to.params['proj'] as string}/automations/integrations` }, ], }, // Full-width editor, a sibling of the hub so it isn't constrained by the tab shell. { path: 'automations/editor/:id?', name: 'automation-editor', component: () => import('./pages/project/automations/AutomationEditor.vue'), meta: { title: 'Automation editor' } }, // Back-compat for the old standalone Integrations route/bookmarks. { path: 'integrations', redirect: (to) => `/p/${to.params['proj'] as string}/automations/integrations` }, ], }, ], },];
export const router = createRouter({ history: createWebHistory(), routes,});
// Role gate: routes can declare `meta.roles`. Members can't reach Users / API// tokens; only the owner reaches the audit log. The backend enforces this too —// this just keeps the UI honest on direct navigation. Falls through to '/' when// the signed-in user's role isn't allowed.router.beforeEach(async (to) => { const roles = to.meta['roles'] as string[] | undefined; if (!roles) return true;
const session = useSessionStore(); if (!session.user) { try { await session.load(); } catch { /* unauthenticated — AppShell redirects to /login */ } } const role = session.user?.role; if (role && !roles.includes(role)) return { path: '/' }; return true;});