0032d2dc44
- docs/plans/2026-06-14-deferred-followups.md: mark D1 as executed (commit 4af24b9; metric emitted at DashboardSnapshotService.cs:198); note D2 resolved as no-op; D3-D5 remain pending - docs/AlarmClientDiscovery.md §5: rewrite STA "production fix needed" to past tense — alarms now route through GatewayAlarmMonitor/worker STA - EventsHub.cs: replace stale "publisher side is a future follow-up" comment; DashboardEventBroadcaster is live and DI-registered - CLAUDE.md: fix all project-name drift (src/MxGateway.* → src/ZB.MOM.WW.MxGateway.*; MxGateway.sln → ZB.MOM.WW.MxGateway.slnx; clients/dotnet/MxGateway.Client.sln → ZB.MOM.WW.MxGateway.Client.slnx) - GalaxyRepositoryGrpcService.cs: remove dead MapSqlException method and its IDE0051 suppression pragma; drop now-unused ILogger ctor param and Microsoft.Data.SqlClient using; build confirmed 0 warnings/errors
69 lines
3.1 KiB
C#
69 lines
3.1 KiB
C#
using Microsoft.AspNetCore.Authorization;
|
|
using Microsoft.AspNetCore.SignalR;
|
|
|
|
namespace ZB.MOM.WW.MxGateway.Server.Dashboard.Hubs;
|
|
|
|
/// <summary>
|
|
/// SignalR hub for per-session MxEvent push. Clients call
|
|
/// <see cref="SubscribeSession"/> to join the group for a specific
|
|
/// session; <see cref="DashboardEventBroadcaster"/> sends messages to
|
|
/// <c>session:{id}</c> as events arrive from the live gRPC stream.
|
|
/// </summary>
|
|
[Authorize(Policy = DashboardAuthenticationDefaults.HubClientsPolicy)]
|
|
public sealed class EventsHub : Hub
|
|
{
|
|
/// <summary>Method name used to push individual <c>MxEvent</c> values to clients.</summary>
|
|
public const string EventMessage = "MxEvent";
|
|
|
|
/// <summary>Computes the SignalR group name for a given session id.</summary>
|
|
/// <param name="sessionId">The session id.</param>
|
|
/// <returns>The group name for the session.</returns>
|
|
public static string GroupName(string sessionId) => $"session:{sessionId}";
|
|
|
|
/// <summary>
|
|
/// Subscribes the calling SignalR connection to the per-session events
|
|
/// group, so that events broadcast by
|
|
/// <see cref="DashboardEventBroadcaster"/> for that session reach this
|
|
/// client.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Server-038: in v1 the hub-level <see cref="AuthorizeAttribute"/>
|
|
/// (<c>HubClientsPolicy</c>) only checks that the caller carries one of
|
|
/// the dashboard roles (Admin or Viewer); both roles may subscribe to
|
|
/// any session id they choose. This is acceptable today because (a) the
|
|
/// dashboard's per-session views show non-secret session metadata that
|
|
/// any authenticated dashboard user can already see, and (b) value
|
|
/// logging in the source gRPC stream is gated by the same redaction
|
|
/// policy that protects logs. The per-session ACL that gates the gRPC
|
|
/// <c>StreamEvents</c> RPC is intentionally not yet mirrored here.
|
|
/// TODO(per-session-acl): once a role/scope is introduced that scopes a
|
|
/// Viewer to a specific session or tenant, add a session-access check
|
|
/// at this seam — either inline (consult the per-user allowed-session
|
|
/// set on <c>Context.User</c> claims / <c>Context.Items</c>) or via a
|
|
/// dedicated authorization policy applied to the hub method itself.
|
|
/// </remarks>
|
|
/// <param name="sessionId">Session id to subscribe the caller to.</param>
|
|
public Task SubscribeSession(string sessionId)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(sessionId))
|
|
{
|
|
return Task.CompletedTask;
|
|
}
|
|
|
|
return Groups.AddToGroupAsync(Context.ConnectionId, GroupName(sessionId));
|
|
}
|
|
|
|
/// <summary>Unsubscribes the calling SignalR connection from the per-session events group.</summary>
|
|
/// <param name="sessionId">Session id to unsubscribe the caller from.</param>
|
|
/// <returns>A task representing the unsubscription operation.</returns>
|
|
public Task UnsubscribeSession(string sessionId)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(sessionId))
|
|
{
|
|
return Task.CompletedTask;
|
|
}
|
|
|
|
return Groups.RemoveFromGroupAsync(Context.ConnectionId, GroupName(sessionId));
|
|
}
|
|
}
|