From 5c754ecffd9c4fff4372c787b917e0977de1e679 Mon Sep 17 00:00:00 2001 From: Joseph Doherty Date: Tue, 26 May 2026 07:38:58 -0400 Subject: [PATCH] =?UTF-8?q?docs(v2):=20F15=20UX=20kickoff=20=E2=80=94=20Ad?= =?UTF-8?q?minUI=20rebuild=20plan?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- docs/v2/AdminUI-rebuild-plan.md | 265 ++++++++++++++++++++++++++++++++ 1 file changed, 265 insertions(+) create mode 100644 docs/v2/AdminUI-rebuild-plan.md diff --git a/docs/v2/AdminUI-rebuild-plan.md b/docs/v2/AdminUI-rebuild-plan.md new file mode 100644 index 0000000..b5236ed --- /dev/null +++ b/docs/v2/AdminUI-rebuild-plan.md @@ -0,0 +1,265 @@ +# 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 B–D 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)