6733519ae5
SEC-27: DashboardSnapshot.GatewayStatus was hardcoded Healthy. It is now the aggregate HealthCheckService status, evaluated once per snapshot tick (2s bound, zero-wait gate) and cached for GetSnapshot; Unknown before the first evaluation, on failure, or without a health service. The startup-static EffectiveGatewayConfiguration is JsonIgnore'd on the snapshot, so /hubs/snapshot ticks carry only volatile state. The hub sends it once per connection (ConfigurationUpdated) and serves GetConfiguration(). Snapshot broadcasts are time-bounded (1s) so one slow client cannot delay ticks. StatusBadge maps Unhealthy to Bad.
59 lines
2.6 KiB
C#
59 lines
2.6 KiB
C#
using Microsoft.AspNetCore.Authorization;
|
|
using Microsoft.AspNetCore.SignalR;
|
|
using ZB.MOM.WW.MxGateway.Server.Configuration;
|
|
|
|
namespace ZB.MOM.WW.MxGateway.Server.Dashboard.Hubs;
|
|
|
|
/// <summary>
|
|
/// SignalR hub that pushes a fresh <see cref="DashboardSnapshot"/> on every
|
|
/// snapshot refresh. New connections receive the effective configuration and the
|
|
/// current snapshot immediately via <see cref="OnConnectedAsync"/>; subsequent
|
|
/// refreshes are broadcast by <see cref="DashboardSnapshotPublisher"/> without the
|
|
/// configuration, which is startup-static and serialized only once per connection.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Connections are counted into <see cref="DashboardSnapshotHubConnectionCounter"/>
|
|
/// so <see cref="DashboardSnapshotPublisher"/> can stop building and broadcasting
|
|
/// snapshots while nobody is watching.
|
|
/// </remarks>
|
|
[Authorize(Policy = DashboardAuthenticationDefaults.HubClientsPolicy)]
|
|
public sealed class DashboardSnapshotHub(
|
|
IDashboardSnapshotService snapshotService,
|
|
DashboardSnapshotHubConnectionCounter connectionCounter) : Hub
|
|
{
|
|
/// <summary>Method name used to push snapshot updates to clients.</summary>
|
|
public const string SnapshotMessage = "SnapshotUpdated";
|
|
|
|
/// <summary>
|
|
/// Method name of the one-per-connection effective-configuration push. Snapshot
|
|
/// ticks omit the configuration, so a client keeps the copy it got here.
|
|
/// </summary>
|
|
public const string ConfigurationMessage = "ConfigurationUpdated";
|
|
|
|
/// <summary>
|
|
/// Returns the effective gateway configuration — the same object
|
|
/// <see cref="ConfigurationMessage"/> carries — for a client that needs it again.
|
|
/// </summary>
|
|
/// <returns>The effective configuration.</returns>
|
|
public EffectiveGatewayConfiguration GetConfiguration() => snapshotService.GetSnapshot().Configuration;
|
|
|
|
/// <inheritdoc />
|
|
public override async Task OnConnectedAsync()
|
|
{
|
|
// Count the viewer before seeding it, so the publisher resumes its tick
|
|
// no later than the first snapshot this connection renders.
|
|
connectionCounter.Increment();
|
|
DashboardSnapshot snapshot = snapshotService.GetSnapshot();
|
|
await Clients.Caller.SendAsync(ConfigurationMessage, snapshot.Configuration).ConfigureAwait(false);
|
|
await Clients.Caller.SendAsync(SnapshotMessage, snapshot).ConfigureAwait(false);
|
|
await base.OnConnectedAsync().ConfigureAwait(false);
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public override async Task OnDisconnectedAsync(Exception? exception)
|
|
{
|
|
connectionCounter.Decrement();
|
|
await base.OnDisconnectedAsync(exception).ConfigureAwait(false);
|
|
}
|
|
}
|