diff --git a/MoonlightServers.ApiServer/Http/Controllers/Admin/Servers/ServersController.cs b/MoonlightServers.ApiServer/Http/Controllers/Admin/Servers/ServersController.cs index f55bc22..831b1e4 100644 --- a/MoonlightServers.ApiServer/Http/Controllers/Admin/Servers/ServersController.cs +++ b/MoonlightServers.ApiServer/Http/Controllers/Admin/Servers/ServersController.cs @@ -8,6 +8,7 @@ using MoonCore.Helpers; using MoonCore.Models; using Moonlight.ApiServer.Database.Entities; using MoonlightServers.ApiServer.Database.Entities; +using MoonlightServers.ApiServer.Services; using MoonlightServers.Shared.Http.Requests.Admin.Servers; using MoonlightServers.Shared.Http.Responses.Admin.Servers; @@ -24,6 +25,8 @@ public class ServersController : Controller private readonly DatabaseRepository VariableRepository; private readonly DatabaseRepository ServerRepository; private readonly DatabaseRepository UserRepository; + private readonly ILogger Logger; + private readonly ServerService ServerService; public ServersController( CrudHelper crudHelper, @@ -32,7 +35,10 @@ public class ServersController : Controller DatabaseRepository allocationRepository, DatabaseRepository variableRepository, DatabaseRepository serverRepository, - DatabaseRepository userRepository) + DatabaseRepository userRepository, + ILogger logger, + ServerService serverService + ) { CrudHelper = crudHelper; StarRepository = starRepository; @@ -41,6 +47,8 @@ public class ServersController : Controller VariableRepository = variableRepository; ServerRepository = serverRepository; UserRepository = userRepository; + ServerService = serverService; + Logger = logger; CrudHelper.QueryModifier = servers => servers .Include(x => x.Node) @@ -146,7 +154,7 @@ public class ServersController : Controller foreach (var variable in star.Variables) { var requestVar = request.Variables.FirstOrDefault(x => x.Key == variable.Key); - + var serverVar = new ServerVariable() { Key = variable.Key, @@ -162,10 +170,23 @@ public class ServersController : Controller server.Node = node; server.Star = star; - // TODO: Call node - var finalServer = await ServerRepository.Add(server); + try + { + await ServerService.Sync(finalServer); + } + catch (Exception e) + { + Logger.LogError("Unable to sync server to node the server is assigned to: {e}", e); + + // We are deleting the server from the database after the creation has failed + // to ensure we wont have a bugged server in the database which doesnt exist on the node + await ServerRepository.Remove(finalServer); + + throw; + } + return CrudHelper.MapToResult(finalServer); } @@ -186,7 +207,7 @@ public class ServersController : Controller .Where(x => x.Server == null || x.Server.Id == server.Id) .Where(x => x.Node.Id == server.Node.Id) .FirstOrDefaultAsync(x => x.Id == allocationId); - + // ^ This loads the allocations specified in the request. // Valid allocations are either free ones or ones which are already allocated to this server @@ -207,31 +228,53 @@ public class ServersController : Controller // Set allocations server.Allocations = allocations; - + // Process variables foreach (var variable in request.Variables) { // Search server variable associated to the variable in the request var serverVar = server.Variables .FirstOrDefault(x => x.Key == variable.Key); - - if(serverVar == null) + + if (serverVar == null) continue; // Update value serverVar.Value = variable.Value; } - - // TODO: Call node - + await ServerRepository.Update(server); + // Notify the node about the changes + await ServerService.Sync(server); + return CrudHelper.MapToResult(server); } [HttpDelete("{id:int}")] - public async Task Delete([FromRoute] int id) + public async Task Delete([FromRoute] int id, [FromQuery] bool force = false) { + var server = await CrudHelper.GetSingleModel(id); + + try + { + // If the sync fails on the node and we aren't forcing the deletion, + // we don't want to delete it from the database yet + await ServerService.SyncDelete(server); + } + catch (Exception e) + { + if (force) + { + Logger.LogWarning( + "An error occured while syncing deletion of a server to the node. Continuing anyways. Error: {e}", + e + ); + } + else + throw; + } + await CrudHelper.Delete(id); } } \ No newline at end of file diff --git a/MoonlightServers.ApiServer/Http/Controllers/Client/ServerPowerController.cs b/MoonlightServers.ApiServer/Http/Controllers/Client/ServerPowerController.cs index b73992b..c17bbf0 100644 --- a/MoonlightServers.ApiServer/Http/Controllers/Client/ServerPowerController.cs +++ b/MoonlightServers.ApiServer/Http/Controllers/Client/ServerPowerController.cs @@ -4,6 +4,7 @@ using Microsoft.EntityFrameworkCore; using MoonCore.Exceptions; using MoonCore.Extended.Abstractions; using MoonCore.Helpers; +using Moonlight.ApiServer.Database.Entities; using MoonlightServers.ApiServer.Database.Entities; using MoonlightServers.ApiServer.Services; @@ -15,113 +16,69 @@ namespace MoonlightServers.ApiServer.Http.Controllers.Client; public class ServerPowerController : Controller { private readonly DatabaseRepository ServerRepository; - private readonly NodeService NodeService; + private readonly DatabaseRepository UserRepository; + private readonly ServerService ServerService; - public ServerPowerController(DatabaseRepository serverRepository, NodeService nodeService) + public ServerPowerController( + DatabaseRepository serverRepository, + DatabaseRepository userRepository, + ServerService serverService + ) { ServerRepository = serverRepository; - NodeService = nodeService; + UserRepository = userRepository; + ServerService = serverService; } [HttpPost("{serverId:int}/start")] [Authorize] public async Task Start([FromRoute] int serverId) { - var server = await GetServerWithPermCheck(serverId); - - using var apiClient = await NodeService.CreateApiClient(server.Node); - - try - { - await apiClient.Post($"api/servers/{server.Id}/start"); - } - catch (HttpRequestException e) - { - throw new HttpApiException("Unable to access the node the server is running on", 502); - } + var server = await GetServerById(serverId); + await ServerService.Start(server); } [HttpPost("{serverId:int}/stop")] [Authorize] public async Task Stop([FromRoute] int serverId) { - var server = await GetServerWithPermCheck(serverId); - - using var apiClient = await NodeService.CreateApiClient(server.Node); - - try - { - await apiClient.Post($"api/servers/{server.Id}/stop"); - } - catch (HttpRequestException e) - { - throw new HttpApiException("Unable to access the node the server is running on", 502); - } + var server = await GetServerById(serverId); + await ServerService.Stop(server); } [HttpPost("{serverId:int}/kill")] [Authorize] public async Task Kill([FromRoute] int serverId) { - var server = await GetServerWithPermCheck(serverId); - - using var apiClient = await NodeService.CreateApiClient(server.Node); - - try - { - await apiClient.Post($"api/servers/{server.Id}/kill"); - } - catch (HttpRequestException e) - { - throw new HttpApiException("Unable to access the node the server is running on", 502); - } + var server = await GetServerById(serverId); + await ServerService.Kill(server); } [HttpPost("{serverId:int}/install")] [Authorize] public async Task Install([FromRoute] int serverId) { - var server = await GetServerWithPermCheck(serverId); - - using var apiClient = await NodeService.CreateApiClient(server.Node); - - try - { - await apiClient.Post($"api/servers/{server.Id}/install"); - } - catch (HttpRequestException e) - { - throw new HttpApiException("Unable to access the node the server is running on", 502); - } + var server = await GetServerById(serverId); + await ServerService.Install(server); } - private async Task GetServerWithPermCheck(int serverId, - Func, IQueryable>? queryModifier = null) + private async Task GetServerById(int serverId) { - var userIdClaim = User.Claims.First(x => x.Type == "userId"); - var userId = int.Parse(userIdClaim.Value); - - var query = ServerRepository + var server = await ServerRepository .Get() - .Include(x => x.Node) as IQueryable; - - if (queryModifier != null) - query = queryModifier.Invoke(query); - - var server = await query + .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 == userId) // The current user is the owner - return server; + var userIdClaim = User.Claims.First(x => x.Type == "userId"); + var userId = int.Parse(userIdClaim.Value); + var user = await UserRepository.Get().FirstAsync(x => x.Id == userId); - var permissions = User.Claims.First(x => x.Type == "permissions").Value.Split(";", StringSplitOptions.RemoveEmptyEntries); - - if (PermissionHelper.HasPermission(permissions, "admin.servers.get")) // The current user is an admin - return server; + if (!ServerService.IsAllowedToAccess(user, server)) + throw new HttpApiException("No server with this id found", 404); - throw new HttpApiException("No server with this id found", 404); + return server; } } \ No newline at end of file diff --git a/MoonlightServers.ApiServer/Http/Controllers/Client/ServersController.cs b/MoonlightServers.ApiServer/Http/Controllers/Client/ServersController.cs index a21b284..41909fb 100644 --- a/MoonlightServers.ApiServer/Http/Controllers/Client/ServersController.cs +++ b/MoonlightServers.ApiServer/Http/Controllers/Client/ServersController.cs @@ -3,8 +3,8 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using MoonCore.Exceptions; using MoonCore.Extended.Abstractions; -using MoonCore.Helpers; using MoonCore.Models; +using Moonlight.ApiServer.Database.Entities; using MoonlightServers.ApiServer.Database.Entities; using MoonlightServers.ApiServer.Extensions; using MoonlightServers.ApiServer.Services; @@ -17,13 +17,17 @@ namespace MoonlightServers.ApiServer.Http.Controllers.Client; [Route("api/client/servers")] public class ServersController : Controller { + private readonly ServerService ServerService; private readonly DatabaseRepository ServerRepository; + private readonly DatabaseRepository UserRepository; private readonly NodeService NodeService; - public ServersController(DatabaseRepository serverRepository, NodeService nodeService) + public ServersController(DatabaseRepository serverRepository, NodeService nodeService, ServerService serverService, DatabaseRepository userRepository) { ServerRepository = serverRepository; NodeService = nodeService; + ServerService = serverService; + UserRepository = userRepository; } [HttpGet] @@ -71,13 +75,22 @@ public class ServersController : Controller [Authorize] public async Task Get([FromRoute] int serverId) { - var server = await GetServerWithPermCheck( - serverId, - query => - query - .Include(x => x.Allocations) - .Include(x => x.Star) - ); + var server = await ServerRepository + .Get() + .Include(x => x.Allocations) + .Include(x => x.Star) + .Include(x => x.Node) + .FirstOrDefaultAsync(x => x.Id == serverId); + + if(server == null) + throw new HttpApiException("No server with this id found", 404); + + var userIdClaim = User.Claims.First(x => x.Type == "userId"); + var userId = int.Parse(userIdClaim.Value); + var user = await UserRepository.Get().FirstAsync(x => x.Id == userId); + + if(!ServerService.IsAllowedToAccess(user, server)) + throw new HttpApiException("No server with this id found", 404); return new ServerDetailResponse() { @@ -98,32 +111,20 @@ public class ServersController : Controller [Authorize] public async Task GetStatus([FromRoute] int serverId) { - var server = await GetServerWithPermCheck(serverId); + var server = await GetServerById(serverId); + var status = await ServerService.GetStatus(server); - var apiClient = await NodeService.CreateApiClient(server.Node); - - try + return new ServerStatusResponse() { - var data = await apiClient.GetJson( - $"api/servers/{server.Id}/status" - ); - - return new ServerStatusResponse() - { - State = data.State.ToServerPowerState() - }; - } - catch (HttpRequestException e) - { - throw new HttpApiException("Unable to access the node the server is running on", 502); - } + State = status.State.ToServerPowerState() + }; } [HttpGet("{serverId:int}/ws")] [Authorize] public async Task GetWebSocket([FromRoute] int serverId) { - var server = await GetServerWithPermCheck(serverId); + var server = await GetServerById(serverId); // TODO: Handle transparent node proxy @@ -135,11 +136,7 @@ public class ServersController : Controller var url = ""; - if (server.Node.UseSsl) - url += "https://"; - else - url += "http://"; - + url += server.Node.UseSsl ? "https://" : "http://"; url += $"{server.Node.Fqdn}:{server.Node.HttpPort}/api/servers/ws"; return new ServerWebSocketResponse() @@ -153,54 +150,33 @@ public class ServersController : Controller [Authorize] public async Task GetLogs([FromRoute] int serverId) { - var server = await GetServerWithPermCheck(serverId); + var server = await GetServerById(serverId); - var apiClient = await NodeService.CreateApiClient(server.Node); - - try + var logs = await ServerService.GetLogs(server); + + return new ServerLogsResponse() { - var data = await apiClient.GetJson( - $"api/servers/{server.Id}/logs" - ); - - return new ServerLogsResponse() - { - Messages = data.Messages - }; - } - catch (HttpRequestException e) - { - throw new HttpApiException("Unable to access the node the server is running on", 502); - } + Messages = logs.Messages + }; } - private async Task GetServerWithPermCheck(int serverId, - Func, IQueryable>? queryModifier = null) + private async Task GetServerById(int serverId) { + 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); + var userIdClaim = User.Claims.First(x => x.Type == "userId"); var userId = int.Parse(userIdClaim.Value); - - var query = ServerRepository - .Get() - .Include(x => x.Node) as IQueryable; - - if (queryModifier != null) - query = queryModifier.Invoke(query); - - var server = await query - .FirstOrDefaultAsync(x => x.Id == serverId); - - if (server == null) + var user = await UserRepository.Get().FirstAsync(x => x.Id == userId); + + if(!ServerService.IsAllowedToAccess(user, server)) throw new HttpApiException("No server with this id found", 404); - if (server.OwnerId == userId) // The current user is the owner - return server; - - var permissions = User.Claims.First(x => x.Type == "permissions").Value.Split(";", StringSplitOptions.RemoveEmptyEntries); - - if (PermissionHelper.HasPermission(permissions, "admin.servers.get")) // The current user is an admin - return server; - - throw new HttpApiException("No server with this id found", 404); + return server; } } \ No newline at end of file diff --git a/MoonlightServers.ApiServer/Http/Controllers/Remote/ServersController.cs b/MoonlightServers.ApiServer/Http/Controllers/Remote/ServersController.cs index 2f26f78..82ce01c 100644 --- a/MoonlightServers.ApiServer/Http/Controllers/Remote/ServersController.cs +++ b/MoonlightServers.ApiServer/Http/Controllers/Remote/ServersController.cs @@ -37,7 +37,7 @@ public class ServersController : Controller var node = await NodeRepository .Get() .FirstAsync(x => x.TokenId == tokenId); - + var total = await ServerRepository .Get() .Where(x => x.Node.Id == node.Id) @@ -58,47 +58,12 @@ public class ServersController : Controller foreach (var server in servers) { - var dockerImage = server.Star.DockerImages - .Skip(server.DockerImageIndex) - .FirstOrDefault(); + var convertedData = ConvertToServerData(server); - if (dockerImage == null) - { - dockerImage = server.Star.DockerImages - .Skip(server.Star.DefaultDockerImage) - .FirstOrDefault(); - } - - if (dockerImage == null) - dockerImage = server.Star.DockerImages.LastOrDefault(); - - if (dockerImage == null) - { - Logger.LogWarning("Unable to map server data for server {id}: No docker image available", server.Id); + if (convertedData == null) continue; - } - serverData.Add(new ServerDataResponse() - { - Id = server.Id, - StartupCommand = server.StartupOverride ?? server.Star.StartupCommand, - Allocations = server.Allocations.Select(x => new AllocationDataResponse() - { - IpAddress = x.IpAddress, - Port = x.Port - }).ToArray(), - Variables = server.Variables.ToDictionary(x => x.Key, x => x.Value), - Bandwidth = server.Bandwidth, - Cpu = server.Cpu, - Disk = server.Disk, - Memory = server.Memory, - OnlineDetection = server.Star.OnlineDetection, - DockerImage = dockerImage.Identifier, - PullDockerImage = dockerImage.AutoPulling, - ParseConiguration = server.Star.ParseConfiguration, - StopCommand = server.Star.StopCommand, - UseVirtualDisk = server.UseVirtualDisk - }); + serverData.Add(convertedData); } return new PagedData() @@ -111,6 +76,38 @@ public class ServersController : Controller }; } + [HttpGet("{id:int}")] + public async Task Get([FromRoute] int id) + { + // Load the node via the token id + var tokenId = User.Claims.First(x => x.Type == "iss").Value; + + var node = await NodeRepository + .Get() + .FirstAsync(x => x.TokenId == tokenId); + + // Load the server with the star data attached. We filter by the node to ensure the node can only access + // servers linked to it + var server = await ServerRepository + .Get() + .Where(x => x.Node.Id == node.Id) + .Include(x => x.Star) + .ThenInclude(x => x.DockerImages) + .Include(x => x.Variables) + .Include(x => x.Allocations) + .FirstOrDefaultAsync(x => x.Id == id); + + if (server == null) + throw new HttpApiException("No server with this id found", 404); + + var convertedData = ConvertToServerData(server); + + if (convertedData == null) + throw new HttpApiException("An error occured while creating the server data model", 500); + + return convertedData; + } + [HttpGet("{id:int}/install")] public async Task GetInstall([FromRoute] int id) { @@ -120,7 +117,7 @@ public class ServersController : Controller var node = await NodeRepository .Get() .FirstAsync(x => x.TokenId == tokenId); - + // Load the server with the star data attached. We filter by the node to ensure the node can only access // servers linked to it var server = await ServerRepository @@ -139,4 +136,58 @@ public class ServersController : Controller Shell = server.Star.InstallShell }; } + + private ServerDataResponse? ConvertToServerData(Server server) + { + // Find the docker image to use for this server + StarDockerImage? dockerImage = null; + + // Handle server set image if specified + if (server.DockerImageIndex != -1) + { + dockerImage = server.Star.DockerImages + .Skip(server.DockerImageIndex) + .FirstOrDefault(); + } + + // Handle star default image if set + if (dockerImage == null && server.Star.DefaultDockerImage != -1) + { + dockerImage = server.Star.DockerImages + .Skip(server.Star.DefaultDockerImage) + .FirstOrDefault(); + } + + if (dockerImage == null) + dockerImage = server.Star.DockerImages.LastOrDefault(); + + if (dockerImage == null) + { + Logger.LogWarning("Unable to map server data for server {id}: No docker image available", server.Id); + return null; + } + + // Convert model + return new ServerDataResponse() + { + Id = server.Id, + StartupCommand = server.StartupOverride ?? server.Star.StartupCommand, + Allocations = server.Allocations.Select(x => new AllocationDataResponse() + { + IpAddress = x.IpAddress, + Port = x.Port + }).ToArray(), + Variables = server.Variables.ToDictionary(x => x.Key, x => x.Value), + Bandwidth = server.Bandwidth, + Cpu = server.Cpu, + Disk = server.Disk, + Memory = server.Memory, + OnlineDetection = server.Star.OnlineDetection, + DockerImage = dockerImage.Identifier, + PullDockerImage = dockerImage.AutoPulling, + ParseConiguration = server.Star.ParseConfiguration, + StopCommand = server.Star.StopCommand, + UseVirtualDisk = server.UseVirtualDisk + }; + } } \ No newline at end of file diff --git a/MoonlightServers.ApiServer/Services/NodeService.cs b/MoonlightServers.ApiServer/Services/NodeService.cs index cefe66e..ea9f3ec 100644 --- a/MoonlightServers.ApiServer/Services/NodeService.cs +++ b/MoonlightServers.ApiServer/Services/NodeService.cs @@ -14,27 +14,6 @@ namespace MoonlightServers.ApiServer.Services; [Singleton] public class NodeService { - public async Task CreateApiClient(Node node) - { - var url = ""; - - if (node.UseSsl) - url += "https://"; - else - url += "http://"; - - url += $"{node.Fqdn}:{node.HttpPort}/"; - - var httpClient = new HttpClient() - { - BaseAddress = new Uri(url) - }; - - httpClient.DefaultRequestHeaders.Add("Authorization", $"Bearer {node.Token}"); - - return new HttpApiClient(httpClient); - } - public string CreateAccessToken(Node node, Action> parameters, TimeSpan duration) { var claims = new Dictionary(); @@ -63,7 +42,7 @@ public class NodeService public async Task GetSystemStatus(Node node) { - using var apiClient = await CreateApiClient(node); + using var apiClient = CreateApiClient(node); return await apiClient.GetJson("api/system/status"); } @@ -71,21 +50,66 @@ public class NodeService public async Task GetApplicationStatistics(Node node) { - using var apiClient = await CreateApiClient(node); + using var apiClient = CreateApiClient(node); return await apiClient.GetJson("api/statistics/application"); } public async Task GetHostStatistics(Node node) { - using var apiClient = await CreateApiClient(node); + using var apiClient = CreateApiClient(node); return await apiClient.GetJson("api/statistics/host"); } public async Task GetDockerStatistics(Node node) { - using var apiClient = await CreateApiClient(node); + using var apiClient = CreateApiClient(node); return await apiClient.GetJson("api/statistics/docker"); } #endregion + + #region Helpers + + private string GenerateJwt(Node node) + { + var jwtSecurityTokenHandler = new JwtSecurityTokenHandler(); + + var securityTokenDescriptor = new SecurityTokenDescriptor() + { + //Expires = DateTime.UtcNow.AddYears(1), + Expires = DateTime.UtcNow.AddMinutes(1), + NotBefore = DateTime.UtcNow.AddSeconds(-1), + IssuedAt = DateTime.UtcNow, + SigningCredentials = new SigningCredentials( + new SymmetricSecurityKey(Encoding.UTF8.GetBytes( + node.Token + )), + SecurityAlgorithms.HmacSha256 + ) + }; + + var securityToken = jwtSecurityTokenHandler.CreateJwtSecurityToken(securityTokenDescriptor); + + return jwtSecurityTokenHandler.WriteToken(securityToken); + } + + public HttpApiClient CreateApiClient(Node node) + { + var url = ""; + + url += node.UseSsl ? "https://" : "http://"; + url += $"{node.Fqdn}:{node.HttpPort}/"; + + var httpClient = new HttpClient() + { + BaseAddress = new Uri(url) + }; + + var jwt = GenerateJwt(node); + httpClient.DefaultRequestHeaders.Add("Authorization", $"Bearer {jwt}"); + + return new HttpApiClient(httpClient); + } + + #endregion } \ No newline at end of file diff --git a/MoonlightServers.ApiServer/Services/ServerService.cs b/MoonlightServers.ApiServer/Services/ServerService.cs new file mode 100644 index 0000000..7a8aeb8 --- /dev/null +++ b/MoonlightServers.ApiServer/Services/ServerService.cs @@ -0,0 +1,165 @@ +using System.Text.Json; +using Microsoft.EntityFrameworkCore; +using MoonCore.Attributes; +using MoonCore.Exceptions; +using MoonCore.Extended.Abstractions; +using MoonCore.Helpers; +using Moonlight.ApiServer.Database.Entities; +using MoonlightServers.ApiServer.Database.Entities; +using MoonlightServers.DaemonShared.DaemonSide.Http.Responses.Servers; + +namespace MoonlightServers.ApiServer.Services; + +[Scoped] +public class ServerService +{ + private readonly NodeService NodeService; + private readonly DatabaseRepository ServerRepository; + + public ServerService(NodeService nodeService, DatabaseRepository serverRepository) + { + NodeService = nodeService; + ServerRepository = serverRepository; + } + + #region Power Actions + + public async Task Start(Server server) + { + try + { + using var apiClient = await GetApiClient(server); + await apiClient.Post($"api/servers/{server.Id}/start"); + } + catch (HttpRequestException e) + { + throw new HttpApiException("Unable to access the node the server is running on", 502); + } + } + + public async Task Stop(Server server) + { + try + { + using var apiClient = await GetApiClient(server); + await apiClient.Post($"api/servers/{server.Id}/stop"); + } + catch (HttpRequestException e) + { + throw new HttpApiException("Unable to access the node the server is running on", 502); + } + } + + public async Task Kill(Server server) + { + try + { + using var apiClient = await GetApiClient(server); + await apiClient.Post($"api/servers/{server.Id}/kill"); + } + catch (HttpRequestException e) + { + throw new HttpApiException("Unable to access the node the server is running on", 502); + } + } + + #endregion + + public async Task Install(Server server) + { + try + { + using var apiClient = await GetApiClient(server); + await apiClient.Post($"api/servers/{server.Id}/install"); + } + catch (HttpRequestException e) + { + throw new HttpApiException("Unable to access the node the server is running on", 502); + } + } + + public async Task Sync(Server server) + { + try + { + using var apiClient = await GetApiClient(server); + await apiClient.Post($"api/servers/{server.Id}/sync"); + } + catch (HttpRequestException e) + { + throw new HttpApiException("Unable to access the node the server is running on", 502); + } + } + + public async Task SyncDelete(Server server) + { + try + { + using var apiClient = await GetApiClient(server); + await apiClient.Delete($"api/servers/{server.Id}"); + } + catch (HttpRequestException e) + { + throw new HttpApiException("Unable to access the node the server is running on", 502); + } + } + + public async Task GetStatus(Server server) + { + try + { + using var apiClient = await GetApiClient(server); + return await apiClient.GetJson($"api/servers/{server.Id}/status"); + } + catch (HttpRequestException e) + { + throw new HttpApiException("Unable to access the node the server is running on", 502); + } + } + + public async Task GetLogs(Server server) + { + try + { + using var apiClient = await GetApiClient(server); + return await apiClient.GetJson($"api/servers/{server.Id}/logs"); + } + catch (HttpRequestException e) + { + throw new HttpApiException("Unable to access the node the server is running on", 502); + } + } + + #region Helpers + + public bool IsAllowedToAccess(User user, Server server) + { + if (server.OwnerId == user.Id) + return true; + + var permissions = JsonSerializer.Deserialize( + user.PermissionsJson + ) ?? []; + + return PermissionHelper.HasPermission(permissions, "admin.servers.get"); + } + + private async Task GetApiClient(Server server) + { + var serverWithNode = server; + + // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract + // It can be null when its not included when loading via ef !!! + if (server.Node == null) + { + serverWithNode = await ServerRepository + .Get() + .Include(x => x.Node) + .FirstAsync(x => x.Id == server.Id); + } + + return NodeService.CreateApiClient(serverWithNode.Node); + } + + #endregion +} \ No newline at end of file diff --git a/MoonlightServers.Daemon/Abstractions/Server.Installation.cs b/MoonlightServers.Daemon/Abstractions/Server.Installation.cs index b536d31..9326244 100644 --- a/MoonlightServers.Daemon/Abstractions/Server.Installation.cs +++ b/MoonlightServers.Daemon/Abstractions/Server.Installation.cs @@ -22,9 +22,17 @@ public partial class Server await LogToConsole("Fetching installation configuration"); - // Fetching remote configuration + // Fetching remote configuration and install config var remoteService = ServiceProvider.GetRequiredService(); + var installData = await remoteService.GetServerInstallation(Configuration.Id); + var serverData = await remoteService.GetServer(Configuration.Id); + + // We are updating the regular server config here as well + // as changes to variables and other settings wouldn't sync otherwise + // because they won't trigger a sync + var serverConfiguration = serverData.ToServerConfiguration(); + UpdateConfiguration(serverConfiguration); var dockerImageService = ServiceProvider.GetRequiredService(); diff --git a/MoonlightServers.Daemon/Abstractions/Server.Start.cs b/MoonlightServers.Daemon/Abstractions/Server.Start.cs index bff61ff..259cfe3 100644 --- a/MoonlightServers.Daemon/Abstractions/Server.Start.cs +++ b/MoonlightServers.Daemon/Abstractions/Server.Start.cs @@ -1,5 +1,7 @@ using Docker.DotNet; using MoonlightServers.Daemon.Enums; +using MoonlightServers.Daemon.Extensions; +using MoonlightServers.Daemon.Services; namespace MoonlightServers.Daemon.Abstractions; @@ -11,6 +13,17 @@ public partial class Server { try { + await LogToConsole("Fetching configuration"); + + var remoteService = ServiceProvider.GetRequiredService(); + var serverData = await remoteService.GetServer(Configuration.Id); + + // We are updating the server config here + // as changes to variables and other settings wouldn't sync otherwise + // because they won't trigger a sync + var serverConfiguration = serverData.ToServerConfiguration(); + UpdateConfiguration(serverConfiguration); + await ReCreate(); await LogToConsole("Starting container"); diff --git a/MoonlightServers.Daemon/Abstractions/Server.cs b/MoonlightServers.Daemon/Abstractions/Server.cs index 9c6eeb3..26c8727 100644 --- a/MoonlightServers.Daemon/Abstractions/Server.cs +++ b/MoonlightServers.Daemon/Abstractions/Server.cs @@ -49,4 +49,7 @@ public partial class Server RuntimeContainerName = $"moonlight-runtime-{Configuration.Id}"; InstallationContainerName = $"moonlight-install-{Configuration.Id}"; } + + public void UpdateConfiguration(ServerConfiguration configuration) + => Configuration = configuration; } \ No newline at end of file diff --git a/MoonlightServers.Daemon/Extensions/ServerConfigurationExtensions.cs b/MoonlightServers.Daemon/Extensions/ServerConfigurationExtensions.cs index 97d6993..2b04e7f 100644 --- a/MoonlightServers.Daemon/Extensions/ServerConfigurationExtensions.cs +++ b/MoonlightServers.Daemon/Extensions/ServerConfigurationExtensions.cs @@ -2,11 +2,35 @@ using Docker.DotNet.Models; using Mono.Unix.Native; using MoonCore.Helpers; using MoonlightServers.Daemon.Models.Cache; +using MoonlightServers.DaemonShared.PanelSide.Http.Responses; namespace MoonlightServers.Daemon.Extensions; public static class ServerConfigurationExtensions { + public static ServerConfiguration ToServerConfiguration(this ServerDataResponse response) + { + return new ServerConfiguration() + { + Id = response.Id, + StartupCommand = response.StartupCommand, + Allocations = response.Allocations.Select(y => new ServerConfiguration.AllocationConfiguration() + { + IpAddress = y.IpAddress, + Port = y.Port + }).ToArray(), + Variables = response.Variables, + OnlineDetection = response.OnlineDetection, + DockerImage = response.DockerImage, + UseVirtualDisk = response.UseVirtualDisk, + Bandwidth = response.Bandwidth, + Cpu = response.Cpu, + Disk = response.Disk, + Memory = response.Memory, + StopCommand = response.StopCommand + }; + } + public static CreateContainerParameters ToRuntimeCreateParameters(this ServerConfiguration configuration, string hostPath, string containerName) { diff --git a/MoonlightServers.Daemon/Http/Controllers/Servers/ServerPowerController.cs b/MoonlightServers.Daemon/Http/Controllers/Servers/ServerPowerController.cs index 718dd36..e5031ca 100644 --- a/MoonlightServers.Daemon/Http/Controllers/Servers/ServerPowerController.cs +++ b/MoonlightServers.Daemon/Http/Controllers/Servers/ServerPowerController.cs @@ -1,3 +1,4 @@ +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using MoonCore.Exceptions; using MoonlightServers.Daemon.Enums; @@ -5,6 +6,7 @@ using MoonlightServers.Daemon.Services; namespace MoonlightServers.Daemon.Http.Controllers.Servers; +[Authorize] [ApiController] [Route("api/servers")] public class ServerPowerController : Controller diff --git a/MoonlightServers.Daemon/Http/Controllers/Servers/ServersController.cs b/MoonlightServers.Daemon/Http/Controllers/Servers/ServersController.cs index c79201b..b7568ec 100644 --- a/MoonlightServers.Daemon/Http/Controllers/Servers/ServersController.cs +++ b/MoonlightServers.Daemon/Http/Controllers/Servers/ServersController.cs @@ -1,3 +1,4 @@ +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using MoonCore.Exceptions; using MoonlightServers.Daemon.Services; @@ -6,6 +7,7 @@ using MoonlightServers.DaemonShared.Enums; namespace MoonlightServers.Daemon.Http.Controllers.Servers; +[Authorize] [ApiController] [Route("api/servers")] public class ServersController : Controller @@ -17,18 +19,32 @@ public class ServersController : Controller ServerService = serverService; } + [HttpPost("{serverId:int}/sync")] + public async Task Sync([FromRoute] int serverId) + { + await ServerService.Sync(serverId); + } + + [HttpDelete("{serverId:int}")] + public async Task Delete([FromRoute] int serverId) + { + await ServerService.Delete(serverId); + } + [HttpGet("{serverId:int}/status")] - public async Task GetStatus(int serverId) + public Task GetStatus([FromRoute] int serverId) { var server = ServerService.GetServer(serverId); if (server == null) throw new HttpApiException("No server with this id found", 404); - return new ServerStatusResponse() + var result = new ServerStatusResponse() { State = (ServerState)server.State }; + + return Task.FromResult(result); } [HttpGet("{serverId:int}/logs")] diff --git a/MoonlightServers.Daemon/Http/Controllers/Statistics/StatisticsApplicationController.cs b/MoonlightServers.Daemon/Http/Controllers/Statistics/StatisticsApplicationController.cs index 20d7b8a..89c7991 100644 --- a/MoonlightServers.Daemon/Http/Controllers/Statistics/StatisticsApplicationController.cs +++ b/MoonlightServers.Daemon/Http/Controllers/Statistics/StatisticsApplicationController.cs @@ -1,3 +1,4 @@ +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using MoonlightServers.Daemon.Helpers; using MoonlightServers.DaemonShared.DaemonSide.Http.Responses.Statistics; @@ -6,6 +7,7 @@ namespace MoonlightServers.Daemon.Http.Controllers.Statistics; // This controller hosts endpoints for the statistics for the daemon application itself +[Authorize] [ApiController] [Route("api/statistics/application")] public class StatisticsApplicationController : Controller diff --git a/MoonlightServers.Daemon/Http/Controllers/Statistics/StatisticsDockerController.cs b/MoonlightServers.Daemon/Http/Controllers/Statistics/StatisticsDockerController.cs index c00337b..93b2fa4 100644 --- a/MoonlightServers.Daemon/Http/Controllers/Statistics/StatisticsDockerController.cs +++ b/MoonlightServers.Daemon/Http/Controllers/Statistics/StatisticsDockerController.cs @@ -1,3 +1,4 @@ +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using MoonlightServers.Daemon.Services; using MoonlightServers.DaemonShared.DaemonSide.Http.Responses.Statistics; @@ -6,6 +7,7 @@ namespace MoonlightServers.Daemon.Http.Controllers.Statistics; // This controller hosts endpoints for the statistics for the docker environment +[Authorize] [ApiController] [Route("api/statistics/docker")] public class StatisticsDockerController : Controller diff --git a/MoonlightServers.Daemon/Http/Controllers/Statistics/StatisticsHostController.cs b/MoonlightServers.Daemon/Http/Controllers/Statistics/StatisticsHostController.cs index 18226ac..10337d2 100644 --- a/MoonlightServers.Daemon/Http/Controllers/Statistics/StatisticsHostController.cs +++ b/MoonlightServers.Daemon/Http/Controllers/Statistics/StatisticsHostController.cs @@ -1,3 +1,4 @@ +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using MoonlightServers.Daemon.Helpers; using MoonlightServers.DaemonShared.DaemonSide.Http.Responses.Statistics; @@ -6,6 +7,7 @@ namespace MoonlightServers.Daemon.Http.Controllers.Statistics; // This controller hosts endpoints for the statistics for host system the daemon runs on +[Authorize] [ApiController] [Route("api/statistics/host")] public class StatisticsHostController : Controller diff --git a/MoonlightServers.Daemon/Http/Controllers/Sys/SystemStatusController.cs b/MoonlightServers.Daemon/Http/Controllers/Sys/SystemStatusController.cs index 2e7a399..8f4208a 100644 --- a/MoonlightServers.Daemon/Http/Controllers/Sys/SystemStatusController.cs +++ b/MoonlightServers.Daemon/Http/Controllers/Sys/SystemStatusController.cs @@ -1,10 +1,12 @@ using System.Diagnostics; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using MoonlightServers.Daemon.Services; using MoonlightServers.DaemonShared.DaemonSide.Http.Responses.Sys; namespace MoonlightServers.Daemon.Http.Controllers.Sys; +[Authorize] [ApiController] [Route("api/system/status")] public class SystemStatusController : Controller diff --git a/MoonlightServers.Daemon/Services/RemoteService.cs b/MoonlightServers.Daemon/Services/RemoteService.cs index 123cc06..15a2de4 100644 --- a/MoonlightServers.Daemon/Services/RemoteService.cs +++ b/MoonlightServers.Daemon/Services/RemoteService.cs @@ -31,6 +31,13 @@ public class RemoteService ); } + public async Task GetServer(int serverId) + { + return await ApiClient.GetJson( + $"api/remote/servers/{serverId}" + ); + } + public async Task GetServerInstallation(int serverId) { return await ApiClient.GetJson( diff --git a/MoonlightServers.Daemon/Services/ServerService.cs b/MoonlightServers.Daemon/Services/ServerService.cs index 3ce32bb..7b2d264 100644 --- a/MoonlightServers.Daemon/Services/ServerService.cs +++ b/MoonlightServers.Daemon/Services/ServerService.cs @@ -1,8 +1,11 @@ using Docker.DotNet; using Docker.DotNet.Models; using MoonCore.Attributes; +using MoonCore.Exceptions; using MoonCore.Models; using MoonlightServers.Daemon.Abstractions; +using MoonlightServers.Daemon.Enums; +using MoonlightServers.Daemon.Extensions; using MoonlightServers.Daemon.Models.Cache; using MoonlightServers.DaemonShared.PanelSide.Http.Responses; @@ -16,11 +19,15 @@ public class ServerService : IHostedLifecycleService private readonly RemoteService RemoteService; private readonly IServiceProvider ServiceProvider; private readonly ILoggerFactory LoggerFactory; - private bool IsInitialized = false; private CancellationTokenSource Cancellation = new(); + private bool IsInitialized = false; - public ServerService(RemoteService remoteService, ILogger logger, IServiceProvider serviceProvider, - ILoggerFactory loggerFactory) + public ServerService( + RemoteService remoteService, + ILogger logger, + IServiceProvider serviceProvider, + ILoggerFactory loggerFactory + ) { RemoteService = remoteService; Logger = logger; @@ -35,8 +42,8 @@ public class ServerService : IHostedLifecycleService Logger.LogWarning("Ignoring initialize call: Already initialized"); return; } - - IsInitialized = true; + else + IsInitialized = true; // Loading models and converting them Logger.LogInformation("Fetching servers from panel"); @@ -45,25 +52,9 @@ public class ServerService : IHostedLifecycleService await RemoteService.GetServers(page, pageSize) ); - var configurations = servers.Select(x => new ServerConfiguration() - { - Id = x.Id, - StartupCommand = x.StartupCommand, - Allocations = x.Allocations.Select(y => new ServerConfiguration.AllocationConfiguration() - { - IpAddress = y.IpAddress, - Port = y.Port - }).ToArray(), - Variables = x.Variables, - OnlineDetection = x.OnlineDetection, - DockerImage = x.DockerImage, - UseVirtualDisk = x.UseVirtualDisk, - Bandwidth = x.Bandwidth, - Cpu = x.Cpu, - Disk = x.Disk, - Memory = x.Memory, - StopCommand = x.StopCommand - }).ToArray(); + var configurations = servers + .Select(x => x.ToServerConfiguration()) + .ToArray(); Logger.LogInformation("Initializing {count} servers", servers.Length); @@ -91,7 +82,7 @@ public class ServerService : IHostedLifecycleService await Cancellation.CancelAsync(); } - private async Task AttachToDockerEvents() + private Task AttachToDockerEvents() { var dockerClient = ServiceProvider.GetRequiredService(); @@ -123,11 +114,11 @@ public class ServerService : IHostedLifecycleService await server.NotifyRuntimeContainerDied(); return; } - + // Check if it's an installation container lock (Servers) server = Servers.FirstOrDefault(x => x.InstallationContainerId == message.ID); - + if (server != null) { await server.NotifyInstallationContainerDied(); @@ -144,9 +135,11 @@ public class ServerService : IHostedLifecycleService } } }); + + return Task.CompletedTask; } - private async Task InitializeServerRange(ServerConfiguration[] serverConfigurations) + public async Task InitializeServerRange(ServerConfiguration[] serverConfigurations) { var dockerClient = ServiceProvider.GetRequiredService(); @@ -173,7 +166,7 @@ public class ServerService : IHostedLifecycleService await InitializeServer(configuration, existingContainers); } - private async Task InitializeServer( + public async Task InitializeServer( ServerConfiguration serverConfiguration, IList existingContainers ) @@ -190,6 +183,70 @@ public class ServerService : IHostedLifecycleService lock (Servers) Servers.Add(server); + + return server; + } + + public async Task Sync(int serverId) + { + var serverData = await RemoteService.GetServer(serverId); + var serverConfiguration = serverData.ToServerConfiguration(); + + var server = GetServer(serverId); + + if (server == null) + await InitializeServer(serverConfiguration, []); + else + server.UpdateConfiguration(serverConfiguration); + } + + public async Task Delete(int serverId) + { + var server = GetServer(serverId); + + // If a server with this id doesn't exist we can just exit + if(server == null) + return; + + if (server.State == ServerState.Installing) + throw new HttpApiException("Unable to delete a server while it is installing", 400); + + #region Callbacks + + var deleteCompletion = new TaskCompletionSource(); + + async Task HandleStateChange(ServerState state) + { + if (state == ServerState.Offline) + await DeleteServer(); + } + + async Task DeleteServer() + { + await server.CancelTasks(); + await server.RemoveInstallationVolume(); + await server.RemoveRuntimeVolume(); + + deleteCompletion.SetResult(); + + lock (Servers) + Servers.Remove(server); + } + + #endregion + + // If the server is still online, we are killing it and then + // waiting for the callback to trigger notifying us that the server is now offline + // so we can delete it. The request will pause until then using the deleteCompletion task + if (server.State != ServerState.Offline) + { + server.OnStateChanged += HandleStateChange; + await server.Kill(); + + await deleteCompletion.Task; + } + else + await DeleteServer(); } public Server? GetServer(int id) @@ -197,7 +254,7 @@ public class ServerService : IHostedLifecycleService lock (Servers) return Servers.FirstOrDefault(x => x.Id == id); } - + #region Lifecycle public Task StartAsync(CancellationToken cancellationToken) diff --git a/MoonlightServers.Daemon/Startup.cs b/MoonlightServers.Daemon/Startup.cs index 49a86b4..5838ba4 100644 --- a/MoonlightServers.Daemon/Startup.cs +++ b/MoonlightServers.Daemon/Startup.cs @@ -1,5 +1,8 @@ +using System.Text; using System.Text.Json; using Docker.DotNet; +using Microsoft.AspNetCore.Authentication.JwtBearer; +using Microsoft.IdentityModel.Tokens; using MoonCore.Configuration; using MoonCore.EnvConfiguration; using MoonCore.Extended.Extensions; @@ -41,6 +44,7 @@ public class Startup await RegisterAppConfiguration(); await RegisterLogging(); await RegisterBase(); + await RegisterAuth(); await RegisterDocker(); await RegisterServers(); await RegisterSignalR(); @@ -49,6 +53,7 @@ public class Startup await BuildWebApplication(); await UseBase(); + await UseAuth(); await UseCors(); await UseBaseMiddleware(); @@ -289,4 +294,40 @@ public class Startup } #endregion + + #region Authentication + + private Task RegisterAuth() + { + WebApplicationBuilder.Services + .AddAuthentication(JwtBearerDefaults.AuthenticationScheme) + .AddJwtBearer(options => + { + options.TokenValidationParameters = new() + { + IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes( + Configuration.Security.Token + )), + ValidateIssuerSigningKey = true, + ValidateLifetime = true, + ValidateAudience = false, + ValidateIssuer = false, + ClockSkew = TimeSpan.Zero + }; + }); + + WebApplicationBuilder.Services.AddAuthorization(); + + return Task.CompletedTask; + } + + private Task UseAuth() + { + WebApplication.UseAuthentication(); + WebApplication.UseAuthorization(); + + return Task.CompletedTask; + } + + #endregion } \ No newline at end of file diff --git a/MoonlightServers.Frontend/Services/ServerService.cs b/MoonlightServers.Frontend/Services/ServerService.cs new file mode 100644 index 0000000..1835c95 --- /dev/null +++ b/MoonlightServers.Frontend/Services/ServerService.cs @@ -0,0 +1,84 @@ +using MoonCore.Attributes; +using MoonCore.Helpers; +using MoonCore.Models; +using MoonlightServers.Shared.Http.Responses.Users.Servers; + +namespace MoonlightServers.Frontend.Services; + +[Scoped] +public class ServerService +{ + private readonly HttpApiClient HttpApiClient; + + public ServerService(HttpApiClient httpApiClient) + { + HttpApiClient = httpApiClient; + } + + public async Task> GetServers(int page, int perPage) + { + return await HttpApiClient.GetJson>( + $"api/client/servers?page={page}&pageSize={perPage}" + ); + } + + public async Task GetServer(int serverId) + { + return await HttpApiClient.GetJson( + $"api/client/servers/{serverId}" + ); + } + + public async Task GetStatus(int serverId) + { + return await HttpApiClient.GetJson( + $"api/client/servers/{serverId}/status" + ); + } + + public async Task GetLogs(int serverId) + { + return await HttpApiClient.GetJson( + $"api/client/servers/{serverId}/logs" + ); + } + + public async Task GetWebSocket(int serverId) + { + return await HttpApiClient.GetJson( + $"api/client/servers/{serverId}/ws" + ); + } + + public async Task Install(int serverId) + { + await HttpApiClient.Post( + $"api/client/servers/{serverId}/install" + ); + } + + #region Power actions + + public async Task Start(int serverId) + { + await HttpApiClient.Post( + $"api/client/servers/{serverId}/start" + ); + } + + public async Task Stop(int serverId) + { + await HttpApiClient.Post( + $"api/client/servers/{serverId}/stop" + ); + } + + public async Task Kill(int serverId) + { + await HttpApiClient.Post( + $"api/client/servers/{serverId}/kill" + ); + } + + #endregion +} \ No newline at end of file diff --git a/MoonlightServers.Frontend/UI/Components/Servers/ServerCard.razor b/MoonlightServers.Frontend/UI/Components/Servers/ServerCard.razor index 958cffe..842706b 100644 --- a/MoonlightServers.Frontend/UI/Components/Servers/ServerCard.razor +++ b/MoonlightServers.Frontend/UI/Components/Servers/ServerCard.razor @@ -1,8 +1,8 @@ -@using MoonCore.Helpers +@using MoonlightServers.Frontend.Services @using MoonlightServers.Shared.Enums @using MoonlightServers.Shared.Http.Responses.Users.Servers -@inject HttpApiClient ApiClient +@inject ServerService ServerService @inject ILogger Logger @{ @@ -158,9 +158,7 @@ try { - Status = await ApiClient.GetJson( - $"api/client/servers/{Server.Id}/status" - ); + Status = await ServerService.GetStatus(Server.Id); } catch (Exception e) { diff --git a/MoonlightServers.Frontend/UI/Components/Servers/ServerTabs/SettingsTab.razor b/MoonlightServers.Frontend/UI/Components/Servers/ServerTabs/SettingsTab.razor index 46ccb7d..9946a2b 100644 --- a/MoonlightServers.Frontend/UI/Components/Servers/ServerTabs/SettingsTab.razor +++ b/MoonlightServers.Frontend/UI/Components/Servers/ServerTabs/SettingsTab.razor @@ -1,11 +1,12 @@ @using MoonCore.Blazor.Tailwind.Alerts @using MoonCore.Helpers @using MoonCore.Blazor.Tailwind.Components +@using MoonlightServers.Frontend.Services @using MoonlightServers.Shared.Enums @inherits BaseServerTab -@inject HttpApiClient HttpApiClient +@inject ServerService ServerService @inject AlertService AlertService
@@ -34,7 +35,7 @@ await AlertService.ConfirmDanger( "Server installation", "Do you really want to reinstall the server? This can potentially lead to loss of data", - () => HttpApiClient.Post($"api/client/servers/{Server.Id}/install") + () => ServerService.Install(Server.Id) ); } } \ No newline at end of file diff --git a/MoonlightServers.Frontend/UI/Views/Client/Index.razor b/MoonlightServers.Frontend/UI/Views/Client/Index.razor index f45f326..ed15789 100644 --- a/MoonlightServers.Frontend/UI/Views/Client/Index.razor +++ b/MoonlightServers.Frontend/UI/Views/Client/Index.razor @@ -1,12 +1,12 @@ @page "/servers" -@using MoonCore.Helpers @using MoonlightServers.Frontend.UI.Components.Servers @using MoonCore.Blazor.Tailwind.Components @using MoonCore.Models +@using MoonlightServers.Frontend.Services @using MoonlightServers.Shared.Http.Responses.Users.Servers -@inject HttpApiClient ApiClient +@inject ServerService ServerService
@@ -42,9 +42,7 @@ private async Task Load(LazyLoader lazyLoader) { Servers = await PagedData.All(async (page, pageSize) => - await ApiClient.GetJson>( - $"api/client/servers?page={page}&pageSize={pageSize}" - ) + await ServerService.GetServers(page, pageSize) ); } } \ No newline at end of file diff --git a/MoonlightServers.Frontend/UI/Views/Client/Manage.razor b/MoonlightServers.Frontend/UI/Views/Client/Manage.razor index 092b131..4d097c7 100644 --- a/MoonlightServers.Frontend/UI/Views/Client/Manage.razor +++ b/MoonlightServers.Frontend/UI/Views/Client/Manage.razor @@ -7,11 +7,12 @@ @using MoonCore.Helpers @using MoonlightServers.Frontend.Interfaces @using MoonlightServers.Frontend.Models +@using MoonlightServers.Frontend.Services @using MoonlightServers.Shared.Enums @using MoonlightServers.Frontend.UI.Components @using MoonlightServers.Frontend.UI.Components.Servers.ServerTabs -@inject HttpApiClient ApiClient +@inject ServerService ServerService @inject IEnumerable TabProviders @implements IAsyncDisposable @@ -164,25 +165,19 @@ try { // Load meta data - Server = await ApiClient.GetJson( - $"api/client/servers/{ServerId}" - ); + Server = await ServerService.GetServer(ServerId); // Load server tabs foreach (var serverTabProvider in TabProviders) Tabs.AddRange(await serverTabProvider.GetTabs(Server)); // Load initial status for first render - var status = await ApiClient.GetJson( - $"api/client/servers/{ServerId}/status" - ); + var status = await ServerService.GetStatus(ServerId); State = status.State; // Load initial messages - var initialLogs = await ApiClient.GetJson( - $"api/client/servers/{ServerId}/logs" - ); + var initialLogs = await ServerService.GetLogs(ServerId); InitialConsoleMessage = ""; @@ -190,9 +185,7 @@ InitialConsoleMessage += message; // Load websocket meta - var websocketDetails = await ApiClient.GetJson( - $"api/client/servers/{ServerId}/ws" - ); + var websocketDetails = await ServerService.GetWebSocket(ServerId); // Build signal r HubConnection = new HubConnectionBuilder() @@ -232,13 +225,13 @@ } private async Task Start() - => await ApiClient.Post($"api/client/servers/{Server.Id}/start"); + => await ServerService.Start(ServerId); private async Task Stop() - => await ApiClient.Post($"api/client/servers/{Server.Id}/stop"); + => await ServerService.Stop(ServerId); private async Task Kill() - => await ApiClient.Post($"api/client/servers/{Server.Id}/kill"); + => await ServerService.Kill(ServerId); public async ValueTask DisposeAsync() { diff --git a/MoonlightServers.Shared/Http/Requests/Admin/Servers/CreateServerRequest.cs b/MoonlightServers.Shared/Http/Requests/Admin/Servers/CreateServerRequest.cs index 1801f98..272722a 100644 --- a/MoonlightServers.Shared/Http/Requests/Admin/Servers/CreateServerRequest.cs +++ b/MoonlightServers.Shared/Http/Requests/Admin/Servers/CreateServerRequest.cs @@ -24,8 +24,8 @@ public class CreateServerRequest public int Bandwidth { get; set; } public string? StartupOverride { get; set; } - - public int DockerImageIndex { get; set; } + + public int DockerImageIndex { get; set; } = -1; public int StarId { get; set; }