R2-D2
Dashboard
Node Red
Restreaming
The Force
UpdatedNever
v1.0.0

Overview

Loading…
R2-D2
Dashboard
Node Red
Restreaming
The Force
UpdatedNever
v1.0.0
DDroidspeak / Docs
Operator handbook
Droidspeak
What is R2-D2?
Runtime architectureAuth — Keycloak migration plan
FleetRestreamingGalaxy MapThe RepublicTemple ArchivesSTANAG 4817The Force
Tech StackNext.js 15 + React 19Tailwind v4ZustandTanStack Table + DataViewhls.jsLow-latency playerreagraphoglreact-grid-layoutMonacoScalar API Referencefumadocs
Cluster InfraTrailBaseReductStoreRestreamer (datarhei/core)TBMQKeycloakLonghornkube-vipIngress (Caddy + nginx-ingress + Traefik)Netbird
Operator QA Runbook
Install — RKE2Install — Dokploy
Contributor guideRelease Notes
Components

Restreaming

The video plane — Restreamer feeds, HoloVids, Holoprojector layouts/panels/groups, and Magic URLs.

The Restreaming plane is the most complex slice of the dashboard. It has three layers:

LayerWhere it shows upWhat it owns
Inputs / management/restreaming/holovidsCamera feed sources, per-feed configuration, snapshots
Organization/restreaming/layoutsLayouts — geometry only: cols × rows + tile rectangles, no feed bindings
Projection/restreaming/holoprojector + /p/<token>Views — a Layout (FK) plus per-slot camera_id bindings, optional public token

The vocabulary is fixed and the slicing is load-bearing — read it twice. The same word ("panel", "group") shows up in both this UI and the upstream Restreamer; here it means our row in TrailBase, not theirs.

For field checks, stream probes, and network triage commands, use the Operator QA Runbook. That page is the operator-first path for FFmpeg, GStreamer, ping, nc, and nmap diagnostics.

The upstream

R2-D2 talks to a single datarhei/core container — the "Restreamer" — at a fixed in-cluster URL (RESTREAMER_API_URL). Restreamer owns only the live FFmpeg processes — RTSP/RTMP inputs, transcoding, HLS/RTMP outputs. Everything else (layouts, panels, groups, magic URLs, feed metadata) is the dashboard's responsibility and is persisted in TrailBase.

