Added state streaming. Started working on server console

This commit is contained in:
Marcel Baumgartner
2024-02-02 22:21:31 +01:00
parent 95507fd41f
commit 9e515d9ed7
8 changed files with 108 additions and 23 deletions

View File

@@ -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", "/console", "bx bx-sm bxs-terminal");
}
public override Task BuildAdminView(ServiceViewContext context)

View File

@@ -24,4 +24,15 @@ public class MetaCache<T>
return Task.CompletedTask;
}
public Task<T> Get(int id)
{
lock (Cache)
{
if(!Cache.ContainsKey(id))
Cache.Add(id, Activator.CreateInstance<T>());
return Task.FromResult(Cache[id]);
}
}
}

View File

@@ -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<ActionResult> 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<ActionResult> 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>("serverStateUpdate");
await wsPacketConnection.RegisterPacket<ServerOutputMessage>("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);
}
}

View File

@@ -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<string> OnConsoleMessage { get; set; } = new();
public List<string> ConsoleMessages { get; set; } = new();
}

View File

@@ -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<NodeException> CreateHttpClient(Server server)
{
using var scope = ServiceProvider.CreateScope();

View File

@@ -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)

View File

@@ -0,0 +1,49 @@
@using Moonlight.Features.Servers.Models.Abstractions
@using Moonlight.Features.Servers.UI.Components
@implements IDisposable
<div class="card card-body bg-black p-3">
<Terminal @ref="Terminal" />
<div class="mt-3">
<div class="input-group">
<input class="form-control form-control-transparent text-white" placeholder="Enter command"/>
<button class="btn btn-secondary rounded-start">Execute</button>
</div>
</div>
</div>
@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;
}
}

View File

@@ -37,6 +37,8 @@
<link href="/css/sweetalert2dark.css" rel="stylesheet" type="text/css"/>
<link href="/css/interfont.css" rel="stylesheet" type="text/css"/>
@* <link href="https://fonts.googleapis.com/css?family=Inter:300,400,500,600,700" rel="stylesheet" type="text/css"> *@
<link href="/_content/XtermBlazor/XtermBlazor.css" rel="stylesheet" type="text/css" />
</head>
<body data-kt-app-header-fixed="true"
data-kt-app-header-fixed-mobile="true"
@@ -57,6 +59,11 @@
<script src="/js/sweetalert2.js"></script>
<script src="/js/ckeditor.js"></script>
<script src="/_content/XtermBlazor/XtermBlazor.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/xterm-addon-fit@0.8.0/lib/xterm-addon-fit.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/xterm-addon-web-links@0.9.0/lib/xterm-addon-web-links.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/xterm-addon-search@0.13.0/lib/xterm-addon-search.min.js"></script>
@foreach (var theme in themes)
{
if (!string.IsNullOrEmpty(theme.JsUrl))