From 87e41721493bec6704b7ba9efe792258851d182f Mon Sep 17 00:00:00 2001 From: ChiaraBm Date: Sat, 28 Dec 2024 17:24:38 +0100 Subject: [PATCH] Implemented server list and power state display --- .../Extensions/ServerStateExtensions.cs | 20 ++ .../Admin/Servers/ServersController.cs | 2 - .../Controllers/Users/ServersController.cs | 115 ++++++++++++ .../Implementations/SidebarImplementation.cs | 7 + .../UI/Components/Servers/ServerCard.razor | 174 ++++++++++++++++++ .../UI/Components/Servers/TestServerCrd.razor | 125 +++++++++++++ .../UI/Views/User/Index.razor | 32 ++++ .../Enums/ServerPowerState.cs | 10 + .../Allocations/AllocationDetailResponse.cs | 8 + .../Users/Servers/ServerDetailResponse.cs | 15 ++ .../Users/Servers/ServerStatusResponse.cs | 8 + 11 files changed, 514 insertions(+), 2 deletions(-) create mode 100644 MoonlightServers.ApiServer/Extensions/ServerStateExtensions.cs create mode 100644 MoonlightServers.ApiServer/Http/Controllers/Users/ServersController.cs create mode 100644 MoonlightServers.Frontend/UI/Components/Servers/ServerCard.razor create mode 100644 MoonlightServers.Frontend/UI/Components/Servers/TestServerCrd.razor create mode 100644 MoonlightServers.Frontend/UI/Views/User/Index.razor create mode 100644 MoonlightServers.Shared/Enums/ServerPowerState.cs create mode 100644 MoonlightServers.Shared/Http/Responses/Users/Allocations/AllocationDetailResponse.cs create mode 100644 MoonlightServers.Shared/Http/Responses/Users/Servers/ServerDetailResponse.cs create mode 100644 MoonlightServers.Shared/Http/Responses/Users/Servers/ServerStatusResponse.cs diff --git a/MoonlightServers.ApiServer/Extensions/ServerStateExtensions.cs b/MoonlightServers.ApiServer/Extensions/ServerStateExtensions.cs new file mode 100644 index 0000000..c92a050 --- /dev/null +++ b/MoonlightServers.ApiServer/Extensions/ServerStateExtensions.cs @@ -0,0 +1,20 @@ +using MoonlightServers.DaemonShared.Enums; +using MoonlightServers.Shared.Enums; + +namespace MoonlightServers.ApiServer.Extensions; + +public static class ServerStateExtensions +{ + public static ServerPowerState ToServerPowerState(this ServerState state) + { + return state switch + { + ServerState.Installing => ServerPowerState.Installing, + ServerState.Stopping => ServerPowerState.Stopping, + ServerState.Online => ServerPowerState.Online, + ServerState.Starting => ServerPowerState.Starting, + ServerState.Offline => ServerPowerState.Offline, + _ => ServerPowerState.Offline + }; + } +} \ No newline at end of file diff --git a/MoonlightServers.ApiServer/Http/Controllers/Admin/Servers/ServersController.cs b/MoonlightServers.ApiServer/Http/Controllers/Admin/Servers/ServersController.cs index 1134abb..b91923c 100644 --- a/MoonlightServers.ApiServer/Http/Controllers/Admin/Servers/ServersController.cs +++ b/MoonlightServers.ApiServer/Http/Controllers/Admin/Servers/ServersController.cs @@ -9,9 +9,7 @@ using MoonCore.Models; using Moonlight.ApiServer.Database.Entities; using MoonlightServers.ApiServer.Database.Entities; using MoonlightServers.Shared.Http.Requests.Admin.Servers; -using MoonlightServers.Shared.Http.Responses.Admin.NodeAllocations; using MoonlightServers.Shared.Http.Responses.Admin.Servers; -using MoonlightServers.Shared.Http.Responses.Admin.ServerVariables; namespace MoonlightServers.ApiServer.Http.Controllers.Admin.Servers; diff --git a/MoonlightServers.ApiServer/Http/Controllers/Users/ServersController.cs b/MoonlightServers.ApiServer/Http/Controllers/Users/ServersController.cs new file mode 100644 index 0000000..fc634fb --- /dev/null +++ b/MoonlightServers.ApiServer/Http/Controllers/Users/ServersController.cs @@ -0,0 +1,115 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using MoonCore.Attributes; +using MoonCore.Exceptions; +using MoonCore.Extended.Abstractions; +using MoonCore.Extensions; +using MoonCore.Models; +using Moonlight.ApiServer.Database.Entities; +using MoonlightServers.ApiServer.Database.Entities; +using MoonlightServers.ApiServer.Extensions; +using MoonlightServers.ApiServer.Services; +using MoonlightServers.Shared.Http.Responses.User.Allocations; +using MoonlightServers.Shared.Http.Responses.Users.Servers; + +namespace MoonlightServers.ApiServer.Http.Controllers.Users; + +[ApiController] +[Route("api/servers")] +public class ServersController : Controller +{ + private readonly DatabaseRepository ServerRepository; + private readonly NodeService NodeService; + + public ServersController(DatabaseRepository serverRepository, NodeService nodeService) + { + ServerRepository = serverRepository; + NodeService = nodeService; + } + + [HttpGet("list")] + [RequirePermission("meta.authenticated")] + public async Task> List([FromQuery] int page, [FromQuery] int pageSize) + { + var user = User.AsIdentity(); + + var query = ServerRepository + .Get() + .Include(x => x.Allocations) + .Include(x => x.Star) + .Include(x => x.Node) + .Where(x => x.OwnerId == user.Id); + + var count = await query.CountAsync(); + var items = await query.Skip(page * pageSize).Take(pageSize).ToArrayAsync(); + + var mappedItems = items.Select(x => new ServerDetailResponse() + { + Id = x.Id, + Name = x.Name, + NodeName = x.Node.Name, + StarName = x.Star.Name, + Allocations = x.Allocations.Select(y => new AllocationDetailResponse() + { + Id = y.Id, + Port = y.Port, + IpAddress = y.IpAddress + }).ToArray() + }).ToArray(); + + return new PagedData() + { + Items = mappedItems, + CurrentPage = page, + PageSize = pageSize, + TotalItems = count, + TotalPages = count == 0 ? 0 : count / pageSize + }; + } + + [HttpGet("{serverId:int}/status")] + [RequirePermission("meta.authenticated")] + public async Task GetStatus([FromRoute] int serverId) + { + var server = await GetServerWithPermCheck(serverId); + + var apiClient = await NodeService.CreateApiClient(server.Node); + + try + { + var data = await apiClient.GetJson( + $"api/servers/{server.Id}/status" + ); + + return new ServerStatusResponse() + { + PowerState = data.State.ToServerPowerState() + }; + } + catch (HttpRequestException e) + { + throw new HttpApiException("Unable to access the node the server is running on", 502); + } + } + + private async Task GetServerWithPermCheck(int serverId) + { + var user = User.AsIdentity(); + + var server = await ServerRepository + .Get() + .Include(x => x.Node) + .FirstOrDefaultAsync(x => x.Id == serverId); + + if (server == null) + throw new HttpApiException("No server with this id found", 404); + + if (server.OwnerId == user.Id) // The current user is the owner + return server; + + if (User.HasPermission("admin.servers.get")) // The current user is an admin + return server; + + throw new HttpApiException("No server with this id found", 404); + } +} \ No newline at end of file diff --git a/MoonlightServers.Frontend/Implementations/SidebarImplementation.cs b/MoonlightServers.Frontend/Implementations/SidebarImplementation.cs index 6b3edf8..e416f7f 100644 --- a/MoonlightServers.Frontend/Implementations/SidebarImplementation.cs +++ b/MoonlightServers.Frontend/Implementations/SidebarImplementation.cs @@ -9,6 +9,13 @@ public class SidebarImplementation : ISidebarItemProvider { return [ + new SidebarItem() + { + Name = "Servers", + Path = "/servers", + Icon = "icon-server", + Priority = 4 + }, new SidebarItem() { Name = "Servers", diff --git a/MoonlightServers.Frontend/UI/Components/Servers/ServerCard.razor b/MoonlightServers.Frontend/UI/Components/Servers/ServerCard.razor new file mode 100644 index 0000000..fae16c9 --- /dev/null +++ b/MoonlightServers.Frontend/UI/Components/Servers/ServerCard.razor @@ -0,0 +1,174 @@ +@using MoonCore.Helpers +@using MoonlightServers.Shared.Enums +@using MoonlightServers.Shared.Http.Responses.Users.Servers + +@inject HttpApiClient ApiClient +@inject ILogger Logger + +@{ + var gradient = "from-gray-600/20"; + var border = "border-gray-600"; + + if (IsLoaded && !IsFailed) + { + gradient = Status.PowerState switch + { + ServerPowerState.Installing => "from-primary-600/20", + ServerPowerState.Offline => "from-danger-600/20", + ServerPowerState.Starting => "from-warning-600/20", + ServerPowerState.Stopping => "from-warning-600/20", + ServerPowerState.Online => "from-success-600/20", + _ => "from-gray-600/20" + }; + + border = Status.PowerState switch + { + ServerPowerState.Installing => "border-primary-600", + ServerPowerState.Offline => "border-danger-600", + ServerPowerState.Starting => "border-warning-600", + ServerPowerState.Stopping => "border-warning-600", + ServerPowerState.Online => "border-success-600", + _ => "border-gray-600" + }; + } +} + + +
+
+
+ +
+ @Server.Name +
+
+
+ + + + +
+
+ +@code +{ + [Parameter] public ServerDetailResponse Server { get; set; } + + private ServerStatusResponse Status; + + private bool IsFailed = false; + private bool IsLoaded = false; + + protected override async Task OnAfterRenderAsync(bool firstRender) + { + if (!firstRender) + return; + + try + { + Status = await ApiClient.GetJson( + $"api/servers/{Server.Id}/status" + ); + } + catch (Exception e) + { + IsFailed = true; + Logger.LogWarning("Unable to fetch status from server {id}: {e}", Server.Id, e); + } + + IsLoaded = true; + await InvokeAsync(StateHasChanged); + } +} \ No newline at end of file diff --git a/MoonlightServers.Frontend/UI/Components/Servers/TestServerCrd.razor b/MoonlightServers.Frontend/UI/Components/Servers/TestServerCrd.razor new file mode 100644 index 0000000..0a503a4 --- /dev/null +++ b/MoonlightServers.Frontend/UI/Components/Servers/TestServerCrd.razor @@ -0,0 +1,125 @@ +@using MoonlightServers.Shared.Http.Responses.Users.Servers +@{ + var gradient = Status switch + { + 4 => "from-primary-600/20", + 3 => "from-danger-600/20", + 2 => "from-warning-600/20", + 1 => "from-success-600/20", + _ => "from-gray-600/20" + }; + + var border = Status switch + { + 4 => "border-primary-600", + 3 => "border-danger-600", + 2 => "border-warning-600", + 1 => "border-success-600", + _ => "border-gray-600" + }; +} + + +
+
+
+ +
+ @Server.Name +
+
+
+ + + + +
+ + +
+ +@code +{ + [Parameter] + public ServerDetailResponse Server { get; set; } + + [Parameter] + public int Status { get; set; } +} diff --git a/MoonlightServers.Frontend/UI/Views/User/Index.razor b/MoonlightServers.Frontend/UI/Views/User/Index.razor new file mode 100644 index 0000000..5a956a3 --- /dev/null +++ b/MoonlightServers.Frontend/UI/Views/User/Index.razor @@ -0,0 +1,32 @@ +@page "/servers" + +@using MoonCore.Helpers +@using MoonlightServers.Frontend.UI.Components.Servers +@using MoonCore.Blazor.Tailwind.Components +@using MoonCore.Models +@using MoonlightServers.Shared.Http.Responses.Users.Servers + +@inject HttpApiClient ApiClient + + +
+ @foreach (var server in Servers) + { + + } +
+
+ +@code +{ + private ServerDetailResponse[] Servers; + + private async Task Load(LazyLoader lazyLoader) + { + Servers = await PagedData.All(async (page, pageSize) => + await ApiClient.GetJson>( + $"api/servers/list?page={page}&pageSize={pageSize}" + ) + ); + } +} \ No newline at end of file diff --git a/MoonlightServers.Shared/Enums/ServerPowerState.cs b/MoonlightServers.Shared/Enums/ServerPowerState.cs new file mode 100644 index 0000000..112d2de --- /dev/null +++ b/MoonlightServers.Shared/Enums/ServerPowerState.cs @@ -0,0 +1,10 @@ +namespace MoonlightServers.Shared.Enums; + +public enum ServerPowerState +{ + Offline = 0, + Starting = 1, + Online = 2, + Stopping = 3, + Installing = 4 +} \ No newline at end of file diff --git a/MoonlightServers.Shared/Http/Responses/Users/Allocations/AllocationDetailResponse.cs b/MoonlightServers.Shared/Http/Responses/Users/Allocations/AllocationDetailResponse.cs new file mode 100644 index 0000000..5d544b4 --- /dev/null +++ b/MoonlightServers.Shared/Http/Responses/Users/Allocations/AllocationDetailResponse.cs @@ -0,0 +1,8 @@ +namespace MoonlightServers.Shared.Http.Responses.User.Allocations; + +public class AllocationDetailResponse +{ + public int Id { get; set; } + public string IpAddress { get; set; } + public int Port { get; set; } +} \ No newline at end of file diff --git a/MoonlightServers.Shared/Http/Responses/Users/Servers/ServerDetailResponse.cs b/MoonlightServers.Shared/Http/Responses/Users/Servers/ServerDetailResponse.cs new file mode 100644 index 0000000..afa69da --- /dev/null +++ b/MoonlightServers.Shared/Http/Responses/Users/Servers/ServerDetailResponse.cs @@ -0,0 +1,15 @@ +using MoonlightServers.Shared.Http.Responses.User.Allocations; + +namespace MoonlightServers.Shared.Http.Responses.Users.Servers; + +public class ServerDetailResponse +{ + public int Id { get; set; } + + public string Name { get; set; } + + public string NodeName { get; set; } + public string StarName { get; set; } + + public AllocationDetailResponse[] Allocations { get; set; } +} \ No newline at end of file diff --git a/MoonlightServers.Shared/Http/Responses/Users/Servers/ServerStatusResponse.cs b/MoonlightServers.Shared/Http/Responses/Users/Servers/ServerStatusResponse.cs new file mode 100644 index 0000000..513451c --- /dev/null +++ b/MoonlightServers.Shared/Http/Responses/Users/Servers/ServerStatusResponse.cs @@ -0,0 +1,8 @@ +using MoonlightServers.Shared.Enums; + +namespace MoonlightServers.Shared.Http.Responses.Users.Servers; + +public class ServerStatusResponse +{ + public ServerPowerState PowerState { get; set; } +} \ No newline at end of file