The dashboard's /api/restreaming/* routes are a typed wrapper over Restreamer's upstream /api/v3/* surface.

Read-before-write is non-negotiable. Every mutation fetches the current process config, diffs in-memory, and PUTs the merged result. Without this, we'd corrupt processes mid-flight when two operators edit overlapping fields. The audit log is the second line of defence — see Known constraints at the end of this page.

Feeds — the inputs

A feed is one running FFmpeg process inside Restreamer. The dashboard's primary read route is GET /api/restreaming/feeds, which returns enriched feeds — the upstream process state joined with TrailBase metadata (alias, platform, tags, notes).

Feed lifecycle routes:

RoutePurpose
GET /api/restreaming/feedsList enriched feeds
POST /api/restreaming/feedsCreate a new feed (input + output config)
GET /api/restreaming/feeds/{id}Read one
PUT /api/restreaming/feeds/{id}Update (input + output)
DELETE /api/restreaming/feeds/{id}Remove
POST /api/restreaming/feeds/{id}/commandstart | stop | restart | reload
POST /api/restreaming/feeds/bulkBulk create / delete / command, with optional dryRun
POST /api/restreaming/feeds/importImport a YAML stack, with dryRun + skipExisting

The bulk and import routes are the workhorses for operators rolling out 20 cameras at once. Every bulk call returns a per-item result ({ id, ok, error? }) — the toolbar surfaces that as a status panel; it never claims success on partial failure.

Feed metadata

/api/restreaming/feed-meta stores the human layer on top of the raw upstream feed:

  • alias — what the operator calls this camera (vs. its IP-encoded ID)
  • platform_id — FK into the platforms table (e.g. "M300 #14", "SeaFLIR-02")
  • tags — string array
  • notes — free-form

This data is persisted in TrailBase and joined on every list read. The upstream feed ID is the join key — if someone re-creates the feed with the same reference, the metadata sticks.

Tags and platforms

Two small auxiliary tables, both in TrailBase: /api/restreaming/tags and /api/restreaming/platforms. CRUD is intentionally minimal — there is no UI for deletion yet; tags are GC'd lazily.

HoloVids — the feed grid

The page at /restreaming/holovids is a grid of one tile per feed. The header tile shows feed status; the body is an HLS player (via hls.js). Snapshots (/api/restreaming/snapshots) populate the placeholder image when a feed is offline, so the grid never goes to a black square.

Snapshots are ingested by a periodic job (currently triggered by the dashboard itself on focus) that grabs a single JPEG per feed and POSTs to /api/restreaming/snapshots. The read route returns a data URI per feed.

Layouts — geometry only

A layout is a cols × rows grid with a list of tile rectangles. No feed bindings. That's by design: a layout is reusable — the same 2×2 left-rail, big-center, two-bottom arrangement might be reused for ten different combinations of feeds.

Layouts live in TrailBase (restreamer_layouts). The Settings → Layout Management page is the canonical CRUD surface, but the routes (/api/restreaming/panels etc.) accept layout snapshots inline.

Panels — a layout + bindings + identity

A panel is a saved combination: layout (by reference) + per-slot camera bindings + aliases + a name. It's the first thing that can be projected on the Holoprojector page.

/api/restreaming/panels is full CRUD. /api/restreaming/panels/{id}/publish mints (POST) or revokes (DELETE) a public token; while the token is alive, /p/<token> renders the panel without any auth — the "Magic URL" for kiosk/external display.

Groups — a curated set of panels

A group is an ordered list of panel IDs with a name. Groups have the same publish mechanism — /api/restreaming/groups/{id}/publish mints a public token, and /api/restreaming/groups/public/{token} returns the member panels for the public viewer.

The public route is the only one in this plane that bypasses auth. It does not leak the token in any response field (no token oracle), it returns minimum data (no upstream feed URLs — only the layout + bindings needed to render), and it carries X-Robots-Tag: noindex, nofollow.

Audit log

Every mutating call writes one line to data/restreamer-audit.jsonl:

{"ts":1716...,"user":"alice","action":"feed.update","feedId":"cam-14","before":{...},"after":{...},"ok":true}

/api/restreaming/audit returns recent entries. The Settings → Restreaming page shows the last 20 in a "Recent activity" panel and supports "undo last N" rollbacks (which themselves go through the same routes, so they also audit).

Known constraints (do not break)

  1. Read-before-write on every mutation. Non-negotiable. Without it we'd corrupt processes mid-flight.
  2. No partial bulk silent failures. Bulk routes always return per-item results.
  3. Audit is mandatory. Every POST/PUT/DELETE writes an audit line. No exceptions.
  4. Live config is source of truth, not localStorage. localStorage is display-only. Always reconcile against GET /api/v3/process after a mutation.
  5. Validation in Next, not in the browser. Route validates ProcessConfig shape, address scheme, port range, reference uniqueness — so a malformed paste from the bulk wizard doesn't reach Restreamer.
  6. One bulk job at a time per browser tab. Client-side mutex; bulk-delete is disabled while bulk-restart is in flight.
  7. No force-dynamic magic on mutation routes. Plain JSON in, plain JSON out, no streaming, no SSE.
  8. Auth-gated. When AUTH_ENABLED=true, mutation routes require a TrailBase session cookie. Read routes stay public-within-cluster. Magic URLs bypass auth by design.

Built on

LayerTechPage
Default video enginehls.jshls.js
Opt-in low-latency engineCustom-chrome wrapper + @ffmpeg/ffmpeg (in-flight)Low-latency player
Layout editor gridreact-grid-layoutreact-grid-layout
Feed picker / panels list@tanstack/react-table via DataViewTanStack Table
Upstream live processesRestreamer (datarhei/core)Restreamer
Metadata / panels / groupsTrailBase tablesTrailBase
Snapshot historyReductStore (dedicated cluster)ReductStore
Auth gateKeycloak session via jose-verified JWT + TrailBase cookieKeycloak
HUD state slicesrc/lib/playerHud.ts, src/lib/hudVariants.tsLow-latency player

Fleet

The Node-RED swarm — registry, status probes, flow lifecycle, and the staging→prod promotion path.

Galaxy Map

A live network graph of every sector and page in the console, built from the nav config and rendered client-side.

On this page

The upstreamFeeds — the inputsFeed metadataTags and platformsHoloVids — the feed gridLayouts — geometry onlyPanels — a layout + bindings + identityGroups — a curated set of panelsAudit logKnown constraints (do not break)Built on