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.Servers.UI.Layouts;
|
||||||
using Moonlight.Features.ServiceManagement.Models.Abstractions;
|
using Moonlight.Features.ServiceManagement.Models.Abstractions;
|
||||||
|
using Console = Moonlight.Features.Servers.UI.UserViews.Console;
|
||||||
|
|
||||||
namespace Moonlight.Features.Servers.Actions;
|
namespace Moonlight.Features.Servers.Actions;
|
||||||
|
|
||||||
@@ -8,9 +8,12 @@ public class ServerServiceDefinition : ServiceDefinition
|
|||||||
{
|
{
|
||||||
public override ServiceActions Actions => new ServerActions();
|
public override ServiceActions Actions => new ServerActions();
|
||||||
public override Type ConfigType => typeof(ServerConfig);
|
public override Type ConfigType => typeof(ServerConfig);
|
||||||
|
|
||||||
public override async Task BuildUserView(ServiceViewContext context)
|
public override async Task BuildUserView(ServiceViewContext context)
|
||||||
{
|
{
|
||||||
context.Layout = typeof(UserLayout);
|
context.Layout = typeof(UserLayout);
|
||||||
|
|
||||||
|
await context.AddPage<Console>("Console", "/console", "bx bx-sm bxs-terminal");
|
||||||
}
|
}
|
||||||
|
|
||||||
public override Task BuildAdminView(ServiceViewContext context)
|
public override Task BuildAdminView(ServiceViewContext context)
|
||||||
|
|||||||
@@ -24,4 +24,15 @@ public class MetaCache<T>
|
|||||||
|
|
||||||
return Task.CompletedTask;
|
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 System.Net.WebSockets;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using MoonCore.Helpers;
|
using MoonCore.Helpers;
|
||||||
|
|
||||||
using Moonlight.Features.Servers.Entities;
|
using Moonlight.Features.Servers.Entities;
|
||||||
using Moonlight.Features.Servers.Extensions.Attributes;
|
using Moonlight.Features.Servers.Extensions.Attributes;
|
||||||
using Moonlight.Features.Servers.Models.Packets;
|
using Moonlight.Features.Servers.Models.Packets;
|
||||||
@@ -29,10 +28,7 @@ public class NodeController : Controller
|
|||||||
// Load node from request context
|
// Load node from request context
|
||||||
var node = (HttpContext.Items["Node"] as ServerNode)!;
|
var node = (HttpContext.Items["Node"] as ServerNode)!;
|
||||||
|
|
||||||
await NodeService.Meta.Update(node.Id, meta =>
|
await NodeService.Meta.Update(node.Id, meta => { meta.IsBooting = true; });
|
||||||
{
|
|
||||||
meta.IsBooting = true;
|
|
||||||
});
|
|
||||||
|
|
||||||
return Ok();
|
return Ok();
|
||||||
}
|
}
|
||||||
@@ -43,10 +39,7 @@ public class NodeController : Controller
|
|||||||
// Load node from request context
|
// Load node from request context
|
||||||
var node = (HttpContext.Items["Node"] as ServerNode)!;
|
var node = (HttpContext.Items["Node"] as ServerNode)!;
|
||||||
|
|
||||||
await NodeService.Meta.Update(node.Id, meta =>
|
await NodeService.Meta.Update(node.Id, meta => { meta.IsBooting = false; });
|
||||||
{
|
|
||||||
meta.IsBooting = false;
|
|
||||||
});
|
|
||||||
|
|
||||||
return Ok();
|
return Ok();
|
||||||
}
|
}
|
||||||
@@ -55,7 +48,7 @@ public class NodeController : Controller
|
|||||||
public async Task<ActionResult> Ws()
|
public async Task<ActionResult> Ws()
|
||||||
{
|
{
|
||||||
// Validate if it is even a websocket connection
|
// 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");
|
return BadRequest("This endpoint is only available for websockets");
|
||||||
|
|
||||||
// Accept websocket connection
|
// Accept websocket connection
|
||||||
@@ -79,6 +72,22 @@ public class NodeController : Controller
|
|||||||
meta.State = serverStateUpdate.State;
|
meta.State = serverStateUpdate.State;
|
||||||
meta.LastChangeTimestamp = DateTime.UtcNow;
|
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;
|
using Moonlight.Features.Servers.Models.Enums;
|
||||||
|
|
||||||
namespace Moonlight.Features.Servers.Models.Abstractions;
|
namespace Moonlight.Features.Servers.Models.Abstractions;
|
||||||
@@ -5,5 +6,8 @@ namespace Moonlight.Features.Servers.Models.Abstractions;
|
|||||||
public class ServerMeta
|
public class ServerMeta
|
||||||
{
|
{
|
||||||
public ServerState State { get; set; }
|
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()}");
|
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)
|
private HttpApiClient<NodeException> CreateHttpClient(Server server)
|
||||||
{
|
{
|
||||||
using var scope = ServiceProvider.CreateScope();
|
using var scope = ServiceProvider.CreateScope();
|
||||||
|
|||||||
@@ -212,10 +212,8 @@
|
|||||||
.Include(x => x.MainAllocation)
|
.Include(x => x.MainAllocation)
|
||||||
.First(x => x.Service.Id == Service.Id);
|
.First(x => x.Service.Id == Service.Id);
|
||||||
|
|
||||||
Meta = new ServerMeta();
|
|
||||||
/*
|
|
||||||
// Load meta and setup event handlers
|
// Load meta and setup event handlers
|
||||||
Meta = await ServerService.Meta.Get(Server);
|
Meta = await ServerService.Meta.Get(Server.Id);
|
||||||
Meta.OnStateChanged += async Task () =>
|
Meta.OnStateChanged += async Task () =>
|
||||||
{
|
{
|
||||||
await InvokeAsync(StateHasChanged);
|
await InvokeAsync(StateHasChanged);
|
||||||
@@ -247,7 +245,7 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Send console subscription and add auto resubscribe for it
|
// 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
|
// 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
|
// in the console logs. By default the expiration time is 15 minutes from last
|
||||||
@@ -257,7 +255,7 @@
|
|||||||
while (!BackgroundCancel.IsCancellationRequested)
|
while (!BackgroundCancel.IsCancellationRequested)
|
||||||
{
|
{
|
||||||
await Task.Delay(TimeSpan.FromMinutes(10));
|
await Task.Delay(TimeSpan.FromMinutes(10));
|
||||||
await ServerService.Console.Subscribe(Server);
|
await ServerService.SubscribeToConsole(Server);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -270,8 +268,6 @@
|
|||||||
await InvokeAsync(StateHasChanged);
|
await InvokeAsync(StateHasChanged);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
*/
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task OnInstallConsoleMessage(string message)
|
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/sweetalert2dark.css" rel="stylesheet" type="text/css"/>
|
||||||
<link href="/css/interfont.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="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>
|
</head>
|
||||||
<body data-kt-app-header-fixed="true"
|
<body data-kt-app-header-fixed="true"
|
||||||
data-kt-app-header-fixed-mobile="true"
|
data-kt-app-header-fixed-mobile="true"
|
||||||
@@ -57,6 +59,11 @@
|
|||||||
<script src="/js/sweetalert2.js"></script>
|
<script src="/js/sweetalert2.js"></script>
|
||||||
<script src="/js/ckeditor.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)
|
@foreach (var theme in themes)
|
||||||
{
|
{
|
||||||
if (!string.IsNullOrEmpty(theme.JsUrl))
|
if (!string.IsNullOrEmpty(theme.JsUrl))
|
||||||
|
|||||||
Reference in New Issue
Block a user