diff --git a/MoonlightServers.ApiServer/Http/Controllers/Admin/Servers/ServersController.cs b/MoonlightServers.ApiServer/Http/Controllers/Admin/Servers/ServersController.cs index b0fba4d..671651a 100644 --- a/MoonlightServers.ApiServer/Http/Controllers/Admin/Servers/ServersController.cs +++ b/MoonlightServers.ApiServer/Http/Controllers/Admin/Servers/ServersController.cs @@ -68,6 +68,7 @@ public class ServersController : Controller .Include(x => x.Star) .Skip(page * pageSize) .Take(pageSize) + .OrderBy(x => x.Id) .ToArrayAsync(); var mappedItems = items @@ -213,7 +214,7 @@ public class ServersController : Controller } [HttpPatch("{id:int}")] - [Authorize(Policy = "permissions.admin.servers.write")] + [Authorize(Policy = "permissions:admin.servers.write")] public async Task Update([FromRoute] int id, [FromBody] UpdateServerRequest request) { //TODO: Handle shrinking virtual disk @@ -294,11 +295,16 @@ public class ServersController : Controller .Include(x => x.Star) .Include(x => x.Variables) .Include(x => x.Backups) + .Include(x => x.Allocations) .FirstOrDefaultAsync(x => x.Id == id); if (server == null) throw new HttpApiException("No server with that id found", 404); + server.Variables.Clear(); + server.Backups.Clear(); + server.Allocations.Clear(); + try { // If the sync fails on the node and we aren't forcing the deletion, diff --git a/MoonlightServers.ApiServer/Http/Controllers/Client/ServersController.cs b/MoonlightServers.ApiServer/Http/Controllers/Client/ServersController.cs index 4f0fdc0..d846d00 100644 --- a/MoonlightServers.ApiServer/Http/Controllers/Client/ServersController.cs +++ b/MoonlightServers.ApiServer/Http/Controllers/Client/ServersController.cs @@ -11,6 +11,7 @@ using MoonlightServers.ApiServer.Extensions; using MoonlightServers.ApiServer.Models; using MoonlightServers.ApiServer.Services; using MoonlightServers.Shared.Enums; +using MoonlightServers.Shared.Http.Requests.Client.Servers; using MoonlightServers.Shared.Http.Responses.Client.Servers; using MoonlightServers.Shared.Http.Responses.Client.Servers.Allocations; using MoonlightServers.Shared.Models; @@ -293,6 +294,17 @@ public class ServersController : Controller }; } + [HttpPost("{serverId:int}/command")] + public async Task RunCommand([FromRoute] int serverId, [FromBody] ServerCommandRequest request) + { + var server = await GetServerById( + serverId, + permission => permission is { Name: "console", Type: >= ServerPermissionType.ReadWrite } + ); + + await ServerService.RunCommand(server, request.Command); + } + private async Task GetServerById(int serverId, Func? filter = null) { var server = await ServerRepository diff --git a/MoonlightServers.ApiServer/Services/ServerService.cs b/MoonlightServers.ApiServer/Services/ServerService.cs index a59482b..26d863d 100644 --- a/MoonlightServers.ApiServer/Services/ServerService.cs +++ b/MoonlightServers.ApiServer/Services/ServerService.cs @@ -6,6 +6,7 @@ using MoonCore.Extended.Abstractions; using MoonCore.Helpers; using Moonlight.ApiServer.Database.Entities; using MoonlightServers.ApiServer.Database.Entities; +using MoonlightServers.DaemonShared.DaemonSide.Http.Requests; using MoonlightServers.DaemonShared.DaemonSide.Http.Responses.Servers; namespace MoonlightServers.ApiServer.Services; @@ -36,7 +37,7 @@ public class ServerService throw new HttpApiException("Unable to access the node the server is running on", 502); } } - + public async Task Stop(Server server) { try @@ -49,7 +50,7 @@ public class ServerService throw new HttpApiException("Unable to access the node the server is running on", 502); } } - + public async Task Kill(Server server) { try @@ -64,7 +65,7 @@ public class ServerService } #endregion - + public async Task Install(Server server) { try @@ -90,7 +91,7 @@ public class ServerService throw new HttpApiException("Unable to access the node the server is running on", 502); } } - + public async Task SyncDelete(Server server) { try @@ -103,7 +104,7 @@ public class ServerService throw new HttpApiException("Unable to access the node the server is running on", 502); } } - + public async Task GetStatus(Server server) { try @@ -137,7 +138,27 @@ public class ServerService using var apiClient = await GetApiClient(server); return await apiClient.GetJson($"api/servers/{server.Id}/stats"); } - catch (HttpRequestException e) + catch (HttpRequestException) + { + throw new HttpApiException("Unable to access the node the server is running on", 502); + } + } + + public async Task RunCommand(Server server, string command) + { + try + { + using var apiClient = await GetApiClient(server); + + await apiClient.Post( + $"api/servers/{server.Id}/command", + new ServerCommandRequest() + { + Command = command + } + ); + } + catch (HttpRequestException) { throw new HttpApiException("Unable to access the node the server is running on", 502); } @@ -149,10 +170,10 @@ public class ServerService { if (server.OwnerId == user.Id) return true; - + return PermissionHelper.HasPermission(user.Permissions, "admin.servers.get"); } - + private async Task GetApiClient(Server server) { var serverWithNode = server; diff --git a/MoonlightServers.Daemon/Http/Controllers/Servers/ServersController.cs b/MoonlightServers.Daemon/Http/Controllers/Servers/ServersController.cs index cf9fc18..1930530 100644 --- a/MoonlightServers.Daemon/Http/Controllers/Servers/ServersController.cs +++ b/MoonlightServers.Daemon/Http/Controllers/Servers/ServersController.cs @@ -3,6 +3,7 @@ using Microsoft.AspNetCore.Mvc; using MoonCore.Exceptions; using MoonlightServers.Daemon.ServerSystem.SubSystems; using MoonlightServers.Daemon.Services; +using MoonlightServers.DaemonShared.DaemonSide.Http.Requests; using MoonlightServers.DaemonShared.DaemonSide.Http.Responses.Servers; using MoonlightServers.DaemonShared.Enums; @@ -10,7 +11,7 @@ namespace MoonlightServers.Daemon.Http.Controllers.Servers; [Authorize] [ApiController] -[Route("api/servers")] +[Route("api/servers/{serverId:int}")] public class ServersController : Controller { private readonly ServerService ServerService; @@ -20,19 +21,19 @@ public class ServersController : Controller ServerService = serverService; } - [HttpPost("{serverId:int}/sync")] + [HttpPost("sync")] public async Task Sync([FromRoute] int serverId) { await ServerService.Sync(serverId); } - [HttpDelete("{serverId:int}")] + [HttpDelete] public async Task Delete([FromRoute] int serverId) { await ServerService.Delete(serverId); } - [HttpGet("{serverId:int}/status")] + [HttpGet("status")] public Task GetStatus([FromRoute] int serverId) { var server = ServerService.Find(serverId); @@ -48,7 +49,7 @@ public class ServersController : Controller return Task.FromResult(result); } - [HttpGet("{serverId:int}/logs")] + [HttpGet("logs")] public async Task GetLogs([FromRoute] int serverId) { var server = ServerService.Find(serverId); @@ -65,7 +66,7 @@ public class ServersController : Controller }; } - [HttpGet("{serverId:int}/stats")] + [HttpGet("stats")] public Task GetStats([FromRoute] int serverId) { var server = ServerService.Find(serverId); @@ -85,4 +86,17 @@ public class ServersController : Controller IoWrite = statsSubSystem.CurrentStats.IoWrite }); } + + [HttpPost("command")] + public async Task Command([FromRoute] int serverId, [FromBody] ServerCommandRequest request) + { + var server = ServerService.Find(serverId); + + if (server == null) + throw new HttpApiException("No server with this id found", 404); + + var consoleSubSystem = server.GetRequiredSubSystem(); + + await consoleSubSystem.WriteInput(request.Command); + } } \ No newline at end of file diff --git a/MoonlightServers.Daemon/MoonlightServers.Daemon.csproj b/MoonlightServers.Daemon/MoonlightServers.Daemon.csproj index 2ddb7f1..b129cad 100644 --- a/MoonlightServers.Daemon/MoonlightServers.Daemon.csproj +++ b/MoonlightServers.Daemon/MoonlightServers.Daemon.csproj @@ -49,6 +49,13 @@ <_ContentIncludedByDefault Remove="storage\volumes\2\usercache.json" /> <_ContentIncludedByDefault Remove="storage\volumes\2\version_history.json" /> <_ContentIncludedByDefault Remove="storage\volumes\2\whitelist.json" /> + <_ContentIncludedByDefault Remove="volumes\3\banned-ips.json" /> + <_ContentIncludedByDefault Remove="volumes\3\banned-players.json" /> + <_ContentIncludedByDefault Remove="volumes\3\ops.json" /> + <_ContentIncludedByDefault Remove="volumes\3\plugins\spark\config.json" /> + <_ContentIncludedByDefault Remove="volumes\3\usercache.json" /> + <_ContentIncludedByDefault Remove="volumes\3\version_history.json" /> + <_ContentIncludedByDefault Remove="volumes\3\whitelist.json" /> diff --git a/MoonlightServers.DaemonShared/DaemonSide/Http/Requests/ServerCommandRequest.cs b/MoonlightServers.DaemonShared/DaemonSide/Http/Requests/ServerCommandRequest.cs new file mode 100644 index 0000000..22f270b --- /dev/null +++ b/MoonlightServers.DaemonShared/DaemonSide/Http/Requests/ServerCommandRequest.cs @@ -0,0 +1,9 @@ +using System.ComponentModel.DataAnnotations; + +namespace MoonlightServers.DaemonShared.DaemonSide.Http.Requests; + +public class ServerCommandRequest +{ + [Required(ErrorMessage = "The command is required")] + public string Command { get; set; } +} \ No newline at end of file diff --git a/MoonlightServers.Frontend/Services/ServerService.cs b/MoonlightServers.Frontend/Services/ServerService.cs index 70bc91d..c093b32 100644 --- a/MoonlightServers.Frontend/Services/ServerService.cs +++ b/MoonlightServers.Frontend/Services/ServerService.cs @@ -1,6 +1,7 @@ using MoonCore.Attributes; using MoonCore.Helpers; using MoonCore.Models; +using MoonlightServers.Shared.Http.Requests.Client.Servers; using MoonlightServers.Shared.Http.Requests.Client.Servers.Variables; using MoonlightServers.Shared.Http.Responses.Client.Servers; using MoonlightServers.Shared.Http.Responses.Client.Servers.Variables; @@ -59,6 +60,17 @@ public class ServerService ); } + public async Task RunCommand(int serverId, string command) + { + await HttpApiClient.Post( + $"api/client/servers/{serverId}/command", + new ServerCommandRequest() + { + Command = command + } + ); + } + public async Task GetWebSocket(int serverId) { return await HttpApiClient.GetJson( diff --git a/MoonlightServers.Frontend/UI/Components/FullScreenModal.razor b/MoonlightServers.Frontend/UI/Components/FullScreenModal.razor new file mode 100644 index 0000000..da75e29 --- /dev/null +++ b/MoonlightServers.Frontend/UI/Components/FullScreenModal.razor @@ -0,0 +1,89 @@ +@using System.Text.Json.Serialization +@using Microsoft.Extensions.Logging +@using XtermBlazor +@inherits MoonCore.Blazor.FlyonUi.Modals.Components.BaseModal + +@inject IJSRuntime JsRuntime +@inject ILogger Logger + +@implements IAsyncDisposable + +
+ @if (IsInitialized) + { + + } + +
+ +
+
+ +@code +{ + [Parameter] public XtermConsole Parent { get; set; } + + private bool IsInitialized = false; + private bool IsReadyToWrite = false; + private Xterm Terminal; + + protected override async Task OnAfterRenderAsync(bool firstRender) + { + if (!firstRender) + return; + + // Initialize addons + + try + { + await JsRuntime.InvokeVoidAsync("moonlightServers.loadAddons"); + } + catch (Exception e) + { + Logger.LogError("An error occured while initializing addons: {e}", e); + } + + // Subscribe to parent events + Parent.OnWrite += HandleWrite; + + IsInitialized = true; + await InvokeAsync(StateHasChanged); + } + + private async Task HandleFirstRender() + { + IsReadyToWrite = true; + + try + { + await Terminal.Addon("addon-fit").InvokeVoidAsync("fit"); + } + catch (Exception e) + { + Logger.LogError("An error occured while calling addons: {e}", e); + } + + var outputToWrite = string.Concat(Parent.OutputCache.ToArray()); + await Terminal.Write(outputToWrite); + } + + private async Task HandleWrite(string content) + { + if (!IsReadyToWrite) + return; + + await Terminal.Write(content); + } + + public async ValueTask DisposeAsync() + { + Parent.OnWrite -= HandleWrite; + await Terminal.DisposeAsync(); + } +} diff --git a/MoonlightServers.Frontend/UI/Components/Nodes/UpdateNodePartials/AdvancedNodeUpdate.razor b/MoonlightServers.Frontend/UI/Components/Nodes/UpdatePartials/Advanced.razor similarity index 100% rename from MoonlightServers.Frontend/UI/Components/Nodes/UpdateNodePartials/AdvancedNodeUpdate.razor rename to MoonlightServers.Frontend/UI/Components/Nodes/UpdatePartials/Advanced.razor diff --git a/MoonlightServers.Frontend/UI/Components/Nodes/UpdateNodePartials/AllocationsNodeUpdate.razor b/MoonlightServers.Frontend/UI/Components/Nodes/UpdatePartials/Allocations.razor similarity index 99% rename from MoonlightServers.Frontend/UI/Components/Nodes/UpdateNodePartials/AllocationsNodeUpdate.razor rename to MoonlightServers.Frontend/UI/Components/Nodes/UpdatePartials/Allocations.razor index 1200105..0e44a57 100644 --- a/MoonlightServers.Frontend/UI/Components/Nodes/UpdateNodePartials/AllocationsNodeUpdate.razor +++ b/MoonlightServers.Frontend/UI/Components/Nodes/UpdatePartials/Allocations.razor @@ -18,7 +18,7 @@
- Actions + Actions
diff --git a/MoonlightServers.Frontend/UI/Components/Nodes/UpdateNodePartials/GeneralNodeUpdate.razor b/MoonlightServers.Frontend/UI/Components/Nodes/UpdatePartials/General.razor similarity index 100% rename from MoonlightServers.Frontend/UI/Components/Nodes/UpdateNodePartials/GeneralNodeUpdate.razor rename to MoonlightServers.Frontend/UI/Components/Nodes/UpdatePartials/General.razor diff --git a/MoonlightServers.Frontend/UI/Components/Nodes/UpdateNodePartials/OverviewNodeUpdate.razor b/MoonlightServers.Frontend/UI/Components/Nodes/UpdatePartials/Overview.razor similarity index 93% rename from MoonlightServers.Frontend/UI/Components/Nodes/UpdateNodePartials/OverviewNodeUpdate.razor rename to MoonlightServers.Frontend/UI/Components/Nodes/UpdatePartials/Overview.razor index bc8b154..2dd9029 100644 --- a/MoonlightServers.Frontend/UI/Components/Nodes/UpdateNodePartials/OverviewNodeUpdate.razor +++ b/MoonlightServers.Frontend/UI/Components/Nodes/UpdatePartials/Overview.razor @@ -8,7 +8,7 @@ @inject NodeService NodeService @inject ToastService ToastService -@inject ILogger Logger +@inject ILogger Logger @implements IDisposable @@ -26,7 +26,7 @@

-
+
CPU: @Statistics.Cpu.Model @@ -42,7 +42,7 @@

-
+
Memory
@@ -56,7 +56,7 @@
-
+
Swap
@@ -76,7 +76,7 @@ var percentRounded = Math.Round(usage, 2);
-
+
#@(i)
@@ -108,7 +108,7 @@
-
+
Device: @disk.Device - Mounted at: @disk.MountPath
@@ -136,7 +136,7 @@

-
+
Images
@@ -148,7 +148,7 @@

-
+
Containers
@@ -160,7 +160,7 @@

-
+
Build Cache
diff --git a/MoonlightServers.Frontend/UI/Components/Servers/CreateServerPartials/AdvancedServerCreate.razor b/MoonlightServers.Frontend/UI/Components/Servers/CreatePartials/Advanced.razor similarity index 100% rename from MoonlightServers.Frontend/UI/Components/Servers/CreateServerPartials/AdvancedServerCreate.razor rename to MoonlightServers.Frontend/UI/Components/Servers/CreatePartials/Advanced.razor diff --git a/MoonlightServers.Frontend/UI/Components/Servers/CreateServerPartials/AllocationsServerCreate.razor b/MoonlightServers.Frontend/UI/Components/Servers/CreatePartials/Allocations.razor similarity index 100% rename from MoonlightServers.Frontend/UI/Components/Servers/CreateServerPartials/AllocationsServerCreate.razor rename to MoonlightServers.Frontend/UI/Components/Servers/CreatePartials/Allocations.razor diff --git a/MoonlightServers.Frontend/UI/Components/Servers/CreateServerPartials/GeneralServerCreate.razor b/MoonlightServers.Frontend/UI/Components/Servers/CreatePartials/General.razor similarity index 100% rename from MoonlightServers.Frontend/UI/Components/Servers/CreateServerPartials/GeneralServerCreate.razor rename to MoonlightServers.Frontend/UI/Components/Servers/CreatePartials/General.razor diff --git a/MoonlightServers.Frontend/UI/Components/Servers/CreateServerPartials/VariablesServerCreate.razor b/MoonlightServers.Frontend/UI/Components/Servers/CreatePartials/Variables.razor similarity index 100% rename from MoonlightServers.Frontend/UI/Components/Servers/CreateServerPartials/VariablesServerCreate.razor rename to MoonlightServers.Frontend/UI/Components/Servers/CreatePartials/Variables.razor diff --git a/MoonlightServers.Frontend/UI/Components/Servers/ServerCard.razor b/MoonlightServers.Frontend/UI/Components/Servers/ServerCard.razor index 581946f..587d978 100644 --- a/MoonlightServers.Frontend/UI/Components/Servers/ServerCard.razor +++ b/MoonlightServers.Frontend/UI/Components/Servers/ServerCard.razor @@ -8,8 +8,8 @@ @inject ILogger Logger @{ - var gradient = "from-base-content/20"; - var border = "border-base-content"; + var gradient = "from-base-100/20"; + var border = "border-base-content/80"; if (IsLoaded && !IsFailed) { @@ -20,7 +20,7 @@ ServerState.Starting => "from-warning/20", ServerState.Stopping => "from-warning/20", ServerState.Online => "from-success/20", - _ => "from-base-content/20" + _ => "from-base-100" }; border = Status.State switch @@ -30,13 +30,13 @@ ServerState.Starting => "border-warning", ServerState.Stopping => "border-warning", ServerState.Online => "border-success", - _ => "border-base-content" + _ => "border-base-content/80" }; } } + class="w-full bg-gradient-to-r @gradient to-base-100/75 to-25% px-5 py-3.5 rounded-xl border-l-8 @border">
@@ -54,7 +54,7 @@ Status.State is ServerState.Starting or ServerState.Stopping or ServerState.Online ) { -
+
@@ -62,7 +62,7 @@
@(Stats.CpuUsage)%
-
+
@@ -70,7 +70,7 @@
@(Formatter.FormatSize(Stats.MemoryUsage)) / @(Formatter.FormatSize(ByteConverter.FromMegaBytes(Server.Memory).Bytes))
-
+
@@ -82,7 +82,7 @@ { if (!IsLoaded) { -
+
@@ -92,7 +92,7 @@ } else if (IsFailed) { -
+
@@ -102,7 +102,7 @@ } else if (IsLoaded && !IsFailed && Status.State is ServerState.Offline) { -
+
@@ -112,7 +112,7 @@ } else if (IsLoaded && !IsFailed && Status.State is ServerState.Installing) { -
+
@@ -127,7 +127,7 @@ @if (Server.Share != null) { -
+
@@ -137,7 +137,7 @@ } else { -
+
@@ -145,7 +145,7 @@
@Server.StarName
-
+
diff --git a/MoonlightServers.Frontend/UI/Components/Servers/ServerTabs/ConsoleTab.razor b/MoonlightServers.Frontend/UI/Components/Servers/ServerTabs/ConsoleTab.razor index eb2834f..d88e8ba 100644 --- a/MoonlightServers.Frontend/UI/Components/Servers/ServerTabs/ConsoleTab.razor +++ b/MoonlightServers.Frontend/UI/Components/Servers/ServerTabs/ConsoleTab.razor @@ -1,9 +1,15 @@ @using Microsoft.AspNetCore.SignalR.Client +@using MoonlightServers.Frontend.Services @inherits BaseServerTab +@inject ServerService ServerService +
- +
@code @@ -16,10 +22,10 @@ HubConnection.On("ConsoleOutput", async content => { - if(XtermConsole != null) + if (XtermConsole != null) await XtermConsole.Write(content); }); - + return Task.CompletedTask; } @@ -27,4 +33,7 @@ { await XtermConsole!.Write(InitialConsoleMessage); } + + private async Task OnCommand(string command) + => await ServerService.RunCommand(Server.Id, command + "\n"); } diff --git a/MoonlightServers.Frontend/UI/Components/Servers/ServerTabs/SettingsTab.razor b/MoonlightServers.Frontend/UI/Components/Servers/ServerTabs/SettingsTab.razor index afb892c..65ebf9e 100644 --- a/MoonlightServers.Frontend/UI/Components/Servers/ServerTabs/SettingsTab.razor +++ b/MoonlightServers.Frontend/UI/Components/Servers/ServerTabs/SettingsTab.razor @@ -20,7 +20,7 @@ else { - + Reinstall } diff --git a/MoonlightServers.Frontend/UI/Components/Servers/ServerTabs/SharesTab.razor b/MoonlightServers.Frontend/UI/Components/Servers/ServerTabs/SharesTab.razor index 2b976f6..75abc00 100644 --- a/MoonlightServers.Frontend/UI/Components/Servers/ServerTabs/SharesTab.razor +++ b/MoonlightServers.Frontend/UI/Components/Servers/ServerTabs/SharesTab.razor @@ -34,7 +34,7 @@
@foreach (var share in Shares) { -
+
@share.Username diff --git a/MoonlightServers.Frontend/UI/Components/Servers/ServerTabs/VariablesTab.razor b/MoonlightServers.Frontend/UI/Components/Servers/ServerTabs/VariablesTab.razor index 26e1c35..483009e 100644 --- a/MoonlightServers.Frontend/UI/Components/Servers/ServerTabs/VariablesTab.razor +++ b/MoonlightServers.Frontend/UI/Components/Servers/ServerTabs/VariablesTab.razor @@ -12,7 +12,7 @@
@foreach (var variable in Variables) { -
+
diff --git a/MoonlightServers.Frontend/UI/Components/Servers/UpdateServerPartials/AdvancedServerUpdate.razor b/MoonlightServers.Frontend/UI/Components/Servers/UpdatePartials/Advanced.razor similarity index 100% rename from MoonlightServers.Frontend/UI/Components/Servers/UpdateServerPartials/AdvancedServerUpdate.razor rename to MoonlightServers.Frontend/UI/Components/Servers/UpdatePartials/Advanced.razor diff --git a/MoonlightServers.Frontend/UI/Components/Servers/UpdateServerPartials/AllocationsServerUpdate.razor b/MoonlightServers.Frontend/UI/Components/Servers/UpdatePartials/Allocations.razor similarity index 89% rename from MoonlightServers.Frontend/UI/Components/Servers/UpdateServerPartials/AllocationsServerUpdate.razor rename to MoonlightServers.Frontend/UI/Components/Servers/UpdatePartials/Allocations.razor index 2bad237..4100af5 100644 --- a/MoonlightServers.Frontend/UI/Components/Servers/UpdateServerPartials/AllocationsServerUpdate.razor +++ b/MoonlightServers.Frontend/UI/Components/Servers/UpdatePartials/Allocations.razor @@ -4,6 +4,7 @@ @using MoonlightServers.Shared.Http.Responses.Admin.NodeAllocations @using MoonlightServers.Shared.Http.Responses.Admin.Servers @using MoonCore.Blazor.FlyonUi.Forms +@using MoonlightServers.Frontend.UI.Views.Admin.All @inject HttpApiClient ApiClient @@ -12,7 +13,7 @@
@@ -25,7 +26,7 @@ { [Parameter] public UpdateServerRequest Request { get; set; } [Parameter] public ServerResponse Server { get; set; } - [Parameter] public List Allocations { get; set; } + [Parameter] public Update Parent { get; set; } private async Task Loader() { diff --git a/MoonlightServers.Frontend/UI/Components/Servers/UpdateServerPartials/GeneralServerUpdate.razor b/MoonlightServers.Frontend/UI/Components/Servers/UpdatePartials/General.razor similarity index 94% rename from MoonlightServers.Frontend/UI/Components/Servers/UpdateServerPartials/GeneralServerUpdate.razor rename to MoonlightServers.Frontend/UI/Components/Servers/UpdatePartials/General.razor index 9e43cff..8085cd2 100644 --- a/MoonlightServers.Frontend/UI/Components/Servers/UpdateServerPartials/GeneralServerUpdate.razor +++ b/MoonlightServers.Frontend/UI/Components/Servers/UpdatePartials/General.razor @@ -3,6 +3,7 @@ @using MoonCore.Models @using Moonlight.Shared.Http.Responses.Admin.Users @using MoonCore.Blazor.FlyonUi.Forms +@using MoonlightServers.Frontend.UI.Views.Admin.All @inject HttpApiClient ApiClient @@ -20,7 +21,7 @@ @@ -61,7 +62,7 @@ @code { [Parameter] public UpdateServerRequest Request { get; set; } - [Parameter] public UserResponse Owner { get; set; } + [Parameter] public Update Parent { get; set; } private async Task Loader() { diff --git a/MoonlightServers.Frontend/UI/Components/Servers/UpdateServerPartials/VariablesServerUpdate.razor b/MoonlightServers.Frontend/UI/Components/Servers/UpdatePartials/Variables.razor similarity index 94% rename from MoonlightServers.Frontend/UI/Components/Servers/UpdateServerPartials/VariablesServerUpdate.razor rename to MoonlightServers.Frontend/UI/Components/Servers/UpdatePartials/Variables.razor index 0eb0a8b..c50e20c 100644 --- a/MoonlightServers.Frontend/UI/Components/Servers/UpdateServerPartials/VariablesServerUpdate.razor +++ b/MoonlightServers.Frontend/UI/Components/Servers/UpdatePartials/Variables.razor @@ -11,7 +11,7 @@
- @foreach (var variable in Variables) + @foreach (var variable in ServerVariables) { var reqVariable = Request.Variables.FirstOrDefault(x => x.Key == variable.Key); var starVariable = StarVariables.FirstOrDefault(x => x.Key == variable.Key); @@ -46,7 +46,7 @@ [Parameter] public ServerResponse Server { get; set; } private StarVariableDetailResponse[] StarVariables; - private ServerVariableResponse[] Variables; + private ServerVariableResponse[] ServerVariables; private async Task Load(LazyLoader _) { @@ -56,7 +56,7 @@ ) ); - Variables = await PagedData.All(async (page, pageSize) => + ServerVariables = await PagedData.All(async (page, pageSize) => await ApiClient.GetJson>( $"api/admin/servers/{Server.Id}/variables?page={page}&pageSize={pageSize}" ) diff --git a/MoonlightServers.Frontend/UI/Components/Stars/UpdateStarPartials/DockerImageStarUpdate.razor b/MoonlightServers.Frontend/UI/Components/Stars/UpdatePartials/DockerImage.razor similarity index 100% rename from MoonlightServers.Frontend/UI/Components/Stars/UpdateStarPartials/DockerImageStarUpdate.razor rename to MoonlightServers.Frontend/UI/Components/Stars/UpdatePartials/DockerImage.razor diff --git a/MoonlightServers.Frontend/UI/Components/Stars/UpdateStarPartials/GeneralStarUpdate.razor b/MoonlightServers.Frontend/UI/Components/Stars/UpdatePartials/General.razor similarity index 100% rename from MoonlightServers.Frontend/UI/Components/Stars/UpdateStarPartials/GeneralStarUpdate.razor rename to MoonlightServers.Frontend/UI/Components/Stars/UpdatePartials/General.razor diff --git a/MoonlightServers.Frontend/UI/Components/Stars/UpdateStarPartials/InstallationStarUpdate.razor b/MoonlightServers.Frontend/UI/Components/Stars/UpdatePartials/Installation.razor similarity index 100% rename from MoonlightServers.Frontend/UI/Components/Stars/UpdateStarPartials/InstallationStarUpdate.razor rename to MoonlightServers.Frontend/UI/Components/Stars/UpdatePartials/Installation.razor diff --git a/MoonlightServers.Frontend/UI/Components/Stars/UpdateStarPartials/MiscStarUpdate.razor b/MoonlightServers.Frontend/UI/Components/Stars/UpdatePartials/Misc.razor similarity index 100% rename from MoonlightServers.Frontend/UI/Components/Stars/UpdateStarPartials/MiscStarUpdate.razor rename to MoonlightServers.Frontend/UI/Components/Stars/UpdatePartials/Misc.razor diff --git a/MoonlightServers.Frontend/UI/Components/Stars/UpdateStarPartials/ParseConfigStarUpdate.razor b/MoonlightServers.Frontend/UI/Components/Stars/UpdatePartials/ParseConfig.razor similarity index 98% rename from MoonlightServers.Frontend/UI/Components/Stars/UpdateStarPartials/ParseConfigStarUpdate.razor rename to MoonlightServers.Frontend/UI/Components/Stars/UpdatePartials/ParseConfig.razor index 377d422..f7a1243 100644 --- a/MoonlightServers.Frontend/UI/Components/Stars/UpdateStarPartials/ParseConfigStarUpdate.razor +++ b/MoonlightServers.Frontend/UI/Components/Stars/UpdatePartials/ParseConfig.razor @@ -7,7 +7,7 @@ @using MoonlightServers.Shared.Http.Requests.Admin.Stars @using MoonlightServers.Shared.Models -@inject ILogger Logger +@inject ILogger Logger @inject ModalService ModalService @inject AlertService AlertService @inject ToastService ToastService diff --git a/MoonlightServers.Frontend/UI/Components/Stars/UpdateStarPartials/StartStopStatusStarUpdate.razor b/MoonlightServers.Frontend/UI/Components/Stars/UpdatePartials/StartStopStatus.razor similarity index 100% rename from MoonlightServers.Frontend/UI/Components/Stars/UpdateStarPartials/StartStopStatusStarUpdate.razor rename to MoonlightServers.Frontend/UI/Components/Stars/UpdatePartials/StartStopStatus.razor diff --git a/MoonlightServers.Frontend/UI/Components/Stars/UpdateStarPartials/VariablesStarUpdate.razor b/MoonlightServers.Frontend/UI/Components/Stars/UpdatePartials/Variables.razor similarity index 95% rename from MoonlightServers.Frontend/UI/Components/Stars/UpdateStarPartials/VariablesStarUpdate.razor rename to MoonlightServers.Frontend/UI/Components/Stars/UpdatePartials/Variables.razor index fcb9fd5..2e73e97 100644 --- a/MoonlightServers.Frontend/UI/Components/Stars/UpdateStarPartials/VariablesStarUpdate.razor +++ b/MoonlightServers.Frontend/UI/Components/Stars/UpdatePartials/Variables.razor @@ -20,7 +20,7 @@
- @foreach (var variable in Variables) + @foreach (var variable in CurrentVariables) {
@@ -48,7 +48,7 @@ { [Parameter] public StarDetailResponse Star { get; set; } - private StarVariableDetailResponse[] Variables; + private StarVariableDetailResponse[] CurrentVariables; private LazyLoader LazyLoader; private async Task Load(LazyLoader arg) @@ -57,7 +57,7 @@ $"api/admin/servers/stars/{Star.Id}/variables?page=0&pageSize=50" ); - Variables = pagedVariables.Items; + CurrentVariables = pagedVariables.Items; } private async Task AddVariable() diff --git a/MoonlightServers.Frontend/UI/Components/XtermConsole.razor b/MoonlightServers.Frontend/UI/Components/XtermConsole.razor index ea754e1..0853d3b 100644 --- a/MoonlightServers.Frontend/UI/Components/XtermConsole.razor +++ b/MoonlightServers.Frontend/UI/Components/XtermConsole.razor @@ -1,31 +1,72 @@ +@using System.Collections.Concurrent @using Microsoft.Extensions.Logging +@using MoonCore.Blazor.FlyonUi.Modals +@using MoonCore.Helpers @using XtermBlazor +@using MoonCore.Blazor.FlyonUi.Components @inject IJSRuntime JsRuntime +@inject ModalService ModalService @inject ILogger Logger -
+@implements IAsyncDisposable + +
@if (IsInitialized) { } + +
+ + + + +
+ +
+
+ @if (IsPaused) + { + + } + else + { + + } + + +
+
@code { [Parameter] public Func? OnAfterInitialized { get; set; } [Parameter] public Func? OnFirstRender { get; set; } - - private Xterm Terminal; - private bool IsInitialized = false; - private bool HadFirstRender = false; + [Parameter] public bool ShowActions { get; set; } = true; + [Parameter] public bool ShowInput { get; set; } = false; + [Parameter] public int MaxOutputCacheSize { get; set; } = 250; + [Parameter] public Func? OnCommand { get; set; } - private readonly Queue MessageCache = new(); - private readonly HashSet Addons = ["addon-fit"]; - private readonly TerminalOptions Options = new() + [Parameter] public IList CommandHistory { get; set; } = new ConcurrentList(); + + public event Func? OnWrite; + + private Xterm Terminal; + public HashSet Addons { get; } = ["addon-fit"]; + + public TerminalOptions Options { get; } = new() { CursorBlink = false, CursorStyle = CursorStyle.Bar, @@ -36,17 +77,23 @@ Theme = { Background = "#000000" - } + }, }; - + + public ConcurrentList OutputCache { get; private set; } = new(); + public ConcurrentList WriteQueue { get; private set; } = new(); + private int CommandIndex = -1; + private bool IsReadyToWrite = false; + private bool IsPaused = false; + private bool IsInitialized = false; + + private string CommandInput = ""; protected override async Task OnAfterRenderAsync(bool firstRender) { - if(!firstRender) + if (!firstRender) return; - // Initialize addons - try { await JsRuntime.InvokeVoidAsync("moonlightServers.loadAddons"); @@ -59,7 +106,7 @@ IsInitialized = true; await InvokeAsync(StateHasChanged); - if(OnAfterInitialized != null) + if (OnAfterInitialized != null) await OnAfterInitialized.Invoke(); } @@ -74,13 +121,13 @@ Logger.LogError("An error occured while calling addons: {e}", e); } - HadFirstRender = true; + IsReadyToWrite = true; - // Write out cache - while (MessageCache.Count > 0) - { - await Terminal.Write(MessageCache.Dequeue()); - } + // Write queued content since initialisation started + var queueContent = string.Concat(WriteQueue); + WriteQueue.Clear(); + + await Terminal.Write(queueContent); if (OnFirstRender != null) await OnFirstRender.Invoke(); @@ -89,10 +136,103 @@ public async Task Write(string content) { // We cache messages here as there is the chance that the console isn't ready for input while receiving write tasks - - if (HadFirstRender) - await Terminal.Write(content); + + if (IsReadyToWrite && !IsPaused) + await HandleWrite(content); else - MessageCache.Enqueue(content); + WriteQueue.Add(content); + } + + private async Task HandleWrite(string content) + { + // Update output cache and prune it if required + if (OutputCache.Count > MaxOutputCacheSize) + { + for (var i = 0; i < 50; i++) + OutputCache.RemoveAt(i); + } + + OutputCache.Add(content); + + // Trigger events + if (OnWrite != null) + await OnWrite.Invoke(content); + + // Write in own terminal + await Terminal.Write(content); + } + + private async Task OpenFullscreen() + { + await ModalService.Launch(parameters => { parameters["Parent"] = this; }, size: "max-w-none"); + } + + private async Task TogglePause() + { + IsPaused = !IsPaused; + await InvokeAsync(StateHasChanged); + + if (IsPaused) + return; + + var queueContent = string.Concat(WriteQueue); + WriteQueue.Clear(); + + await HandleWrite(queueContent); + } + + private async Task SubmitCommand() + { + CommandHistory.Add(CommandInput); + + if (OnCommand != null) + await OnCommand.Invoke(CommandInput); + + CommandIndex = -1; + CommandInput = ""; + + await InvokeAsync(StateHasChanged); + } + + private async Task HandleKey(KeyboardEventArgs keyboard) + { + switch (keyboard.Code) + { + case "Enter": + await SubmitCommand(); + break; + + case "ArrowUp" or "ArrowDown": + { + var highestIndex = CommandHistory.Count - 1; + + if (CommandIndex == -1) + CommandIndex = highestIndex; + else + { + if (keyboard.Code is "ArrowUp") + CommandIndex++; + else if (keyboard.Code is "ArrowDown") + CommandIndex--; + } + + if (CommandIndex > highestIndex) + CommandIndex = highestIndex; + + if (CommandIndex < 0) + CommandIndex = 0; + + if (CommandIndex <= highestIndex || CommandHistory.Count > 0) + CommandInput = CommandHistory[CommandIndex]; + + await InvokeAsync(StateHasChanged); + break; + } + } + } + + public async ValueTask DisposeAsync() + { + await Terminal.DisposeAsync(); } } \ No newline at end of file diff --git a/MoonlightServers.Frontend/UI/Views/Admin/All/Create.razor b/MoonlightServers.Frontend/UI/Views/Admin/All/Create.razor index f5b6088..8c47f84 100644 --- a/MoonlightServers.Frontend/UI/Views/Admin/All/Create.razor +++ b/MoonlightServers.Frontend/UI/Views/Admin/All/Create.razor @@ -5,7 +5,7 @@ @using MoonCore.Helpers @using Moonlight.Shared.Http.Responses.Admin.Users @using MoonlightServers.Shared.Http.Requests.Admin.Servers -@using MoonlightServers.Frontend.UI.Components.Servers.CreateServerPartials +@using MoonlightServers.Frontend.UI.Components.Servers.CreatePartials @using MoonlightServers.Shared.Http.Responses.Admin.NodeAllocations @using MoonlightServers.Shared.Http.Responses.Admin.Nodes @using MoonlightServers.Shared.Http.Responses.Admin.Stars @@ -18,11 +18,11 @@ - + Back - + Create @@ -31,16 +31,16 @@ - + - + - + - + diff --git a/MoonlightServers.Frontend/UI/Views/Admin/All/Index.razor b/MoonlightServers.Frontend/UI/Views/Admin/All/Index.razor index b1de660..3866882 100644 --- a/MoonlightServers.Frontend/UI/Views/Admin/All/Index.razor +++ b/MoonlightServers.Frontend/UI/Views/Admin/All/Index.razor @@ -33,7 +33,13 @@ - + + + + @context.Name + + + @{ diff --git a/MoonlightServers.Frontend/UI/Views/Admin/All/Update.razor b/MoonlightServers.Frontend/UI/Views/Admin/All/Update.razor index 59d8c66..89f01e3 100644 --- a/MoonlightServers.Frontend/UI/Views/Admin/All/Update.razor +++ b/MoonlightServers.Frontend/UI/Views/Admin/All/Update.razor @@ -6,7 +6,7 @@ @using Moonlight.Shared.Http.Responses.Admin.Users @using MoonlightServers.Shared.Http.Requests.Admin.Servers @using MoonlightServers.Shared.Http.Responses.Admin.Servers -@using MoonlightServers.Frontend.UI.Components.Servers.UpdateServerPartials +@using MoonlightServers.Frontend.UI.Components.Servers.UpdatePartials @using MoonlightServers.Shared.Http.Responses.Admin.NodeAllocations @inject HttpApiClient ApiClient @@ -18,11 +18,11 @@ - + Back - + Update @@ -31,16 +31,16 @@ - + - + - + - + @@ -55,8 +55,8 @@ private UpdateServerRequest Request; private ServerResponse Server; - private List Allocations = new(); - private UserResponse Owner; + public List Allocations = new(); + public UserResponse Owner; private async Task Load(LazyLoader _) { diff --git a/MoonlightServers.Frontend/UI/Views/Admin/Nodes/Create.razor b/MoonlightServers.Frontend/UI/Views/Admin/Nodes/Create.razor index a50f7e9..57c0747 100644 --- a/MoonlightServers.Frontend/UI/Views/Admin/Nodes/Create.razor +++ b/MoonlightServers.Frontend/UI/Views/Admin/Nodes/Create.razor @@ -13,11 +13,11 @@ - + Back - + Create diff --git a/MoonlightServers.Frontend/UI/Views/Admin/Nodes/Update.razor b/MoonlightServers.Frontend/UI/Views/Admin/Nodes/Update.razor index 01628fc..df521f3 100644 --- a/MoonlightServers.Frontend/UI/Views/Admin/Nodes/Update.razor +++ b/MoonlightServers.Frontend/UI/Views/Admin/Nodes/Update.razor @@ -5,7 +5,7 @@ @using MoonCore.Helpers @using MoonlightServers.Shared.Http.Requests.Admin.Nodes @using MoonlightServers.Shared.Http.Responses.Admin.Nodes -@using MoonlightServers.Frontend.UI.Components.Nodes.UpdateNodePartials +@using MoonlightServers.Frontend.UI.Components.Nodes.UpdatePartials @inject HttpApiClient ApiClient @inject NavigationManager Navigation @@ -16,11 +16,11 @@
- + Back - + Update @@ -30,19 +30,19 @@ - + - + - + - + diff --git a/MoonlightServers.Frontend/UI/Views/Admin/Stars/Create.razor b/MoonlightServers.Frontend/UI/Views/Admin/Stars/Create.razor index 20f6491..3653c4c 100644 --- a/MoonlightServers.Frontend/UI/Views/Admin/Stars/Create.razor +++ b/MoonlightServers.Frontend/UI/Views/Admin/Stars/Create.razor @@ -14,11 +14,11 @@ - + Back - + Create diff --git a/MoonlightServers.Frontend/UI/Views/Admin/Stars/Index.razor b/MoonlightServers.Frontend/UI/Views/Admin/Stars/Index.razor index 8708f7f..ab90c1a 100644 --- a/MoonlightServers.Frontend/UI/Views/Admin/Stars/Index.razor +++ b/MoonlightServers.Frontend/UI/Views/Admin/Stars/Index.razor @@ -25,7 +25,7 @@
- +
@if (CurrentTab == null) @@ -201,6 +193,8 @@ private HubConnection? HubConnection; + public ConcurrentList CommandHistory = new(); + private async Task Load(LazyLoader _) { try diff --git a/MoonlightServers.Shared/Http/Requests/Client/Servers/ServerCommandRequest.cs b/MoonlightServers.Shared/Http/Requests/Client/Servers/ServerCommandRequest.cs new file mode 100644 index 0000000..664fdde --- /dev/null +++ b/MoonlightServers.Shared/Http/Requests/Client/Servers/ServerCommandRequest.cs @@ -0,0 +1,9 @@ +using System.ComponentModel.DataAnnotations; + +namespace MoonlightServers.Shared.Http.Requests.Client.Servers; + +public class ServerCommandRequest +{ + [Required(ErrorMessage = "The command is required")] + public string Command { get; set; } +} \ No newline at end of file