# uopi

A web-based HMI (Human-Machine Interface) for monitoring and controlling EPICS-based scientific and industrial control systems.

uopi runs as a **single portable binary** with no runtime dependencies. Point any browser at it — including over an SSH tunnel — and you get a full drag-and-drop HMI builder and live data viewer.

---

## Features

| Feature | Details |
|---------|---------|
| **Live data** | EPICS Channel Access (CA) / PVAccess and user-defined synthetic signals |
| **HMI editor** | Drag-and-drop widgets, resize, align/distribute, undo/redo, saved as XML |
| **Widget library** | Text view/label, gauge, LED, multi-LED, bar, set-point control, button, image, link, plots |
| **Plot types** | Time-series (uPlot), FFT, waterfall, histogram, bar chart, logic analyser |
| **Plot panels** | Dedicated chart panels with a recursive split layout (tmux/IDE-style) where plots fill the viewport |
| **Panel logic** | In-editor node-graph flow editor (triggers → actions, dialogs) that runs client-side in view mode |
| **Control logic** | Server-side always-on flow graphs with cron/alarm triggers and Lua blocks |
| **Configuration manager** | Versioned configuration **sets** (typed signal schemas, grouped) and **instances** (values) with validation, array/CSV editing, apply-to-signals, and JSON import/export |
| **Version history & diff** | Git-style history for panels, synthetic signals, control logic and config sets/instances: view, fork, promote any revision, with unified / side-by-side diff |
| **Local variables** | Panel-scoped state variables for set-points, toggles and counters used by logic |
| **Historical data** | EPICS Archive Appliance integration via REST; time range picker in UI |
| **Synthetic signals** | Compose, filter, and transform signals via a wizard or visual node-graph editor (gain, offset, moving average, lowpass, highpass, derivative, integral, clamp, formula); panel/user/global visibility scopes |
| **Access control** | Identity via trusted proxy header; per-user global level (write/read-only/no-access); per-panel ownership + sharing; nested folders; optional logic-editor allowlist |
| **Multi-client** | Subscriptions are multiplexed — N clients share one upstream connection per signal |
| **Signal discovery** | Filter/search, CSV import, manual add, Channel Finder proxy |
| **Observability** | Prometheus-format metrics at `/metrics` |
| **Single binary** | Frontend assets embedded at build time — no web server needed |

---

## Quick Start

### Prerequisites

- **Go 1.22+** — the only build dependency (the frontend is bundled by a pure-Go esbuild wrapper)
- EPICS Base (`libca`) — only required for the `epics` build tag; the stub and synthetic data sources work without it

### Build

```bash
# Full build — bundles the frontend then compiles the binary
make all
```

The binary is written to `dist/uopi`.

### Run

```bash
./dist/uopi                    # uses default config (stub data source only)
./dist/uopi --config uopi.toml # explicit config file
```

