Dashboards
A dashboard is a page of cards somebody built. Gauges, trends, lists of entities, a button that starts the pump, a floor plan with the temperatures sitting where the zones are. Build as many as the plant needs, give each to the roles that need it or to the one screen it lives on, and the old Board is just one of them.
It is in HotLoop IoT, Edge and Gateway. The Edge Relay has no UI and no dashboards. The Edge HMI is built out of this page and nothing else, which is the reason the rest of it is the way it is.
Why it is built this way
Section titled “Why it is built this way”A card commands nothing. The first design question for any page of buttons
is whether the page is a second way to run the plant. It isn’t. A card names a
tag, an entity, a script or a recipe, and the browser does what every other
screen does with them. A write button posts to POST /api/write. An entity’s
controls call POST /api/services. A script button opens the Scripts screen’s
run prompt and a recipe card opens the Recipes screen’s apply drawer. Every one
of those has its own permission and meets the write gate, so a dashboard can’t
be a way round either. What dashboards.write guards is who decides what the
buttons are, which is an engineer’s call and is admin-only for the same reason
writing a script is.
The consequence: a dashboard saved by an admin does nothing until an operator presses something, and what the press does is exactly what pressing it on any other screen does, down to the audit row.
A layout is a flow, not coordinates. Cards go in reading order. Each says how wide it is out of twelve columns and how tall in rows, and knows nothing about where it lands. Twelve columns on a panel PC, six on a laptop with another window beside it, two on a tablet, one on a phone. A layout of pixel positions is right on exactly the screen it was drawn on, and the Edge HMI has to be right on whichever screen the machine has.
The consequence: nobody ever builds the phone version. There isn’t one.
Saves name the version. Two people editing one dashboard is a thing that
happens at shift change. A save carries the version it was opened at, and a
stale one is refused with a 409 that says who got there first. Without that
the second person to press Save deletes the first one’s afternoon without
either of them seeing it.
The model
Section titled “The model”{ "id": "line-2", "name": "Line 2", "description": "The extruder, for the floor.", "kind": "cards", "roles": ["operator", "admin"], "homeFor": ["operator"], "screens": ["line-2-panel"], "position": 1, "version": 3, "cards": [ {"id": "temp", "type": "gauge", "title": "Zone 1", "w": 3, "h": 3, "config": {"tag": "press-01/Zone1_Temp"}}, {"id": "go", "type": "button", "w": 3, "h": 1, "config": {"tag": "line-02/Speed", "value": 250, "label": "Run at 250", "confirm": true}} ]}idis lowercase letters, digits, hyphens and underscores, starting with a letter. It is in the address and never changes.wallis the wall board’s.kindiscards, orwallfor the wall board.rolesare the roles offered the dashboard. Empty is every role. Whoever holdsdashboards.writesees every dashboard whatever it says, because they have to be able to fix the one nobody else can open. A role that isn’t offered one gets a404for it by name, so the list of what exists isn’t something a viewer can learn by asking.homeForare the roles it opens for when they come in with no address asking for anything. A role has at most one; the dashboard has to be offered to the roles it is home for.screensare the physical screens it is for. A screen name belongs to one dashboard.positionorders the switcher. New ones go last.cardsare in reading order, up to 60 per dashboard, 2 MB for all of them.versiongoes up by one on every save.
A card is id (unique in its dashboard, made by the page), type, an optional
title, w (1 to 12) and h (1 to 8), and a config that depends on the
type. Leave w and h out and the type’s default is used. A config is checked
strictly: a key the type doesn’t have is an error, and what is stored has every
default filled in. A card that names a tag, entity, script or recipe that
doesn’t exist is refused when the dashboard is saved. A button on a tag that
isn’t marked writable, or a button whose number is outside the tag’s range, is
refused too: the gate would refuse every press, and the person building it
should hear that and not the person pressing it.
The cards
Section titled “The cards”| Type | What it is | Config | Default size |
|---|---|---|---|
status |
Devices up, tags reporting, alarms, historian, uptime: the Board’s strip | none | 12 x 1 |
gauge |
One tag as the Board’s dial, with its range and its alarm limits marked | tag |
3 x 3 |
value |
One tag as a big number, colored by its alarm state | tag, decimals |
3 x 2 |
trend |
Up to six tags over 15 minutes to a week | tags, minutes, labels |
6 x 3 |
entities |
The entities you pick, as their own tiles with their own controls | entities (up to 30) |
6 x 3 |
alarms |
The alarm list, with Acknowledge for whoever may | outstanding, limit, devices, equipment |
6 x 3 |
button |
Writes one value to one tag, after asking | tag, value, label, confirm |
3 x 1 |
script |
Runs a script through the Scripts screen’s prompt | script, label |
3 x 1 |
recipe |
Picks one of up to eight recipes and opens its apply drawer | recipes |
4 x 2 |
markdown |
Text: headings, lists, bold, italic, code, http(s) links | text |
4 x 2 |
camera |
A snapshot that refreshes, or a motion JPEG stream | url, stream, refreshSec |
6 x 4 |
map |
A floor plan with live tags placed on it | image, markers |
6 x 5 |
Things worth knowing about the ones that aren’t obvious:
-
buttonsendsvalueas the type it was saved as. A number is a number,trueis a boolean, a string is text.1and1.0are different writes on a PLC and the gate decides by the JSON type, so the card doesn’t turn one into the other.confirmis on unless it’s switched off, and then it asks “this commands real equipment” with the current value and a reason that goes in the audit trail (dashboard <id> / <card>if left empty). Switching it off is for a panel where holding the button is the point; it is an engineer’s decision and the Settings screen’s “Confirm before writing” overrides it back on. -
alarmslists the whole plant’s alarms unless you point it somewhere.devicesis a list of device ids.equipmentis a list of equipment node ids fromGET /api/equipment, any level (a site, an area, a line, a cell), and a node stands for every device placed at or under it, so an area’s page can show the area’s alarms and nobody else’s. With both, an alarm is listed when it is on either one. Leave them out, or empty, and the card is the whole plant, which is what every card saved before these fields did. The limit counts what is left after the filter, andoutstandingstill drops what is back to normal and acknowledged. A card limited to a place with no alarms says “here” so nobody reads it as a quiet plant, and a card whose equipment has since been deleted says that instead of showing an empty list. Equipment is stored as the node’s id, so renaming or moving a node doesn’t break the card. The dashboard is refused at save if a device or a node isn’t there. In the builder you type the path (Plant 2 / Scrubbers / Kettle 3) and it keeps the id. This card reads the tree, so a role needsequipment.read(operators and viewers have it).{"id": "kettle-alarms", "type": "alarms", "title": "Scrubber alarms", "w": 6, "h": 3,"config": { "outstanding": true, "limit": 10, "equipment": [14], "devices": ["oven-1"] }} -
trenddraws the window you asked for, ending now, and not just the span the readings happened to cover:minutes: 480is an axis of the last eight hours with a tick on each round hour, in the time zone of whoever is looking. A tag that hasn’t moved is a flat line at its last good reading, because that is what a stopped mill or a closed valve looks like, and the historian only keeps a reading when a value moves past its deadband, so there may be one reading in the window, or none. Nothing on the line is made up. What the tag held when the window began is its last good reading before it (GET /api/historysays so withcarry=true), and the line is that value from the start of the window to the first reading inside it. After the last reading it is held on to the last time the tag was heard from, never past now. There is no line before the first reading anybody has, and a tag whose last reading before the window was bad has no carry at all, because what it held is not known. A stretch of bad readings is a gap, the same as it always was. One reading on its own is a line to now, or a dot if there is nowhere to draw it to.The legend names each line by the tag’s own name, the same name the number card shows, without the device in front. Two lines with the same name from different devices keep their full ids so you can tell them apart.
labelsis an optional object from a tag intagsto the text you want instead, 1 to 80 characters, and a label for a tag that isn’t plotted is refused. The hover tooltip always has the full id next to the label.{"id": "mills", "type": "trend", "title": "Mill temperatures", "w": 6, "h": 3,"config": {"tags": ["plant-5-temperatures/Large Sand Mill Temperature", "plant-5-temperatures/Small Sand Mill Temperature"],"minutes": 480,"labels": { "plant-5-temperatures/Large Sand Mill Temperature": "Large mill" }}} -
cameraloads the address in the browser, not on the server. A card’s config is readable by every role that opens the dashboard, so an address with a login or a token in it is refused when the dashboard is saved. Use an address on the plant network that needs none, or a proxy that adds the login. A still is fetched again everyrefreshSecseconds with a_hlquery parameter so the browser doesn’t show the first one forever. -
mapis a picture and markers. The picture is an http(s) address or one uploaded into the card (png, jpeg, webp or gif, up to 500 KB, stored in the card). Markers are tags, placed in percent of the picture’s width and height so they stay where they were put at any size. This is a floor plan, not a street map: a tile map needs a tile server on the internet, and a plant’s panel PC usually can’t reach one. -
markdownis built into DOM nodes and never into HTML, so nothing typed into it can become markup or a script. -
gauge,value,mapshow “not on this gateway any more” if their tag is deleted after the dashboard was saved, instead of a dial that never moves.
The wall board
Section titled “The wall board”The wall board is dashboard wall, kind wall. It is always there, it can’t be
deleted, and it has no cards: its gauges are the tags pinned on the Board
screen, kept in the board’s own setting where they always were. GET and PUT /api/board, the #board address, the pin picker and the drag to arrange are
untouched.
What it gets from being a dashboard is the switcher (it is the first pill on
every dashboard and the Board has the bar too) and the assignments: it can be
offered to some roles, be a role’s home, and be a physical screen’s. A TV on the
wall opens /?screen=wall-tv and gets the Board with the header and tabs gone.
Roles and screens
Section titled “Roles and screens”Per role. roles decides who is offered a dashboard. homeFor decides where
a role lands. An operator whose home is line-2 opens HotLoop on Line 2. With a
URL fragment in the address (#alarms) the page opens where the address says.
Per screen. A screen is a physical display. Open HotLoop at
/?screen=<name> and it shows the dashboard whose screens has that name with
no header, no tabs and bigger touch targets, because nobody walking past a panel
PC has a use for a way out of the page. A name that no dashboard has says so
instead of showing something else. The panel signs in like anyone: give it an
operator or viewer account that stays signed in, and keep the account’s role to
what the panel is for.
This is what the Edge HMI is: a dashboard with screens set, on a machine that
opens that address.
Building
Section titled “Building”Whoever holds dashboards.write (admins) sees Edit on a dashboard and
New dashboard in the bar. While editing:
- cards are inert, so grabbing a button to move it can’t press it;
- drag a card onto another to put it there, or use the arrows on its toolbar, which are what a phone has, because a touchscreen doesn’t start a drag;
- drag the corner to resize: the width snaps to a column of twelve, the height to a row;
- the gear edits the card, the copy button duplicates it, the cross deletes it;
- Settings is the name, who it is offered to, who it opens first for, its physical screens and its place in the switcher.
Nothing is saved until Save. Discard throws the changes away. A 409 on
save means somebody saved first: the page offers to load theirs.
Layout
Section titled “Layout”| Width | Columns | A card of w takes |
|---|---|---|
| Over 1100 px | 12 | w |
| 700 to 1100 px | 6 | half of w, rounded up |
| 480 to 700 px | 2 | one, or both when w is over 4 |
| Under 480 px | 1 | all of it |
Rows are 76 px with a 16 px gap on the wide layouts. On a phone a card is as tall as its content with a floor from its rows, so nothing is cut off. The switcher scrolls sideways inside itself and the page never does.
Permissions
Section titled “Permissions”| Permission | Viewer | Operator | Admin |
|---|---|---|---|
dashboards.read |
yes | yes | yes |
dashboards.write |
no | no | yes |
Reading is every role’s, filtered by roles. Writing is the admin’s. What a card
can do when pressed is the pressing person’s own permissions: a viewer sees a
write button disabled, and the server would refuse it if they got past that.
The API
Section titled “The API”| Method | Route | Permission |
|---|---|---|
GET |
/api/dashboards |
dashboards.read |
POST |
/api/dashboards |
dashboards.write |
GET |
/api/dashboards/{id} |
dashboards.read |
PUT |
/api/dashboards/{id} |
dashboards.write |
DELETE |
/api/dashboards/{id} |
dashboards.write |
The reference with bodies and errors is in API.md. Every
create, save and delete is on the record as a config event on subject
dashboard:<id>, so the Logbook says who changed what a panel shows.
The table is dashboards, added in migration 0023 for PostgreSQL, TimescaleDB
and SQLite. It only adds (MIGRATIONS.md). The wall board’s row is
made by the application when it is first asked for, not by the migration, so a
database restored from a backup taken before dashboards has it again.
What isn’t here
Section titled “What isn’t here”- Pixel-exact layouts. On purpose, above. If a machine’s HMI really needs a control at a fixed spot, that is a screen built for that machine, and it is not what this is.
- Per-card visibility. A dashboard is offered to roles; a card isn’t. Two audiences with different cards are two dashboards.
- A tile map. See the
mapcard. - Holding a button. A button writes once when pressed. A momentary control that writes on press and again on release is a different card and isn’t built.
- Dashboards in the Edge Relay. It has no UI.