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
+1
View File
@@ -39,6 +39,7 @@ instance from the portal first if this browser has not recently.
| Inspect flows | `list_flows`, `get_flow`, `get_node_source`, `list_node_types`, `get_graph`, `list_shared_nodes` |
| Inspect values | `get_flow_state`, `get_message_history` |
| Inspect the instance | `list_secrets` (names only), `get_modules`, `get_health`, `get_metrics`, `list_failures`, `list_runs` (live cascades) |
| Inspect devices | `list_devices`, `get_device` |
| Edit flows | `save_flow`, `save_node_source`, `validate_flow`, `publish_flow`, `discard_draft`, `delete_flow` |
| Operate flows | `run_flow`, `trigger_node`, `start_flow`, `stop_flow`, `pause_flow`, `resume_flow`, `cancel_node` |
| Install node packages | `apply_modules` |
+11 -6
View File
@@ -179,7 +179,7 @@ a run, a current message or a flow input's initial value still references the
digest, the call returns `409`. The hourly sweep removes unreferenced bytes
on its own, so this is for reclaiming space sooner.
## Dashboards and panels
## Dashboards and devices
| Method | Path | Action |
|---|---|---|
@@ -190,11 +190,16 @@ on its own, so this is for reclaiming space sooner.
| `PUT` | `/dashboards/{name}` | save a draft |
| `POST` | `/dashboards/{name}/publish`, `/dashboards/{name}/discard`, `/dashboards/{name}/rename` | publish, discard, or rename |
| `POST` | `/dashboards/{name}/duplicate` | copy into an unpublished `{name}_copy` |
| `GET`, `PUT` | `/panels/` | read or replace panel assignments; send the current `version` |
| `POST` | `/panels/pair` | start device pairing |
| `GET` | `/panels/pair/{code}` | poll pairing with its `secret` |
| `POST` | `/panels/{id}/pair` | approve the pairing |
| `POST` | `/panels/{id}/unpair` | revoke the panel credential |
| `GET` | `/devices/` | list devices, with `online` and `last_seen` |
| `POST`, `GET`, `PUT`, `DELETE` | `/devices/{id}` | create, read, save or delete one; a save sends the `version` it read and gets 409 when someone saved first |
| `POST` | `/devices/pair` | start device pairing, optionally with `{name, board, firmware}`; unauthenticated |
| `GET` | `/devices/pair/{code}` | poll pairing with its `secret`; answers `device` (and `panel` for older firmware); unauthenticated |
| `GET` | `/devices/pending` | devices waiting for a code, as they describe themselves |
| `POST` | `/devices/{id}/pair` | adopt by `{code}`, or by `{request}` from the pending list for a request from your network |
| `POST` | `/devices/{id}/unpair` | revoke the device credential |
`/panels/pair`, `/panels/pair/{code}` and `GET /panels/{id}` answer as their
`/devices/` counterparts, for firmware that has not updated yet.
## Secrets, modules, and alerts
+1 -1
View File
@@ -366,7 +366,7 @@ directory. Common choices:
| `FLUKSIO_HOME` | Select the CLI's data directory |
| `NODE_VENV` | `auto` adopts an existing venv; `managed` requests a separate one; a path selects an interpreter |
| `REDIS_HOST` | Use Redis for flow state and queues |
| `FRONTEND_HOST` | Browser address used in mail, OAuth metadata and panel links |
| `FRONTEND_HOST` | Browser address used in mail, OAuth metadata and device links |
| `ENVIRONMENT` | `production` disables interactive API documentation |
| `MCP_ENABLED` | Enable the [agent endpoint](agents.md) |
+1 -1
View File
@@ -111,7 +111,7 @@ Create a dashboard and add a gauge bound to `house.living_temperature`.
Publish the dashboard to see the current reading. Dashboard controls can also
publish messages, so choose their bindings with care.
See [Dashboards and panels](../interface/dashboards.md) for widgets and pairing
See [Dashboards and devices](../interface/dashboards.md) for widgets and pairing
a wall tablet.
For longer-term history, add an **InfluxDB** node that consumes
+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.
+1 -1
View File
@@ -22,7 +22,7 @@
- [The dashboard app](/interface/): the shell, Home, health and activity
- [The flow editor](/interface/flow-editor/): canvas, node panel, dock, shortcuts, drafts
- [Dashboards and panels](/interface/dashboards/): widgets, typed bindings, wall screens
- [Dashboards and devices](/interface/dashboards/): widgets, typed bindings, wall screens
- [Secrets, modules and alerts](/interface/operations/): credentials, packages, failure routing
- [Accounts and the portal](/interface/portal/): enrolment, remote users, revoking
+2 -1
View File
@@ -23,7 +23,8 @@ For CLI installations, select storage with `--data-dir`, `FLUKSIO_HOME` or
| `ALERTS_FILE` | `$DATA_DIR/alerts.json` | alert channels and rules |
| `WEBPUSH_FILE` | `$DATA_DIR/webpush.json` | push keys and browser subscriptions |
| `PROVISIONERS_FILE` | `$DATA_DIR/provisioners.json` | optional worker provisioning configuration |
| `PANELS_FILE` | `$DATA_DIR/panels.json` | wall-panel pairings |
| `DEVICES_FILE` | `$DATA_DIR/devices.json` | device credential nonces, outside the flow repository |
| `PANELS_FILE` | `$DATA_DIR/panels.json` | panels of an instance from before devices, moved into devices on first start and renamed `.migrated` |
| `OAUTH_PRIVATE_KEY_FILE` | `$DATA_DIR/oauth-key.pem` | agent and worker signing key |
| `CLOUD_CONFIG_FILE` | `$DATA_DIR/cloud.json` | optional portal enrollment |
| `AI_CONFIG_FILE` | `$DATA_DIR/ai.json` | optional assistant provider configuration |
+1 -1
View File
@@ -170,4 +170,4 @@ port and received value. It is not published downstream.
- [Flows, nodes and messages](../concepts/flows.md)
- [Node types](node-types.md)
- [Dashboards and panels](../interface/dashboards.md)
- [Dashboards and devices](../interface/dashboards.md)