docs
This commit is contained in:
+137
-244
@@ -1,324 +1,217 @@
|
||||
# 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.
|
||||
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.
|
||||
|
||||
## Building one
|
||||
|
||||
**Dashboards → New dashboard**, then drag widgets onto the grid from the widget
|
||||
picker and bind each to a message.
|
||||
1. Open **Dashboards → New dashboard**.
|
||||
2. Enter edit mode, choose **Add widget** and select a type.
|
||||
3. Bind the widget to a message in its settings. On desktop, drag and resize
|
||||
tiles on the grid.
|
||||
4. **Publish** when the dashboard is ready.
|
||||
|
||||
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.
|
||||
Edits stay in a draft; paired screens show the published version. On phones,
|
||||
tiles stack in one column and dragging is disabled, but settings remain editable.
|
||||
|
||||
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.
|
||||
Controls can publish real values while you edit, so choose their targets with
|
||||
care. Your new Switch may already be flipping something!
|
||||
|
||||
## The widgets
|
||||
|
||||
### Display
|
||||
|
||||
| Widget | Binds to | Notes |
|
||||
| Widget | Binds to | Shows |
|
||||
|---|---|---|
|
||||
| **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 |
|
||||
| **Player** | `record` | what a streamer is playing, with its transport; see *Player tiles* below |
|
||||
| **Clock** | — | the time, in a size a wall can read |
|
||||
| **Website** | — | another site's page in the tile; see *Website tiles* below |
|
||||
| **Value** | anything | formatted reading, unit and precision |
|
||||
| **Gauge** | `float`, `int` | reading within a min/max range |
|
||||
| **Chart** | numeric messages, a query response or run metrics | up to five lines |
|
||||
| **Bar** | `float`, `int` | up to eight readings with separate scales |
|
||||
| **Icon** | numbers, booleans or strings | a glyph selected from the value |
|
||||
| **Text** | — | Markdown |
|
||||
| **Agenda** | `list` | upcoming items |
|
||||
| **Forecast** | `list` | an outlook strip |
|
||||
| **Notification** | `record` | title, body and severity |
|
||||
| **Media** | `image`, `audio`, `video`, `artifact` | image, audio or video |
|
||||
| **Player** | `record` | playback status and transport controls |
|
||||
| **Clock** | — | current time |
|
||||
| **Website** | — | embedded page or image |
|
||||
|
||||
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.
|
||||
Every widget has a title. **Show title** controls its visibility; the title
|
||||
still names the widget for accessibility and published values.
|
||||
|
||||
### Input
|
||||
|
||||
| Widget | Publishes | Notes |
|
||||
| Widget | Publishes | Settings |
|
||||
|---|---|---|
|
||||
| **Button** | a fixed value | one-shot: run it, open it, reset it |
|
||||
| **Button** | fixed value | one-shot action |
|
||||
| **Buttons** | a value per button | grid of actions |
|
||||
| **Switch** | `bool` | on/off |
|
||||
| **Slider** | `float`, `int` | min, max, step; **Drawn as** makes it a vertical fader |
|
||||
| **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 |
|
||||
| **Slider** | `float`, `int` | min, max, step; horizontal or vertical |
|
||||
| **Input** | text or number | free entry |
|
||||
| **Selector** | selected value | menu or row of choices |
|
||||
| **Colour** | list or string | HSV, RGB or hex |
|
||||
|
||||
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`.
|
||||
Colour defaults to `[h, s, v]`: hue 0–360°, saturation and brightness 0–100%.
|
||||
**Sends** can select RGB (`[r, g, b]`, 0–255) or hex (`"#rrggbb"`).
|
||||
HSV and RGB bind to `list`; hex binds to `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.
|
||||
Controls publish to their bound messages. The flow canvas shows them as
|
||||
endpoints feeding the nodes that consume those messages.
|
||||
|
||||
### Typed bindings
|
||||
Bindings are checked in the editor and on the server. See
|
||||
[Payload types](../reference/payload-types.md).
|
||||
|
||||
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
|
||||
|
||||
Charts draw their series in one fixed order of colours: the primary first, then
|
||||
hues picked to stay apart, colour blindness included. A chart with more than
|
||||
one series still always draws a legend, so no line is told by colour alone.
|
||||
Choose the chart's data source:
|
||||
|
||||
## Two kinds of chart
|
||||
- **Live:** recent numeric message history, updated as values arrive.
|
||||
- **Query:** publishes a `record` request containing `range_s` and
|
||||
`interval_s`, then draws a flow's `series` response. Include those fields in
|
||||
the answer so the chart can reject responses for a different window.
|
||||
- **Runs:** compares a numeric metric from named runs, a sweep or the latest
|
||||
runs of a flow. See [Runs](../concepts/runs.md).
|
||||
|
||||
**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".
|
||||
Multi-line charts show a legend as well as different colours.
|
||||
|
||||
**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.
|
||||
!!! tip "Skip the unfinished bucket"
|
||||
|
||||
The widget never learns which database answered it, so swapping the store
|
||||
changes one flow and nothing else. An answer states what it was computed for,
|
||||
so an answer to a different question is ignored.
|
||||
|
||||
!!! 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.
|
||||
If your query aggregates into time buckets, leave out the bucket still
|
||||
filling. Otherwise an incomplete total can look like a sudden drop.
|
||||
|
||||
## 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.
|
||||
The tile takes the bytes behind whichever reference the message holds, and
|
||||
redraws when a new one arrives.
|
||||
Media messages contain artifact references; the widget loads the bytes they
|
||||
name. **Crop or fit** controls image sizing. **Play as it arrives** enables
|
||||
autoplay, subject to the browser's rules for sound.
|
||||
|
||||
**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.
|
||||
For local streams, `volatile=True` artifacts can be pushed over the websocket
|
||||
with their message. Stored artifacts are fetched individually, as are frames
|
||||
viewed through a portal. Use occasional snapshots for remote screens.
|
||||
|
||||
Rate is the thing to get right, and it is decided by the node publishing rather
|
||||
than by the tile. A frame the engine is holding in memory is pushed down the
|
||||
same websocket that carries the value, so ten a second is a real view on the
|
||||
local network; a stored one is fetched, a round trip each, which suits a glance
|
||||
at a door every second or two. Through the portal everything is fetched, so
|
||||
make it every few seconds there. Above that, 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 is sent, and may
|
||||
fetch, the bytes its own tiles are showing and nothing else.
|
||||
For continuous video, set **Live stream** to a browser-compatible camera URL.
|
||||
The browser must be able to reach that URL; enrollment does not expose the
|
||||
camera itself. See [Media payloads](../reference/payload-types.md#image-audio-video).
|
||||
|
||||
## Player tiles
|
||||
|
||||
A player is the one tile that both reads and publishes, so it has two bindings.
|
||||
It **shows** a `record` describing what is playing and **publishes to** a `str`
|
||||
carrying what to do about it:
|
||||
A Player reads a status `record` and publishes commands to a separate `str`
|
||||
message.
|
||||
|
||||
| Field of the record | Means |
|
||||
| Status field | Meaning |
|
||||
|---|---|
|
||||
| `title`, `artist`, `album` | what is playing |
|
||||
| `status` | `play`, `pause`, `stop`, `load`, or `off` for a streamer with no power |
|
||||
| `position`, `duration` | seconds, both |
|
||||
| `title`, `artist`, `album` | track details |
|
||||
| `status` | `play`, `pause`, `stop`, `load` or `off` |
|
||||
| `position`, `duration` | seconds |
|
||||
|
||||
The buttons and the bar publish words: `toggle`, `next`, `prev` and
|
||||
`seek:<seconds>`. Those are a streamer's own vocabulary rather than this app's,
|
||||
which is what lets one tile drive whatever is on the other end: the node that
|
||||
receives them decides what they mean for its device.
|
||||
|
||||
The position counts forward in the browser between readings, so the bar moves
|
||||
at one second while the device is polled at whatever rate suits it. Every
|
||||
reading that arrives is taken as the truth and the count restarts from it.
|
||||
Controls send `toggle`, `next`, `prev` and `seek:<seconds>`. Your receiving
|
||||
node translates those into device actions. The position advances in the
|
||||
browser between readings and resets to each fresh reading.
|
||||
|
||||
## Website tiles
|
||||
|
||||
A website tile frames whatever page its **Address** points at: a weather map, a
|
||||
camera's own web interface, a timetable. A site that refuses to be framed shows
|
||||
its own refusal in the tile.
|
||||
Set **Address** to a page to embed, or choose **Image** for a snapshot or MJPEG
|
||||
URL. A website may refuse embedding.
|
||||
|
||||
The page runs sandboxed. By default it gets an origin of its own that matches
|
||||
no site, so it can run scripts but has no cookies or storage, and many
|
||||
interactive pages will not load like that. **Trusted site** lets it keep its
|
||||
real origin, with its cookies and storage, which is what visiting the site
|
||||
would give it. Turn it on only for a site you trust. A trusted tile pointing at
|
||||
this app's own address, or the hub's when the app is reached through one, shows
|
||||
a notice instead: on that origin the page could reach into the app and read
|
||||
your session.
|
||||
|
||||
**Image** draws the address as a picture scaled to fit the tile, rather than
|
||||
as a page. Use it for a camera's MJPEG stream (`http://<camera>/stream`) or a
|
||||
snapshot URL, which a page frame would show at the camera's native size.
|
||||
Embedded pages run in a sandbox without cookies or storage by default.
|
||||
**Trusted site** permits the site's own origin, cookies and storage.
|
||||
Use it only for a trusted destination. The app blocks trusted embeds of its
|
||||
own origin, including the portal origin, to protect your session.
|
||||
|
||||
## 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.
|
||||
Open **Dashboard settings** from the dock. Each setting can hold a fixed value
|
||||
or follow a message chosen in **Driven by**. The fixed value is the fallback
|
||||
when no usable message value is available.
|
||||
|
||||
| Setting | Is | Driven by |
|
||||
| Setting | Choices | Message type |
|
||||
|---|---|---|
|
||||
| **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 |
|
||||
| **Lock** | read-only on or off | a `bool` message |
|
||||
| **Look** | `fluksio`, `material`, `glass` | `str` |
|
||||
| **Theme** | `system`, `light`, `dark` | `str` |
|
||||
| **Palette** | ordered hex colours | `list` |
|
||||
| **Background** | image URL | `str` |
|
||||
| **Lock** | read-only on/off | `bool` |
|
||||
|
||||
They all work the same way, and both halves are optional:
|
||||
To change one on a schedule, have a scheduled node publish its bound message.
|
||||
|
||||
- **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.
|
||||
### Look and theme
|
||||
|
||||
A bound setting is type-checked like a widget binding, so a Theme pointed at a
|
||||
`float` is refused by the editor and by the server. The panel's credential
|
||||
is extended to it, so a paired screen may read its own theme message and
|
||||
nothing further.
|
||||
**Fluksio** is the default look. **Material** uses flat tonal cards; **Glass**
|
||||
uses translucent tiles. Widgets work the same in all three.
|
||||
|
||||
There is no schedule field. **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". The same channel
|
||||
drives anything else, 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.
|
||||
Theme follows the chosen preference unless a palette sets the background
|
||||
colour.
|
||||
|
||||
### Palette
|
||||
|
||||
A palette is an ordered list of colours, and **position is the role**:
|
||||
Paste a colour list or a [Coolors](https://coolors.co) palette link. Colours
|
||||
have roles by position:
|
||||
|
||||
| # | Role |
|
||||
| Position | 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 |
|
||||
| 1 | background |
|
||||
| 2 | widget surface |
|
||||
| 3 | primary controls and first chart line |
|
||||
| 4 | accent |
|
||||
| 5 | text |
|
||||
| 6+ | the next chart lines, before the app's own chart colours |
|
||||
| 6+ | additional chart lines |
|
||||
|
||||
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.
|
||||
Missing roles are derived, so three colours are enough to get started.
|
||||
**Rotate** shifts their order. Text contrast is corrected automatically;
|
||||
failure and success indicators keep their status colours.
|
||||
|
||||
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, so a failure is never
|
||||
paintable as a reading.
|
||||
### Background and lock
|
||||
|
||||
### 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.
|
||||
|
||||
### 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.
|
||||
Background draws an image beneath the widgets. Lock disables publishing from
|
||||
controls and shows **Read-only** when viewing the dashboard. It does not
|
||||
restrict API permissions and does not disable controls in edit mode.
|
||||
|
||||
## 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.
|
||||
Open `/view/{name}` for a dashboard-only view with your normal session.
|
||||
For a dedicated screen, pair a panel.
|
||||
|
||||
## Panels: hanging a screen on a wall
|
||||
|
||||
A wall tablet has no keyboard, so it pairs.
|
||||
As a superuser:
|
||||
|
||||
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, with each dashboard's own icon on it. On a phone the rail is a
|
||||
row floating along the bottom; the app shows it there too, above its own
|
||||
buttons, for a dashboard that hangs on such a panel.
|
||||
2. Tick **Touch friendly** if the screen is touched rather than pointed at.
|
||||
Controls and the rail grow to a finger's size — the rail gets taller without
|
||||
taking a wider column, so the arrangement does not move. It sits here rather
|
||||
than on a dashboard because it describes the screen: the same dashboard may
|
||||
also be open in a browser with a mouse. A phone gets it anyway, from its own
|
||||
width.
|
||||
3. Point the device's browser at the link the dialog shows. The device then
|
||||
displays a six-character code.
|
||||
4. 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.
|
||||
1. Open **Dashboards → Panels**, add a panel and select its dashboards.
|
||||
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**.
|
||||
|
||||
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 screen collects its credential and shows the assigned published dashboards.
|
||||
Several dashboards get a navigation rail.
|
||||
|
||||
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.
|
||||
A panel credential permits its assigned dashboards, their bound messages and
|
||||
the artifacts they show. It also permits publishing from its controls and query
|
||||
charts. **Lock** changes the viewing surface; it does not narrow the credential.
|
||||
|
||||
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.
|
||||
**Unpair screen** revokes a locally paired credential and keeps the panel's
|
||||
dashboard assignments. Deleting the panel removes the assignment and its
|
||||
access.
|
||||
|
||||
!!! note "If the link is wrong"
|
||||
!!! note "If the local link is wrong"
|
||||
|
||||
The pairing link is built from the instance'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.
|
||||
Pairing links use `FRONTEND_HOST`. Set it to the app address reachable
|
||||
from the screen's network.
|
||||
|
||||
### A screen somewhere you cannot reach
|
||||
|
||||
Another building, someone else's network, no route in. An instance
|
||||
[enrolled with a portal](portal.md) shows a second link,
|
||||
`https://hub.${DOMAIN}/i/{instance-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*.
|
||||
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*.
|
||||
|
||||
The portal names the panel and nothing else. What the panel may read is decided
|
||||
on the instance, on every call, by the same check a locally paired screen
|
||||
passes. Two differences: it acts as the account the instance 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 instance signed, does
|
||||
not reach a remote screen. Revoke that one at the hub.
|
||||
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
|
||||
instance.
|
||||
|
||||
## 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
|
||||
- [Payload types](../reference/payload-types.md)
|
||||
- [Flows, nodes and messages](../concepts/flows.md)
|
||||
- [Accounts and the portal](portal.md)
|
||||
|
||||
+99
-127
@@ -1,177 +1,149 @@
|
||||
# The flow editor
|
||||
|
||||
The canvas is where most of the work happens. It floats its chrome over a
|
||||
full-bleed graph, so what you are looking at is always the flow.
|
||||
Build a flow by naming its messages and configuring its nodes. Fluksio draws
|
||||
the connections and arranges the canvas for you.
|
||||
|
||||
## The canvas
|
||||
|
||||
Nodes are laid out for you, left to right on a desktop and top to bottom on a
|
||||
phone, and the arrangement is recomputed as the graph changes. There is
|
||||
nothing to drag and nothing to tidy.
|
||||
The graph runs left to right on desktop and top to bottom on phones.
|
||||
|
||||
The edges come from message names, so there is no layout to store or to merge
|
||||
when two people edit the same flow.
|
||||
- **Nodes** show their execution status.
|
||||
- **Edges** pulse when values pass through. Click one to inspect its payload.
|
||||
- **Endpoints** show flow inputs, dashboard widgets and nodes in other flows
|
||||
that exchange messages with this flow.
|
||||
|
||||
What is drawn:
|
||||
|
||||
- **Your nodes**, with a status dot — running, succeeded, failed, or nothing at
|
||||
all when idle. Status is always named in words too, never colour alone.
|
||||
- **Edges**, which pulse when a value goes down them. Click one to inspect it.
|
||||
- **Endpoints** — anything wired into this flow that is not a node in it: a
|
||||
dashboard control writing a message, a tile reading one, a node in another
|
||||
flow on the far side of a dotted name. Drawn as labelled boxes at the
|
||||
boundary, so a value never appears to come from nowhere.
|
||||
|
||||
Values are live. The editor holds a websocket to the engine, and the connection
|
||||
banner tells you when it is not.
|
||||
Values update over a websocket. A connection banner tells you when the editor
|
||||
cannot reach the engine.
|
||||
|
||||
## Adding a node
|
||||
|
||||
**Add node** on the dock, or ⌘P / Ctrl-P for the command palette, which also
|
||||
jumps between flows and offers your shared nodes. Pick a type and it appears on
|
||||
the canvas with its panel open.
|
||||
Choose **Add node** in the bottom dock, or use ⌘P / Ctrl-P. The command palette
|
||||
also jumps between flows and offers reusable nodes. Select a type to add it
|
||||
and open its settings.
|
||||
|
||||
## The node panel
|
||||
|
||||
Floats on the right; a full-screen sheet on a phone.
|
||||
**Consumes** and **Provides** declare the node's ports. Each has a port name,
|
||||
payload type, bound message and delivery interval. Outputs also have a
|
||||
streaming toggle.
|
||||
|
||||
**Consumes** and **Provides** are the ports. Each port has two lines: its name
|
||||
and payload type (a `list` also says what its items are), then the message it
|
||||
is bound to, how often at most it is delivered, and, on an output, a
|
||||
*streaming* toggle for ports that publish repeatedly during one execution. The
|
||||
message field suggests messages already in the flow, which is usually how you
|
||||
wire something: type the name that already exists.
|
||||
Message fields suggest existing names. Matching an output's message is how
|
||||
you connect an input.
|
||||
|
||||
A port is named after its message until you name it yourself. Its name field
|
||||
then stays empty and shows the name it follows. A name typed there stays when
|
||||
the message changes, which is how a port is bound to a message called
|
||||
something else, as a [`wren`](../reference/node-types.md#wren) target is;
|
||||
clear it to follow the message again. The name is what the node's code sees,
|
||||
so it is letters, digits and `_`, not starting with a digit, and no two inputs
|
||||
or two outputs of a node share one. The field keeps the name it had otherwise.
|
||||
A blank port-name field follows the message's last segment. Set a port name
|
||||
to keep the argument name fixed while changing its message binding. Names use
|
||||
letters, digits and `_`, cannot start with a digit, and must be unique among
|
||||
the node's inputs or outputs.
|
||||
|
||||
Renaming an output renames the message everywhere it is read. Inputs are not
|
||||
renamed, because an input is more often re-pointed at a different message than
|
||||
renamed.
|
||||
Renaming an output's message updates its consumers in the flow. Changing an
|
||||
input binding points that input at a different message.
|
||||
|
||||
**Settings** is generated from the node type's parameter schema, so an MQTT
|
||||
node shows broker, topic and QoS, and an ntfy node shows server, topic and
|
||||
priority. A field marked as a credential renders as a picker over your stored
|
||||
[secrets](operations.md) instead of a text box. A setting holding JSON, such as
|
||||
a Switch node's rules, shows one line of it; click it to edit the whole value
|
||||
in a larger editor, which saves only once the text parses. Escape or Cancel
|
||||
leaves the setting as it was. An inject's payload and a flow input's starting
|
||||
value of type `json`, `record` or `list` open the same editor, which also holds
|
||||
the shape (a `record` is an object, a `list` an array); emptying it leaves no
|
||||
value.
|
||||
### Settings
|
||||
|
||||
A Function node gets a second settings section where you add your own: a
|
||||
setting is a constant of this node's code and arrives as an argument beside its
|
||||
Built-in nodes show forms for their configuration. Credential fields offer
|
||||
stored [secrets](operations.md#secrets). JSON settings open a larger editor
|
||||
and save only when the value parses.
|
||||
|
||||
Function nodes also let you add settings passed as arguments alongside the
|
||||
ports. See [Where a node's values come from](../concepts/values.md).
|
||||
|
||||
**Timeout** (Function nodes) is how many seconds the code may be *silent*
|
||||
before it is stopped: it measures silence, not duration, so a node that yields
|
||||
every few seconds can run for hours under a timeout of 300. Left empty there is
|
||||
no limit, which is the default. Set one where a node going quiet means it is
|
||||
stuck rather than working. A `0` says so explicitly.
|
||||
**Timeout** on a Function node limits silence, not total duration. A node
|
||||
yielding regularly can run longer than its timeout. Empty or `0` means no
|
||||
limit, the default.
|
||||
|
||||
**Code** is the editor. It saves as you type and applies on ⌘S; the node
|
||||
reloads without the flow stopping. A node file defines `process(...)`; if it
|
||||
defines exactly one public function under another name, that one is used
|
||||
instead.
|
||||
**Resources** selects a shared worker pool, a named flavor or custom CPU,
|
||||
memory and GPU requirements. Placement by `device` and `device_policy` is
|
||||
configured through the API or flow JSON; see [Remote workers](../code/workers.md).
|
||||
|
||||
Code runs as the engine, so changing it is a superuser's. Anyone else sees the
|
||||
editor read-only, a Command node's settings disabled, and no Function, Command
|
||||
or reusable nodes to add.
|
||||
### Code and reuse
|
||||
|
||||
**Shared** turns a node's code into a library entry other flows can reuse.
|
||||
Editing a shared node edits the copy every flow using it runs, which is the
|
||||
point and also the caution.
|
||||
Define `process(...)` in **Code**. If the file instead contains exactly one
|
||||
public function, Fluksio uses that function.
|
||||
|
||||
!!! note "Not in the panel yet"
|
||||
Code saves as you type. ⌘S / Ctrl-S in the code editor saves immediately;
|
||||
**Publish** applies ordinary node code to the running flow. Generated code
|
||||
from `fluksio sync` is read-only: edit its source repository and sync again.
|
||||
|
||||
`device` and `device_policy`, which machine a node's code runs on, are set
|
||||
through the API rather than the panel, with `PUT /flows/{name}`. See
|
||||
[Remote workers](../code/workers.md).
|
||||
Only superusers can add or edit code-running nodes. Other users can read code
|
||||
and configure permitted built-in nodes.
|
||||
|
||||
**Reuse** shares node code as a library entry. Saving shared code takes effect
|
||||
immediately in every flow using it, so check its users before changing it.
|
||||
|
||||
## The flow panel
|
||||
|
||||
Click the canvas title to open the flow's own settings.
|
||||
Open **Flow settings** from the pencil button in the dock.
|
||||
|
||||
- **Running** — the switch that starts and stops the flow. Stopped means none
|
||||
of its subscriptions, schedules or webhooks exist.
|
||||
- **Mode** — `live` or `batch`. Live reacts to what arrives; batch only runs
|
||||
when a run asks it to. See [Runs](../concepts/runs.md).
|
||||
- **Inputs** — messages the flow takes from outside, with the value each starts
|
||||
from. For a batch flow these are also the run's parameters.
|
||||
- **Outputs** (batch only) — which messages a run reports as its result.
|
||||
- **Contents** — a reminder that this flow's name namespaces every message
|
||||
inside it.
|
||||
| Setting | Purpose |
|
||||
|---|---|
|
||||
| **Running** | starts or stops the published live flow's subscriptions, schedules and webhooks |
|
||||
| **Mode** | chooses continuous `live` operation or requested `batch` runs |
|
||||
| **Inputs** | declares external messages and starting values; batch inputs become run parameters |
|
||||
| **Result** | selects batch output messages |
|
||||
| **Contents** | shows the node count and message namespace |
|
||||
|
||||
For live inputs, **Publish this value now** sends the starting value immediately.
|
||||
Changing a stored starting value alone does not replace an existing message.
|
||||
|
||||
## Inspecting an edge
|
||||
|
||||
Click a wire. You get the last payload that went down it, when, and a sparkline
|
||||
of its recent numeric history. You can also republish that value from the node
|
||||
that produced it, which re-runs everything downstream. It is the fastest way to
|
||||
test a change without waiting for the real sensor.
|
||||
Click an edge to see its latest payload, timestamp and recent numeric history.
|
||||
Republish its value from the producer to run downstream nodes without waiting
|
||||
for the next sensor reading.
|
||||
|
||||
## The dock
|
||||
|
||||
Along the bottom, floating over the canvas.
|
||||
|
||||
| Control | What it does |
|
||||
| Control | Action |
|
||||
|---|---|
|
||||
| **Add node** | opens the palette |
|
||||
| **Fit** | returns to the view the flow opened with |
|
||||
| **Run** | injects a value by hand; on a batch flow, opens the parameters dialog |
|
||||
| **Pause / Resume** | holds messages instead of running them |
|
||||
| **Step** | releases exactly one held message |
|
||||
| **Publish** | puts the draft live |
|
||||
| **Discard** | throws the draft away |
|
||||
| **Issues** | what is stopping this flow from running, per node |
|
||||
| **Logs** | what the nodes printed, filterable |
|
||||
| **Add node** | opens the command palette |
|
||||
| **Fit to screen** | fits the graph; desktop only |
|
||||
| **Run** | executes the current live flow once; for batch flows, asks for run parameters |
|
||||
| **Pause / Resume** | holds or releases pending work while values still arrive |
|
||||
| **Step** | releases one held item while paused |
|
||||
| **Flow settings** | opens the flow panel |
|
||||
| **Publish** | puts the saved draft live |
|
||||
| **Discard** | removes unpublished changes |
|
||||
| **Issues** | lists problems by node |
|
||||
| **Logs** | shows filterable node output |
|
||||
|
||||
Pause and step together are how you test a flow before it moves anything
|
||||
physical: pause it, inject a value, step it through, watch the values appear on
|
||||
the edges, then publish.
|
||||
**Run** uses the current draft when one exists without publishing it. It
|
||||
executes real node code, including device and network actions. To try a single
|
||||
value in a live flow, publish it from a flow input or edge inspector.
|
||||
|
||||
Pause and step are useful for inspecting held work. They do not make a draft
|
||||
run a simulation.
|
||||
|
||||
## Shortcuts
|
||||
|
||||
| Chord | Action |
|
||||
Use ⌘ on macOS and Ctrl elsewhere.
|
||||
|
||||
| Shortcut | Action |
|
||||
|---|---|
|
||||
| ⌘P / Ctrl-P | command palette |
|
||||
| ⌘S / Ctrl-S | publish the flow — or, with focus in the code editor, apply the code |
|
||||
| ⌘Z / ⌘⇧Z | undo / redo (the flow; the code editor has its own) |
|
||||
| ⌘C / ⌘V | copy and paste nodes, including between flows |
|
||||
| ⌘S / Ctrl-S | publish the flow; inside the code editor, save code |
|
||||
| ⌘Z / Ctrl-Z | undo a graph edit |
|
||||
| ⌘⇧Z / Ctrl-Shift-Z | redo a graph edit |
|
||||
| ⌘C / Ctrl-C | copy selected nodes |
|
||||
| ⌘V / Ctrl-V | paste nodes, including into another flow |
|
||||
|
||||
## Drafts and publishing
|
||||
Text fields and the code editor keep their own undo and clipboard behavior.
|
||||
|
||||
Saving writes a draft; the engine keeps running what was published. Publishing
|
||||
promotes it and reloads.
|
||||
## Drafts, conflicts and issues
|
||||
|
||||
If someone else saved the same flow while you were working, publishing answers
|
||||
with a conflict rather than overwriting them. The Flows list offers **Publish
|
||||
all changes** when several flows are sitting on drafts.
|
||||
Edits are saved as drafts. The engine keeps running the published version until
|
||||
you publish; shared code follows the exception above. **Discard** returns to
|
||||
the published version. Delete a never-published flow to remove it.
|
||||
|
||||
## Issues
|
||||
If another editor saved the same flow, a stale save or publish returns a
|
||||
conflict. Reload and review their changes before retrying.
|
||||
|
||||
The canvas validates as you edit and marks the node each issue belongs to:
|
||||
|
||||
- a port that needs a message nothing in reach provides
|
||||
- a message that exists but has never held a value
|
||||
- a dependency cycle
|
||||
- a node reading a message it also writes, with nothing to start it from
|
||||
- code that did not load
|
||||
- a webhook with no shared secret (advisory — it does not stop the flow)
|
||||
- a node that loaded but reports itself down, such as a connector that cannot
|
||||
reach its device
|
||||
|
||||
A flow with any of these except the last two does not run, and the health
|
||||
summary on Home counts it. The last one clears on its own once the node reports
|
||||
itself well again.
|
||||
The canvas reports unconnected inputs, missing starting values, cycles and
|
||||
code-load errors. These prevent the affected flow from running. Unauthenticated
|
||||
webhooks are advisory; runtime node-health problems clear when the node
|
||||
recovers. See [Flow issues](../concepts/flows.md#when-a-flow-needs-attention).
|
||||
|
||||
## See also
|
||||
|
||||
- [Flows, nodes and messages](../concepts/flows.md) — the model behind the canvas
|
||||
- [Node types](../reference/node-types.md) — everything the palette offers
|
||||
- [Writing node code](../code/nodes.md) — what goes in the editor
|
||||
- [Flows, nodes and messages](../concepts/flows.md)
|
||||
- [Node types](../reference/node-types.md)
|
||||
- [Writing node code](../code/nodes.md)
|
||||
|
||||
+56
-97
@@ -1,128 +1,87 @@
|
||||
# The dashboard app
|
||||
|
||||
The web interface is a single-page app served at `app.${DOMAIN}`, or, for an
|
||||
instance reached through a portal, at `${DOMAIN}/i/{instance-id}`.
|
||||
Either way it is the same application, and it is a client of the same REST API
|
||||
you can script against.
|
||||
Open your instance's app address, or select it at [fluksio.com](https://fluksio.com)
|
||||
after enrollment. For the local Docker setup, use
|
||||
[app.localhost](http://app.localhost) and the admin login printed by
|
||||
`make init`. See [Getting started](../getting-started/index.md) for setup.
|
||||
|
||||
Sign in with the account the instance was created with. On a fresh
|
||||
instance that account was printed once, on the first start.
|
||||
## Finding your way around
|
||||
|
||||
## The shell
|
||||
The sidebar opens as a sheet on phones.
|
||||
|
||||
A floating sidebar on the left, the selected screen filling the rest. On a
|
||||
phone the sidebar collapses to a sheet.
|
||||
|
||||
| Entry | What lives there |
|
||||
| Entry | Use it to |
|
||||
|---|---|
|
||||
| **Home** | the brain graph, health, and everything that recently happened |
|
||||
| **Flows** | the list of flows, and the canvas for each |
|
||||
| **Dashboards** | the widget canvases, and the panels that display them |
|
||||
| **Runs** | starting a run with values you choose, and the log of what ran |
|
||||
| **Workers** | the machines that nodes can be placed on |
|
||||
| **Secrets** | credentials your nodes reference without holding |
|
||||
| **Modules** | the Python packages your node code may import |
|
||||
| **Alerts** | where failures get sent |
|
||||
| **Admin** | users (superusers only) |
|
||||
| **Search** | anything in this instance, by name |
|
||||
| **Settings** | your account, appearance, remote access, and the assistant |
|
||||
| **Home** | see the brain graph, health and recent activity |
|
||||
| **Flows** | create flows and open their editors |
|
||||
| **Dashboards** | build widget dashboards and pair wall panels |
|
||||
| **Runs** | start batch runs and review their results |
|
||||
| **Workers** | manage machines that execute nodes |
|
||||
| **Secrets** | store credentials; superusers only |
|
||||
| **Modules** | see or install packages used by node code |
|
||||
| **Alerts** | choose where failures are reported |
|
||||
| **Admin** | manage users and OAuth clients; superusers only |
|
||||
| **Search** | find items by name |
|
||||
| **Settings** | change your account, appearance and remote access |
|
||||
|
||||
**Assistant** appears when a provider is configured. **Documentation** opens
|
||||
the docs in another tab.
|
||||
|
||||
### Search
|
||||
|
||||
**Search** at the foot of the sidebar, or ⌘K / Ctrl-K from anywhere, opens a
|
||||
panel that finds things by name as you type: flows with the nodes inside them
|
||||
and the inputs and outputs they declare, dashboards and the widgets on them,
|
||||
panels, secrets, modules, workers and alert channels. Picking a node opens its
|
||||
flow with that node in focus, an input or output opens its flow's settings,
|
||||
where it is declared, and a widget opens its dashboard.
|
||||
Use **Search**, or ⌘K / Ctrl-K, to find flows, nodes, flow inputs and outputs,
|
||||
dashboards, widgets, panels, secrets, modules, workers and alert channels.
|
||||
Selecting a result opens its editor or settings.
|
||||
|
||||
It searches this instance. Reached through a portal, other instances
|
||||
are behind **All instances** at the top of the sidebar.
|
||||
Search covers the current instance. When connected through a portal,
|
||||
**All instances** returns to the instance list.
|
||||
|
||||
## Home
|
||||
|
||||
The one screen you leave open. Four things share it.
|
||||
|
||||
### The brain graph
|
||||
|
||||
Every flow drawn as a neuron, wired to the flows it exchanges messages with.
|
||||
This is the brand mark made live, and it is also the fastest read on the
|
||||
instance: a neuron pulses when its flow is running work, and its ring turns
|
||||
terracotta when the flow cannot run as written. A neuron with a problem keeps
|
||||
its label showing so you can see which one it is without hovering.
|
||||
The graph combines nodes from published flows. Nodes talking to the same
|
||||
broker topic, URL or other external target share a neuron; computation nodes
|
||||
appear individually. Messages connect them, and activity makes them pulse.
|
||||
|
||||
Each flow also has a switch beside it in the list, which starts and stops it.
|
||||
|
||||
A [`wren` node](../reference/node-types.md#wren) anywhere adds a chat button
|
||||
at the graph's top right, in the header on a phone, which opens the chat on
|
||||
its [Control tab](../code/agents.md#control).
|
||||
A problem keeps the affected neuron's label visible. A
|
||||
[`wren` node](../reference/node-types.md#wren) also adds a chat button for its
|
||||
[Control tab](../code/agents.md#control).
|
||||
|
||||
### Flows and dashboards
|
||||
|
||||
Two columns under the graph, exactly as tall as each other, most recently
|
||||
worked on first. The flows column is the list with the switches; the dashboards
|
||||
column is a mosaic, each tile a schematic of that dashboard's layout: blocks
|
||||
where its widgets sit, shaded by what kind of widget each one is. Neither
|
||||
column grows past about six rows: past that it scrolls in place rather than
|
||||
pushing the health block down the page.
|
||||
|
||||
The tiles are a footprint, not a live view. They show you which dashboard is
|
||||
which at a glance; the readings are on the dashboard itself.
|
||||
The flow list includes switches to start and stop flows. Dashboard tiles show
|
||||
a small sketch of each layout; open one for its live readings.
|
||||
|
||||
### Health
|
||||
|
||||
Always answers, degraded or not. The tiles cover:
|
||||
|
||||
- **Flows** — total, running, paused, quarantined, and how many cannot run
|
||||
because their graph does not validate
|
||||
- **Nodes** — how many failed to load
|
||||
- **Runs running** — batch runs in flight right now, and how many are
|
||||
waiting. Only on an instance that has run something
|
||||
- **Queue** — depth, and how old the oldest pending item is
|
||||
- **Loop lag** — whether the engine's event loop is keeping up
|
||||
|
||||
`status: degraded` comes with a list of named problems, in words. "3 flow(s)
|
||||
cannot run: house, pv, hallway" is more useful than a red dot, so that is what
|
||||
it says.
|
||||
Health shows flow status, node load errors, queue depth and event-loop lag.
|
||||
After you have used batch runs, it also shows running and queued runs.
|
||||
A degraded instance lists the problems that need attention.
|
||||
|
||||
### Activity
|
||||
|
||||
Charts of executions and failures over the selected range (1h / 6h / 24h / 7d),
|
||||
with the recent runs, recent failures, dead-lettered work and the audit trail
|
||||
underneath.
|
||||
Choose 1h, 6h, 24h or 7d to review executions and failures, recent runs,
|
||||
dead-lettered work and the audit trail. Hover a chart to filter the lists to
|
||||
that moment; click to hold the selection while you read.
|
||||
|
||||
The charts are scrubbable: hover a moment and the lists below filter to it,
|
||||
click to hold it while you read. That turns "something went wrong around two
|
||||
o'clock" into the actual rows.
|
||||
## Managing flows and dashboards
|
||||
|
||||
## Flows
|
||||
The lists show titles, unpublished changes and status. Use **New flow** or
|
||||
**New dashboard** to create one. The Flows list offers **Publish all changes**
|
||||
when drafts are waiting.
|
||||
|
||||
The list shows each flow's title, node count, whether it has unpublished
|
||||
changes, and whether it is enabled, paused or quarantined. The toolbar searches,
|
||||
creates, and offers **Publish all changes** when several flows have drafts.
|
||||
|
||||
Opening one takes you to [the flow editor](flow-editor.md).
|
||||
|
||||
### Deleting several at once
|
||||
|
||||
Press and hold a card, or ctrl-click it, to pick it, then tap the rest. While
|
||||
anything is picked, **New flow** in the toolbar becomes a trash button, and it
|
||||
asks once before deleting the lot. Unpicking the last one puts the list back;
|
||||
so does Escape. The Dashboards screen works the same way.
|
||||
|
||||
## Everything else
|
||||
|
||||
- [The flow editor](flow-editor.md) — the canvas, the code editor, running and
|
||||
testing
|
||||
- [Dashboards and panels](dashboards.md) — widgets, bindings, and hanging a
|
||||
screen on a wall
|
||||
- [Secrets, modules and alerts](operations.md) — the three screens that keep an
|
||||
instance running
|
||||
- [Accounts and the portal](portal.md) — reaching an instance from outside
|
||||
its network
|
||||
To delete several items, hold a card or Ctrl-click it, then select the rest.
|
||||
The create button becomes a trash button. Deletion asks for confirmation;
|
||||
Escape clears the selection.
|
||||
|
||||
## Appearance
|
||||
|
||||
Light and dark follow your system by default; **Settings → Appearance**
|
||||
overrides it. Both themes are first-class, and the wall-panel view is designed
|
||||
to be legible in dark from across a room.
|
||||
Light and dark follow your system by default. Change this under
|
||||
**Settings → Appearance**. Dashboards also have their own theme settings.
|
||||
|
||||
## Next stops
|
||||
|
||||
- [The flow editor](flow-editor.md)
|
||||
- [Dashboards and panels](dashboards.md)
|
||||
- [Secrets, modules and alerts](operations.md)
|
||||
- [Accounts and the portal](portal.md)
|
||||
|
||||
@@ -1,139 +1,106 @@
|
||||
# Secrets, modules and alerts
|
||||
|
||||
Three screens that have nothing to do with each other except that an
|
||||
instance you actually depend on needs all of them.
|
||||
Store credentials, manage node dependencies and choose where failures go.
|
||||
These screens help keep an instance humming.
|
||||
|
||||
## Secrets
|
||||
|
||||
A node never holds a credential. A parameter written as
|
||||
Open **Secrets** as a superuser, then add a name and value. The list shows
|
||||
names only; saved values are not displayed again.
|
||||
|
||||
Reference a secret from a node setting:
|
||||
|
||||
```json
|
||||
{"$secret": "influx-token"}
|
||||
```
|
||||
|
||||
is replaced with the stored value when the node is built, and the editor
|
||||
renders any field a node type marks as a credential as a picker over what you
|
||||
have stored.
|
||||
Fluksio resolves it when building the node. Credential fields in the editor
|
||||
offer a secret picker. Missing names produce an error on the affected node.
|
||||
|
||||
**Secrets** is where the values live. Add a name and a value; the value is
|
||||
never shown again, and the list only ever shows names.
|
||||
Secrets are encrypted with a key derived from `SECRET_KEY` and stored outside
|
||||
the flow repository. Using references keeps credentials out of saved flows.
|
||||
|
||||
They are encrypted at rest with a key derived from the instance's
|
||||
`SECRET_KEY`, and kept **outside** the flow repository. That matters because
|
||||
flows are a git repository you may well push somewhere: what gets committed and
|
||||
shared never contains a password.
|
||||
!!! warning "Keep your encryption key"
|
||||
|
||||
A name that no longer resolves is a clear error on the node that wanted it
|
||||
("No secret named 'x'; add it under Secrets") rather than a mysterious
|
||||
authentication failure.
|
||||
|
||||
!!! warning "Rotating `SECRET_KEY`"
|
||||
|
||||
The secrets store is encrypted with a key derived from it. Change
|
||||
`SECRET_KEY` and the store no longer decrypts. Re-enter the secrets, or
|
||||
plan the rotation properly.
|
||||
Changing `SECRET_KEY` makes existing secrets unreadable. Keep the old key
|
||||
for re-encryption, or re-enter the secrets. Source installations include
|
||||
`backend/scripts/rotate_secret_key.py` for rotating the secrets store.
|
||||
|
||||
## Modules
|
||||
|
||||
Node code runs in a virtual environment of its own, on the instance's data
|
||||
volume, separate from the packages Fluksio itself runs on. A pin of yours can
|
||||
never shadow one of ours, and vice versa.
|
||||
**Modules** shows the Python environment used by node code, its installed
|
||||
packages and Python version. There are two setups:
|
||||
|
||||
**Modules** is a pip manifest and an **Apply** button, which only a superuser
|
||||
sees: a package runs its own code on install. It is installed with
|
||||
`uv pip sync`, so a line you delete is uninstalled rather than left behind.
|
||||
The manifest is versioned alongside your flows, so what a deployment installed
|
||||
is recorded with what uses it.
|
||||
- **Managed environment:** Docker uses a separate node environment. A
|
||||
superuser edits **Requirements** and presses **Apply**. Packages are synced
|
||||
with `uv pip sync`; deleting a requirement removes it. The manifest is
|
||||
versioned with the flows, and successful installs refresh the workers.
|
||||
- **Adopted environment:** a fresh pip installation inside your own virtual
|
||||
environment can use that environment for nodes too. Modules is read-only.
|
||||
Install dependencies there with pip or uv, then sync or restart Fluksio.
|
||||
|
||||
The page shows what is currently installed, the interpreter's Python version,
|
||||
and whether the manifest and the environment agree. A failed resolve comes back
|
||||
as `uv`'s own output, in the pane, because that is the only thing anyone can
|
||||
act on.
|
||||
Set `NODE_VENV=managed` to request a separate environment. Installation
|
||||
failures show the package manager's output on the page.
|
||||
|
||||
An install takes effect immediately: the worker processes pick up the new
|
||||
interpreter state without the engine restarting.
|
||||
|
||||
!!! note "Big scientific stacks"
|
||||
|
||||
Installing torch into this venv works and is often the simplest thing. The
|
||||
alternative, when you already have an environment you would rather not
|
||||
duplicate, is to attach it as a worker: `fluksio-worker --python
|
||||
/path/to/venv/bin/python`. See [Remote workers](../code/workers.md).
|
||||
For an existing scientific environment on another machine, attach a worker
|
||||
with `fluksio-worker --python /path/to/venv/bin/python`. See
|
||||
[Remote workers](../code/workers.md).
|
||||
|
||||
## Alerts
|
||||
|
||||
Everything that goes wrong already travels the engine's event bus. Alerts is
|
||||
where you say who hears about it.
|
||||
Create a channel, choose events in a rule, and use **Test** to check delivery.
|
||||
Enable alerts for the rules to send.
|
||||
|
||||
### Channels
|
||||
|
||||
| Kind | Settings |
|
||||
| Kind | Destination |
|
||||
|---|---|
|
||||
| **ntfy** | server, topic, and a token for a protected topic |
|
||||
| **SMTP** | an address to send to (the instance's mail settings do the rest) |
|
||||
| **Webhook** | a URL to POST to |
|
||||
| **Dashboard** | a message name a notification widget reads |
|
||||
| **ntfy** | server and topic; optional token |
|
||||
| **SMTP** | recipient address, using the instance's mail settings |
|
||||
| **Webhook** | URL that receives a POST |
|
||||
| **Dashboard** | declared message read by a Notification widget |
|
||||
| **webpush** | browsers subscribed from the Alerts page |
|
||||
|
||||
Any channel setting may hold a `{"$secret": "name"}` reference instead of a
|
||||
literal, so a webhook URL with a token in it does not sit in the config.
|
||||
Channel settings may use `{"$secret": "name"}` references. Browser push also
|
||||
needs notification permission and a secure browser context.
|
||||
|
||||
### Rules
|
||||
|
||||
A rule is a set of events and the channels they go to. **A rule with no events
|
||||
ticked covers all of them**, including ones added in later versions, which is
|
||||
usually what you want for the first rule you write.
|
||||
A rule selects events and destination channels. Leaving all events unchecked
|
||||
matches every supported alert event, including future additions.
|
||||
|
||||
| Event | Fires when |
|
||||
| Event | Trigger |
|
||||
|---|---|
|
||||
| `node_error` | a node failed |
|
||||
| `node_health` | a connection dropped |
|
||||
| `flow_quarantined` | a flow crashed often enough that the engine stopped restarting it |
|
||||
| `task_crashed` | a background task crashed |
|
||||
| `engine_degraded` | the engine is struggling |
|
||||
| `cascade_dropped` | work was given up on |
|
||||
| `queue_unavailable` | the work queue is unreachable |
|
||||
| `node_error` | a node fails |
|
||||
| `node_health` | a connection goes down |
|
||||
| `flow_quarantined` | repeated crashes stop a flow's background work |
|
||||
| `task_crashed` | a background task crashes |
|
||||
| `engine_degraded` | engine health degrades |
|
||||
| `cascade_dropped` | the engine drops work |
|
||||
| `queue_unavailable` | the work queue becomes unreachable |
|
||||
|
||||
**Test** sends one message through a channel so you find out it is misconfigured
|
||||
now rather than at three in the morning.
|
||||
Repeated faults are deduplicated with a 15-minute default cooldown.
|
||||
Flapping connections are suppressed, and the engine caps alert dispatches at
|
||||
ten per hour. A quiet channel may be cooling down; **Test** helps distinguish
|
||||
that from a broken configuration.
|
||||
|
||||
### What it mostly does is not send
|
||||
## Admin: users and clients
|
||||
|
||||
This is the part worth knowing before you decide it is broken:
|
||||
Superusers use **Admin** to add, edit and remove accounts, and manage OAuth
|
||||
clients for [agents](../code/agents.md).
|
||||
|
||||
- the same fault repeating is **one** alert, with a cooldown (15 minutes by
|
||||
default per rule)
|
||||
- a connection flapping is one story, not one alert per transition
|
||||
- however bad it gets, **ten alerts an hour** is the ceiling
|
||||
|
||||
An engine that is genuinely on fire produces a handful of messages, not
|
||||
thirty-six thousand.
|
||||
|
||||
## Admin: users
|
||||
|
||||
Superusers get an **Admin** screen: add, edit and remove accounts, and see who
|
||||
is a superuser.
|
||||
|
||||
Accounts that arrived through a portal are badged *Portal*. They have no
|
||||
password (they sign in at the portal, not here) and they are never
|
||||
superusers, so they read node code but cannot change it. Deleting one cuts their access immediately and independently of the
|
||||
portal, so it works even if the portal cannot be reached. See
|
||||
[Accounts and the portal](portal.md).
|
||||
|
||||
The same screen manages OAuth clients, which is how [agents](../code/agents.md)
|
||||
are registered.
|
||||
Accounts admitted from a portal are marked *Portal*. They sign in through the
|
||||
portal, have no local password and are not superusers. To block local access,
|
||||
edit the account and clear **Is active?**. See [Accounts and the portal](portal.md).
|
||||
|
||||
## Settings
|
||||
|
||||
Your own account: name, email, password, and appearance (light, dark, or
|
||||
follow the system).
|
||||
Change your name, email, password and appearance under **Settings**.
|
||||
|
||||
**Remote access** is where an instance is paired with a portal, remote
|
||||
users are admitted, and the link is cut again. That has [its own
|
||||
page](portal.md).
|
||||
**Remote access** manages enrollment and admitted users; see
|
||||
[Accounts and the portal](portal.md).
|
||||
|
||||
**Assistant** turns on the chat that builds and edits flows: pick a provider,
|
||||
paste a key, fetch the model list and choose one. The key is stored encrypted
|
||||
on the instance and never shown again. One provider serves everyone who signs
|
||||
in here, and until one is set nothing in the interface mentions the feature.
|
||||
How the chat works, and what it may do, is on the [agents
|
||||
page](../code/agents.md#the-assistant).
|
||||
A superuser can configure **Assistant**: choose a provider, store its key,
|
||||
fetch models and select one. The encrypted key is shared for assistant access
|
||||
on this instance. The chat appears after configuration; see
|
||||
[The assistant](../code/agents.md#the-assistant).
|
||||
|
||||
+59
-115
@@ -1,142 +1,86 @@
|
||||
# Accounts and the portal
|
||||
|
||||
Fluksio is self-hosted by default. An instance runs offline, keeps its data
|
||||
on its own disk, and never contacts anything unless you tell it to.
|
||||
The optional portal lets you open an instance from [fluksio.com](https://fluksio.com),
|
||||
including a pip installation with no local dashboard. The instance connects
|
||||
outward over a WebSocket, so you do not need to open an inbound port.
|
||||
|
||||
The **portal** is optional, and it exists to solve two specific problems:
|
||||
Flows still run on your instance. The portal serves the interface and carries
|
||||
API requests, responses and live updates through the connection.
|
||||
|
||||
1. **Your machine has no inbound route.** A homelab behind CGNAT, a cluster
|
||||
node with no open ports, a laptop. Opening one is work, and often not
|
||||
allowed.
|
||||
2. **You want a browser on it anyway.** A `pip install fluksio` instance
|
||||
has no web server for the dashboard at all.
|
||||
## Connect an instance
|
||||
|
||||
An enrolled instance dials *out* to the portal and holds one websocket
|
||||
open. The portal serves the dashboard from its own side, and only the API calls
|
||||
travel down the tunnel, so the interface loads at portal speed and your
|
||||
machine stays unreachable from the internet.
|
||||
1. Sign up or sign in at **fluksio.com**.
|
||||
2. Open **Instances → Add instance** and copy the code.
|
||||
3. On the machine running Fluksio, open another terminal in the same directory
|
||||
as `fluksio serve` and run:
|
||||
|
||||
## Enrolling
|
||||
```sh
|
||||
fluksio enroll <code>
|
||||
```
|
||||
|
||||
Two halves: whoever performs the second step decides what the instance's owner
|
||||
gets.
|
||||
The CLI uses `https://hub.fluksio.com` by default. If you started the engine
|
||||
with `--data-dir` or `--global`, use the same option when enrolling. A running
|
||||
engine picks up the connection automatically; otherwise start it with
|
||||
`fluksio serve`.
|
||||
|
||||
**On the portal** (`hub.${DOMAIN}`, or [fluksio.com](https://fluksio.com) for
|
||||
the hosted one): **Instances → Add instance**, give it a name, and copy
|
||||
the code.
|
||||
From a local dashboard, you can instead use **Settings → Remote access**:
|
||||
leave the default portal URL, paste the code, and press **Connect**.
|
||||
Enrollment requires a superuser account.
|
||||
|
||||
**On the instance**, either from the dashboard:
|
||||
|
||||
> **Settings → Remote access**, enter the portal URL and the code, press
|
||||
> **Connect**.
|
||||
|
||||
or from the command line, which is the path for an instance with no web
|
||||
interface of its own:
|
||||
The portal owner's sessions use the local account that enrolled the instance.
|
||||
Check **Acting as** under Remote access. With several local superusers, the
|
||||
CLI can choose one explicitly:
|
||||
|
||||
```sh
|
||||
fluksio enroll ABCD-1234 --portal https://hub.fluksio.com
|
||||
fluksio enroll <code> --as someone@example.com
|
||||
```
|
||||
|
||||
The code is single-use and expires in fifteen minutes. A fresh code on an
|
||||
instance that is already paired replaces the pairing, so moving an instance to
|
||||
another portal is one command rather than a disconnect and a reconnect.
|
||||
A new valid code replaces an existing connection. An expired or invalid code
|
||||
leaves the current connection intact. Once connected, open the instance from
|
||||
the portal's **Instances** list.
|
||||
|
||||
A portal session then arrives as *that local account*, which the settings screen
|
||||
states plainly. Use `--as someone@example.com` to enrol as a specific local
|
||||
account when the instance has several superusers.
|
||||
## Let someone else in
|
||||
|
||||
Once enrolled, the instance appears under **Instances** with its
|
||||
status, when it was last seen and its version. **Open** takes you to its
|
||||
dashboard at `${DOMAIN}/i/{instance-id}`.
|
||||
Each person gets an ordinary local account, with no superuser rights. You can
|
||||
admit them in either of these ways:
|
||||
|
||||
**Upgrade a self-hosted portal before its instances.** An instance dials the
|
||||
tunnel with its credential in an `Authorization` header, where it stays out of
|
||||
access logs, and a portal from before that reads it only from the URL. The
|
||||
portal still takes the old `?token=` for one release, so older instances keep
|
||||
attaching meanwhile.
|
||||
- Share an invitation link from the portal's **Instances** list.
|
||||
- Ask them for a code from **Instances → Join an instance**. Enter it on your
|
||||
local instance under **Settings → Remote access → Add remote user**.
|
||||
|
||||
## What the portal can and cannot do
|
||||
Their account appears under **Admin → Users** with a *Portal* badge. They
|
||||
sign in through the portal rather than with a local password. Invitations are
|
||||
time-limited; generate a fresh one if it expires.
|
||||
|
||||
The portal holds one credential for your instance and proxies requests down
|
||||
the tunnel. What those requests may do is decided **on the instance**, by
|
||||
the same checks a local session passes.
|
||||
## Manage or end remote access
|
||||
|
||||
The trust anchor is a signing keypair on the portal. Every instance pins
|
||||
its public half at enrolment and rejects anything else, which is what stops a
|
||||
hijacked DNS entry or a mis-issued certificate from impersonating the portal.
|
||||
**Settings → Remote access** shows the connection status and the latest
|
||||
connection error. The instance needs outbound access to the portal, and any
|
||||
proxy on that route must support WebSockets.
|
||||
|
||||
## Letting someone else in
|
||||
- To remove a person's access, remove them from the portal's sharing dialog
|
||||
or edit their account under **Admin → Users** and clear **Is active?**.
|
||||
- To end all portal access from the instance, press **Disconnect**. This stops
|
||||
the connection and invalidates portal credentials locally, without requiring
|
||||
the portal to be online.
|
||||
- To replace the pairing, use **Connect with a new code**. Remote users and
|
||||
panels may need to pair again.
|
||||
|
||||
Anyone else on the portal reaches your instance only if you let them, and they
|
||||
arrive as a local user of their own rather than as you. There are two ways,
|
||||
and the difference is who starts.
|
||||
Local sign-in and flows continue after disconnecting. Portal requests follow
|
||||
the instance's normal account permissions, and the instance verifies portal
|
||||
tokens against the public signing key pinned during enrollment.
|
||||
|
||||
### Share a link
|
||||
## Custom portals
|
||||
|
||||
On the portal, the share icon on your instance's row gives a QR code and a URL.
|
||||
Whoever opens it, signed in to Fluksio on that device, joins. It works once and
|
||||
expires in fifteen minutes; **New link** replaces it, which is also how a link
|
||||
you would rather not have sent is cancelled.
|
||||
The CLI accepts another compatible portal with `--portal https://<portal-host>`;
|
||||
the dashboard has the same URL field. HTTPS is required except for a portal on
|
||||
loopback.
|
||||
|
||||
Nothing has to be entered on the instance. It learns of the new member when
|
||||
they first arrive and asks the portal who they are, then gives them their local
|
||||
account.
|
||||
|
||||
### Take a code
|
||||
|
||||
For a machine whose owner would rather admit people at the machine itself.
|
||||
|
||||
1. **They**: **Instances → Join an instance**, and copy the code. It is
|
||||
bound to their portal account and expires in fifteen minutes.
|
||||
2. **You**, on the instance: **Settings → Remote access → Add remote
|
||||
user**, and enter the code.
|
||||
|
||||
Either way they now see the instance under **Instances**, marked *Shared by*,
|
||||
with **Open** and nothing else. Renaming, re-keying and removing stay with you.
|
||||
|
||||
The instance redeems the code against the portal using its own credential.
|
||||
A portal session cannot do this, which is what stops somebody you let in from
|
||||
letting others in.
|
||||
|
||||
On the instance they appear under **Admin → Users**, badged *Portal*, never
|
||||
a superuser and with no password.
|
||||
|
||||
## Cutting it off
|
||||
|
||||
| From | Action | Effect |
|
||||
|---|---|---|
|
||||
| The portal | share dialog → **Remove** | that member stops reaching it from the portal; their user on the machine stays until it is deleted there |
|
||||
| The portal | **Leave**, on a row shared with you | gives up your own access to somebody else's instance |
|
||||
| The portal | **New code** | rotates the credential and drops the current link |
|
||||
| The portal | **Remove** | deletes the registration and cuts the connection |
|
||||
| The instance | **Disconnect** | unilateral and immediate — the portal's tokens stop verifying here whatever the portal still has on file |
|
||||
| The instance | delete a user under **Admin → Users** | that one person, immediately, independent of the portal |
|
||||
|
||||
**New code** also cuts every credential the portal minted for this instance,
|
||||
including [wall panels paired through it](dashboards.md#a-screen-somewhere-you-cannot-reach).
|
||||
|
||||
The instance's own **Disconnect** is the one to reach for if you are ever
|
||||
unsure: it does not need the portal's cooperation.
|
||||
|
||||
## Running your own portal
|
||||
|
||||
The portal is the `index` stack's `hub` service: accounts, the registry of
|
||||
connected instances, and the websocket each one dials in on. Two things
|
||||
about it are load-bearing:
|
||||
|
||||
- **It runs a single process.** It keeps its attached instances in the
|
||||
memory of the process holding their sockets, so a second worker would answer
|
||||
for links it does not hold. Scaling out needs a routing layer first.
|
||||
- **Back up the signing keypair with the database.** Replacing it forces every
|
||||
instance to be enrolled again.
|
||||
|
||||
Websocket upgrades must be enabled on the `hub` hostname in whatever proxy
|
||||
fronts it. Without them every instance sits in a reconnect loop and the
|
||||
portal shows them all offline.
|
||||
This public repository contains the instance and its connector. The portal
|
||||
service is not included, so the Docker stack does not start one.
|
||||
|
||||
## See also
|
||||
|
||||
- [The `fluksio` command](../code/cli.md) — `fluksio enroll`, and what it writes
|
||||
- [Dashboards and panels](dashboards.md) — pairing a wall screen through the portal
|
||||
- [Getting started: data science](../getting-started/data-science.md) — the
|
||||
shortest path from a pip install to a browser
|
||||
- [The fluksio command](../code/cli.md): data directories and enrollment options.
|
||||
- [Dashboards and panels](dashboards.md): pair a wall screen through the portal.
|
||||
- [Getting started](../getting-started/index.md): install and open your first instance.
|
||||
|
||||
Reference in New Issue
Block a user