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:
+15
-12
@@ -205,20 +205,23 @@ SQL-injection framing does not apply to a constant-query layer.
|
||||
`MXGATEWAY_RUN_LIVE_LDAP_TESTS=1` is set because it binds against the GLAuth
|
||||
service described in `glauth.md`.
|
||||
|
||||
The suite builds the authenticator with a default `GatewayOptions`, so
|
||||
`LdapOptions.RequiredGroup` keeps its `GwAdmin` default. `GwAdmin` is the
|
||||
gateway-specific dashboard-admin role and is **not** part of the five baseline
|
||||
GLAuth role groups — it must be provisioned before the LDAP live tests pass.
|
||||
`AuthenticateAsync_AdminInGwAdminGroup_Succeeds` fails (rather than skips) when
|
||||
GLAuth has only the baseline groups, so this is a hard prerequisite beyond "LDAP
|
||||
is up." See the "Adding a gw-specific group" section of `glauth.md` for the
|
||||
provisioning step that adds `GwAdmin` and grants it to `admin`.
|
||||
The suite builds the authenticator with `GatewayOptions.Dashboard.GroupToRole`
|
||||
set to `{ GwAdmin: Admin }`. `GwAdmin` is the gateway-specific
|
||||
dashboard-admin role and is **not** part of the five baseline GLAuth role
|
||||
groups — it must be provisioned before the LDAP live tests pass.
|
||||
`AuthenticateAsync_AdminInGwAdminGroup_Succeeds` fails (rather than skips)
|
||||
when GLAuth has only the baseline groups, so this is a hard prerequisite
|
||||
beyond "LDAP is up." See the "Adding a gw-specific group" section of
|
||||
`glauth.md` for the provisioning step that adds `GwAdmin` and grants it to
|
||||
`admin`.
|
||||
|
||||
The suite covers both the success path and the `DashboardAuthenticator` failure
|
||||
branches: `admin` in `GwAdmin` succeeds; `readonly` is denied for missing group;
|
||||
`admin` with a wrong password is rejected by the candidate bind without leaking
|
||||
the password into `FailureMessage`; an unknown username yields no candidate; and
|
||||
an unreachable LDAP server is absorbed into a failed result rather than throwing.
|
||||
branches: `admin` whose LDAP groups resolve to the `Admin` role succeeds and
|
||||
emits the role claim; `readonly` is denied because no group in their `memberOf`
|
||||
appears in `GroupToRole`; `admin` with a wrong password is rejected by the
|
||||
candidate bind without leaking the password into `FailureMessage`; an unknown
|
||||
username yields no candidate; and an unreachable LDAP server is absorbed into a
|
||||
failed result rather than throwing.
|
||||
|
||||
Run the LDAP live tests explicitly:
|
||||
|
||||
|
||||
Reference in New Issue
Block a user