Phase 7 Stream F — Admin UI for scripts + test harness + historian diagnostics #185

Merged
dohertj2 merged 1 commits from phase-7-stream-f-admin-ui into v2 2026-04-20 20:01:42 -04:00
Owner

Adds the draft-editor tab + page surface for authoring Phase 7 virtual tags + scripted alarms, plus the /alarms/historian operator diagnostics page. Monaco loads from CDN via a progressive-enhancement JS shim — the textarea works immediately so the page is functional even if the CDN is unreachable, and Blazor @bind stays authoritative with Monaco mirroring into the hidden textarea on every keystroke.

New services

  • ScriptService — CRUD for Script. SHA-256 SourceHash recomputed on save so CompiledScriptCache hits on re-publish of unchanged source + misses when source actually changes (test pins the cache-hit behavior).
  • VirtualTagService — CRUD for VirtualTag + Enabled toggle.
  • ScriptedAlarmService — CRUD for ScriptedAlarm + lookup of persistent ScriptedAlarmState.
  • ScriptTestHarnessService — pre-publish dry-run. Enforces plan decision #22: only inputs the DependencyExtractor identifies can be supplied; missing / extra inputs surface as dedicated outcomes, compile errors surface as Threw. Captures SetVirtualTag writes + Serilog events from the script.
  • HistorianDiagnosticsService — surfaces IAlarmHistorianSink state on /alarms/historian. Null sink reports Disabled + swallows retry; live SqliteStoreAndForwardSink routes the Retry-dead-lettered button through.

New UI

  • ScriptsTab.razor inside DraftEditor — list + create/edit/delete scripts with Monaco editor + dependency preview (reads / writes / rejections with source-span messages) + test-harness run panel (output + writes + log emissions).
  • ScriptEditor.razor — reusable Monaco-backed textarea. Loads editor from CDN via wwwroot/js/monaco-loader.js. Textarea stays authoritative for Blazor binding; Monaco mirrors into it on every keystroke.
  • AlarmsHistorian.razor at /alarms/historian — queue depth + dead-letter depth + drain state badge + last-error banner + Retry-dead-lettered button that's disabled when dead-letter depth is 0.
  • DraftEditor.razor — new Scripts tab (5th alongside Equipment / UNS / Namespaces / Drivers / ACLs).

DI wiring

All five services registered in Program.cs. Null historian sink bound at Admin composition time — the real SqliteStoreAndForwardSink lives in the Server process; Admin reads status from whichever sink is present.

Tests — 13/13

Phase7ServicesTests covers:

  • ScriptService: Add generates logical id + hash, Update recomputes hash on source change, Update same-source keeps hash (cache-hit preservation), Delete is idempotent
  • VirtualTagService: round-trips trigger flags, Enabled toggle works
  • ScriptedAlarmService: HistorizeToAveva defaults true per plan decision #15
  • ScriptTestHarness: successful run captures output + writes + log events, rejects missing / extra inputs, rejects non-literal paths, compile errors surface as Threw
  • HistorianDiagnosticsService: null sink reports Disabled + retry returns 0

Next

Stream H (exit gate): full-solution test baseline + compliance script + merge to v2.

