# Client UI

## Overview

`ZB.MOM.WW.OtOpcUa.Client.UI` is a cross-platform Avalonia desktop application for connecting to and interacting with the OtOpcUa OPC UA server. It targets .NET 10 and uses the shared `IOpcUaClientService` from `Client.Shared` for all OPC UA operations.

The UI provides a single-window interface for browsing the address space, reading and writing values, monitoring live subscriptions, managing alarms, and querying historical data.

## Build and Run

```bash
cd src/Client/ZB.MOM.WW.OtOpcUa.Client.UI
dotnet build
dotnet run
```

## Technology Stack

| Component | Technology |
|-----------|-----------|
| Framework | .NET 10 |
| UI Toolkit | Avalonia 11.2 |
| MVVM | CommunityToolkit.Mvvm |
| OPC UA | OPCFoundation.NetStandard.Opc.Ua.Client |
| Logging | Serilog |
| Theme | Avalonia Fluent |

## Window Layout

The application uses a single-window layout with five main areas:

```
┌─────────────────────────────────────────────────────────────┐
│ [Endpoint URL                    ] [Connect] [Disconnect]   │
│ ▸ Connection Settings                                       │
│ Redundancy: Warm  Service Level: 200  URI: urn:...          │
├──────────────┬──────────────────────────────────────────────┤
│              │ ┌─Read/Write─┬─Subscriptions─┬─Alarms─┬─History─┐│
│  Address     │ │                                              ││
│  Space       │ │                                              ││
│  Tree        │ │         (active tab content)                 ││
│  Browser     │ │                                              ││
│              │ │                                              ││
│  (lazy-load) │ └──────────────────────────────────────────────┘│
├──────────────┴──────────────────────────────────────────────┤
│ Connected to opc.tcp://... | OtOpcUa Server | Session: ... | 3 subs│
└─────────────────────────────────────────────────────────────┘
```

## Connection Panel

![Connection Panel](images/connection-panel.png)

The top bar provides the endpoint URL, Connect, and Disconnect buttons. The **Connection Settings** expander reveals additional options when expanded:

| Setting | Description |
|---------|-------------|
| Endpoint URL | OPC UA server endpoint (e.g., `opc.tcp://localhost:4840/OtOpcUa`) |
| Username / Password | Credentials for `UserName` token authentication |
| Security Mode | Transport security: None, Sign, SignAndEncrypt |
| Failover URLs | Comma-separated backup endpoints for redundancy failover |
| Session Timeout | Session timeout in seconds (1–3600, default 60) |
| Certificate Store Path | Path to the client certificate store (folder chooser) |
| Auto-accept certificates | Whether to accept untrusted server certificates |

### Settings Persistence

Connection settings are saved to `{LocalAppData}/OtOpcUaClient/settings.json` after each successful connection, on disconnect, and on window close. Dev boxes upgrading from a pre-task-#208 build still have the legacy `LmxOpcUaClient/` folder on disk; `ClientStoragePaths` in `Client.Shared` moves it to the canonical path on first launch so existing trusted certs + saved settings persist without operator action. The settings are reloaded on next launch, including:

- All connection parameters
- Active subscription node IDs (restored after reconnection)
- Alarm subscription source node (restored with condition refresh)

### Redundancy

When connected, the redundancy row displays the server's redundancy mode, service level, and application URI. The shared service handles automatic failover to backup endpoints if configured.

## Browse Tree

The left panel shows the OPC UA address space as a lazy-loaded tree. Nodes are loaded on demand when expanded.

### Context Menu

Right-click on tree nodes to access:

| Action | Description |
|--------|-------------|
| **Subscribe** | Subscribe to data changes on the selected node(s). For Object nodes, recursively subscribes all Variable descendants (up to 10 levels deep). Switches to the Subscriptions tab. |
| **View History** | Set the selected Variable node as the target in the History tab and switch to it. Only enabled for Variable nodes. |
| **Monitor Alarms** | Stop any active alarm subscription and subscribe to alarm events on the selected node. Switches to the Alarms tab with automatic condition refresh. |

Multi-select is supported (Ctrl+Click, Shift+Click) for the Subscribe action.

## Read/Write Tab

Select a node in the browse tree to auto-read its current value. The tab displays:

- Node ID
- Current value (arrays displayed as `[0,1,2,3]`)
- Status code (e.g., `0x00000000 (Good)`)
- Source and server timestamps

To write a value, enter the new value and click Write. The shared `OpcUaClientService.WriteValueAsync` pre-reads the node's current value to determine its type, then calls `ValueConverter.ConvertValue` to produce a typed value client-side before sending a typed `DataValue` to the server. Type resolution happens in the client, not on the server.

## Subscriptions Tab

![Subscriptions Tab](images/subscriptions-tab.png)

Monitor live data changes from subscribed nodes. The tab shows a data grid with:

| Column | Description |
|--------|-------------|
| Node ID | The monitored node identifier |
| Value | Current value (arrays formatted as `[v1,v2,...]`) |
| Status | OPC UA status code with description (e.g., `0x00000000 (Good)`) |
| Timestamp | Source timestamp in ISO 8601 format |

### Adding Subscriptions

- Type a node ID and click **Add**, or
- Right-click nodes in the browse tree and select **Subscribe**

### Removing Subscriptions

Select one or more rows (Ctrl+Click for multi-select) and click **Remove**.

### Writing Values

Double-click a subscription row to open a write dialog. The dialog:

