AdminUI /admin/secrets renders but is non-interactive — Secrets.Ui RCL pages get no render mode #483

Closed
opened 2026-07-18 16:00:34 -04:00 by dohertj2 · 1 comment
Owner

Found while verifying clustered-secrets enablement on the docker-dev rig (2026-07-18).

Symptom

/admin/secrets returns HTTP 200 and renders the secrets list (auth fine), but every interactive element is dead — "Add secret" (@onclick) does nothing. Verified with Playwright: the SSR HTML contains zero Blazor interactive markers and no /_blazor circuit is ever negotiated on that page (button click → no editor, no WebSocket). Every other admin page is interactive.

Cause

The ZB.MOM.WW.Secrets.Ui RCL components (SecretsPage/SecretsList) declare no @rendermode, and OtOpcUa's App.razor/Routes.razor applies interactivity per-page (@rendermode InteractiveServer on each of its own pages). Routable pages contributed by the additional assembly therefore fall back to static SSR.

Suggested fix

Either apply an InteractiveServer render mode to the additional-assembly routes in OtOpcUa's router, or add @rendermode in the RCL (a library-side fix in ZB.MOM.WW.Secrets.Ui would benefit every consumer — check how the other three apps host the same page before choosing).

Notes

  • Pre-existing; unrelated to replication (Secrets:Replication:Enabled off/on makes no difference).
  • Until fixed, secrets on OtOpcUa nodes are manageable via the secret CLI against the node's store, not the AdminUI page.
  • Worth a quick check in mxaccessgw / HistorianGateway / ScadaBridge for the same pattern — any host that applies render modes per-page and mounts the RCL routes will have the same dead page.
Found while verifying clustered-secrets enablement on the docker-dev rig (2026-07-18). ## Symptom `/admin/secrets` returns HTTP 200 and renders the secrets list (auth fine), but **every interactive element is dead** — "Add secret" (`@onclick`) does nothing. Verified with Playwright: the SSR HTML contains zero Blazor interactive markers and **no `/_blazor` circuit is ever negotiated on that page** (button click → no editor, no WebSocket). Every other admin page is interactive. ## Cause The `ZB.MOM.WW.Secrets.Ui` RCL components (`SecretsPage`/`SecretsList`) declare no `@rendermode`, and OtOpcUa's `App.razor`/`Routes.razor` applies interactivity **per-page** (`@rendermode InteractiveServer` on each of its own pages). Routable pages contributed by the additional assembly therefore fall back to static SSR. ## Suggested fix Either apply an InteractiveServer render mode to the additional-assembly routes in OtOpcUa's router, or add `@rendermode` in the RCL (a library-side fix in `ZB.MOM.WW.Secrets.Ui` would benefit every consumer — check how the other three apps host the same page before choosing). ## Notes - Pre-existing; unrelated to replication (`Secrets:Replication:Enabled` off/on makes no difference). - Until fixed, secrets on OtOpcUa nodes are manageable via the `secret` CLI against the node's store, not the AdminUI page. - Worth a quick check in mxaccessgw / HistorianGateway / ScadaBridge for the same pattern — any host that applies render modes per-page and mounts the RCL routes will have the same dead page.
Author
Owner

Fixed — master @ 5f72ff85, live-verified

Fix location: this host, not the RCL. The other three family apps render the RCL page under a globally interactive router (<Routes @rendermode="InteractiveServer"/>), so a page-level @rendermode inside ZB.MOM.WW.Secrets.Ui would throw at runtime for all of them (nested render mode on an interactive parent). OtOpcUa alone keeps a static router with per-page opt-in — so the fix is a host-side wrapper: Pages/SecretsAdmin.razor owns /admin/secrets, carries @rendermode RenderMode.InteractiveServer, re-states the RCL page's secrets:manage policy (the router only enforces [Authorize] on the routed component, which is now the wrapper), and renders the RCL SecretsPage as an ordinary child. The RCL assembly is de-registered from both AdditionalAssemblies sites so the route is unambiguous.

Guards: SecretsPageWiringTests pins the render mode (THE regression for this issue), route parity with the RCL page, and policy parity; the PageAuthorizationGuardTests census classifies the new page. AdminUI.Tests 665/665.

Live verification on the rebuilt docker-dev rig (Playwright): /_blazor circuit negotiated on /admin/secrets and Add secret opens the editor — before the fix the same probe found zero interactive markers, no circuit, and a dead click.

RCL-side note (no change shipped there): the render-mode gap is inherent to any consumer with a static router; the wrapper pattern in 5f72ff85 is the reference approach for such hosts, and the wiring tests keep it from drifting.

## Fixed — `master` @ `5f72ff85`, live-verified **Fix location: this host, not the RCL.** The other three family apps render the RCL page under a globally interactive router (`<Routes @rendermode="InteractiveServer"/>`), so a page-level `@rendermode` inside `ZB.MOM.WW.Secrets.Ui` would throw at runtime for all of them (nested render mode on an interactive parent). OtOpcUa alone keeps a static router with per-page opt-in — so the fix is a host-side wrapper: `Pages/SecretsAdmin.razor` owns `/admin/secrets`, carries `@rendermode RenderMode.InteractiveServer`, re-states the RCL page's `secrets:manage` policy (the router only enforces `[Authorize]` on the routed component, which is now the wrapper), and renders the RCL `SecretsPage` as an ordinary child. The RCL assembly is de-registered from both `AdditionalAssemblies` sites so the route is unambiguous. **Guards:** `SecretsPageWiringTests` pins the render mode (THE regression for this issue), route parity with the RCL page, and policy parity; the `PageAuthorizationGuardTests` census classifies the new page. AdminUI.Tests **665/665**. **Live verification on the rebuilt docker-dev rig (Playwright):** `/_blazor` circuit negotiated on `/admin/secrets` and **Add secret opens the editor** — before the fix the same probe found zero interactive markers, no circuit, and a dead click. **RCL-side note (no change shipped there):** the render-mode gap is inherent to any consumer with a static router; the wrapper pattern in `5f72ff85` is the reference approach for such hosts, and the wiring tests keep it from drifting.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: dohertj2/lmxopcua#483