docs(audit): apply per-cluster judgment fixes across living docs
Resolve audit findings: correct WorkerEnvelope proto/route/metric/session facts; rewrite auth (ZB.MOM.WW.Auth migration), dashboard (ZB.MOM.WW.Theme), and StyleGuide (foreign-project copy-paste); document alarm subsystem, Ldap options, and gateway alarm broker; fix client CLI flags and package paths.
This commit is contained in:
+120
-38
@@ -9,11 +9,13 @@ statistics in real time.
|
||||
|
||||
## Technology Choice
|
||||
|
||||
Decision: Blazor Server with Bootstrap CSS/JS.
|
||||
Decision: Blazor Server with the shared `ZB.MOM.WW.Theme` kit layered over
|
||||
Bootstrap CSS/JS.
|
||||
|
||||
Allowed UI stack:
|
||||
|
||||
- ASP.NET Core Blazor Server,
|
||||
- the `ZB.MOM.WW.Theme` kit (layout chassis, status components, design tokens),
|
||||
- Bootstrap CSS,
|
||||
- Bootstrap JavaScript,
|
||||
- small local CSS for layout and status styling,
|
||||
@@ -30,7 +32,35 @@ Not allowed for v1:
|
||||
|
||||
Rationale: Blazor Server keeps the dashboard in the gateway process, avoids a
|
||||
separate frontend build, and gives real-time UI updates through the Blazor
|
||||
SignalR circuit. Bootstrap is sufficient for a basic dashboard.
|
||||
SignalR circuit. The `ZB.MOM.WW.Theme` kit gives the dashboard the same chassis,
|
||||
status vocabulary, and visual identity as the other ZB.MOM.WW operations UIs
|
||||
without re-implementing layout and status styling per project.
|
||||
|
||||
## Theme Kit
|
||||
|
||||
The dashboard depends on the shared `ZB.MOM.WW.Theme` NuGet package
|
||||
(version `0.2.0`, referenced in `ZB.MOM.WW.MxGateway.Server.csproj`). The kit is
|
||||
a Razor Class Library that ships the technical-light design system: a layout
|
||||
chassis, a small set of UI components, the design tokens, and the head/script
|
||||
asset wiring. The dashboard takes its chrome and status presentation from the
|
||||
kit and adds only its own pages and view CSS on top.
|
||||
|
||||
Components and assets used:
|
||||
|
||||
| Kit member | Role in the dashboard |
|
||||
|---|---|
|
||||
| `<ThemeShell>` | The application chassis — vertical side rail (brand, hamburger, responsive collapse) plus a content area. `MainLayout.razor` wraps it and supplies `Nav`, `RailFooter`, and `ChildContent` slots. |
|
||||
| `<NavRailSection>` / `<NavRailItem>` | Grouped navigation items in the rail. Section expand/collapse persistence is owned by the kit (`<details>` + `ThemeScripts`); the app runs no JS interop for it. |
|
||||
| `<LoginCard>` | The centered login card on `Login.razor`. Renders a native static `<form method="post" action="/login">` so the submit reaches the minimal-API endpoint rather than a Blazor event. |
|
||||
| `<StatusPill State="…">` | The status chip. `StatusBadge.razor` is a thin adapter that maps domain state text to one of four `StatusState` values (`Ok`, `Warn`, `Bad`, `Idle`) and renders this pill. |
|
||||
| `<ThemeHead/>` | Loaded in `App.razor`'s `<head>`; injects the kit's `theme.css` and related head assets. |
|
||||
| `<ThemeScripts/>` | Loaded at the end of `App.razor`'s `<body>`; supplies the rail's interactive behavior. |
|
||||
| Token system | `theme.css` defines all design tokens (`var(--card)`, `var(--ink)`, `var(--accent)`, `var(--mono)`, the state colors, etc.). The local `site.css` references these tokens and defines no hard-coded colors. |
|
||||
|
||||
The dependency on this kit is the reason the layout shell, navigation, status
|
||||
chips, and tokens differ from a stock Bootstrap dashboard. See
|
||||
[Dashboard Interface Design](./DashboardInterfaceDesign.md) for how the kit's
|
||||
tokens and components shape the visual language.
|
||||
|
||||
## Hosting Model
|
||||
|
||||
@@ -67,8 +97,8 @@ Endpoint layout:
|
||||
The `/galaxy` page surfaces the Galaxy Repository browse summary
|
||||
(deployed object hierarchy size, last deploy timestamp, attribute totals,
|
||||
template usage, and connectivity sync info). The summary is fed by
|
||||
`GalaxySummaryCache`, which is refreshed off the request path by
|
||||
`GalaxySummaryRefreshService` on the
|
||||
`GalaxyHierarchyCache`, which is refreshed off the request path by
|
||||
`GalaxyHierarchyRefreshService` on the
|
||||
`MxGateway:Galaxy:DashboardRefreshIntervalSeconds` cadence so the dashboard
|
||||
never blocks on SQL. See [Galaxy Repository Browse](./GalaxyRepository.md) for
|
||||
the underlying gRPC service.
|
||||
@@ -79,24 +109,31 @@ the underlying gRPC service.
|
||||
ZB.MOM.WW.MxGateway.Server
|
||||
Dashboard/
|
||||
Components/
|
||||
App.razor
|
||||
App.razor (loads <ThemeHead/> / <ThemeScripts/>)
|
||||
Routes.razor
|
||||
DashboardPageBase.cs
|
||||
DashboardDisplay.cs
|
||||
Layout/
|
||||
DashboardLayout.razor
|
||||
MainLayout.razor (ThemeShell side-rail chassis)
|
||||
LoginLayout.razor (minimal, no rail; hosts <LoginCard>)
|
||||
Pages/
|
||||
DashboardHome.razor
|
||||
Login.razor
|
||||
SessionsPage.razor
|
||||
SessionDetailsPage.razor
|
||||
WorkersPage.razor
|
||||
EventsPage.razor
|
||||
AlarmsPage.razor
|
||||
GalaxyPage.razor
|
||||
BrowsePage.razor
|
||||
ApiKeysPage.razor
|
||||
SettingsPage.razor
|
||||
Shared/
|
||||
MetricCard.razor
|
||||
StatusBadge.razor
|
||||
StatusBadge.razor (adapter over kit <StatusPill>)
|
||||
FaultList.razor
|
||||
BrowseTreeNodeView.razor
|
||||
ConfirmDialog.razor
|
||||
DashboardSnapshotService.cs
|
||||
DashboardAuthorizationHandler.cs
|
||||
DashboardAuthenticator.cs
|
||||
@@ -244,10 +281,14 @@ Show:
|
||||
- admin Close session / Kill worker controls (Admin role only).
|
||||
|
||||
The Sessions list, the Workers list, and this details page all render the same
|
||||
admin controls when the signed-in principal carries the `Admin` role; viewers
|
||||
admin controls when the signed-in principal carries the `Administrator` role; viewers
|
||||
and the localhost-anonymous bypass see no action affordances and the server
|
||||
re-checks the role on every invocation. Every destructive admin action is
|
||||
gated by a confirmation dialog before it reaches `ISessionManager`.
|
||||
gated by the shared `ConfirmDialog` component before it reaches
|
||||
`ISessionManager`. `ConfirmDialog` is a reusable Bootstrap modal (title,
|
||||
message, confirm/cancel buttons, and a busy state that disables both buttons
|
||||
while the action runs); each page binds its open state and confirm/cancel
|
||||
callbacks. The API keys page uses the same component.
|
||||
|
||||
- **Close session** routes through `ISessionManager.CloseSessionAsync`: the
|
||||
worker is asked to shut down gracefully and is killed only as a fallback if
|
||||
@@ -289,7 +330,8 @@ it opt-in and redacted.
|
||||
### Browse page
|
||||
|
||||
`/browse` lets an operator explore the Galaxy tag hierarchy and watch
|
||||
live values. The tree is built in-process by `DashboardBrowseTreeBuilder` from
|
||||
live values. The tree is built in-process by the static
|
||||
`DashboardBrowseTreeBuilder` (in `DashboardBrowseModel.cs`) from
|
||||
`IGalaxyHierarchyCache.Current` — the same cache the Galaxy page reads — so a
|
||||
render costs no gRPC call and no SQL round-trip. Each node shows its child
|
||||
objects and, when expanded, its attributes with attribute name, data type
|
||||
@@ -307,7 +349,10 @@ diagnostic session/worker views.
|
||||
### Alarms page
|
||||
|
||||
`/alarms` lists the alarms the gateway's central alarm monitor
|
||||
currently holds as Active or ActiveAcked, refreshed every three seconds. It
|
||||
currently holds as Active or ActiveAcked. The page injects
|
||||
`IDashboardLiveDataService` and drives a `PeriodicTimer` poll loop that calls
|
||||
`QueryAlarmsAsync` every three seconds, rather than subscribing to the snapshot
|
||||
hub or holding a `CurrentAlarms` reference directly. It
|
||||
defaults to showing unacknowledged `Active` alarms; filters add acknowledged
|
||||
alarms and narrow by area, severity range, and a reference/source/description
|
||||
text search. Cleared alarms are not retained — the gateway holds no
|
||||
@@ -358,7 +403,7 @@ for what each constraint means and how it is enforced on the gRPC path.
|
||||
|
||||
Create, Rotate, Revoke, and Delete controls render only when the signed-in
|
||||
user is authorized. `DashboardApiKeyAuthorization.CanManage` requires an
|
||||
authenticated principal carrying the `Admin` role claim (resolved at login
|
||||
authenticated principal carrying the `Administrator` role claim (resolved at login
|
||||
from the user's LDAP groups via `MxGateway:Dashboard:GroupToRole`). A
|
||||
`Viewer` role can read the table but sees no action controls, and an
|
||||
anonymous localhost session shows the same read-only view.
|
||||
@@ -385,10 +430,11 @@ Create and Rotate return the assembled `mxgw_<keyId>_<secret>` token **once**,
|
||||
in a one-time banner. It is never shown again, so the operator must copy it
|
||||
immediately. This mirrors the `apikey create-key` / `rotate-key` CLI.
|
||||
|
||||
Every management action appends an `api_key_audit` entry
|
||||
(`dashboard-create-key`, `dashboard-rotate-key`, `dashboard-revoke-key`,
|
||||
`dashboard-delete-key`) with the key id and the caller's remote address.
|
||||
Secrets and pepper values are never logged.
|
||||
Every management action writes an entry to the canonical `audit_event` store
|
||||
through `IAuditWriter` (`dashboard-create-key`, `dashboard-rotate-key`,
|
||||
`dashboard-revoke-key`, `dashboard-delete-key`) with the key id, the caller's
|
||||
remote address, and a correlation id. Secrets and pepper values are never
|
||||
logged.
|
||||
|
||||
### Settings page
|
||||
|
||||
@@ -408,23 +454,33 @@ Do not show API key secrets or pepper values.
|
||||
|
||||
Dashboard authentication is LDAP-backed, distinct from the API-key model used
|
||||
on the gRPC API. Users sign in with directory credentials; the gateway maps
|
||||
their LDAP groups to one of two dashboard roles (`Admin` or `Viewer`) and
|
||||
their LDAP groups to one of two dashboard roles (`Administrator` or `Viewer`) and
|
||||
issues a cookie carrying those role claims.
|
||||
|
||||
Implemented behavior:
|
||||
|
||||
- a static `/login` HTML form posts username/password to the gateway;
|
||||
- `DashboardAuthenticator` binds against `MxGateway:Ldap` (service-account bind,
|
||||
user search, candidate bind) using `Novell.Directory.Ldap.NETStandard`;
|
||||
- the user's `memberOf` (or short CN) is matched against
|
||||
`MxGateway:Dashboard:GroupToRole`; the resolved role(s) are emitted as
|
||||
`ClaimTypes.Role` claims, alongside the per-group `mxgateway:ldap_group`
|
||||
claims;
|
||||
- a successful login signs in the `MxGateway.Dashboard` cookie scheme
|
||||
(`MxGatewayDashboard`, HttpOnly, SameSite=Strict, Secure);
|
||||
- `GET /login` is served by the `[AllowAnonymous]` Blazor `Login.razor`
|
||||
component (under `LoginLayout`), which renders the shared kit's `<LoginCard>`.
|
||||
`LoginCard` emits a native static `<form method="post" action="/login">`
|
||||
(username, password, hidden returnUrl) plus an `<AntiforgeryToken/>`. A native
|
||||
form submit is not a Blazor event, so it reaches the minimal-API `POST /login`
|
||||
endpoint regardless of the app's InteractiveServer render mode;
|
||||
- `DashboardAuthenticator` delegates bind/search to the shared
|
||||
`ZB.MOM.WW.Auth.Ldap` provider, registered by `AddZbLdapAuth(configuration,
|
||||
"MxGateway:Ldap")`. The provider performs a service-account bind, user search,
|
||||
then candidate bind, and fails closed;
|
||||
- the user's group membership (stripped to its first RDN by the provider) is
|
||||
matched against `MxGateway:Dashboard:GroupToRole`; the resolved role(s) are
|
||||
emitted as `ClaimTypes.Role` claims, alongside the per-group
|
||||
`mxgateway:ldap_group` claims;
|
||||
- a successful login signs in the `MxGateway.Dashboard` cookie scheme. The
|
||||
cookie defaults to the name `MxGatewayDashboard` (HttpOnly, SameSite=Strict,
|
||||
Secure) and can be overridden via `MxGateway:Dashboard:CookieName`;
|
||||
- a user with no matching group cannot sign in — the login screen returns the
|
||||
generic credential-rejected message;
|
||||
- antiforgery tokens guard the login and logout POSTs.
|
||||
generic credential-rejected message via `/login?error=…`;
|
||||
- antiforgery tokens guard the login and logout POSTs. `POST /logout` (and a
|
||||
`GET /logout` convenience redirect) sign the cookie out and return to
|
||||
`/login`.
|
||||
|
||||
Three authorization policies are registered:
|
||||
|
||||
@@ -443,8 +499,8 @@ Viewer role.
|
||||
|
||||
### Hub bearer flow
|
||||
|
||||
SignalR connections cannot reuse the `__Host-` cookie when the JS client
|
||||
upgrades to WebSocket — the cookie's `SameSite=Strict; Path=/` keeps it from
|
||||
SignalR connections cannot reuse the `MxGatewayDashboard` cookie when the JS
|
||||
client upgrades to WebSocket — the cookie's `SameSite=Strict; Path=/` keeps it from
|
||||
being forwarded by the browser's WebSocket layer in some edge cases. The
|
||||
dashboard mints short-lived bearer tokens for the connection:
|
||||
|
||||
@@ -480,8 +536,10 @@ Effective configuration:
|
||||
"RecentFaultLimit": 100,
|
||||
"RecentSessionLimit": 200,
|
||||
"ShowTagValues": false,
|
||||
"CookieName": null,
|
||||
"RequireHttpsCookie": true,
|
||||
"GroupToRole": {
|
||||
"GwAdmin": "Admin",
|
||||
"GwAdmin": "Administrator",
|
||||
"GwReader": "Viewer"
|
||||
}
|
||||
}
|
||||
@@ -489,6 +547,15 @@ Effective configuration:
|
||||
}
|
||||
```
|
||||
|
||||
Two cookie keys tune the auth cookie:
|
||||
|
||||
- `CookieName` overrides the cookie name. Null or blank keeps the canonical
|
||||
default `MxGatewayDashboard`, so a misconfiguration cannot leave the cookie
|
||||
unnamed.
|
||||
- `RequireHttpsCookie` (default `true`) sets the cookie `SecurePolicy` to
|
||||
`Always`. Set it to `false` for dev HTTP deployments, which relaxes the policy
|
||||
to `SameAsRequest`.
|
||||
|
||||
See [Gateway Configuration](./GatewayConfiguration.md#dashboard-options) for
|
||||
the full option table and the policies/hubs that derive from these values.
|
||||
|
||||
@@ -504,17 +571,31 @@ the full option table and the policies/hubs that derive from these values.
|
||||
|
||||
## Styling
|
||||
|
||||
The dashboard serves Bootstrap 5.3.3 assets from
|
||||
`src/ZB.MOM.WW.MxGateway.Server/wwwroot/lib/bootstrap/` and local layout/status styling
|
||||
from `src/ZB.MOM.WW.MxGateway.Server/wwwroot/css/dashboard.css`.
|
||||
Styling is layered. From base to top:
|
||||
|
||||
1. Bootstrap 5.3.3 assets served from
|
||||
`src/ZB.MOM.WW.MxGateway.Server/wwwroot/lib/bootstrap/`.
|
||||
2. The `ZB.MOM.WW.Theme` kit's `theme.css` (the technical-light design system),
|
||||
which owns the design tokens and the kit component styles. `App.razor` loads
|
||||
it through the kit's `<ThemeHead/>` component, and pairs it with
|
||||
`<ThemeScripts/>` at the end of `<body>` for the rail's interactive behavior.
|
||||
3. The local view stylesheet
|
||||
`src/ZB.MOM.WW.MxGateway.Server/wwwroot/css/site.css`, which wires the
|
||||
dashboard's own class names and Bootstrap widgets onto the kit tokens. It
|
||||
defines no hard-coded colors.
|
||||
|
||||
The minimal `/denied` page is rendered outside the Blazor circuit, so it loads
|
||||
the kit CSS directly from the static-web-asset path
|
||||
(`/_content/ZB.MOM.WW.Theme/css/theme.css` and `…/layout.css`) plus Bootstrap
|
||||
and `site.css`.
|
||||
|
||||
Recommended visual language:
|
||||
|
||||
- compact tables,
|
||||
- status badges,
|
||||
- the kit `StatusPill` for state,
|
||||
- metric cards,
|
||||
- Bootstrap alerts for faults,
|
||||
- restrained colors,
|
||||
- restrained colors drawn from the kit tokens,
|
||||
- no decorative hero sections,
|
||||
- no charting dependency for v1.
|
||||
|
||||
@@ -530,7 +611,7 @@ Dashboard unit/component tests should cover:
|
||||
|
||||
- snapshot projection,
|
||||
- dashboard auth authorization decisions,
|
||||
- login API-key validation behavior,
|
||||
- login LDAP bind and group-to-role mapping behavior,
|
||||
- pages render with empty state,
|
||||
- pages render with active sessions,
|
||||
- pages render with faulted sessions,
|
||||
@@ -557,7 +638,8 @@ Integration tests should verify:
|
||||
The first dashboard slice implements:
|
||||
|
||||
1. Blazor Server hosting in `ZB.MOM.WW.MxGateway.Server`.
|
||||
2. local Bootstrap static assets.
|
||||
2. local Bootstrap static assets plus the `ZB.MOM.WW.Theme` kit layer
|
||||
(chassis, tokens, status components).
|
||||
3. dashboard configuration binding.
|
||||
4. dashboard auth using LDAP bind + role-mapped HTTP-only cookie.
|
||||
5. `DashboardSnapshotService` projecting gateway state for read views.
|
||||
|
||||
Reference in New Issue
Block a user