diff --git a/Moonlight/Features/Servers/Actions/ServerServiceDefinition.cs b/Moonlight/Features/Servers/Actions/ServerServiceDefinition.cs index 755c2c5d..9b405c4f 100644 --- a/Moonlight/Features/Servers/Actions/ServerServiceDefinition.cs +++ b/Moonlight/Features/Servers/Actions/ServerServiceDefinition.cs @@ -1,6 +1,6 @@ - using Moonlight.Features.Servers.UI.Layouts; using Moonlight.Features.ServiceManagement.Models.Abstractions; +using Console = Moonlight.Features.Servers.UI.UserViews.Console; namespace Moonlight.Features.Servers.Actions; @@ -8,9 +8,12 @@ public class ServerServiceDefinition : ServiceDefinition { public override ServiceActions Actions => new ServerActions(); public override Type ConfigType => typeof(ServerConfig); + public override async Task BuildUserView(ServiceViewContext context) { context.Layout = typeof(UserLayout); + + await context.AddPage("Console", "/console", "bx bx-sm bxs-terminal"); } public override Task BuildAdminView(ServiceViewContext context) diff --git a/Moonlight/Features/Servers/Helpers/MetaCache.cs b/Moonlight/Features/Servers/Helpers/MetaCache.cs index de4b5609..cb25932f 100644 --- a/Moonlight/Features/Servers/Helpers/MetaCache.cs +++ b/Moonlight/Features/Servers/Helpers/MetaCache.cs @@ -24,4 +24,15 @@ public class MetaCache return Task.CompletedTask; } + + public Task Get(int id) + { + lock (Cache) + { + if(!Cache.ContainsKey(id)) + Cache.Add(id, Activator.CreateInstance()); + + return Task.FromResult(Cache[id]); + } + } } \ No newline at end of file diff --git a/Moonlight/Features/Servers/Http/Controllers/NodeController.cs b/Moonlight/Features/Servers/Http/Controllers/NodeController.cs index 1d807819..8bbfc640 100644 --- a/Moonlight/Features/Servers/Http/Controllers/NodeController.cs +++ b/Moonlight/Features/Servers/Http/Controllers/NodeController.cs @@ -1,7 +1,6 @@ using System.Net.WebSockets; using Microsoft.AspNetCore.Mvc; using MoonCore.Helpers; - using Moonlight.Features.Servers.Entities; using Moonlight.Features.Servers.Extensions.Attributes; using Moonlight.Features.Servers.Models.Packets; @@ -29,24 +28,18 @@ public class NodeController : Controller // Load node from request context var node = (HttpContext.Items["Node"] as ServerNode)!; - await NodeService.Meta.Update(node.Id, meta => - { - meta.IsBooting = true; - }); + await NodeService.Meta.Update(node.Id, meta => { meta.IsBooting = true; }); return Ok(); } - + [HttpPost("notify/finish")] public async Task NotifyBootFinish() { // Load node from request context var node = (HttpContext.Items["Node"] as ServerNode)!; - await NodeService.Meta.Update(node.Id, meta => - { - meta.IsBooting = false; - }); + await NodeService.Meta.Update(node.Id, meta => { meta.IsBooting = false; }); return Ok(); } @@ -55,15 +48,15 @@ public class NodeController : Controller public async Task Ws() { // Validate if it is even a websocket connection - if (HttpContext.WebSockets.IsWebSocketRequest) + if (!HttpContext.WebSockets.IsWebSocketRequest) return BadRequest("This endpoint is only available for websockets"); // Accept websocket connection var websocket = await HttpContext.WebSockets.AcceptWebSocketAsync(); - + // Build connection wrapper var wsPacketConnection = new WsPacketConnection(websocket); - + // Register packets await wsPacketConnection.RegisterPacket("serverStateUpdate"); await wsPacketConnection.RegisterPacket("serverOutputMessage"); @@ -79,6 +72,22 @@ public class NodeController : Controller meta.State = serverStateUpdate.State; meta.LastChangeTimestamp = DateTime.UtcNow; }); + + await (await ServerService.Meta.Get(serverStateUpdate.Id)).OnStateChanged.Invoke(); + } + + if (packet is ServerOutputMessage serverOutputMessage) + { + await ServerService.Meta.Update(serverOutputMessage.Id, meta => + { + lock (meta.ConsoleMessages) + meta.ConsoleMessages.Add(serverOutputMessage.Message); + + meta.LastChangeTimestamp = DateTime.UtcNow; + }); + + await (await ServerService.Meta.Get(serverOutputMessage.Id)).OnConsoleMessage.Invoke(serverOutputMessage + .Message); } } diff --git a/Moonlight/Features/Servers/Models/Abstractions/ServerMeta.cs b/Moonlight/Features/Servers/Models/Abstractions/ServerMeta.cs index 7af0ff74..a002f742 100644 --- a/Moonlight/Features/Servers/Models/Abstractions/ServerMeta.cs +++ b/Moonlight/Features/Servers/Models/Abstractions/ServerMeta.cs @@ -1,3 +1,4 @@ +using MoonCore.Helpers; using Moonlight.Features.Servers.Models.Enums; namespace Moonlight.Features.Servers.Models.Abstractions; @@ -5,5 +6,8 @@ namespace Moonlight.Features.Servers.Models.Abstractions; public class ServerMeta { public ServerState State { get; set; } - public DateTime LastChangeTimestamp { get; set; } + public DateTime LastChangeTimestamp { get; set; } = DateTime.UtcNow; + public SmartEventHandler OnStateChanged { get; set; } = new(); + public SmartEventHandler OnConsoleMessage { get; set; } = new(); + public List ConsoleMessages { get; set; } = new(); } \ No newline at end of file diff --git a/Moonlight/Features/Servers/Services/ServerService.cs b/Moonlight/Features/Servers/Services/ServerService.cs index ae43d795..4caccfb6 100644 --- a/Moonlight/Features/Servers/Services/ServerService.cs +++ b/Moonlight/Features/Servers/Services/ServerService.cs @@ -41,6 +41,12 @@ public class ServerService await httpClient.Post($"servers/{server.Id}/power/{powerAction.ToString().ToLower()}"); } + public async Task SubscribeToConsole(Server server) + { + using var httpClient = CreateHttpClient(server); + await httpClient.Post($"servers/{server.Id}/subscribe"); + } + private HttpApiClient CreateHttpClient(Server server) { using var scope = ServiceProvider.CreateScope(); diff --git a/Moonlight/Features/Servers/UI/Layouts/UserLayout.razor b/Moonlight/Features/Servers/UI/Layouts/UserLayout.razor index 725de514..379eac53 100644 --- a/Moonlight/Features/Servers/UI/Layouts/UserLayout.razor +++ b/Moonlight/Features/Servers/UI/Layouts/UserLayout.razor @@ -211,11 +211,9 @@ .Include(x => x.Allocations) .Include(x => x.MainAllocation) .First(x => x.Service.Id == Service.Id); - - Meta = new ServerMeta(); -/* + // Load meta and setup event handlers - Meta = await ServerService.Meta.Get(Server); + Meta = await ServerService.Meta.Get(Server.Id); Meta.OnStateChanged += async Task () => { await InvokeAsync(StateHasChanged); @@ -247,7 +245,7 @@ }; // Send console subscription and add auto resubscribe for it - await ServerService.Console.Subscribe(Server); + await ServerService.SubscribeToConsole(Server); // We need this to revalidate to the daemon that we are still interested // in the console logs. By default the expiration time is 15 minutes from last @@ -257,7 +255,7 @@ while (!BackgroundCancel.IsCancellationRequested) { await Task.Delay(TimeSpan.FromMinutes(10)); - await ServerService.Console.Subscribe(Server); + await ServerService.SubscribeToConsole(Server); } }); @@ -270,8 +268,6 @@ await InvokeAsync(StateHasChanged); } }); - - */ } private async Task OnInstallConsoleMessage(string message) diff --git a/Moonlight/Features/Servers/UI/UserViews/Console.razor b/Moonlight/Features/Servers/UI/UserViews/Console.razor new file mode 100644 index 00000000..478e5468 --- /dev/null +++ b/Moonlight/Features/Servers/UI/UserViews/Console.razor @@ -0,0 +1,49 @@ +@using Moonlight.Features.Servers.Models.Abstractions +@using Moonlight.Features.Servers.UI.Components + +@implements IDisposable + +
+ +
+
+ + +
+
+
+ +@code +{ + [CascadingParameter] + public ServerMeta Meta { get; set; } + + private Terminal Terminal; + + protected override async Task OnAfterRenderAsync(bool firstRender) + { + if (firstRender) + { + string[] messages; + + lock (Meta.ConsoleMessages) + messages = Meta.ConsoleMessages.TakeLast(100).ToArray(); + + foreach (var message in messages) + await Terminal.WriteLine(message); + + Meta.OnConsoleMessage += OnConsoleMessage; + } + } + + private async Task OnConsoleMessage(string message) + { + await Terminal.WriteLine(message); + } + + public void Dispose() + { + if(Meta != null) + Meta.OnConsoleMessage -= OnConsoleMessage; + } +} \ No newline at end of file diff --git a/Moonlight/Pages/_Host.cshtml b/Moonlight/Pages/_Host.cshtml index a3b98b2a..ee93fdb0 100644 --- a/Moonlight/Pages/_Host.cshtml +++ b/Moonlight/Pages/_Host.cshtml @@ -37,6 +37,8 @@ @* *@ + + + + + + + @foreach (var theme in themes) { if (!string.IsNullOrEmpty(theme.JsUrl))