Adds the draft-editor tab + page surface for authoring Phase 7 virtual tags + scripted alarms, plus the `/alarms/historian` operator diagnostics page. Monaco loads from CDN via a progressive-enhancement JS shim — the textarea works immediately so the page is functional even if the CDN is unreachable, and Blazor @bind stays authoritative with Monaco mirroring into the hidden textarea on every keystroke. ## New services - `ScriptService` — CRUD for `Script`. SHA-256 `SourceHash` recomputed on save so `CompiledScriptCache` hits on re-publish of unchanged source + misses when source actually changes (test pins the cache-hit behavior). - `VirtualTagService` — CRUD for `VirtualTag` + Enabled toggle. - `ScriptedAlarmService` — CRUD for `ScriptedAlarm` + lookup of persistent `ScriptedAlarmState`. - `ScriptTestHarnessService` — pre-publish dry-run. Enforces plan decision #22: only inputs the `DependencyExtractor` identifies can be supplied; missing / extra inputs surface as dedicated outcomes, compile errors surface as `Threw`. Captures `SetVirtualTag` writes + Serilog events from the script. - `HistorianDiagnosticsService` — surfaces `IAlarmHistorianSink` state on `/alarms/historian`. Null sink reports `Disabled` + swallows retry; live `SqliteStoreAndForwardSink` routes the Retry-dead-lettered button through. ## New UI - `ScriptsTab.razor` inside DraftEditor — list + create/edit/delete scripts with Monaco editor + dependency preview (reads / writes / rejections with source-span messages) + test-harness run panel (output + writes + log emissions). - `ScriptEditor.razor` — reusable Monaco-backed textarea. Loads editor from CDN via `wwwroot/js/monaco-loader.js`. Textarea stays authoritative for Blazor binding; Monaco mirrors into it on every keystroke. - `AlarmsHistorian.razor` at `/alarms/historian` — queue depth + dead-letter depth + drain state badge + last-error banner + Retry-dead-lettered button that's disabled when dead-letter depth is 0. - `DraftEditor.razor` — new Scripts tab (5th alongside Equipment / UNS / Namespaces / Drivers / ACLs). ## DI wiring All five services registered in `Program.cs`. Null historian sink bound at Admin composition time — the real `SqliteStoreAndForwardSink` lives in the Server process; Admin reads status from whichever sink is present. ## Tests — 13/13 `Phase7ServicesTests` covers: - ScriptService: Add generates logical id + hash, Update recomputes hash on source change, Update same-source keeps hash (cache-hit preservation), Delete is idempotent - VirtualTagService: round-trips trigger flags, Enabled toggle works - ScriptedAlarmService: HistorizeToAveva defaults true per plan decision #15 - ScriptTestHarness: successful run captures output + writes + log events, rejects missing / extra inputs, rejects non-literal paths, compile errors surface as Threw - HistorianDiagnosticsService: null sink reports Disabled + retry returns 0 ## Next Stream H (exit gate): full-solution test baseline + compliance script + merge to v2.
dohertj2 added 1 commit 2026-04-20 20:01:32 -04:00
Adds the draft-editor tab + page surface for authoring Phase 7 virtual tags and
scripted alarms, plus the /alarms/historian operator diagnostics page. Monaco loads
from CDN via a progressive-enhancement JS shim — the textarea works immediately so
the page is functional even if the CDN is unreachable.

## New services (Admin)

- ScriptService — CRUD for Script entity. SHA-256 SourceHash recomputed on save so
  Core.Scripting's CompiledScriptCache hits on re-publish of unchanged source + misses
  when the source actually changes.
- VirtualTagService — CRUD for VirtualTag, with Enabled toggle.
- ScriptedAlarmService — CRUD for ScriptedAlarm + lookup of persistent ScriptedAlarmState
  (logical-id-keyed per plan decision #14).
- ScriptTestHarnessService — pre-publish dry-run. Enforces plan decision #22: only
  inputs the DependencyExtractor identifies can be supplied. Missing / extra synthetic
  inputs surface as dedicated outcomes. Captures SetVirtualTag writes + Serilog events
  from the script so the operator can see both the output + the log output before
  publishing.
- HistorianDiagnosticsService — surfaces the local-process IAlarmHistorianSink state
  on /alarms/historian. Null sink reports Disabled + swallows retry. Live
  SqliteStoreAndForwardSink reports real queue depth + last-error + drain state and
  routes the Retry-dead-lettered button through.

## New UI

- ScriptsTab.razor (inside DraftEditor tabs) — list + create/edit/delete scripts with
  Monaco editor + dependency preview + test-harness run panel showing output + writes
  + log emissions.
- ScriptEditor.razor — reusable Monaco-backed textarea. Loads editor from CDN via
  wwwroot/js/monaco-loader.js. Textarea stays authoritative for Blazor binding; Monaco
  mirrors into it on every keystroke.
- AlarmsHistorian.razor (/alarms/historian) — queue depth + dead-letter depth + drain
  state badge + last-error banner + Retry-dead-lettered button.
- DraftEditor.razor — new "Scripts" tab.

## DI wiring

All five services registered in Program.cs. Null historian sink bound at Admin
composition time (real SqliteStoreAndForwardSink lives in the Server process).

## Tests — 13/13

Phase7ServicesTests covers:
- ScriptService: Add generates logical id + hash, Update recomputes hash on source
  change, Update same-source keeps hash (cache-hit preservation), Delete is idempotent
- VirtualTagService: round-trips trigger flags, Enabled toggle works
- ScriptedAlarmService: HistorizeToAveva defaults true per plan decision #15
- ScriptTestHarness: successful run captures output + writes, rejects missing /
  extra inputs, rejects non-literal paths, compile errors surface as Threw
- HistorianDiagnosticsService: null sink reports Disabled + retry returns 0
dohertj2 merged commit 6df069b083 into v2 2026-04-20 20:01:42 -04:00
Sign in to join this conversation.
No Reviewers
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: dohertj2/lmxopcua#185