using FrameworkBLL.Dashboard; using GB5Shared.DTO.Framework.Login; using Microsoft.AspNetCore.SignalR; using Microsoft.Extensions.Logging; namespace FrameworkSL.Hubs.Dashboard { /// /// DashboardHub — server-push hub so an open dashboard can refetch a widget's data the moment /// its underlying dataset actually changes (FFINANCE posting run, KPI posting run, a BRS /// instrument status change, a new Compliance alert, ...), instead of only on a manual reload /// or a fixed poll interval. Mirrors SysJobHub's own shape exactly. /// /// Clients connect and are placed into a tenant-scoped group — a dashboard's "did this /// dataset/OU change" question is tenant-wide, not per-user. /// /// No client-initiated methods — this hub is server-push only. /// public class DashboardHub(ILogger _logger) : Hub { internal static string TenantGroup(int clientId) => $"dashboard:client:{clientId}"; public override async Task OnConnectedAsync() { var login = GetLoginDTO(); await Groups.AddToGroupAsync(Context.ConnectionId, TenantGroup(login.ClientId)); _logger.LogInformation("DashboardHub connected: {ConnectionId} tenant {ClientId}", Context.ConnectionId, login.ClientId); await base.OnConnectedAsync(); } public override async Task OnDisconnectedAsync(Exception? exception) { var login = GetLoginDTO(); await Groups.RemoveFromGroupAsync(Context.ConnectionId, TenantGroup(login.ClientId)); if (exception is not null) _logger.LogWarning(exception, "DashboardHub disconnected with error: {ConnectionId}", Context.ConnectionId); await base.OnDisconnectedAsync(exception); } private LoginDTO GetLoginDTO() { var http = Context.GetHttpContext() ?? throw new InvalidOperationException("Hub invoked outside HTTP context."); var raw = http.Request.Headers["Login"].FirstOrDefault() ?? throw new InvalidOperationException("Login header missing."); return System.Text.Json.JsonSerializer.Deserialize(raw) ?? throw new InvalidOperationException("Login header is invalid JSON."); } } }