1. Pre-fills the current value
2. Validates the input can parse to the target type before writing
3. Shows the write result status
4. Closes automatically on success, shows error in red on failure

### Tab Header

The tab header shows the active subscription count: `Subscriptions (26)`.

### Persistence

Active subscription node IDs are saved when the application closes or disconnects, and restored on the next connection.

## Alarms Tab

![Alarms Tab](images/alarms-tab.png)

Monitor alarm/condition events from the server.

### Subscribing

Enter an optional source node ID and click **Subscribe**. A condition refresh is automatically requested to display current retained alarms. Alternatively, right-click a node in the browse tree and select **Monitor Alarms**.

### Alarm Display

The data grid shows retained alarm conditions with color-coded rows:

| Severity Range | Color |
|---------------|-------|
| Inactive | Light grey |
| Low (0–332) | Light blue |
| Medium (333–665) | Light yellow |
| High (666–899) | Light red |
| Critical (900–1000) | Red |

Alarms are updated in place when the server re-sends condition state changes. Non-retained alarms are automatically removed.

### Alarm Actions (Acknowledge / Shelve / Confirm)

Right-click any alarm row to open the context menu. The menu is suppressed entirely when none of the three actions apply to the selected row.

| Action | Enabled when | Behaviour |
|--------|-------------|-----------|
| **Acknowledge…** | Alarm is active and not yet acknowledged | Opens a comment dialog. Calls OPC UA `Acknowledge` on the condition node. |
| **Shelve…** | Row has a condition node id (any state) | Opens a dialog with a **Shelve Type** selector (OneShot / Timed / Unshelve) and a **Duration (seconds)** field shown only for Timed. No comment is taken — the OPC UA ShelvedStateMachine methods carry none. Calls `OneShotShelve`, `TimedShelve`, or `Unshelve` accordingly. |
| **Confirm…** | Alarm has been acknowledged (OPC UA two-stage Ack → Confirm flow) | Opens a comment dialog. Calls OPC UA `Confirm` on the condition node. |

All three route through `IOpcUaClientService` (`AcknowledgeAlarmAsync` / `ShelveAlarmAsync` / `ConfirmAlarmAsync`) — the same methods the Client.CLI `ack` / `shelve` / `confirm` commands use.

### Tab Header

The tab header shows active unacknowledged alarm count: `Alarms (2)`.

### Persistence

The alarm subscription source node is saved and restored on reconnection with automatic condition refresh.

## History Tab

![History Tab](images/history-tab.png)

Read historical data from the historian (served server-side by the HistorianGateway backend).

### Time Range

![Date/Time Range Picker](images/datetimerangepicker.png)

The date/time range picker provides:

- **Text input** — Type start and end times in `yyyy-MM-dd HH:mm:ss` format (UTC)
- **Preset buttons** — Quick selection: 5m (last 5 minutes), 1h (last hour), 1d (last day), 1w (last week)

All times are in UTC. Invalid input turns red on blur.

### Query Options

| Option | Description |
|--------|-------------|
| Aggregate | Raw (default), Average, Minimum, Maximum, Count, Start, End, Standard Deviation |
| Interval (ms) | Processing interval for aggregate queries (shown only for aggregates) |
| Max Values | Maximum number of raw values to return (default 1000) |

### Results

The results grid displays:

| Column | Description |
|--------|-------------|
| Value | The historical value (arrays formatted) |
| Status | OPC UA status code with description |
| Source Timestamp | When the value was recorded |
| Server Timestamp | When the server processed the value |

## Status Bar

The bottom status bar shows:

- Connection state and endpoint URL
- Server name and session identifier
- Active subscription count

## Architecture

### Deferred Initialization

The OPC UA SDK is not loaded until the user clicks Connect. This keeps application startup instant. The `IOpcUaClientService` and all child ViewModels are created on first connection.

### UI Thread Dispatch

All service event handlers (data changes, alarm events, connection state changes) are dispatched through an `IUiDispatcher` abstraction before updating `ObservableCollection`s. In production this wraps `Dispatcher.UIThread.Post()`; in tests it runs synchronously.

### ViewModels

| ViewModel | Responsibility |
|-----------|---------------|
| `MainWindowViewModel` | Connection lifecycle, tab coordination, settings persistence |
| `BrowseTreeViewModel` | Root node loading, tree clearing |
| `TreeNodeViewModel` | Lazy-load children on expand via `BrowseAsync` |
| `ReadWriteViewModel` | Auto-read on selection, write with type coercion |
| `SubscriptionsViewModel` | Add/remove subscriptions, DataChanged event handling |
| `AlarmsViewModel` | Alarm subscribe/unsubscribe, event filtering, acknowledge |
| `HistoryViewModel` | Raw and aggregate history reads |

### Custom Controls

| Control | Description |
|---------|-------------|
| `DateTimeRangePicker` | UTC start/end text inputs with preset duration buttons |

## Testing

The UI has 102 unit tests covering ViewModel logic and headless rendering:

```bash
dotnet test tests/Client/ZB.MOM.WW.OtOpcUa.Client.UI.Tests
```

Tests use:
- `FakeOpcUaClientService` — configurable fake implementing `IOpcUaClientService`
- `SynchronousUiDispatcher` — runs dispatch actions inline for deterministic testing
- `FakeSettingsService` — tracks save/load calls for settings persistence tests
- Avalonia headless rendering — screenshot capture for visual verification
