dashboard: clear deferred items — EventsHub publisher + doc refresh
EventsHub publisher (closes the v2.1 follow-up flagged in the previous commit)
EventStreamService now mirrors every MxEvent it forwards to a gRPC client
into the `EventsHub` group for the session. The fan-out goes through a new
singleton `IDashboardEventBroadcaster`:
* IDashboardEventBroadcaster — abstraction so EventStreamService doesn't
take a direct dependency on SignalR.
* DashboardEventBroadcaster — singleton implementation that hands the
SendAsync to IHubContext<EventsHub> as fire-and-forget. Errors are
logged at debug and dropped so the source gRPC stream is never
blocked.
EventStreamService now takes IDashboardEventBroadcaster as a ctor parameter
and calls Publish(sessionId, publicEvent) once per event after sequence
filtering, before the bounded queue write. Test fixtures and the live
integration harness pass NullDashboardEventBroadcaster.Instance so the
broadcaster is a no-op in unit tests.
SessionDetailsPage adds a "Recent events" panel:
* implements IAsyncDisposable
* opens a second HubConnection via DashboardHubConnectionFactory targeting
/hubs/events
* calls SubscribeSession(SessionId) on Start
* renders the most recent 50 events in a small table (worker seq, family,
server/item handle, alarm reference when the event is OnAlarmTransition)
* shows a live/offline conn-pill driven by HubConnection.Closed /
Reconnected events
The dashboard mirror is intentionally passive — events appear only while a
gRPC client is also consuming that session's events. Documented as such in
the empty-state copy and in GatewayDashboardDesign.md.
Documentation refresh
Every doc that referenced the retired options (PathBase, RequireAdminScope,
RequiredGroup) and the old API-key-cookie auth flow is updated to describe
the new model:
* CLAUDE.md — Authentication section now explains LDAP bind +
GroupToRole + HubToken bearer flow.
* gateway.md — Dashboard section: root-mounted routes, snapshot/alarms/
events SignalR hubs, LDAP cookie + bearer scheme.
* docs/GatewayConfiguration.md — drop PathBase / RequireAdminScope rows,
add GroupToRole row, append "Authorization policies" and "SignalR hubs"
subsections describing the three policies and the /hubs/* endpoints.
* docs/GatewayDashboardDesign.md — hosting model (root mount, new
endpoint layout), Realtime Updates rewritten as a hub table
(DashboardSnapshotHub / AlarmsHub / EventsHub with producers, payloads,
and routing), Authentication And Authorization rewritten around LDAP +
role mapping + the hub bearer flow, Configuration block updated.
* docs/GatewayProcessDesign.md — security-section dashboard paragraph
and the example config block both refreshed to LDAP/role auth.
* docs/ImplementationPlanGateway.md — dashboard-auth deliverable list
updated (LDAP bind + GroupToRole + /hubs/token bearer mint replace the
API-key login flow).
* docs/GatewayTesting.md — DashboardLdapLiveTests blurb describes the
GroupToRole fixture (`{ GwAdmin: Admin }`) instead of the retired
RequiredGroup default; success-path assertion explains the role-claim
check.
Verification: 475 server tests, 275 worker tests (+ 9 dev-rig skips), 18
integration tests (live MxAccess + LDAP + Galaxy) all pass — including the
live worker smoke test fixture that now constructs EventStreamService with
the new broadcaster parameter.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -45,13 +45,15 @@ paths, timeouts, queue sizes, enum values, or protocol values are invalid.
|
||||
},
|
||||
"Dashboard": {
|
||||
"Enabled": true,
|
||||
"PathBase": "/dashboard",
|
||||
"RequireAdminScope": true,
|
||||
"AllowAnonymousLocalhost": true,
|
||||
"SnapshotIntervalMilliseconds": 1000,
|
||||
"RecentFaultLimit": 100,
|
||||
"RecentSessionLimit": 200,
|
||||
"ShowTagValues": false
|
||||
"ShowTagValues": false,
|
||||
"GroupToRole": {
|
||||
"GwAdmin": "Admin",
|
||||
"GwReader": "Viewer"
|
||||
}
|
||||
},
|
||||
"Protocol": {
|
||||
"WorkerProtocolVersion": 1,
|
||||
@@ -142,17 +144,52 @@ the affected stream while the MXAccess session remains active.
|
||||
|
||||
| Option | Default | Description |
|
||||
|--------|---------|-------------|
|
||||
| `MxGateway:Dashboard:Enabled` | `true` | Enables Blazor Server dashboard route mapping. |
|
||||
| `MxGateway:Dashboard:PathBase` | `/dashboard` | Base path for dashboard routes. When the dashboard is enabled, this value is required and must start with `/`. |
|
||||
| `MxGateway:Dashboard:RequireAdminScope` | `true` | Requires API keys used for dashboard login to carry the `admin` scope. |
|
||||
| `MxGateway:Dashboard:Enabled` | `true` | Enables Blazor Server dashboard route mapping. The dashboard mounts at the host root (`/`); there is no separate path-base prefix. |
|
||||
| `MxGateway:Dashboard:AllowAnonymousLocalhost` | `true` | Allows loopback dashboard requests to bypass the dashboard cookie requirement for local development. Remote requests still require dashboard authentication. |
|
||||
| `MxGateway:Dashboard:SnapshotIntervalMilliseconds` | `1000` | Dashboard snapshot refresh interval used by realtime Blazor pages. |
|
||||
| `MxGateway:Dashboard:SnapshotIntervalMilliseconds` | `1000` | Dashboard snapshot refresh interval used by the snapshot SignalR hub and the pages that subscribe to it. |
|
||||
| `MxGateway:Dashboard:RecentFaultLimit` | `100` | Maximum number of fault summaries projected into each dashboard snapshot. |
|
||||
| `MxGateway:Dashboard:RecentSessionLimit` | `200` | Maximum number of session summaries projected into each dashboard snapshot. |
|
||||
| `MxGateway:Dashboard:ShowTagValues` | `false` | Reserved display control for tag values. The dashboard does not show full tag values by default. |
|
||||
| `MxGateway:Dashboard:GroupToRole` | _(empty)_ | LDAP group → dashboard role mapping. Keys are LDAP group names (short CN or full DN — leading-RDN match). Values must be `Admin` (read/write, API-key CRUD) or `Viewer` (read-only). A user whose LDAP groups don't intersect this map cannot sign in; with no mapping at all, only the loopback bypass admits anyone. |
|
||||
|
||||
`SnapshotIntervalMilliseconds` must be greater than zero. `RecentFaultLimit`
|
||||
and `RecentSessionLimit` must be greater than or equal to zero.
|
||||
`GroupToRole` values are validated at startup; invalid role names fail
|
||||
validation. Emptiness is allowed (a closed deployment that admits no LDAP
|
||||
users) but practical deployments populate at least one Admin group.
|
||||
|
||||
### Authorization policies
|
||||
|
||||
Three authorization policies are registered out of these options:
|
||||
|
||||
- `MxGateway.Dashboard.Viewer` — gates the Razor component routes. Satisfied by
|
||||
either dashboard role (Admin or Viewer), by `AllowAnonymousLocalhost` on
|
||||
loopback, or by `Authentication.Mode = Disabled`.
|
||||
- `MxGateway.Dashboard.Admin` — gates write-capable surfaces (API-key CRUD).
|
||||
Satisfied only by the Admin role (same environmental bypasses).
|
||||
- `MxGateway.Dashboard.HubClients` — attached to the SignalR hubs. Accepts
|
||||
either the dashboard cookie scheme or the `MxGateway.Dashboard.HubToken`
|
||||
bearer scheme (used by SignalR's WebSocket upgrade path where the HttpOnly
|
||||
cookie can't be forwarded).
|
||||
|
||||
### SignalR hubs
|
||||
|
||||
When the dashboard is enabled, three hubs are mapped under `/hubs/*`:
|
||||
|
||||
- `GET /hubs/snapshot` — pushes `DashboardSnapshot` whenever the snapshot
|
||||
service produces a new one. Drives every page that inherits
|
||||
`DashboardPageBase`; replaces the earlier polling loop.
|
||||
- `GET /hubs/alarms` — re-broadcasts the `AlarmFeedMessage` stream from the
|
||||
central alarm monitor to all connected clients (group `__alarms__`).
|
||||
- `GET /hubs/events` — per-session MxEvent feed. Clients call
|
||||
`SubscribeSession(sessionId)` to join `session:{id}`. Events are mirrored
|
||||
from the corresponding gRPC `StreamEvents` call as a fire-and-forget
|
||||
side-effect; the dashboard only sees events while a gRPC client is also
|
||||
subscribed to that session.
|
||||
|
||||
`GET /hubs/token` (cookie-only) mints a 30-minute data-protected bearer
|
||||
token for the calling user; the Blazor pages use it via
|
||||
`DashboardHubConnectionFactory` to authenticate the SignalR connection.
|
||||
|
||||
## Protocol Options
|
||||
|
||||
|
||||
Reference in New Issue
Block a user