import { h } from 'preact'; import { useState } from 'preact/hooks'; interface Props { initialSection?: string; onClose: () => void; } // ── SVG Diagrams ───────────────────────────────────────────────────────────── function DiagramViewMode() { return ( ); } function DiagramEditMode() { return ( ); } function DiagramDragDrop() { return ( ); } function DiagramWidgets() { return ( ); } function DiagramHistory() { return ( ); } function DiagramSignalTree() { return ( ); } // ── Section content ─────────────────────────────────────────────────────────── const SECTIONS = [ { id: 'start', label: 'Getting Started' }, { id: 'tips', label: 'Quick Tips' }, { id: 'view', label: 'View Mode' }, { id: 'edit', label: 'Edit Mode' }, { id: 'widgets', label: 'Widgets' }, { id: 'plots', label: 'Plot Panels' }, { id: 'signals', label: 'Signals' }, { id: 'logic', label: 'Panel Logic' }, { id: 'control', label: 'Control Logic' }, { id: 'sharing', label: 'Sharing & Access' }, { id: 'audit', label: 'Audit Log' }, { id: 'history', label: 'Historical Data' }, { id: 'shortcuts', label: 'Keyboard Shortcuts' }, ]; // Quick tips, grouped by the mode they apply to. Each links to the manual // section that covers it in depth. (Previously surfaced via a floating // contextual-help popover; now consolidated here in the manual.) const TIPS: Record<'view' | 'edit', Array<{ text: string; section: string }>> = { view: [ { text: 'Click any interface in the left panel to load it on the canvas.', section: 'view' }, { text: 'Right-click a widget to view signal info or export data as CSV.', section: 'view' }, { text: 'The ● Live button shows live streaming is active. Use the date pickers to load historical data.', section: 'history' }, { text: 'Click Edit (top-right) to open the drag-and-drop panel builder.', section: 'edit' }, { text: 'Use the Share button on a panel to grant access to users or groups.', section: 'sharing' }, { text: 'The ⚙ Control logic button opens server-side automation that runs even with no panel open.', section: 'control' }, { text: 'The 🛡 Audit button (for permitted users) opens the log of system-affecting actions.', section: 'audit' }, { text: 'The status chip turns green and shows your username when the WebSocket is connected.', section: 'view' }, ], edit: [ { text: 'Drag a signal from the left tree onto the canvas to create a widget.', section: 'edit' }, { text: 'Click a widget to select it, then adjust its properties on the right.', section: 'edit' }, { text: 'Ctrl+click to select multiple widgets; then use the align toolbar.', section: 'edit' }, { text: 'Ctrl+Z to undo, Ctrl+Y to redo. Up to 50 steps are remembered.', section: 'shortcuts' }, { text: 'Use "+ Synthetic" in the signal tree to create computed/filtered signals.', section: 'signals' }, { text: 'Switch to the Logic tab to add interactive behaviour with a node-graph flow editor.', section: 'logic' }, { text: 'Add Local variables for set-points and counters that live inside the panel.', section: 'edit' }, { text: 'Arrow keys nudge the selected widget by 1 px (Shift for grid size).', section: 'shortcuts' }, ], }; function SectionTips({ onNavigate }: { onNavigate: (section: string) => void }) { const groups: Array<['view' | 'edit', string]> = [ ['view', 'In View mode'], ['edit', 'In Edit mode'], ]; return (
A handful of quick pointers to get the most out of uopi. Click any tip to jump to the section that explains it in detail.
{groups.map(([mode, title]) => (uopi is a web-based HMI for monitoring and controlling EPICS-based scientific and industrial systems. It runs as a single binary — open a browser, no installation required.
http://<host>:8080 in any browser.Monitor live signals in read-only HMI panels. Right-click any widget for signal info or to export data as CSV.
Drag signals from the tree onto the canvas, resize and configure widgets, then save your panel to the server.
View mode displays a live HMI panel. Widget values update in real time via WebSocket.
View mode layout: ① interface list · ② live canvas · WebSocket status top-right.
Right-click any widget to:
The coloured chip in the top-right shows the WebSocket connection state. When connected it reads "connected as <your username>", so you can confirm the identity your actions are attributed to in the audit log. A red "disconnected" banner appears at the top while reconnecting — live values will resume automatically.
Edit mode gives you a drag-and-drop canvas, a signal tree on the left, and a properties pane on the right.
Edit mode layout: ① signal tree · ② canvas with snap grid · ③ properties pane.
Drag a signal from the tree → drop on canvas → choose a widget type.
When 2+ widgets are selected the alignment toolbar appears. Buttons align edges or centres; with 3+ selected you can distribute spacing evenly.
The centre of the editor has two tabs:
Panels can define their own local variables — lightweight values that live only inside the panel (no data source needed). Add them from the signal tree's Local group (or the Logic palette). They are written by Set-value widgets, buttons and logic actions, and referenced anywhere a signal is, making them handy for set-points, toggles and counters used by panel logic.
A plot panel is a special interface dedicated to charts. Instead of free-form boxes, plots fill the viewport and you split the space between them like panes in a tiling window manager (tmux / IDE style).
In view mode the saved split layout fills the screen with live, streaming plots.
The Logic tab in the panel editor is a node-graph (Node-RED style) flow editor. You wire trigger nodes to action nodes to give a panel interactive behaviour. Logic runs entirely client-side while the panel is open in view mode.
{'{ds:name}'} and a panel-local variable by its bare name.{'{sys:time}'} — current time in epoch seconds.{'{sys:dt}'} — seconds since the firing trigger last fired (useful for rates).Control logic is server-side automation. Unlike panel logic (which only runs while a panel is open in a browser), control-logic graphs run continuously on the server, independent of any client. Open them with the ⚙ Control logic button in the view-mode toolbar.
Editing both panel logic and control logic can be restricted to an allowlist of users or
groups (server.logic_editors in the config). When restricted, users not on the
list keep full access to everything else but cannot add or change logic — the Logic tab and
the ⚙ Control logic button are hidden for them.
uopi has graduated, server-enforced access control. Identity comes from a trusted reverse-proxy header (with a configurable default for unproxied/LAN use) — there is no login page in the app itself.
When the server is configured with [audit] enabled = true, every
system-affecting action is appended to an append-only log so it can be
reviewed later. Permitted users open it with the 🛡 Audit button in the
view-mode toolbar.
Each entry records the time, the actor (username, or the graph name for automated actions), the action, the affected data source / signal and value, the client address, and whether it succeeded.
Access to the audit log is gated by an allowlist (audit.readers in the config),
following the same model as logic editing: an empty list means every user may view it,
otherwise only the listed users or groups can. The 🛡 Audit button is hidden for users who
are not permitted.
Each widget is configured in the Properties pane on the right. The available options depend on the widget type.
Widget type overview — drag a signal from the tree to place one.
The signal tree (left pane in Edit mode) lets you browse, search, and manage the signals available from each data source.
Click CSV to import a list of signals from a file. Each line should be either:
PV_NAME — added to the epics data source.ds,PV_NAME — added to the specified data source.Lines starting with # are treated as comments.
If your server is configured with an EPICS Channel Finder URL, a 🔍 button appears when you type in the filter box. Click it to search Channel Finder and import matching PV names.
Click + Synthetic to compose a new signal from existing ones. A quick wizard covers the common case (pick an input + a processing node such as gain, offset, moving average, lowpass/highpass, derivative, integral, clamp or a custom formula). For multi-input pipelines, the node-graph editor lets you wire inputs through a chain of DSP blocks visually. The new signal appears in the tree immediately.
Each synthetic signal has a visibility scope: panel (only the panel that created it), user, or global (shared with everyone).
The Local group holds panel-local variables — values stored in the panel itself with no data source. Use them for set-points, toggles and counters driven by panel logic. They are added in the editor and saved with the panel.
Historical data is retrieved from EPICS Archive Appliance when the server is configured with an archive_url.
Only the Plot widget in time-series mode supports historical ranges.
Time navigation toolbar: ① Live button · ② start · ③ end datetime · ④ Load.
All shortcuts that work while the canvas or modal has focus.
| Shortcut | Action |
|---|---|
| {key} | {action} |
/api/v1/datasources, /api/v1/signals, /api/v1/synthetic/api/v1/interfaces, /api/v1/folders, /api/v1/interfaces/{'{id}'}/acl/api/v1/me, /api/v1/usergroups/api/v1/controllogic/metrics · Health check: /healthz · WebSocket: ws://<host>/ws[server]{'\n'}listen = ":8080"{'\n'}storage_dir = "./interfaces"{'\n'}# optional: restrict who may edit panel/control logic{'\n'}# logic_editors = ["operators"]{'\n\n'}[datasource.epics]{'\n'}enabled = true{'\n'}archive_url = "http://archiver:17665"{'\n\n'}[datasource.synthetic]{'\n'}enabled = true