Skip to content

Operator & admin panels

The cloud side of RISER (K1K3 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.

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.

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).

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
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)
v
Backend K1 enqueues a row in the commands table
|
| ... device polls on its own clock ...
v
Edge agent: GET /commands?device_id= (every ~5s)
|
v
CommandProcessor 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)
v
Control loop applies it on the next tick, interlocks get the last word
| POST /commands/{id}/result (applied | rejected | failed)
v
Backend records the outcome; the operator sees it in command history

Two 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.

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.