Implemented power state and task streaming over signalr
This commit is contained in:
@@ -118,6 +118,36 @@ public class ServersController : Controller
|
|||||||
throw new HttpApiException("Unable to access the node the server is running on", 502);
|
throw new HttpApiException("Unable to access the node the server is running on", 502);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[HttpGet("{serverId:int}/console")]
|
||||||
|
[RequirePermission("meta.authenticated")]
|
||||||
|
public async Task<ServerConsoleResponse> GetConsole([FromRoute] int serverId)
|
||||||
|
{
|
||||||
|
var server = await GetServerWithPermCheck(serverId);
|
||||||
|
|
||||||
|
// TODO: Handle transparent proxy
|
||||||
|
|
||||||
|
var accessToken = NodeService.CreateAccessToken(server.Node, parameters =>
|
||||||
|
{
|
||||||
|
parameters.Add("type", "console");
|
||||||
|
parameters.Add("serverId", server.Id);
|
||||||
|
}, TimeSpan.FromMinutes(10));
|
||||||
|
|
||||||
|
var url = "";
|
||||||
|
|
||||||
|
if (server.Node.UseSsl)
|
||||||
|
url += "https://";
|
||||||
|
else
|
||||||
|
url += "http://";
|
||||||
|
|
||||||
|
url += $"{server.Node.Fqdn}:{server.Node.HttpPort}/api/servers/console";
|
||||||
|
|
||||||
|
return new ServerConsoleResponse()
|
||||||
|
{
|
||||||
|
Target = url,
|
||||||
|
AccessToken = accessToken
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
private async Task<Server> GetServerWithPermCheck(int serverId,
|
private async Task<Server> GetServerWithPermCheck(int serverId,
|
||||||
Func<IQueryable<Server>, IQueryable<Server>>? queryModifier = null)
|
Func<IQueryable<Server>, IQueryable<Server>>? queryModifier = null)
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using MoonCore.Attributes;
|
using MoonCore.Attributes;
|
||||||
|
using MoonCore.Extended.Helpers;
|
||||||
using MoonCore.Helpers;
|
using MoonCore.Helpers;
|
||||||
using MoonlightServers.ApiServer.Database.Entities;
|
using MoonlightServers.ApiServer.Database.Entities;
|
||||||
using MoonlightServers.DaemonShared.DaemonSide.Http.Responses.Statistics;
|
using MoonlightServers.DaemonShared.DaemonSide.Http.Responses.Statistics;
|
||||||
@@ -11,7 +12,7 @@ public class NodeService
|
|||||||
{
|
{
|
||||||
public async Task<HttpApiClient> CreateApiClient(Node node)
|
public async Task<HttpApiClient> CreateApiClient(Node node)
|
||||||
{
|
{
|
||||||
string url = "";
|
var url = "";
|
||||||
|
|
||||||
if (node.UseSsl)
|
if (node.UseSsl)
|
||||||
url += "https://";
|
url += "https://";
|
||||||
@@ -29,6 +30,9 @@ public class NodeService
|
|||||||
|
|
||||||
return new HttpApiClient(httpClient);
|
return new HttpApiClient(httpClient);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public string CreateAccessToken(Node node, Action<Dictionary<string, object>> parameters, TimeSpan duration)
|
||||||
|
=> JwtHelper.Encode(node.Token, parameters, duration);
|
||||||
|
|
||||||
public async Task<SystemStatusResponse> GetSystemStatus(Node node)
|
public async Task<SystemStatusResponse> GetSystemStatus(Node node)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -8,5 +8,6 @@ public static class ServerMetaExtensions
|
|||||||
public static async Task NotifyTask(this Server server, ServerTask task)
|
public static async Task NotifyTask(this Server server, ServerTask task)
|
||||||
{
|
{
|
||||||
server.Logger.LogInformation("Task: {task}", task);
|
server.Logger.LogInformation("Task: {task}", task);
|
||||||
|
await server.InvokeTaskAdded(task.ToString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
22
MoonlightServers.Daemon/Helpers/AccessTokenHelper.cs
Normal file
22
MoonlightServers.Daemon/Helpers/AccessTokenHelper.cs
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
using System.Text.Json;
|
||||||
|
using MoonCore.Attributes;
|
||||||
|
using MoonCore.Extended.Helpers;
|
||||||
|
using MoonlightServers.Daemon.Configuration;
|
||||||
|
|
||||||
|
namespace MoonlightServers.Daemon.Helpers;
|
||||||
|
|
||||||
|
[Singleton]
|
||||||
|
public class AccessTokenHelper
|
||||||
|
{
|
||||||
|
private readonly AppConfiguration Configuration;
|
||||||
|
|
||||||
|
public AccessTokenHelper(AppConfiguration configuration)
|
||||||
|
{
|
||||||
|
Configuration = configuration;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Process(string accessToken, out Dictionary<string, JsonElement> data)
|
||||||
|
{
|
||||||
|
return JwtHelper.TryVerifyAndDecodePayload(Configuration.Security.Token, accessToken, out data);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
namespace MoonlightServers.Daemon.Helpers;
|
||||||
|
|
||||||
|
public class ServerConsoleConnection
|
||||||
|
{
|
||||||
|
public DateTime AuthenticatedUntil { get; set; }
|
||||||
|
public int ServerId { get; set; }
|
||||||
|
}
|
||||||
44
MoonlightServers.Daemon/Helpers/ServerConsoleMonitor.cs
Normal file
44
MoonlightServers.Daemon/Helpers/ServerConsoleMonitor.cs
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
using Microsoft.AspNetCore.SignalR;
|
||||||
|
using MoonlightServers.Daemon.Models;
|
||||||
|
using MoonlightServers.DaemonShared.Enums;
|
||||||
|
|
||||||
|
namespace MoonlightServers.Daemon.Helpers;
|
||||||
|
|
||||||
|
public class ServerConsoleMonitor
|
||||||
|
{
|
||||||
|
private readonly Server Server;
|
||||||
|
private readonly IHubClients Clients;
|
||||||
|
|
||||||
|
public ServerConsoleMonitor(Server server, IHubClients clients)
|
||||||
|
{
|
||||||
|
Server = server;
|
||||||
|
Clients = clients;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Initialize()
|
||||||
|
{
|
||||||
|
Server.StateMachine.OnTransitioned += OnPowerStateChanged;
|
||||||
|
Server.OnTaskAdded += OnTaskNotify;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Destroy()
|
||||||
|
{
|
||||||
|
Server.StateMachine.OnTransitioned -= OnPowerStateChanged;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task OnTaskNotify(string task)
|
||||||
|
{
|
||||||
|
await Clients.Group($"server-{Server.Configuration.Id}").SendAsync(
|
||||||
|
"TaskNotify",
|
||||||
|
task
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task OnPowerStateChanged(ServerState serverState)
|
||||||
|
{
|
||||||
|
await Clients.Group($"server-{Server.Configuration.Id}").SendAsync(
|
||||||
|
"PowerStateChanged",
|
||||||
|
serverState.ToString()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
35
MoonlightServers.Daemon/Http/Hubs/ServerConsoleHub.cs
Normal file
35
MoonlightServers.Daemon/Http/Hubs/ServerConsoleHub.cs
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
using Microsoft.AspNetCore.SignalR;
|
||||||
|
using MoonlightServers.Daemon.Helpers;
|
||||||
|
using MoonlightServers.Daemon.Models;
|
||||||
|
using MoonlightServers.Daemon.Services;
|
||||||
|
using MoonlightServers.DaemonShared.Enums;
|
||||||
|
|
||||||
|
namespace MoonlightServers.Daemon.Http.Hubs;
|
||||||
|
|
||||||
|
public class ServerConsoleHub : Hub
|
||||||
|
{
|
||||||
|
private readonly ILogger<ServerConsoleHub> Logger;
|
||||||
|
private readonly ServerConsoleService ConsoleService;
|
||||||
|
|
||||||
|
public ServerConsoleHub(ILogger<ServerConsoleHub> logger, ServerConsoleService consoleService)
|
||||||
|
{
|
||||||
|
Logger = logger;
|
||||||
|
ConsoleService = consoleService;
|
||||||
|
}
|
||||||
|
|
||||||
|
[HubMethodName("Authenticate")]
|
||||||
|
public async Task Authenticate(string accessToken)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await ConsoleService.Authenticate(Context, accessToken);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Logger.LogError("An unhandled error occured in the Authenticate method: {e}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override async Task OnDisconnectedAsync(Exception? exception)
|
||||||
|
=> await ConsoleService.OnClientDisconnected(Context);
|
||||||
|
}
|
||||||
@@ -15,6 +15,7 @@ public class Server
|
|||||||
public StateMachine<ServerState> StateMachine { get; set; }
|
public StateMachine<ServerState> StateMachine { get; set; }
|
||||||
public ServerConfiguration Configuration { get; set; }
|
public ServerConfiguration Configuration { get; set; }
|
||||||
public string? ContainerId { get; set; }
|
public string? ContainerId { get; set; }
|
||||||
|
public event Func<string, Task> OnTaskAdded;
|
||||||
|
|
||||||
// This can be used to stop streaming when the server gets destroyed or something
|
// This can be used to stop streaming when the server gets destroyed or something
|
||||||
public CancellationTokenSource Cancellation { get; set; }
|
public CancellationTokenSource Cancellation { get; set; }
|
||||||
@@ -59,4 +60,16 @@ public class Server
|
|||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
#region Event invokers
|
||||||
|
|
||||||
|
public async Task InvokeTaskAdded(string task)
|
||||||
|
{
|
||||||
|
if(OnTaskAdded == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
await OnTaskAdded.Invoke(task).ConfigureAwait(ConfigureAwaitOptions.SuppressThrowing);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
}
|
}
|
||||||
@@ -8,6 +8,7 @@
|
|||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Docker.DotNet" Version="3.125.15" />
|
<PackageReference Include="Docker.DotNet" Version="3.125.15" />
|
||||||
|
<PackageReference Include="Microsoft.AspNetCore.SignalR" Version="1.1.0" />
|
||||||
<PackageReference Include="MoonCore" Version="1.8.1" />
|
<PackageReference Include="MoonCore" Version="1.8.1" />
|
||||||
<PackageReference Include="MoonCore.Extended" Version="1.2.4" />
|
<PackageReference Include="MoonCore.Extended" Version="1.2.4" />
|
||||||
<PackageReference Include="MoonCore.Unix" Version="1.0.0" />
|
<PackageReference Include="MoonCore.Unix" Version="1.0.0" />
|
||||||
|
|||||||
159
MoonlightServers.Daemon/Services/ServerConsoleService.cs
Normal file
159
MoonlightServers.Daemon/Services/ServerConsoleService.cs
Normal file
@@ -0,0 +1,159 @@
|
|||||||
|
using Microsoft.AspNetCore.SignalR;
|
||||||
|
using MoonCore.Attributes;
|
||||||
|
using MoonlightServers.Daemon.Helpers;
|
||||||
|
using MoonlightServers.Daemon.Http.Hubs;
|
||||||
|
|
||||||
|
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 IHubContext<ServerConsoleHub> HubContext;
|
||||||
|
|
||||||
|
public ServerConsoleService(
|
||||||
|
ILogger<ServerConsoleService> logger,
|
||||||
|
AccessTokenHelper accessTokenHelper,
|
||||||
|
ServerService serverService, IHubContext<ServerConsoleHub> hubContext)
|
||||||
|
{
|
||||||
|
Logger = logger;
|
||||||
|
AccessTokenHelper = accessTokenHelper;
|
||||||
|
ServerService = serverService;
|
||||||
|
HubContext = hubContext;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task OnClientDisconnected(HubCallerContext context)
|
||||||
|
{
|
||||||
|
ServerConsoleConnection? removedConnection;
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task Authenticate(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;
|
||||||
|
|
||||||
|
lock (Monitors)
|
||||||
|
monitor = Monitors.GetValueOrDefault(server.Configuration.Id);
|
||||||
|
|
||||||
|
if (monitor == null)
|
||||||
|
{
|
||||||
|
Logger.LogDebug("Initializing console monitor for server {id}", server.Configuration.Id);
|
||||||
|
|
||||||
|
monitor = new ServerConsoleMonitor(server, HubContext.Clients);
|
||||||
|
monitor.Initialize();
|
||||||
|
|
||||||
|
lock (Monitors)
|
||||||
|
Monitors.Add(server.Configuration.Id, monitor);
|
||||||
|
}
|
||||||
|
|
||||||
|
await HubContext.Groups.AddToGroupAsync(context.ConnectionId, $"server-{server.Configuration.Id}");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,6 +6,7 @@ using MoonCore.Extensions;
|
|||||||
using MoonCore.Helpers;
|
using MoonCore.Helpers;
|
||||||
using MoonCore.Services;
|
using MoonCore.Services;
|
||||||
using MoonlightServers.Daemon.Configuration;
|
using MoonlightServers.Daemon.Configuration;
|
||||||
|
using MoonlightServers.Daemon.Http.Hubs;
|
||||||
using MoonlightServers.Daemon.Services;
|
using MoonlightServers.Daemon.Services;
|
||||||
|
|
||||||
namespace MoonlightServers.Daemon;
|
namespace MoonlightServers.Daemon;
|
||||||
@@ -43,13 +44,17 @@ public class Startup
|
|||||||
await RegisterBase();
|
await RegisterBase();
|
||||||
await RegisterDocker();
|
await RegisterDocker();
|
||||||
await RegisterServers();
|
await RegisterServers();
|
||||||
|
await RegisterSignalR();
|
||||||
|
await RegisterCors();
|
||||||
|
|
||||||
await BuildWebApplication();
|
await BuildWebApplication();
|
||||||
|
|
||||||
await UseBase();
|
await UseBase();
|
||||||
|
await UseCors();
|
||||||
await UseBaseMiddleware();
|
await UseBaseMiddleware();
|
||||||
|
|
||||||
await MapBase();
|
await MapBase();
|
||||||
|
await MapHubs();
|
||||||
|
|
||||||
await WebApplication.RunAsync();
|
await WebApplication.RunAsync();
|
||||||
}
|
}
|
||||||
@@ -71,7 +76,6 @@ public class Startup
|
|||||||
WebApplicationBuilder.Services.AddControllers();
|
WebApplicationBuilder.Services.AddControllers();
|
||||||
|
|
||||||
WebApplicationBuilder.Services.AddApiExceptionHandler();
|
WebApplicationBuilder.Services.AddApiExceptionHandler();
|
||||||
WebApplicationBuilder.Services.AddSignalR();
|
|
||||||
|
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
@@ -248,7 +252,7 @@ public class Startup
|
|||||||
WebApplicationBuilder.Services.AddHostedService<ApplicationStateService>(
|
WebApplicationBuilder.Services.AddHostedService<ApplicationStateService>(
|
||||||
sp => sp.GetRequiredService<ApplicationStateService>()
|
sp => sp.GetRequiredService<ApplicationStateService>()
|
||||||
);
|
);
|
||||||
|
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -258,4 +262,43 @@ public class Startup
|
|||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
#region Maps
|
||||||
|
|
||||||
|
private Task RegisterSignalR()
|
||||||
|
{
|
||||||
|
WebApplicationBuilder.Services.AddSignalR();
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Task MapHubs()
|
||||||
|
{
|
||||||
|
WebApplication.MapHub<ServerConsoleHub>("api/servers/console");
|
||||||
|
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Cors
|
||||||
|
|
||||||
|
private Task RegisterCors()
|
||||||
|
{
|
||||||
|
//TODO: IMPORTANT: CHANGE !!!
|
||||||
|
WebApplicationBuilder.Services.AddCors(x =>
|
||||||
|
x.AddDefaultPolicy(builder =>
|
||||||
|
builder.AllowAnyHeader().AllowAnyOrigin().AllowAnyMethod().Build()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Task UseCors()
|
||||||
|
{
|
||||||
|
WebApplication.UseCors();
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
}
|
}
|
||||||
@@ -9,6 +9,7 @@
|
|||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="8.0.11"/>
|
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="8.0.11"/>
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="8.0.11" PrivateAssets="all"/>
|
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="8.0.11" PrivateAssets="all"/>
|
||||||
|
<PackageReference Include="Microsoft.AspNetCore.SignalR.Client" Version="8.0.11" />
|
||||||
<PackageReference Include="Moonlight.Client" Version="2.1.0"/>
|
<PackageReference Include="Moonlight.Client" Version="2.1.0"/>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,16 @@
|
|||||||
@page "/servers/{ServerId:int}"
|
@page "/servers/{ServerId:int}"
|
||||||
|
|
||||||
|
@using Microsoft.AspNetCore.SignalR.Client
|
||||||
@using MoonlightServers.Shared.Http.Responses.Users.Servers
|
@using MoonlightServers.Shared.Http.Responses.Users.Servers
|
||||||
@using MoonCore.Blazor.Tailwind.Components
|
@using MoonCore.Blazor.Tailwind.Components
|
||||||
@using MoonCore.Exceptions
|
@using MoonCore.Exceptions
|
||||||
@using MoonCore.Helpers
|
@using MoonCore.Helpers
|
||||||
|
@using MoonlightServers.Shared.Enums
|
||||||
|
|
||||||
@inject HttpApiClient ApiClient
|
@inject HttpApiClient ApiClient
|
||||||
|
|
||||||
|
@implements IAsyncDisposable
|
||||||
|
|
||||||
<LazyLoader Load="Load">
|
<LazyLoader Load="Load">
|
||||||
@if (NotFound)
|
@if (NotFound)
|
||||||
{
|
{
|
||||||
@@ -22,7 +26,19 @@
|
|||||||
{
|
{
|
||||||
<div class="card card-body justify-between py-2.5 px-5 flex-row">
|
<div class="card card-body justify-between py-2.5 px-5 flex-row">
|
||||||
<div class="flex flex-row items-center">
|
<div class="flex flex-row items-center">
|
||||||
<div class="p-2.5 rounded-full bg-success-400 me-3"></div>
|
@{
|
||||||
|
var bgColor = PowerState switch
|
||||||
|
{
|
||||||
|
ServerPowerState.Installing => "bg-primary-500",
|
||||||
|
ServerPowerState.Offline => "bg-danger-500",
|
||||||
|
ServerPowerState.Starting => "bg-warning-500",
|
||||||
|
ServerPowerState.Stopping => "bg-warning-500",
|
||||||
|
ServerPowerState.Online => "bg-success-500",
|
||||||
|
_ => "bg-gray-500"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
<div class="p-2.5 rounded-full @bgColor me-3"></div>
|
||||||
|
|
||||||
<div class="flex flex-col">
|
<div class="flex flex-col">
|
||||||
<div class="hidden sm:flex text-lg font-semibold">@Server.Name</div>
|
<div class="hidden sm:flex text-lg font-semibold">@Server.Name</div>
|
||||||
@@ -99,16 +115,57 @@
|
|||||||
|
|
||||||
private ServerDetailResponse Server;
|
private ServerDetailResponse Server;
|
||||||
private bool NotFound = false;
|
private bool NotFound = false;
|
||||||
|
private ServerPowerState PowerState;
|
||||||
|
|
||||||
private string CurrentTask = "";
|
private string CurrentTask = "";
|
||||||
|
private HubConnection ConsoleConnection;
|
||||||
|
|
||||||
private async Task Load(LazyLoader _)
|
private async Task Load(LazyLoader _)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
// Load meta data
|
||||||
Server = await ApiClient.GetJson<ServerDetailResponse>(
|
Server = await ApiClient.GetJson<ServerDetailResponse>(
|
||||||
$"api/servers/{ServerId}"
|
$"api/servers/{ServerId}"
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Load initial status for first render
|
||||||
|
var status = await ApiClient.GetJson<ServerStatusResponse>(
|
||||||
|
$"api/servers/{ServerId}/status"
|
||||||
|
);
|
||||||
|
|
||||||
|
PowerState = status.PowerState;
|
||||||
|
|
||||||
|
// Load console meta
|
||||||
|
var consoleDetails = await ApiClient.GetJson<ServerConsoleResponse>(
|
||||||
|
$"api/servers/{ServerId}/console"
|
||||||
|
);
|
||||||
|
|
||||||
|
// Build signal r
|
||||||
|
ConsoleConnection = new HubConnectionBuilder()
|
||||||
|
.WithUrl(consoleDetails.Target)
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
// Define handlers
|
||||||
|
ConsoleConnection.On<string>("PowerStateChanged", async powerStateStr =>
|
||||||
|
{
|
||||||
|
if(!Enum.TryParse(powerStateStr, out ServerPowerState receivedState))
|
||||||
|
return;
|
||||||
|
|
||||||
|
PowerState = receivedState;
|
||||||
|
await InvokeAsync(StateHasChanged);
|
||||||
|
});
|
||||||
|
|
||||||
|
ConsoleConnection.On<string>("TaskNotify", async task =>
|
||||||
|
{
|
||||||
|
await AddTask(Formatter.ConvertCamelCaseToSpaces(task));
|
||||||
|
});
|
||||||
|
|
||||||
|
// Connect
|
||||||
|
await ConsoleConnection.StartAsync();
|
||||||
|
|
||||||
|
// Authenticate
|
||||||
|
await ConsoleConnection.SendAsync("Authenticate", consoleDetails.AccessToken);
|
||||||
}
|
}
|
||||||
catch (HttpApiException e)
|
catch (HttpApiException e)
|
||||||
{
|
{
|
||||||
@@ -153,4 +210,12 @@
|
|||||||
await InvokeAsync(StateHasChanged);
|
await InvokeAsync(StateHasChanged);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async ValueTask DisposeAsync()
|
||||||
|
{
|
||||||
|
if (ConsoleConnection.State == HubConnectionState.Connected)
|
||||||
|
await ConsoleConnection.StopAsync();
|
||||||
|
|
||||||
|
await ConsoleConnection.DisposeAsync();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
namespace MoonlightServers.Shared.Http.Responses.Users.Servers;
|
||||||
|
|
||||||
|
public class ServerConsoleResponse
|
||||||
|
{
|
||||||
|
public string Target { get; set; }
|
||||||
|
public string AccessToken { get; set; }
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user