using System.Security.Claims; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; using ZB.MOM.WW.MxGateway.Server.Sessions; namespace ZB.MOM.WW.MxGateway.Server.Dashboard; public sealed class DashboardSessionAdminService( ISessionManager sessionManager, IHttpContextAccessor httpContextAccessor, ILogger? logger = null) : IDashboardSessionAdminService { private const string UnauthorizedMessage = "Sign in as an Admin to close sessions or kill workers."; private const string KillReason = "dashboard-admin-kill"; private readonly ILogger _logger = logger ?? NullLogger.Instance; public bool CanManage(ClaimsPrincipal user) { ArgumentNullException.ThrowIfNull(user); return user.Identity?.IsAuthenticated == true && user.IsInRole(DashboardRoles.Admin); } public async Task CloseSessionAsync( ClaimsPrincipal user, string sessionId, CancellationToken cancellationToken) { if (!CanManage(user)) { return DashboardSessionAdminResult.Fail(UnauthorizedMessage); } if (string.IsNullOrWhiteSpace(sessionId)) { return DashboardSessionAdminResult.Fail("Session id is required."); } string actor = ResolveActor(user); try { SessionCloseResult result = await sessionManager .CloseSessionAsync(sessionId, cancellationToken) .ConfigureAwait(false); _logger.LogInformation( "Dashboard admin {Actor} closed session {SessionId} from {RemoteAddress}; alreadyClosed={AlreadyClosed}.", actor, sessionId, ResolveRemoteAddress(), result.AlreadyClosed); return DashboardSessionAdminResult.Success( result.AlreadyClosed ? $"Session {sessionId} was already closed." : $"Session {sessionId} closed."); } catch (SessionManagerException exception) when (exception.ErrorCode == SessionManagerErrorCode.SessionNotFound) { return DashboardSessionAdminResult.Fail($"Session {sessionId} was not found."); } catch (SessionManagerException exception) { _logger.LogWarning( exception, "Dashboard admin {Actor} close failed for session {SessionId}.", actor, sessionId); return DashboardSessionAdminResult.Fail( $"Close failed: {exception.Message}"); } } public async Task KillWorkerAsync( ClaimsPrincipal user, string sessionId, CancellationToken cancellationToken) { if (!CanManage(user)) { return DashboardSessionAdminResult.Fail(UnauthorizedMessage); } if (string.IsNullOrWhiteSpace(sessionId)) { return DashboardSessionAdminResult.Fail("Session id is required."); } string actor = ResolveActor(user); try { SessionCloseResult result = await sessionManager .KillWorkerAsync(sessionId, KillReason, cancellationToken) .ConfigureAwait(false); _logger.LogInformation( "Dashboard admin {Actor} killed worker for session {SessionId} from {RemoteAddress}; alreadyClosed={AlreadyClosed}.", actor, sessionId, ResolveRemoteAddress(), result.AlreadyClosed); return DashboardSessionAdminResult.Success( result.AlreadyClosed ? $"Session {sessionId} was already closed." : $"Worker for session {sessionId} killed."); } catch (SessionManagerException exception) when (exception.ErrorCode == SessionManagerErrorCode.SessionNotFound) { return DashboardSessionAdminResult.Fail($"Session {sessionId} was not found."); } catch (SessionManagerException exception) { _logger.LogWarning( exception, "Dashboard admin {Actor} kill failed for session {SessionId}.", actor, sessionId); return DashboardSessionAdminResult.Fail( $"Kill failed: {exception.Message}"); } } private static string ResolveActor(ClaimsPrincipal user) { return user.Identity?.Name ?? ""; } private string? ResolveRemoteAddress() { return httpContextAccessor.HttpContext?.Connection.RemoteIpAddress?.ToString(); } }