Operator & admin panels
The cloud side of RISER (K1–K3 on the blueprint) is a FastAPI backend +
PostgreSQL and a Next.js operator web app. There isn’t a separate “admin app” and “client app” —
there is one operator console, and what you can see and do is gated by your role. This page
explains the panels, who sees what, and how a click in a browser becomes a relay flip in a boiler room.
The three cloud pieces
Section titled “The three cloud pieces”| Tag | Piece | Stack | Job |
|---|---|---|---|
K1 |
Backend | FastAPI (backend/app/) |
Receives telemetry, serves commands & config, runs alerts, auth, billing |
K2 |
Database | PostgreSQL | Stores devices, telemetry, orgs, users, alerts, invoices, audit log |
K3 |
Operator web app | Next.js + TypeScript + Tailwind (web/) |
The dashboard humans log into |
A public marketing site (marketing/, Astro) sits alongside for lead capture (POST /api/v1/leads). It is not part of the operator console.
Multi-tenant model
Section titled “Multi-tenant model”RISER is multi-tenant: an organization owns buildings, a building holds devices
(controllers), and users join orgs through memberships that carry a role. Every org-scoped API
route is isolation-checked — asking about another org’s data returns 404, so existence never
leaks across tenants (backend/app/core/rbac.py).
Who sees what — the role tiers
Section titled “Who sees what — the role tiers”Think of it as two panels sharing one login: the client/operator panel (your own org’s fleet) and the admin panel (cross-org back office). Your role decides which you land in.
| Role | Panel | Can do |
|---|---|---|
| viewer | client | Read-only: dashboards, live readings, history, alerts |
| operator | client | Everything a viewer can, plus send device commands, manage buildings/devices, ack alerts |
| org_admin | client + org admin | Everything above, plus members, billing, integrations, audit log |
| super_admin | admin | Cross-org bypass: fleet-wide view, staged OTA rollouts, marketing leads |
The panels (web app routes)
Section titled “The panels (web app routes)”| Route | Panel | Shows |
|---|---|---|
/ |
Dashboard | Buildings → devices, live readings, active alerts (ack inline) |
/devices/[id] |
Device detail | Live readings, history chart, on/off + set-mode commands, JSON config editor, command history |
/onboard |
Onboarding | Create a building, link a device |
/alerts |
Alerts center | Alert rules, alerts, notifications |
/reports |
Reports | Telemetry / alerts CSV, compliance PDF |
/billing |
Billing | Plan, subscription status, invoices |
/integrations |
Integrations | SIM fleet, weather-reset refresh, outbound webhooks |
/audit |
Audit log | Sensitive actions (org_admin+) |
/account |
Account | Profile, MFA (TOTP) enroll |
The app is role-aware end to end: viewers get read-only controls, operator+ can act, org_admin sees the audit tab, super_admin sees fleet/OTA.
From a click to a relay: the command round-trip
Section titled “From a click to a relay: the command round-trip”This is the most important flow to understand, because it crosses every layer and it is where safety is enforced.
Operator clicks "Turn ON" in /devices/[id] | POST /devices/{id}/commands (operator+, written to the audit log) vBackend K1 enqueues a row in the commands table | | ... device polls on its own clock ... vEdge agent: GET /commands?device_id= (every ~5s) | vCommandProcessor validates the command against the LOCAL interlocks | (control/interlocks.py) |-- unsafe/malformed --> rejected, output unchanged, result posted v valid --> mutates the control config (never writes the relay directly) vControl loop applies it on the next tick, interlocks get the last word | POST /commands/{id}/result (applied | rejected | failed) vBackend records the outcome; the operator sees it in command historyTwo things make this safe:
The cloud enqueues; it never controls. A command is a request sitting in a queue. The device pulls it on its own 5-second poll and validates it locally before doing anything. An unsafe or malformed command is rejected and recorded; the output does not move. The cloud cannot override a local safety interlock. See The safety rules and From code to copper.
Commands are audited. Operator command enqueues are written to the audit log (
/audit), so there is a record of who told which boiler to do what.
Telemetry the other direction
Section titled “Telemetry the other direction”Coming up, the agent posts telemetry every ~15 s to POST /telemetry. The backend stores it, evaluates
alert rules inline on ingest (services/alerts.py) — raising alerts, deduping while open, firing
notifications — and the dashboards read it back. Auth for device traffic is a per-device API key
(X-API-Key); auth for humans is JWT (login, refresh, optional TOTP MFA). Full endpoint reference
is in Talking to the API.