Added state streaming. Started working on server console
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
@@ -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)
|
||||
|
||||
49
Moonlight/Features/Servers/UI/UserViews/Console.razor
Normal file
49
Moonlight/Features/Servers/UI/UserViews/Console.razor
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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))
|
||||
|
||||
Reference in New Issue
Block a user