Then open [http://localhost:8080](http://localhost:8080) in your browser.

**SSH tunnel example:**

```bash
ssh -L 8080:localhost:8080 user@controlhost
# then open http://localhost:8080 locally
```

---

## Configuration

Create `uopi.toml` (all fields are optional — shown values are defaults):

```toml
[server]
listen      = ":8080"
storage_dir = "./interfaces"  # where interface XML, ACL and logic data are stored

# Identity & access control (all optional)
trusted_user_header = ""      # HTTP header carrying the proxy-authenticated user
default_user        = ""      # identity used when the header is absent (LAN/dev)
# logic_editors     = []      # restrict who may edit panel/control logic (users or groups)

# [[server.blacklist]]        # downgrade a user: level = "readonly" or "noaccess"
# user = "guest"
# level = "readonly"

# [[groups]]                  # named user sets, referenced by panel sharing
# name    = "operators"
# members = ["alice", "bob"]

[datasource.epics]
enabled           = false    # requires build with -tags epics
ca_addr_list      = ""       # overrides EPICS_CA_ADDR_LIST if non-empty
archive_url       = ""       # EPICS Archive Appliance base URL, e.g. http://archiver:17665
channel_finder_url = ""      # EPICS Channel Finder base URL (optional)

[datasource.synthetic]
enabled = true
```

All settings can be overridden with environment variables using the `UOPI_` prefix and double underscores for nesting:

```bash
UOPI_SERVER_LISTEN=":9090" UOPI_DATASOURCE_EPICS_ENABLED=true ./dist/uopi
```

---

## Building with EPICS Support

The EPICS data source uses CGo bindings to `libca`. First set up the EPICS environment variables, then:

```bash
export EPICS_BASE=/path/to/epics/base
export CGO_CFLAGS="-I$EPICS_BASE/include -I$EPICS_BASE/include/os/Linux"
export CGO_LDFLAGS="-L$EPICS_BASE/lib/linux-x86_64 -lca -lCom"

make backend-epics
```

Then enable it in `uopi.toml`:

```toml
[datasource.epics]
enabled = true
```

---

## API

The REST API is available under `/api/v1`. Key endpoints:

| Method | Path | Description |
|--------|------|-------------|
| `GET` | `/api/v1/datasources` | List registered data sources |
| `GET` | `/api/v1/signals?ds=<name>` | List signals for a data source |
| `GET` | `/api/v1/signals/search?q=<query>` | Search signals across all sources |
| `GET` | `/api/v1/channel-finder?q=<query>` | Proxy to EPICS Channel Finder |
| `GET/POST` | `/api/v1/interfaces` | List or create HMI interfaces |
| `GET/PUT/DELETE` | `/api/v1/interfaces/{id}` | Read, update, or delete an interface |
| `POST` | `/api/v1/interfaces/{id}/clone` | Duplicate an interface |
| `POST` | `/api/v1/interfaces/reorder` | Reorder panels / move them between folders |
| `GET/PUT` | `/api/v1/interfaces/{id}/acl` | Read or set a panel's sharing rules |
| `GET/POST` | `/api/v1/folders` | List or create panel folders |
| `PUT/DELETE` | `/api/v1/folders/{id}` | Rename/reparent or delete a folder |
| `GET/POST/DELETE` | `/api/v1/synthetic` | Manage synthetic signal definitions |
| `GET/POST` | `/api/v1/controllogic` | List or create server-side control-logic graphs |
| `GET/PUT/DELETE` | `/api/v1/controllogic/{id}` | Read, update, or delete a control-logic graph |
| `GET/POST` | `/api/v1/config/sets` | List or create configuration sets (schemas) |
| `GET/PUT/DELETE` | `/api/v1/config/sets/{id}` | Read, update, or delete a config set |
| `GET/POST` | `/api/v1/config/instances` | List or create configuration instances (values) |
| `GET/PUT/DELETE` | `/api/v1/config/instances/{id}` | Read, update, or delete a config instance |
| `POST` | `/api/v1/config/instances/{id}/apply` | Write an instance's values to their target signals |
| `GET` | `/api/v1/config/{sets\|instances}/diff` | Structural diff between two revisions |
| `GET` | `/api/v1/{interfaces\|synthetic\|controllogic\|config/sets\|config/instances}/{id}/versions` | List revisions; `…/{version}` fetches one |
| `POST` | `…/{id}/versions/{v}/promote` | Promote a revision to current |
| `POST` | `…/{id}/versions/{v}/fork` | Fork a revision into a new document |
| `GET` | `/api/v1/me` | Caller identity, access level, groups, logic-edit permission |
| `GET` | `/api/v1/usergroups` | List configured users and groups (for sharing) |
| `GET` | `/metrics` | Prometheus-format server metrics |
| `GET` | `/healthz` | Health check |

WebSocket live data is at `ws://<host>/ws`. See [`docs/TECHNICAL_SPEC.md`](docs/TECHNICAL_SPEC.md) for the message protocol.

---

## Development

```bash
# Build frontend only (output → web/dist/)
make frontend

# Build backend only (frontend must already be built)
make backend

# Run the server directly without a binary (uses stub data source)
go run ./cmd/uopi

# Run all tests
make test

# Run tests with race detector
make race

# Run benchmarks
make bench

# Static analysis
make lint

# Clean build artefacts
make clean
```

---

## Observability

The `/metrics` endpoint exposes Prometheus-format counters and gauges:

```
uopi_uptime_seconds          — seconds since start
uopi_ws_connections          — current open WebSocket connections
uopi_signal_subscriptions    — current unique upstream signal subscriptions
uopi_ws_messages_in_total    — total messages received from clients
uopi_ws_messages_out_total   — total messages sent to clients
uopi_write_ops_total         — total signal write operations
uopi_history_requests_total  — total historical data requests
```

Scrape with any Prometheus-compatible tool or view directly in the browser.

---

## Docs

| Document | Description |
|----------|-------------|
| [`docs/FUNCTIONAL_SPEC.md`](docs/FUNCTIONAL_SPEC.md) | User-facing feature specification |
| [`docs/TECHNICAL_SPEC.md`](docs/TECHNICAL_SPEC.md) | Architecture, WebSocket protocol, API design |
| [`docs/WORK_PLAN.md`](docs/WORK_PLAN.md) | Phased development plan with milestones |

---

## Release

Releases are built with [goreleaser](https://goreleaser.com/):

```bash
goreleaser release --clean
```

See [`.goreleaser.yaml`](.goreleaser.yaml). Note that release binaries are built **without** EPICS support (`CGO_ENABLED=0`) for maximum portability. Build from source with `-tags epics` for CA connectivity.

---

## License

TBD
