Add control logic engine, panel logic dialogs, logic-edit restriction

Introduce server-side control-logic flow graphs (cron/alarm triggers,
Lua blocks) with CRUD endpoints, panel-logic lifecycle triggers and
user-interaction dialog nodes, and a synthetic node-graph editor.

Add an optional logic-editor allowlist (server.logic_editors) gating who
may add/edit panel logic and control logic, surfaced via /api/v1/me and
enforced in the API; hide logic affordances in the UI accordingly.

Update README, example config, and functional/technical specs to cover
all current features (plot panels, panel/control logic, local variables,
access control) and refresh the in-app manual and contextual help.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Martino Ferrari
2026-06-19 07:27:35 +02:00
parent aba394b84d
commit afefba3184
35 changed files with 4633 additions and 467 deletions
+133 -46
View File
@@ -8,13 +8,23 @@ uopi is a web-based HMI (Human-Machine Interface) for monitoring and controlling
## 2. Users and Roles
| Role | Description |
|------|-------------|
| Operator | Uses interfaces in View mode; can interact with controls but cannot edit layouts |
| Engineer | Creates and edits interfaces in Edit mode; manages signal lists |
| Administrator | Manages server configuration, data sources, and saved interfaces |
User identity is established from a header set by a trusted authenticating reverse
proxy (`server.trusted_user_header`), with a configurable `default_user` fallback for
unproxied/LAN deployments. There is no login page inside the application.
In the initial version all users share the same access level. Role-based access control is deferred to a future release.
**Global access levels.** Every user is trusted with full **write** access by default. A
configuration blacklist can downgrade specific users to **read-only** (view only, no
writes) or **no access** (denied). Named **groups** are defined in configuration and
referenced by per-panel sharing.
**Per-panel access.** Panels are owned by their creator and private by default. Owners
share a panel with specific users/groups (read or write) or make it public; the owner's
global level always caps the per-panel permission. Panels are organised into nested
folders whose permissions inherit down the chain. See §9.
**Logic-edit restriction.** Adding/editing panel logic and server-side control logic can
optionally be limited to an allowlist of users/groups (`server.logic_editors`); when
unset, any writer may edit logic.
---
@@ -30,27 +40,27 @@ The default mode when opening the application.
- "New interface" button opens Edit mode with a blank canvas.
- The pane width can be adjusted by dragging the resize handle on its right edge.
**Tabs**
- **HMI tab**: the live widget canvas (described below).
- **Plot tab**: a dedicated live multi-signal plot panel (see §3.3).
- **Info tab**: signal info and metadata display for the last right-clicked signal.
**HMI canvas (center)**
- Renders the selected interface as a live, interactive panel.
- Widgets display real-time data; controls (set-value, buttons) are active.
- Panel logic (if any) runs while the panel is open (see §6).
- No drag, resize, or layout operations are possible in this mode.
- Right-clicking any widget opens a context menu:
- **Signal info** — switches to the Info tab showing DS name, type, unit, range, current value and timestamp.
- **Signal info** — shows DS name, type, unit, range, current value and timestamp.
- **Copy signal name** — copies the signal identifier to the clipboard.
- **Export data to CSV** — downloads buffered data for the signal(s) used by the widget.
- **Plot** — adds the signal(s) to the Plot tab.
> Dedicated multi-signal plotting is provided by **plot panels** — a special interface
> kind whose plots fill the viewport (see §3.3) — rather than a separate live "Plot tab".
**Top toolbar**
- Show/hide interface list pane.
- **⏱ History** button: toggle historical time navigation bar.
- **⚙ Control logic** button: open the server-side control-logic editor (see §7).
Shown only to users permitted to edit logic.
- Zoom control (A / % / A+): adjust the UI scale (see §3.4).
- Edit button: switch to Edit mode for the current interface.
- Connection status indicator.
- Connection status indicator and signed-in user chip.
**Historical time navigation bar** (shown when History is active)
- Date/time range pickers (start and end).
@@ -71,9 +81,17 @@ Activated via the "New interface" button or by clicking Edit in the toolbar.
- Filter/search box to narrow the list.
- Synthetic signals show an edit (✎) button to reopen the wizard.
**Widget canvas (center)**
- Free-form canvas where widgets can be placed at arbitrary pixel positions.
- Background grid with snap-to-grid.
**Center area — Layout / Logic tabs**
- **Layout**: free-form canvas where widgets can be placed at arbitrary pixel positions,
over a background grid with snap-to-grid. (For plot panels this is replaced by the
split-layout editor; see §3.3.)
- **Logic**: a node-graph flow editor for panel behaviour (see §6). Hidden when the user
is not permitted to edit logic.
**Local variables**
- A panel may declare panel-scoped variables (data source `local`) with initial values.
- They are written by set-value widgets, buttons and logic actions, and referenced
anywhere a signal is. Added from the signal tree's *Local* group / the Logic palette.
**Properties pane (right, resizable and collapsible)**
- Appears when one or more widgets are selected.
@@ -88,25 +106,27 @@ Activated via the "New interface" button or by clicking Edit in the toolbar.
- Close (return to View mode).
- Zoom control (A / % / A+).
### 3.3 Plot Tab (Live Multi-Signal Plot)
### 3.3 Plot Panels (Split Layout)
A dedicated panel for live time-series plotting of any signals, independent of the interface layout.
A **plot panel** is a special interface kind dedicated to charts. Rather than free-form
widget boxes, plots **fill the viewport** and the user divides the space between them with
a recursive split layout (tmux/IDE-style tiling). Created via **+ Plot** in the interface
list, opening with a single full-viewport empty plot.
**Adding signals:** right-click any widget in the HMI tab → **Plot**. The signal is added to the Plot tab immediately.
**Editing (Edit mode):**
- Hover a pane and use its split buttons (**⬌** vertical / **⬍** horizontal) to divide it;
a new empty plot fills the freed half. Nesting is unlimited.
- Drag the divider between two panes to resize them.
- Click a pane to select it and configure its plot in the Properties pane (plot sub-type,
Y range, time window, legend, per-signal colour). Drag a signal onto a pane to add it.
- A pane's **✕** removes it; the layout collapses onto its sibling.
**Time window selector:** 10s / 30s / 1m / 5m / 15m / 1h buttons control how much history is displayed.
**View mode:** the saved split layout fills the screen with live, streaming plots; the
per-widget right-click menu (signal info / copy / CSV) and historical time navigation
apply as for any time-series plot.
**Per-signal legend:**
- Color swatch and signal name.
- Statistics table: Last / Min / Max / Mean over the current window.
- ✎ button opens an inline style editor:
- **Color**: color picker.
- **Width**: line width (none, 1, 1.5, 2, 3 px).
- **Line**: solid / dashed / dotted.
- **Markers**: none, S (3 px), M (5 px), L (8 px).
- ✕ button removes the signal from the plot.
**Chart area:** rendered with uPlot; auto-scaled Y axis; time axis tracks the rolling window in real time.
Per-plot configuration reuses the standard Plot widget (§4.4), so all plot sub-types and
options are available within a pane.
### 3.4 UI Zoom
@@ -210,16 +230,18 @@ When the signal's metadata reports enum strings (e.g. EPICS mbbi/mbbo records):
A signal defined by composing one or more input signals through a chain of processing nodes.
**New Synthetic Signal Wizard:**
1. Click "New synthetic signal" in the signal tree.
2. Name the signal and optionally set a unit.
3. Add processing nodes from the node type dropdown and connect them.
4. Configure each node's parameters inline.
5. Click Create — the signal appears in the tree and updates live.
**Two authoring surfaces:**
- **Wizard** — for the common single-input case: name the signal, pick an input and a
processing node, set parameters, Create.
- **Node-graph editor** — a visual editor that wires one or more inputs through a chain of
DSP blocks, for multi-input pipelines. It compiles to the same inputs + pipeline model.
The wizard dialog is resizable (drag its corner) and defaults to 600 px wide to accommodate the Lua editor.
**Visibility scope:** each synthetic signal is scoped as *panel* (visible only to the panel
that created it), *user*, or *global* (shared with everyone).
**Editing an existing synthetic signal:** click the ✎ button next to the signal in the tree to reopen the wizard.
The dialogs are resizable and default to a width that accommodates the Lua editor.
**Editing an existing synthetic signal:** click the ✎ button next to the signal in the tree to reopen the editor.
**Built-in node types:**
@@ -237,15 +259,80 @@ The wizard dialog is resizable (drag its corner) and defaults to 600 px wide to
---
## 6. Interface Persistence
## 6. Panel Logic
- Interfaces are saved to the server in XML format and are available to all connected clients.
- Export/Import allows local file exchange of XML files.
- The XML schema records: widget type, position, size, signal bindings, and all property values.
The **Logic** tab in the panel editor is a node-graph (Node-RED-style) flow editor that
gives a panel interactive behaviour. The flow runs **client-side** while the panel is open
in View mode; it is saved as part of the interface XML. The tab and any logic editing are
gated by the logic-edit restriction (§2).
**Authoring:** drag blocks from the palette (or click to add), connect output ports to
input ports, and edit the selected node in the inspector. Expression fields reference a
signal as `{ds:name}` and a panel-local variable by its bare name, and support arithmetic,
comparisons, boolean logic and common math functions. The editor has its own undo/redo and
copy/paste.
**Node categories:**
| Category | Nodes |
|----------|-------|
| Triggers | Button press, threshold crossing, value change, timer/interval, panel loop, On-open / On-close lifecycle |
| Logic | AND gate, If (then/else), Loop (count or while) |
| Actions | Write to signal/variable, Delay, Log; Accumulate / Export-CSV / Clear for in-memory data arrays |
| Dialogs | Info and Error pop-ups; Set-point prompt (asks the user for a number and writes it) |
**System helpers in expressions:** `{sys:time}` (epoch seconds) and `{sys:dt}` (seconds
since the firing trigger last fired).
---
## 7. Historical Data Navigation
## 7. Control Logic
**Control logic** is server-side automation: flow graphs that run continuously on the
server, independent of any connected client (unlike panel logic, which only runs while a
panel is open). Opened with the **⚙ Control logic** button in the View-mode toolbar and
managed through the REST API.
- Triggers include *cron* schedules and signal *alarm*/threshold conditions.
- A **Lua** block provides custom logic; results are written back to signals.
- Each graph can be enabled/disabled independently; saving reloads the engine live.
- Editing is gated by the logic-edit restriction (§2).
---
## 8. Interface Persistence
- Interfaces are saved to the server in XML format and are available to all connected clients.
- Per-panel access rules and panel-folder placement are stored server-side (sidecar JSON).
- Saved versions are retained; a panel's version history can be listed, tagged and promoted.
- Export/Import allows local file exchange of XML files.
- The XML schema records: interface kind (panel/plot) and split layout, widget type,
position, size, signal bindings, all property values, local variables, and panel logic.
---
## 9. Access Control, Sharing & Folders
**Identity** is resolved per request from the trusted proxy header, falling back to
`default_user`. The `/api/v1/me` endpoint reports the caller's identity, global level,
group memberships and whether they may edit logic; the UI hides affordances accordingly.
**Global levels** (§2): write (default), read-only, or no-access via the config blacklist.
**Per-panel sharing:** the **Share** dialog on a panel grants read or write to specific
users or groups, or marks the panel public. New panels are private to their owner. The
owner's global level caps any per-panel grant.
**Folders:** panels are organised into nested folders; permissions inherit down the folder
chain. Panels can be dragged to reorder or move between folders.
**Logic-editor allowlist:** when `server.logic_editors` is set, only the listed users/
groups may add or change panel logic and control logic; everyone else keeps full access to
the rest of the application.
---
## 10. Historical Data Navigation
When the server has archive access configured:
- The **⏱ History** button in the View mode toolbar reveals a time range bar.
@@ -257,7 +344,7 @@ When the server has archive access configured:
---
## 8. Non-functional Requirements
## 11. Non-functional Requirements
| Requirement | Target |
|-------------|--------|