Panels become devices: per-device documents in the flow repo, a Devices tab

- flow/devices.py: DeviceDef in FLOWS_DIR/_devices/<id>.json, committed under
  the flow store's write lock, 409 on a stale version; nonces in DEVICES_FILE
  outside git; in-memory presence announced as device_status
- api/routes/devices.py: CRUD, pairing with an optional {name, board,
  firmware}, a pending list adopted without a code from the LAN only, unpair;
  /panels/pair, /panels/pair/{code} and GET /panels/{id} kept as aliases,
  PUT /panels/ gone
- _device_may replaces _panel_may, JWT claims unchanged
- panels.json moves into devices once at startup, then .migrated
- MCP list_devices, get_device; search category device
- Devices tab (list, detail with presence, pairing, ordered dashboards,
  touch, I/O), PanelsDialog removed, serialised optimistic saves
This commit is contained in:
Melvin Strobl
2026-10-16 15:48:36 +02:00
parent 3c0912a3fe
commit 28a565e501
56 changed files with 3539 additions and 2404 deletions
+43 -27
View File
@@ -1,8 +1,8 @@
# Dashboards and panels
# Dashboards and devices
A **dashboard** is a grid of widgets bound to message names. It can show values
and send controls across several flows. A **panel** assigns published dashboards
to a paired screen, such as a wall tablet.
and send controls across several flows. A **device** is a paired screen, such
as a wall tablet or a knob, and shows an ordered set of published dashboards.
## Building one
@@ -441,46 +441,61 @@ under the finger, as a tile view moves.
## Showing one
Open `/view/{name}` for a dashboard-only view with your normal session.
For a dedicated screen, pair a panel.
For a dedicated screen, pair a device.
## Panels: hanging a screen on a wall
## Devices: hanging a screen on a wall
As a superuser:
1. Open **Dashboards → Panels**, add a panel and select its dashboards. The
name you add it under sets its address, `/panel/{id}`, and a later change of
title keeps it.
2. Enable **Touch friendly** for larger controls.
3. Open the displayed pairing link on the device. It shows a six-character code.
4. Enter that code for the panel, check the device description, then press
**Pair device**.
1. Open **Devices → Add device** and name it after where it hangs. The name
sets its address, `/panel/{id}`, and a later change of title keeps it.
2. Under **Dashboards**, add the ones it shows. The first is what it opens
with, and the arrows set the order of its rail.
3. Enable **Touch friendly** for larger controls.
4. Open the pairing link on the device. It shows a six-character code.
5. Enter that code under **Pairing**, check the device description, then press
**Pair**. A device asking from your own network also appears in the list
below, with what it says about itself, and **Pair this one** adopts it
without the code.
The screen collects its credential and shows the assigned published dashboards.
Several dashboards get a navigation rail.
Several dashboards get a navigation rail. The page says whether the device is
online, which it is while its live socket is open, and when it was last seen
since the instance started.
A panel credential reads its assigned dashboards, their bound messages and the
Each save carries the version it was read at. When someone else saved the
device first, the save is refused and the page shows their version.
A device credential reads its assigned dashboards, their bound messages and the
artifacts they show. It writes only the targets of their controls and query
charts, and the `in` ports of its own [I/O](#the-devices-own-io). A message its
tiles only draw, or a dashboard setting is bound to, stays read only. On a
locked dashboard only the query charts may still publish.
**Unpair screen** revokes a locally paired credential and keeps the panel's
dashboard assignments. Deleting the panel removes the assignment and its
access.
**Unpair screen** revokes a locally paired credential and keeps the device and
its dashboards. Deleting the device removes the assignment and its access, and
a device added again under the same name does not bring the old credential
back.
The device definitions are stored in the flow repository (`_devices/<id>.json`),
so their history is in git with the flows. The credential generation lives in
`DEVICES_FILE` outside it, so reverting the repository never undoes an unpair.
An instance from before devices moves its `panels.json` there on its first
start and renames the file `panels.json.migrated`.
### A screen without a browser
The openpanel firmware, kept in its own repository, draws dashboards as native
widgets on an ESP32-P4 wall panel (Waveshare ESP32-P4-WIFI6-Touch-LCD-7B,
1024×600). Once its config page points it at this instance (the dialog's link
works too, the device keeps only its address), it shows the pairing code itself; enter it as in step 4. It reads the same published
1024×600). Once its config page points it at this instance (the device's link
works too, the device keeps only its address), it shows the pairing code itself; enter it as in step 5. It reads the same published
dashboards and live values and draws every widget type with two exceptions,
both shown as a placeholder: a website tile that embeds a page (image tiles
work), and a chart of runs, which a panel credential may not read. Media tiles
work), and a chart of runs, which a device credential may not read. Media tiles
play MJPEG streams, pushed frames, images, and H.264 Baseline or MJPEG clips up
to 640×360. Re-encode other clips with the [clip recipe](../code/nodes.md#making-clips-playable-on-an-lvgl-panel).
The device decides what the panel's [I/O](#the-devices-own-io) port names
The device decides what its [I/O](#the-devices-own-io) port names
mean. openpanel serves `gpio.<n>`, `backlight`, `touch`, `mic` and `speaker`,
lists each one's `config` in its README and ignores any other name. A browser
screen serves none of them. The [speech recipe](../code/nodes.md#speech-to-and-from-a-panel)
@@ -488,9 +503,8 @@ turns `mic` into text and text into `speaker`.
### The device's own I/O
A panel can also act as a node. Under **I/O**, wire a port on the device to a
message. A new port saves once it has a port name and a message, at the latest
when the dialog closes:
A device can also act as a node. Under **I/O**, wire a port on the device to a
message. A new port saves once it has a port name and a message:
| Field | Meaning |
|---|---|
@@ -515,12 +529,14 @@ by the nodes that produce it.
### A screen somewhere you cannot reach
An [enrolled instance](portal.md) also shows a portal pairing link. Use that
link on the remote screen, then approve its code in the same dialog. The
device description says *via portal*.
link on the remote screen, then approve its code on the device's page. The
device description says *via portal*. Anyone can ask the portal for a code, so
a request that came through it, or from outside your network, is adopted by
its code only and never from the list.
The instance enforces the same dashboard and message limits. Remote
credentials are issued by the portal, so **Unpair screen** does not revoke
them; revoke them at the portal. Deleting the panel removes access on the
them; revoke them at the portal. Deleting the device removes access on the
instance.
## See also
+3 -3
View File
@@ -13,13 +13,13 @@ below, so a large flow grows downwards in one column.
- **Edges** are routed around the nodes, a loop back to an earlier node
included, and pulse when values pass through. Click one to inspect its
payload.
- **Endpoints** show flow inputs, dashboard widgets, panel I/O ports and nodes
- **Endpoints** show flow inputs, dashboard widgets, device I/O ports and nodes
in other flows that exchange messages with this flow. A value passing lights
one up in a sweep: left to right when it sends into the flow, right to left
when it reads out of it.
- **Clicking an endpoint** shows a flow input's type, starting value and
current value, with a pencil that opens the flow settings to edit it. A panel
port opens its panel in the Panels dialog. A node in another flow opens that
current value, with a pencil that opens the flow settings to edit it. A device
port opens the device in the Devices tab. A node in another flow opens that
flow on the label leading back here, with its edges highlighted.
Values update over a websocket. A connection banner tells you when the editor
+4 -3
View File
@@ -13,7 +13,8 @@ The sidebar opens as a sheet on phones.
|---|---|
| **Home** | see the brain graph, health and recent activity |
| **Flows** | create flows and open their editors |
| **Dashboards** | build widget dashboards and pair wall panels |
| **Dashboards** | build widget dashboards |
| **Devices** | pair wall screens and choose the dashboards each shows |
| **Runs** | start batch runs and review their results |
| **Workers** | manage machines that execute nodes |
| **Secrets** | store credentials; superusers only |
@@ -29,7 +30,7 @@ the docs in another tab.
### Search
Use **Search**, or ⌘K / Ctrl-K, to find flows, nodes, flow inputs and outputs,
dashboards, widgets, panels, secrets, modules, workers and alert channels.
dashboards, widgets, devices, secrets, modules, workers and alert channels.
Selecting a result opens its editor or settings.
Search covers the current instance. When connected through a portal,
@@ -91,6 +92,6 @@ Light and dark follow your system by default. Change this under
## Next stops
- [The flow editor](flow-editor.md)
- [Dashboards and panels](dashboards.md)
- [Dashboards and devices](dashboards.md)
- [Secrets, modules and alerts](operations.md)
- [Accounts and the portal](portal.md)
+1 -1
View File
@@ -115,5 +115,5 @@ service is not included, so the Docker stack does not start one.
## See also
- [The fluksio command](../code/cli.md): data directories and enrollment options.
- [Dashboards and panels](dashboards.md): pair a wall screen through the portal.
- [Dashboards and devices](dashboards.md): pair a wall screen through the portal.
- [Getting started](../getting-started/index.md): install and open your first instance.