Backend
The Backend is the administrative interface for the Heimdall platform, built with Next.js 16 and the App Router.
Features
Dashboard
The main dashboard provides an overview of platform statistics and recent activity:
- Active connections count
- GPS data points
- User statistics
- Recent activity feed
- Real-time updates via WebSocket
GPS Management
View and manage GPS tracking data:
- Historical GPS data table with filtering, pagination, and bulk actions
- Live map with real-time multi-device tracking via WebSocket
- Device management (create, edit, assign API keys)
- GPS settings (speed unit, coordinate format, timezone, retention)
Live Map Components
The /gps/map page uses several specialized map components from @elcto/ui/map:
- LiveMarker -- Animated marker with heading arrow and speed badge, updates in real-time
- MapDevicePanel -- Side panel for selecting which devices to display on the map
- MapControls -- Zoom in/out and reset view buttons
- MapTileSelector -- Switch between 6 tile providers (e.g. OpenStreetMap, satellite imagery)
- MapRoute -- Renders waypoint polylines for trip routes
- MapPolygon -- Displays geofence boundaries on the map
User Management
Manage platform users (Admin only):
- View all registered users
- View and edit user profiles
- Assign roles and permissions
- View user activity and login history
- Suspend/ban users
- Force logout sessions
Role Management
Create and manage roles with fine-grained permissions:
- Create custom roles
- Assign permissions to roles
- Set role-based 2FA requirements
- View role assignments
Trip Management
Plan and track trips with full lifecycle support:
- Trip overview with grid/list toggle, status filters, and search
- Create trips from scratch or from reusable templates
- Trip detail view with live map tracking and status timeline
- Edit trip metadata (category, locations, weight, tags, notes)
- Manage trip categories with hierarchical nesting and color coding
- Location directory with address, coordinates, and contact details
- Trip templates for quick creation with pre-filled defaults
API Key Management
Manage API keys for system access:
- Create new API keys with permission scoping
- View and edit existing keys
- View key usage statistics
- Revoke API keys
Developer Tools
Tools for developers integrating with Heimdall:
- OAuth application management
- Create/edit OAuth apps
- View client credentials
- Manage redirect URIs
- API key creation and detail views
Pages & Routes
| Route | Description | Auth Required |
|---|---|---|
/ | Landing page | No |
/login | Login page | No |
/error | Auth error page | No |
/signout | Sign-out with token revocation | No |
/unauthorized | Unauthorized access page | No |
/dashboard | Main dashboard with stats and activity feed | Yes |
| Users | ||
/users | User list with search and role filters | Yes (Admin) |
/users/banned | Banned users list with unban actions | Yes (Admin) |
/users/[id] | User detail with profile, roles, sessions, and bans | Yes (Admin) |
| GPS | ||
/gps | GPS history table with filtering, pagination, and bulk delete | Yes |
/gps/map | Live map with multi-device tracking via WebSocket | Yes |
/gps/devices | Device list with grid/list toggle and status indicators | Yes |
/gps/devices/create | Register a new GPS device with API key assignment | Yes |
/gps/devices/[id]/edit | Edit device name, type, status, and API key binding | Yes |
/gps/settings | GPS display settings (speed unit, coordinate format, timezone, retention) | Yes |
| Trips | ||
/trips | Trip overview with grid/list toggle, status and category filters | Yes |
/trips/create | Create a new trip (or pre-fill from a template) | Yes |
/trips/[id] | Trip detail view with live map, status timeline, and GPS data | Yes |
/trips/[id]/edit | Edit trip metadata, status, category, and locations | Yes |
/trips/categories | Manage hierarchical trip categories with colors | Yes |
/trips/locations | Location directory with grid/list view and search | Yes |
/trips/locations/create | Create a location with address, coordinates, and contacts | Yes |
/trips/locations/[id]/edit | Edit location details and contacts | Yes |
/trips/templates | Trip template list with grid/list view | Yes |
/trips/templates/create | Create a reusable trip template with defaults | Yes |
/trips/templates/[id]/edit | Edit trip template settings | Yes |
| Developer | ||
/developer/api-keys | API key list with usage stats | Yes |
/developer/api-keys/new | Create a new API key with permission scoping | Yes |
/developer/api-keys/[id] | View and edit API key details, regenerate secret | Yes |
/developer/oauth-apps | OAuth application list | Yes |
/developer/oauth-apps/new | Register a new OAuth application | Yes |
/developer/oauth-apps/[id] | Edit OAuth app (credentials, redirect URIs, scopes) | Yes |
| System | ||
/system/audit | Audit log with filters, search, and pagination | Yes (Admin) |
/system/audit/[auditId] | Audit event detail with metadata and geo info | Yes (Admin) |
/system/audit/reports | Audit summary reports | Yes (Admin) |
/system/integrations | Bot and service integration status | Yes (Admin) |
/system/permissions | Permission list and management | Yes (Admin) |
/system/permissions/create | Create a new permission entry | Yes (Admin) |
/system/permissions/[permissionId] | Edit permission details | Yes (Admin) |
/system/roles | Role list | Yes (Admin) |
/system/roles/create | Create a new role with permissions | Yes (Admin) |
/system/roles/[roleId] | Edit role and assigned permissions | Yes (Admin) |
/system/platform/settings | Platform-wide settings overview | Yes (Admin) |
/system/platform/settings/[slug] | Edit a specific platform setting group | Yes (Admin) |
/system/settings | System feature toggles | Yes (Admin) |
| Bots | ||
/bots/discord/guilds | Discord guild overview with member and channel counts | Yes (Admin) |
/bots/discord/guilds/[guildId] | Guild detail dashboard | Yes (Admin) |
/bots/discord/guilds/[guildId]/channels | Guild channel list | Yes (Admin) |
/bots/discord/guilds/[guildId]/members | Guild member list | Yes (Admin) |
/bots/discord/guilds/[guildId]/roles | Guild role list | Yes (Admin) |
/bots/discord/guilds/[guildId]/settings | Guild-specific bot settings | Yes (Admin) |
/bots/discord/settings | Global Discord bot settings | Yes (Admin) |
Authentication
The Backend Dashboard uses NextAuth with a custom Heimdall provider:
// src/lib/auth/heimdall-provider.ts
import { OAuthConfig } from "next-auth/providers";
export const HeimdallProvider: OAuthConfig<Profile> = {
id: "heimdall",
name: "Heimdall",
type: "oauth",
authorization: {
url: `${process.env.HEIMDALL_ID_URL}/oauth/authorize`,
params: { scope: "openid profile email" }
},
token: `${process.env.API_URL}/v1/oauth/token`,
userinfo: `${process.env.API_URL}/v1/oauth/userinfo`,
profile(profile) {
return {
id: profile.sub,
name: profile.name,
email: profile.email,
image: profile.picture
};
}
};
Session Handling
Sessions are managed via NextAuth with JWT strategy. Next.js 16 renamed middleware.ts to proxy.ts:
// proxy.ts (Next.js 16+)
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";
import { getToken } from "next-auth/jwt";
export async function proxy(request: NextRequest) {
const { pathname } = request.nextUrl;
// Get the token (session) - use app-specific cookie name
const token = await getToken({
req: request,
secret: process.env.NEXTAUTH_SECRET,
cookieName: process.env.NODE_ENV === "production"
? "__Secure-backend.session-token"
: "backend.session-token",
});
// Require authentication for protected routes
if (!token && pathname.startsWith("/dashboard")) {
const loginUrl = new URL("/login", request.url);
loginUrl.searchParams.set("callbackUrl", pathname);
return NextResponse.redirect(loginUrl);
}
return NextResponse.next();
}
export const config = {
matcher: ["/((?!_next/static|_next/image|favicon.ico).*)"],
};
Components
Protected Route
Wrap pages that require authentication:
import { ProtectedRoute } from "@/components/auth/ProtectedRoute";
export default function DashboardPage() {
return (
<ProtectedRoute>
<Dashboard />
</ProtectedRoute>
);
}
Protected Content
Conditionally render content based on authentication:
import { ProtectedContent } from "@/components/auth/ProtectedContent";
export default function Page() {
return (
<ProtectedContent
fallback={<LoginPrompt />}
requiredPermissions={["gps:read"]}
>
<GpsDataTable />
</ProtectedContent>
);
}
User Button
Display user info and logout:
import { UserButton } from "@/components/auth/UserButton";
export default function Header() {
return (
<nav>
<UserButton />
</nav>
);
}
Hooks
useAuth
Access authentication state:
import { useAuth } from "@/hooks/useAuth";
function Component() {
const { user, isLoading, isAuthenticated } = useAuth();
if (isLoading) return <Spinner />;
if (!isAuthenticated) return <LoginPrompt />;
return <div>Welcome, {user.name}</div>;
}
usePermissions
Check user permissions:
import { usePermissions } from "@/hooks/usePermissions";
function Component() {
const { permissions, hasPermission, isLoading } = usePermissions();
if (hasPermission("gps:write")) {
return <CreateGpsButton />;
}
return null;
}
API Integration
GraphQL Client
The dashboard uses Apollo Client for GraphQL:
// src/components/providers/ApolloWrapper.tsx
"use client";
import { ApolloProvider, ApolloClient, InMemoryCache } from "@apollo/client";
import { useSession } from "next-auth/react";
export function ApolloWrapper({ children }) {
const { data: session } = useSession();
const client = new ApolloClient({
uri: `${process.env.NEXT_PUBLIC_API_URL}/v1/gql`,
cache: new InMemoryCache(),
headers: {
Authorization: session?.accessToken
? `Bearer ${session.accessToken}`
: ""
}
});
return <ApolloProvider client={client}>{children}</ApolloProvider>;
}
Example Query
import { gql, useQuery } from "@apollo/client";
const GET_GPS_DATA = gql`
query GetGpsData($page: Int!, $limit: Int!) {
gpsList(page: $page, limit: $limit) {
data {
id
latitude
longitude
altitude
timestamp
speed
}
pagination {
page
limit
total
totalPages
}
}
}
`;
function GpsDataTable() {
const { data, loading, error } = useQuery(GET_GPS_DATA, {
variables: { page: 1, limit: 10 }
});
if (loading) return <Spinner />;
if (error) return <Error message={error.message} />;
return (
<table>
{data.gpsList.data.map((gps) => (
<tr key={gps.id}>
<td>{gps.latitude}</td>
<td>{gps.longitude}</td>
<td>{gps.speed}</td>
</tr>
))}
</table>
);
}
Layouts
Dashboard Layout
The main layout for authenticated pages:
// src/app/dashboard/layout.tsx
import { DashboardLayout } from "@/components/layouts/DashboardLayout";
export default function Layout({ children }) {
return <DashboardLayout>{children}</DashboardLayout>;
}
Features:
- Sidebar navigation
- Header with user menu
- Breadcrumbs
- Footer
Overlay Layout
Full-screen overlay layout for modals:
import { OverlayLayout } from "@/layouts/OverlayLayout";
export default function ModalPage() {
return (
<OverlayLayout>
<Modal />
</OverlayLayout>
);
}
UI Components
The dashboard includes a set of reusable UI components:
Button
import { Button } from "@/components/ui/Button";
<Button variant="primary" size="lg" onClick={handleClick}>
Click Me
</Button>
<Button variant="outline" disabled>
Disabled
</Button>
Alert
import { Alert } from "@/components/ui/Alert";
<Alert type="success" title="Success!">
Your changes have been saved.
</Alert>
<Alert type="error" title="Error">
Something went wrong.
</Alert>
Modal
import { Modal } from "@/components/ui/Modal";
<Modal isOpen={isOpen} onClose={handleClose} title="Confirm Action">
<p>Are you sure you want to proceed?</p>
<div>
<Button onClick={handleClose}>Cancel</Button>
<Button variant="primary" onClick={handleConfirm}>
Confirm
</Button>
</div>
</Modal>
Loading Spinner
import { LoadingSpinner } from "@/components/ui/LoadingSpinner";
<LoadingSpinner size="lg" />
Floating Input
import { FloatingInput } from "@/components/ui/FloatingInput";
<FloatingInput
label="Email"
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
/>
Environment Variables
| Variable | Description | Required |
|---|---|---|
NEXTAUTH_URL | The base URL of the app | Yes |
NEXTAUTH_SECRET | Secret for signing tokens | Yes |
NEXT_PUBLIC_API_URL | Public API URL | Yes |
API_URL | Server-side API URL | Yes |
HEIMDALL_ID_URL | Heimdall ID service URL | Yes |
Development
cd platform/backend
# Install dependencies
pnpm install
# Run development server
pnpm dev
# Build for production
pnpm build
# Start production server
pnpm start
# Run linting
pnpm lint
# Run E2E tests
pnpm test:e2e
Next Steps
- ID - Identity service documentation
- Components - Shared component documentation
- Authentication Flow - How auth works