f56798aeb9c5cb105d384202c43540c1faa2d19e
3 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
5744aad028 |
test(dashboard): prove the Secrets nav gate exists, by its absence
The policy tests shipped at
|
||
|
|
1c30611b1e |
feat(dashboard): gate the side rail's Secrets link on secrets:manage
Family-wide nav sweep: the Secrets management page should be linked from each app's UI, visible to Administrator-role users only. The link already existed in MainLayout's Admin section. The gate did not: the rail rendered every item for every visitor, including a Viewer and the anonymous-localhost read-only identity. Not an access hole — the mounted page carries [Authorize(Policy = "secrets:manage")], so a Viewer clicking through was denied — but a dead link presented as a live one. There was also no existing role-gated nav pattern to follow; the rail's only AuthorizeView was the footer's signed-in/signed-out split. Gated on the POLICY rather than a role literal, so nav visibility cannot drift from what the page enforces. In this host the two are equivalent: GatewayOptionsValidator constrains Dashboard:GroupToRole values to Administrator or Viewer, so the shared library's other manage-granting roles (secrets-manager, secrets-reveal) are unreachable. The policy form stays correct if that ever relaxes, where a role literal would then hide the link from users who can use the page. API Keys is deliberately left ungated. It looks like the same case and is not: ApiKeysPage renders for a Viewer with write affordances hidden, so hiding its link would remove legitimate read access. The secrets page has no read-only mode. The rule is "gate the link when the page denies the role outright", not "gate everything under Admin". Coverage: three tests pin the policy's verdict per principal (Administrator admitted, Viewer refused, unauthenticated refused), and /admin/secrets joins the canonical route list — it is the one nav destination mounted from an RCL rather than declared here, so a routing regression could remove it without touching this repo's pages. The principal helper sets an authentication type deliberately: without one the role assertions would pass vacuously for the wrong reason. Not a rendering test — the suite has no component-testing harness, and adding one to assert a single AuthorizeView would be a large dependency for a small claim. Build 0 warnings / 0 errors; suite 895/895. |
||
|
|
01033d7aaf |
fix(dashboard): admin-UI cleanup sweep
Family-wide admin-UI cleanup pass (scadaproj admin_ui_cleanup.md) applied to the Blazor dashboard. Behaviour is unchanged throughout — no @onclick, disabled, binding, auth gate, or arm->confirm flow was touched. Uncontrolled error text is now truncated at the render site. Fault messages, Galaxy load errors, and browse-tree load failures were rendered in full into fixed-width table cells, where a long exception string blows out the column. Each site gets DashboardDisplay.Abbreviate plus a title attribute carrying the untruncated text, so nothing becomes unreachable. Abbreviate is length-checked rather than a bare range slice: `value[..n]` on a shorter string throws and takes the whole page render down with it. The two detail views whose entire purpose is to show one fault in full — SessionDetailsPage and GalaxyPage's Last Error — are deliberately left untruncated. Two classes referenced from markup had no definition anywhere in the sheet. .browse-stale-banner was inert; .tree-load-status was a real visual defect — loading and failed-to-load rows sit among .tree-row siblings and carry the same leading .tree-toggle-empty spacer, but that spacer only takes its width as a flex item, so without a flex container those rows lost their indent. Confirm/cancel pairs in ConfirmDialog and the API-key create form are now btn-groups with role="group" and an aria-label, replacing margin-spaced loose buttons. Removes a paragraph on GalaxyPage naming internal RPCs (DiscoverHierarchy, GetLastDeployTime) — implementation detail with no meaning to a dashboard operator. Verified in a real browser, not bUnit: full build clean, 879/879 tests, and a live gate against a running dashboard with a genuine ~250-char SqlClient exception as the erroring row. Results per check, including the checks that could NOT be exercised without an x86 worker, are recorded in docs/plans/2026-08-11-dashboard-ui-sweeps.md. That plan doc also records a correction: this app is NOT Bootstrap-free. The sweep brief said it was, citing the scadaproj index; libman.json pins bootstrap 5.3.3 and App.razor:7 links it ahead of the theme. The stale claim had already cost this app one skipped family sweep (scadaproj#2, the /admin/secrets modal), so that modal was live-gated here too and passes. |