Files
lmxopcua/docs/v2/AdminUI-rebuild-plan.md
Joseph Doherty 5c754ecffd
Some checks failed
v2-ci / build (push) Failing after 41s
v2-ci / unit-tests (tests/Core/ZB.MOM.WW.OtOpcUa.Cluster.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.ControlPlane.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.Security.Tests) (push) Has been skipped
v2-ci / integration (push) Has been skipped
docs(v2): F15 UX kickoff — AdminUI rebuild plan
47-page legacy inventory mapped to v2 disposition (5 already done, 22 port
as-is, 7 reshape, 5 dropped because live-edit replaces draft/publish, 4
deferred driver-typed editors). Net ~30 active pages to rebuild.

Five open design questions surfaced for review before per-page work starts:
Q1 driver-typed editors (defer vs. ship), Q2 top-level fleet-wide views
(drop vs. keep), Q3 ClusterDetail tabs vs. split routes, Q4 RoleGrants
cluster-scoped vs. LDAP-group fleet-wide, Q5 Login error UX.

Proposed 4-phase sequencing (~5 days total): shell+auth+fleet, cluster
CRUD, config tabs, logic+ops. Each phase independently mergeable.
2026-05-26 07:38:58 -04:00

266 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Admin UI rebuild plan (F15)
**Status:** UX kickoff — proposals to react to before any per-page rebuild starts.
**Last updated:** 2026-05-26 on `v2-akka-fuse`.
## Why this isn't a straight port
The v1 Admin UI was built around `ConfigGeneration` draft → publish:
operators edited a **draft** generation, the system computed a **diff** against the
last published one, and a manual **Publish** sealed it. Six full pages
(`DraftEditor`, `DiffViewer`, `DiffSection`, `Generations`, plus the per-tab
"viewing draft N" header) lived to make this workflow legible.
v2 replaces that with **live-edit + snapshot-deploy** (decisions #14a#14e on this
branch). Edits write directly to live tables guarded by `RowVersion`
concurrency; deploying is a single click that snapshots the current live state
and dispatches it via Akka. Drift between "current live" and "last sealed
deployment" surfaces as a one-line indicator on the
[Deployments](../../src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/Components/Pages/Deployments.razor)
page.
That collapses **six pages → zero** before we ship a line of new Razor. The
remaining ~41 legacy pages map to ~30 v2 pages once redundant fleet-wide views
fold into their cluster-tab equivalents.
## Inventory: 47 legacy pages → v2 disposition
Source: `git show 76310b8^ -- 'src/Server/ZB.MOM.WW.OtOpcUa.Admin/**/*.razor'`.
### Site shell (5 files) — port
| Legacy | v2 status | Notes |
|---|---|---|
| `App.razor`, `Routes.razor`, `_Imports.razor` | Port | Boilerplate; minor render-mode tweaks |
| `Layout/MainLayout.razor` | ✅ Already in v2 | Done in Task 48 |
| `Components/Pages/Login.razor`, `Account.razor` | Port | Auth endpoints changed (cookie+JWT hybrid, Task 26); login form posts to `/auth/login` now |
### Shared widgets (5 files) — port
| Legacy | v2 status |
|---|---|
| `StatusBadge.razor` | ✅ Already in v2 |
| `LoadingSpinner.razor` | ✅ Already in v2 |
| `ToastNotification.razor` | ✅ Already in v2 |
| `ClusterAuthorizeView.razor`, `RedirectToLogin.razor` | Port — adjust for v2 `IUserAuthenticator` |
### Fleet (1 file) — reshape
| Legacy | v2 strategy |
|---|---|
| `Fleet.razor` | **Reshape.** Drop the v1 "poller reads central DB" data source. v2 reads `NodeDeploymentState` (Applied / Failed / Stale per node) + subscribes to `FleetStatusHub` for live `ServiceLevel` updates (already wired in F16) + queries `IFleetDiagnosticsClient.GetDiagnostics` (F17) for per-node driver health. Single page, similar shape to v1. |
### Cluster CRUD (3 files) — port
| Legacy | v2 strategy |
|---|---|
| `ClustersList.razor` | Port |
| `NewCluster.razor` | Port |
| `ClusterDetail.razor` | **Port — drop draft/publish chrome.** No "New draft" button; no "current published" sidebar. Replace with "Last deployed" badge + a "Deploy" button (already a SignalR-aware widget on the Deployments page; this becomes a cluster-scoped variant). |
### Draft/publish workflow (4 files) — **drop entirely**
| Legacy | v2 strategy |
|---|---|
| `DraftEditor.razor` | **Drop.** No drafts in v2. |
| `DiffViewer.razor` | **Drop.** Drift indicator replaces it on Deployments page. |
| `DiffSection.razor` | **Drop.** |
| `Generations.razor` | **Drop — replaced by `Deployments.razor`** (already shipped in v2 ahead of F15). |
### Cluster tabs (11 files) — port as live-edit forms
Each becomes a live-edit surface: load the entity, bind to a form, save with
`RowVersion` concurrency check (409 on conflict → toast + reload). No "viewing
draft N" header; no per-tab snapshot view.
| Legacy tab | v2 strategy |
|---|---|
| `EquipmentTab.razor` | Port — UNS path tree picker stays |
| `UnsTab.razor` | Port — same |
| `NamespacesTab.razor` | Port |
| `DriversTab.razor` | Port — **driver-type-specific editors are a separate question (see below)** |
| `TagsTab.razor` | Port |
| `AclsTab.razor` | Port — wire to v2 LDAP group → role mapping (see RoleGrants question) |
| `RedundancyTab.razor` | Port — surface v2 `ServiceLevel` calc (Task 35) instead of v1 redundancy state machine |
| `ScriptedAlarmsTab.razor` | Port |
| `ScriptsTab.razor` | Port |
| `VirtualTagsTab.razor` | Port |
| `AuditTab.razor` | Port — wire to v2 `ConfigAuditLog` (post-F3 schema: `EventId`, `CorrelationId` columns) |
### Cluster-scoped editors (3 files) — port as reusable inputs
| Legacy | v2 strategy |
|---|---|
| `IdentificationFields.razor` | Port |
| `ImportEquipment.razor` | Port |
| `ScriptEditor.razor` | Port |
### Cross-cluster pages (8 files) — mixed
| Legacy | v2 strategy |
|---|---|
| `Hosts.razor` | Port — reshape to "Akka cluster members" (showing `host:port` NodeIds, roles, redundancy state) |
| `Certificates.razor` | Port — F13a's `PkiStoreRoot` becomes the data source |
| `Reservations.razor` | Port |
| `RoleGrants.razor` | **Reshape.** v1 was cluster-scoped role grants; v2 uses LDAP group → role mapping (see Q4 below) |
| `AlarmsHistorian.razor` | Port — wire to F11's `HistorianAdapterActor.GetStatus` (queue depth + drain state) |
| `ScriptLog.razor` | Port — needs SignalR hub bridge (F16 deferred ScriptLogHub) |
| `ScriptedAlarms.razor` (top-level) | **Possibly drop** (see Q2 below) |
| `VirtualTags.razor` (top-level) | **Possibly drop** (see Q2 below) |
### Driver-typed editors (5 files) — sequencing decision needed
| Legacy | v2 strategy |
|---|---|
| `Drivers/FocasDetail.razor` | Defer — JSON editor in `DriversTab` covers the same config initially |
| `Modbus/ModbusOptionsEditor.razor` | Same |
| `Modbus/ModbusAddressEditor.razor` | Same |
| `Modbus/ModbusAddressPreview.razor` | Same |
| `Modbus/ModbusDiagnostics.razor` | Port — separate from the config editor, this is operational telemetry |
### Account (1 file) — port
| Legacy | v2 strategy |
|---|---|
| `Account.razor` | Port — minor reshape for JWT (token expiry UI, refresh button) |
## Summary by disposition
| Disposition | Count |
|---|---|
| Already in v2 | 5 |
| Port as-is | 22 |
| Port + reshape | 7 |
| **Drop (replaced by live-edit / Deployments page)** | **5** |
| Drop (redundant with cluster tab) | 2 (pending Q2) |
| Defer (driver-typed editors) | 4 |
| **Total active rebuild** | ~30 pages |
## Open design questions
These need answers before per-page sequencing starts. They drive how many
phases the rebuild takes and what gets cut.
### Q1 — Driver-typed editors: ship now or defer?
**Context.** v1 had typed editors for Modbus + FOCAS driver config. They sat
behind a generic JSON editor for the other six driver types. The typed editors
caught operator typos that the JSON editor missed (port ranges, slave-ID
collisions, address-map overlaps).
**Options.**
- **Defer all typed editors.** Ship `DriversTab` with a JSON editor first; add
typed editors per-driver as field requests come in. Saves ~1 day on F15.
- **Port the existing two.** Modbus + FOCAS were already validated against
field use. The other six driver types stay JSON-only.
- **Ship all eight typed editors.** Most work, best UX. ~3 extra days on F15.
**Recommendation:** Defer. The OPC UA dual-endpoint tests + driver
engine wiring (F7-F10) are higher-leverage and need attention first.
### Q2 — Top-level `ScriptedAlarms.razor` and `VirtualTags.razor`: keep or drop?
**Context.** In v1, these were fleet-wide views of every scripted alarm and
virtual tag across every cluster. The cluster tabs let you edit them; the
top-level pages let you find them across clusters.
**Options.**
- **Drop.** Fleet-wide view is rare; cluster scope covers 95% of use.
- **Keep as read-only.** Cross-cluster search + drill-down to the per-cluster tab.
**Recommendation:** Drop, but expose a global search on the top nav that
matches cluster + alarm/tag names if operators ask.
### Q3 — ClusterDetail: 10 tabs or split routes?
**Context.** v1 had 10 nav-tabs inside `ClusterDetail.razor`. Some are very
heavy (Tags can be 10k rows; AuditTab streams). All 10 share render state.
**Options.**
- **Keep tabs.** Familiar; one URL per cluster.
- **Split into routes.** `/clusters/{id}/equipment`, `/clusters/{id}/tags`,
etc. Better deep-linking, better load (one tab's data per page), easier auth
scoping.
**Recommendation:** Split into routes. The v1 monolith was already groaning
under the live-update SignalR fan-in; routes let each surface manage its own
subscription lifecycle.
### Q4 — RoleGrants: cluster-scoped table or LDAP group → role map?
**Context.** v1 had a per-cluster `RoleGrants` table where you mapped users to
cluster-scoped roles (ClusterAdmin, ClusterOperator, etc.). v2 introduced
LDAP-driven auth: LDAP group membership maps to OPC UA permissions
(`ReadOnly`, `WriteOperate`, `WriteTune`, `WriteConfigure`, `AlarmAck`)
fleet-wide.
**Options.**
- **Keep v1 model.** Cluster-scoped grants survive; LDAP just provides the
username.
- **Replace with fleet-wide LDAP-group → role mapping.** v2's `LdapOptions`
already has a `GroupToRole` dictionary; surface that in a single fleet-level
page.
- **Both.** LDAP map for fleet-wide defaults; per-cluster overrides for
scoping.
**Recommendation:** Fleet-wide LDAP-group → role map only. Per-cluster scoping
adds combinatorial complexity that v2's redundancy model doesn't need
(every driver-role node runs every driver in the fleet).
### Q5 — Login UI: backed by `/auth/login` (cookie+JWT hybrid) — what about LDAP error UX?
**Context.** v2's `/auth/login` does an LDAP bind. Failures come back as
specific reasons (invalid creds vs. service-account misconfig vs. server
unreachable). The default behavior is to lump them all into "Login failed."
**Options.**
- **Generic "Login failed."** Safer; doesn't leak whether the username exists.
- **Specific error categories.** Helps operators diagnose deploy issues.
**Recommendation:** Generic for production deployments, specific when
`Authentication:Ldap:AllowInsecureLdap=true` (dev mode signal).
## Proposed sequencing (4 phases)
Each phase is independently mergeable. The branch ships when Phase A is in;
Phases BD can follow as smaller PRs.
### Phase A — Shell + auth + fleet (minimum-viable Admin)
1 day. Ships a working admin surface with no config editing.
- Port `App.razor`, `Routes.razor`, `_Imports.razor`
- Port `Login.razor` (post Q5)
- Port `Account.razor`
- Reshape `Fleet.razor` against v2 data sources
- Port `Hosts.razor` reshape
### Phase B — Cluster CRUD + Overview/Redundancy tabs
~1 day. Adds cluster browse + readonly redundancy view.
- Port `ClustersList`, `NewCluster`, `ClusterDetail` (Overview tab only)
- Port `RedundancyTab` (read-only — surfaces v2 `ServiceLevel`)
- Split into routes if Q3 = split
### Phase C — Config editor tabs
~2 days. The big chunk — the live-edit config surface.
- `EquipmentTab`, `UnsTab`, `NamespacesTab`
- `DriversTab` (JSON-only initially per Q1)
- `TagsTab`
- `AclsTab` post Q4 reshape
- `ImportEquipment`, `IdentificationFields`
### Phase D — Logic + ops pages
~1 day.
- `VirtualTagsTab`, `ScriptedAlarmsTab`, `ScriptsTab`, `ScriptEditor`
- `AuditTab` against new ConfigAuditLog schema
- `RoleGrants` post Q4 reshape
- `Certificates`
- `Reservations`
- `AlarmsHistorian`, `ScriptLog` (depends on F16 ScriptLogHub deferred)
## Out of scope for F15
- Typed driver editors (Q1, deferred unless reversed)
- Top-level fleet-wide ScriptedAlarms / VirtualTags pages (Q2, recommended drop)
- Per-cluster RoleGrants (Q4, recommended drop)
- ScriptLogHub SignalR bridge (F16 deferred — only needed for Phase D's
ScriptLog page; can move to a separate F16-extension follow-up)