Say on the querying-chart page that a flow doing its own binning has to drop the newest bucket, as the observability endpoints already do, and make plain that `inject` is the scheduler while `trigger` is a debounce and hold.
280 lines
13 KiB
Markdown
280 lines
13 KiB
Markdown
# Dashboards and panels
|
|
|
|
A dashboard is a grid of widgets bound to message names — the same names that
|
|
wire the graph. It is its own document rather than a set of nodes, so it reads
|
|
across flows without belonging to any of them, and a flow stays the logic it
|
|
was.
|
|
|
|
There is no second tool and no separate deployment. You build the panel next to
|
|
the thing feeding it.
|
|
|
|
## Building one
|
|
|
|
**Dashboards → New dashboard**, then drag widgets onto the grid from the widget
|
|
picker and bind each to a message.
|
|
|
|
Like flows, dashboards are drafts until you publish. A wall panel reads only
|
|
the published version, so a half-arranged page never reaches the wall.
|
|
|
|
On a phone the dashboard is read, not arranged: the grid stacks to one column
|
|
and dragging is off. Picking a widget and editing its settings still works.
|
|
|
|
## The widgets
|
|
|
|
### Display
|
|
|
|
| Widget | Binds to | Notes |
|
|
|---|---|---|
|
|
| **Value** | anything | a formatted reading with a unit and a precision |
|
|
| **Gauge** | `float`, `int` | min, max, unit |
|
|
| **Chart** | `float`, `int` | up to five series; see *Two kinds of chart* below |
|
|
| **Bar** | `float`, `int` | up to eight readings, a row each, with a scale per row |
|
|
| **Icon** | numbers, booleans, weather strings | maps a value onto a glyph |
|
|
| **Text** | — | markdown you write; a label, a note, an instruction |
|
|
| **Agenda** | `list` | upcoming items, e.g. from a calendar connector |
|
|
| **Forecast** | `list` | a short outlook strip |
|
|
| **Notification** | `record` | title, body and severity — what an alert channel writes |
|
|
| **Media** | `image`, `audio`, `video` | a camera frame, a clip; see *Media tiles* below |
|
|
| **Clock** | — | the time, in a size a wall can read |
|
|
|
|
Every widget carries a **title**, and **Show title** decides whether the panel
|
|
draws it. Turned off, the tile is just the reading — which is what a row of
|
|
gauges under one heading wants. The title is still the widget's name: what a
|
|
screen reader calls its controls, and what a published value is labelled with.
|
|
|
|
### Input
|
|
|
|
| Widget | Publishes | Notes |
|
|
|---|---|---|
|
|
| **Button** | a fixed value | one-shot: run it, open it, reset it |
|
|
| **Switch** | `bool` | on/off |
|
|
| **Slider** | `float`, `int` | min, max, step |
|
|
| **Input** | text or a number | free entry |
|
|
| **Selector** | one of a list | a mode, a scene, a preset — as a menu, or as a row of choices with the active one held |
|
|
| **Colour** | `[h, s, v]`, `[r, g, b]` or `"#rrggbb"` | a hue wheel with saturation and brightness, for an RGB fixture |
|
|
|
|
The colour wheel sends `[h, s, v]` by default — hue 0-360 degrees, saturation
|
|
and value 0-100 percent, which is what a DMX encoder expects — and its
|
|
**Sends** setting switches that to `[r, g, b]` (0-255 each) or to a
|
|
`"#rrggbb"` string, because fixtures differ. The first two bind a `list`
|
|
message, the third a `str`.
|
|
|
|
A control publishes the message it is bound to, exactly as a node would. On the
|
|
flow canvas it is drawn as a labelled endpoint feeding the nodes that read it,
|
|
so nobody has to guess where the value came from.
|
|
|
|
### Typed bindings
|
|
|
|
Widgets are type-checked against the message the same way ports are: a switch
|
|
takes a `bool`, a gauge takes a number, an agenda takes a `list`, a
|
|
notification takes a `record`. Bind one wrong and the editor says so rather
|
|
than drawing nothing. The same table is enforced on the server.
|
|
|
|
Charts are the exception worth knowing: because identity across five series is
|
|
carried by lightness alone, a chart with more than one series always draws a
|
|
legend.
|
|
|
|
## Two kinds of chart
|
|
|
|
**Live.** Bind up to five messages and the chart draws the engine's own ring
|
|
buffer of recent values, extended over the socket as new ones land. Nothing
|
|
else is involved. This is what you want for "the last few hours of the living
|
|
room".
|
|
|
|
**Querying.** The chart publishes a *request* — the window and resolution it
|
|
wants — exactly as a slider publishes a value, and draws the `series` some flow
|
|
answers with. What serves that request is the flow's business: typically a
|
|
small Python node that builds a Flux query, an InfluxDB node that runs it, and
|
|
another Python node that shapes the rows.
|
|
|
|
That indirection is the point. The widget never learns which database answered
|
|
it, so swapping the store is a change to one flow and nothing else. The answer
|
|
also states what it was computed for, so an answer to a different question is
|
|
ignored rather than two charts overwriting each other's picture.
|
|
|
|
!!! note "Drop the bucket that is still filling"
|
|
|
|
The request carries a window and an interval, and the binning is the flow's
|
|
own work — so the newest bucket only ever holds the part of an interval
|
|
that has elapsed. Drawn, it reads as a fall that never happened. The
|
|
engine's own [Activity charts](index.md#activity) end on the last closed
|
|
bin for that reason; a flow answering a chart has to drop or hold back its
|
|
newest bucket the same way.
|
|
|
|
## Media tiles
|
|
|
|
A media widget draws what its message points at: a picture, a clip with
|
|
controls, a video. Media does not travel as a message — a reference to it does
|
|
— so the tile fetches the bytes behind whichever reference the message holds,
|
|
and redraws when a new one arrives.
|
|
|
|
**Crop or fit** decides how a picture fills the tile. **Play as it arrives**
|
|
starts a clip by itself, though a browser only plays sound once somebody has
|
|
touched the page, so a screen nobody has tapped stays silent.
|
|
|
|
Rate is the thing to get right. A frame every second or two is a glance at a
|
|
door, and works; through the portal, make that every few seconds. Live video is
|
|
not something to push through the message plane at all — put the camera's own
|
|
address in **Live stream** and the browser plays it from source, leaving the
|
|
messages to carry the occasional still that a flow can actually react to.
|
|
|
|
Panels see media the same way, and only their own: a screen may fetch the bytes
|
|
its own tiles are showing and nothing else.
|
|
|
|
## Dashboard settings
|
|
|
|
Most of what a dashboard carries is a widget: a tile bound to a message. A
|
|
handful of things are not, because they belong to the whole surface rather than
|
|
to any tile on it — and a screen bolted to a wall has nobody standing at it to
|
|
set them.
|
|
|
|
| Setting | Is | Driven by |
|
|
|---|---|---|
|
|
| **Look** | `Fluksio`, `Material` or `Glass` | a `str` message |
|
|
| **Theme** | `System`, `Light` or `Dark` | a `str` message |
|
|
| **Palette** | the dashboard's colours, in order | a `list` message |
|
|
| **Background** | the URL of an image | a `str` message |
|
|
| **Touch** | touch friendly on or off | a `bool` message |
|
|
| **Lock** | read-only on or off | a `bool` message |
|
|
|
|
They all work the same way, and both halves are optional:
|
|
|
|
- **Just a value.** Set Theme to `Dark` and that dashboard is dark wherever it
|
|
is shown, whatever the device or the browser prefers. This costs no flow at
|
|
all, and it is what most wall panels want.
|
|
- **Driven by a message.** Pick one in **Driven by** and a flow takes the
|
|
setting over, exactly as it drives a tile. The value you set stays the
|
|
fallback: what the dashboard uses before the first message arrives, and
|
|
whenever the message is silent.
|
|
|
|
A bound setting is type-checked like a widget binding — a Theme pointed at a
|
|
`float` is refused by the editor and by the server — and the panel's credential
|
|
is extended to it, so a paired screen may read its own theme message and
|
|
nothing further.
|
|
|
|
There is no schedule field, on purpose. **A schedule is a node publishing to
|
|
the bound message**: an `inject` with a cron expression, feeding a `change`
|
|
node that maps the hour onto a palette, is the whole of "warmer after sunset" —
|
|
and the same channel then serves anything else you want to drive, including
|
|
locking a panel down remotely.
|
|
|
|
### Look
|
|
|
|
**Fluksio** is the app's own design — `--card` surfaces separated by a hairline
|
|
and a low shadow, pill controls, one slate-blue accent — and is what a dashboard
|
|
wears until you choose otherwise. **Material** lays flat, tonal cards on a plain
|
|
ground. **Glass** floats translucent tiles over a soft, slowly moving one.
|
|
|
|
They are three complete sets of components, not three stylesheets — but they are
|
|
the same dashboard: every widget, every control and every keystroke behaves
|
|
identically, so switching look never changes what a panel can do.
|
|
|
|
### Palette
|
|
|
|
A palette is an ordered list of colours, and **position is the role**:
|
|
|
|
| # | Role |
|
|
|---|---|
|
|
| 1 | the ground the dashboard sits on |
|
|
| 2 | the surface a widget is |
|
|
| 3 | the primary — fills, active controls, the first chart line |
|
|
| 4 | the accent — the second chart line |
|
|
| 5 | text |
|
|
| 6+ | further chart colours |
|
|
|
|
Paste a [coolors.co](https://coolors.co) link (or a list of hex colours) and the
|
|
whole dashboard is recoloured — every widget, the rail and the charts. Leave the
|
|
later roles off and they are worked out from the ones you gave, so **three
|
|
colours are a whole dashboard**. **Rotate** turns the list when the roles landed
|
|
in the wrong order. Text that could not be read on a surface is replaced with
|
|
black or white there, so no palette can produce a line nobody can see.
|
|
|
|
While a palette is set, **Theme is idle**: the first colour is the ground, so
|
|
whether the panel reads light or dark is already decided by the palette itself.
|
|
Fault and success keep their own colours in every palette — a failure must never
|
|
be paintable as a reading.
|
|
|
|
### Background
|
|
|
|
An image drawn under the widgets, covering the canvas. It replaces the ground
|
|
the Glass look brings with it. Bound to a message, a flow decides the picture.
|
|
|
|
### Touch
|
|
|
|
Bigger controls, and nothing that only happens on hover. A phone gets this
|
|
anyway, from its own width; a wall panel has no way to say so for itself.
|
|
|
|
### Lock
|
|
|
|
Lock is a read-only *surface*, not a permission. The controls stay visible,
|
|
stop publishing and read as disabled, and the panel says **Read-only** in the
|
|
corner. What a paired screen is allowed to reach is still decided by its own
|
|
credential, below.
|
|
|
|
## Showing one
|
|
|
|
- `/view/{name}` — a browser tab pointed at one dashboard. Needs an ordinary
|
|
session.
|
|
- **Panels** — a named device that pairs instead of logging in. Below.
|
|
|
|
## Panels: hanging a screen on a wall
|
|
|
|
A wall tablet has no keyboard, so it pairs.
|
|
|
|
1. **Dashboards → Panels**, add a panel named after where it hangs, and tick
|
|
the dashboards it shows. More than one and the screen draws a rail to switch
|
|
between them.
|
|
2. Point the device's browser at the link the dialog shows. The device then
|
|
displays a six-character code.
|
|
3. Type that code into the same panel's **Pair device** field. The line under
|
|
it names what is holding the code — check it is the screen you just hung,
|
|
because approving adopts whatever answered. The screen picks the credential
|
|
up within a few seconds and never asks again.
|
|
|
|
What the screen holds is not a login. It reaches that panel's published
|
|
dashboards and the messages its own widgets read or publish, and nothing else —
|
|
a message no tile on it draws is refused in both directions.
|
|
|
|
The *credential* cannot be made strictly read-only, and that is honest rather
|
|
than an oversight: a querying chart publishes its request, and a control on a
|
|
panel is the reason you put one there. The dashboard's own **Lock** setting
|
|
above stops its controls publishing and can be driven by a flow, which is how
|
|
you quieten a screen remotely — but that is the surface behaving, not the
|
|
credential being narrowed.
|
|
|
|
Deleting the panel revokes the credential and the assignment together, which is
|
|
how you retire a device and what it showed. Unpairing revokes only the
|
|
credential: the panel, its dashboards and their arrangement stay exactly where
|
|
they are, and the screen falls back to asking for a new code — which is how you
|
|
swap the device out without rebuilding what hangs there.
|
|
|
|
!!! note "If the link is wrong"
|
|
|
|
The pairing link is built from the installation's `FRONTEND_HOST`. If that
|
|
is not the address devices on your network actually reach, fix the setting
|
|
rather than the link: it is the same one password-reset mails and the OAuth
|
|
metadata are built from.
|
|
|
|
### A screen somewhere you cannot reach
|
|
|
|
Another building, someone else's network, no route in. An installation
|
|
[enrolled with a portal](portal.md) shows a second link,
|
|
`https://hub.${DOMAIN}/i/{installation-id}/panel`, and the same three steps
|
|
work through it: the portal serves that one page without a session, forwards
|
|
the pairing calls down the tunnel, and mints the credential when you approve
|
|
the code. The pairing line then reads *via portal*.
|
|
|
|
The portal names the panel and nothing else. What the panel may read is decided
|
|
on the installation, on every call, by the same check a locally paired screen
|
|
passes. Two differences: it acts as the account the installation was enrolled
|
|
with rather than as whoever approved it, and deleting the panel stops it here
|
|
immediately while the portal's copy of the token expires on its own — which is
|
|
also why unpairing, which works on a credential this installation signed, does
|
|
not reach a remote screen. Revoke that one at the hub.
|
|
|
|
## See also
|
|
|
|
- [Payload types](../reference/payload-types.md) — what a widget can bind to
|
|
- [Flows, nodes and messages](../concepts/flows.md) — where the names come from
|
|
- [Accounts and the portal](portal.md) — reaching all of this from outside
|