Implemented server console streaming in the frontend with xterm. Added logs endpoint for servers

This commit is contained in:
2024-12-31 17:57:39 +01:00
parent 6d674e153a
commit f652945a3f
19 changed files with 419 additions and 163 deletions

View File

@@ -8,152 +8,61 @@ namespace MoonlightServers.Daemon.Services;
[Singleton]
public class ServerConsoleService
{
private readonly Dictionary<int, ServerConsoleMonitor> Monitors = new();
private readonly Dictionary<string, ServerConsoleConnection> Connections = new();
private readonly ILogger<ServerConsoleService> Logger;
private readonly AccessTokenHelper AccessTokenHelper;
private readonly ServerService ServerService;
private readonly IServiceProvider ServiceProvider;
private readonly IHubContext<ServerConsoleHub> HubContext;
private readonly Dictionary<string, ServerConsoleConnection> Connections = new();
public ServerConsoleService(
ILogger<ServerConsoleService> logger,
AccessTokenHelper accessTokenHelper,
ServerService serverService, IHubContext<ServerConsoleHub> hubContext)
IServiceProvider serviceProvider
)
{
Logger = logger;
AccessTokenHelper = accessTokenHelper;
ServerService = serverService;
HubContext = hubContext;
ServiceProvider = serviceProvider;
}
public Task OnClientDisconnected(HubCallerContext context)
public async Task InitializeClient(HubCallerContext context)
{
ServerConsoleConnection? removedConnection;
var connection = new ServerConsoleConnection(
ServiceProvider.GetRequiredService<ServerService>(),
ServiceProvider.GetRequiredService<ILogger<ServerConsoleConnection>>(),
ServiceProvider.GetRequiredService<AccessTokenHelper>(),
ServiceProvider.GetRequiredService<IHubContext<ServerConsoleHub>>()
);
lock (Connections)
{
removedConnection = Connections.GetValueOrDefault(context.ConnectionId);
if(removedConnection != null)
Connections.Remove(context.ConnectionId);
}
// Client never authenticated themselves, nothing to do
if(removedConnection == null)
return Task.CompletedTask;
Logger.LogDebug("Authenticated client {id} disconnected", context.ConnectionId);
// Count remaining clients requesting the same resource
int count;
Connections[context.ConnectionId] = connection;
lock (Connections)
{
count = Connections
.Values
.Count(x => x.ServerId == removedConnection.ServerId);
}
if(count > 0)
return Task.CompletedTask;
ServerConsoleMonitor? monitor;
lock (Monitors)
monitor = Monitors.GetValueOrDefault(removedConnection.ServerId);
if(monitor == null)
return Task.CompletedTask;
Logger.LogDebug("Destroying console monitor for server {id}", removedConnection.ServerId);
monitor.Destroy();
lock (Monitors)
Monitors.Remove(removedConnection.ServerId);
return Task.CompletedTask;
await connection.Initialize(context);
}
public async Task Authenticate(HubCallerContext context, string accessToken)
public async Task AuthenticateClient(HubCallerContext context, string accessToken)
{
// Validate access token
if (!AccessTokenHelper.Process(accessToken, out var accessData))
{
Logger.LogDebug("Received invalid or expired access token. Closing connection");
context.Abort();
return;
}
// Validate access token data
if (!accessData.ContainsKey("type") || !accessData.ContainsKey("serverId"))
{
Logger.LogDebug("Received invalid access token: Required parameters are missing. Closing connection");
context.Abort();
return;
}
// Validate access token type
var type = accessData["type"].GetString()!;
if (type != "console")
{
Logger.LogDebug("Received invalid access token: Invalid type '{type}'. Closing connection", type);
context.Abort();
return;
}
var serverId = accessData["serverId"].GetInt32();
var server = ServerService.GetServer(serverId);
if (server == null)
{
Logger.LogDebug("Received invalid access token: No server found with the requested id. Closing connection");
context.Abort();
return;
}
ServerConsoleConnection? connection;
lock (Connections)
{
connection = Connections
.GetValueOrDefault(context.ConnectionId);
}
if (connection == null) // If no existing connection has been found, we create a new one
{
connection = new()
{
ServerId = server.Configuration.Id,
AuthenticatedUntil = DateTime.UtcNow.AddMinutes(10)
};
lock (Connections)
Connections.Add(context.ConnectionId, connection);
Logger.LogDebug("Connection {id} authenticated successfully", context.ConnectionId);
}
else
Logger.LogDebug("Connection {id} re-authenticated successfully", context.ConnectionId);
ServerConsoleMonitor? monitor;
connection = Connections.GetValueOrDefault(context.ConnectionId);
lock (Monitors)
monitor = Monitors.GetValueOrDefault(server.Configuration.Id);
if(connection == null)
return;
if (monitor == null)
{
Logger.LogDebug("Initializing console monitor for server {id}", server.Configuration.Id);
monitor = new ServerConsoleMonitor(server, HubContext.Clients);
monitor.Initialize();
await connection.Authenticate(context, accessToken);
}
lock (Monitors)
Monitors.Add(server.Configuration.Id, monitor);
}
public async Task DestroyClient(HubCallerContext context)
{
ServerConsoleConnection? connection;
await HubContext.Groups.AddToGroupAsync(context.ConnectionId, $"server-{server.Configuration.Id}");
lock (Connections)
connection = Connections.GetValueOrDefault(context.ConnectionId);
if(connection == null)
return;
await connection.Destroy(context);
lock (Connections)
Connections.Remove(context.ConnectionId);
}
}