diff --git a/Moonlight.ApiServer/Http/Controllers/Admin/Sys/SystemController.cs b/Moonlight.ApiServer/Http/Controllers/Admin/Sys/SystemController.cs new file mode 100644 index 00000000..b0c9f8d2 --- /dev/null +++ b/Moonlight.ApiServer/Http/Controllers/Admin/Sys/SystemController.cs @@ -0,0 +1,38 @@ +using Microsoft.AspNetCore.Mvc; +using MoonCore.Attributes; +using Moonlight.ApiServer.Services; +using Moonlight.Shared.Http.Responses.Admin.Sys; + +namespace Moonlight.ApiServer.Http.Controllers.Admin.Sys; + +[ApiController] +[Route("api/admin/system")] +public class SystemController : Controller +{ + private readonly ApplicationService ApplicationService; + + public SystemController(ApplicationService applicationService) + { + ApplicationService = applicationService; + } + + [HttpGet] + [RequirePermission("admin.system.overview")] + public async Task GetOverview() + { + return new() + { + Uptime = await ApplicationService.GetUptime(), + CpuUsage = await ApplicationService.GetCpuUsage(), + MemoryUsage = await ApplicationService.GetMemoryUsage(), + OperatingSystem = await ApplicationService.GetOsName() + }; + } + + [HttpPost("shutdown")] + [RequirePermission("admin.system.shutdown")] + public async Task Shutdown() + { + await ApplicationService.Shutdown(); + } +} \ No newline at end of file diff --git a/Moonlight.ApiServer/Services/ApplicationService.cs b/Moonlight.ApiServer/Services/ApplicationService.cs new file mode 100644 index 00000000..67616f18 --- /dev/null +++ b/Moonlight.ApiServer/Services/ApplicationService.cs @@ -0,0 +1,95 @@ +using System.Diagnostics; +using System.Runtime.InteropServices; +using MoonCore.Attributes; + +namespace Moonlight.ApiServer.Services; + +[Singleton] +public class ApplicationService +{ + private ILogger Logger; + private readonly IHost Host; + + public ApplicationService(ILogger logger, IHost host) + { + Logger = logger; + Host = host; + } + + public Task GetOsName() + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + // Windows platform detected + var osVersion = Environment.OSVersion.Version; + return Task.FromResult($"Windows {osVersion.Major}.{osVersion.Minor}.{osVersion.Build}"); + } + + if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) + { + var releaseRaw = File + .ReadAllLines("/etc/os-release") + .FirstOrDefault(x => x.StartsWith("PRETTY_NAME=")); + + if (string.IsNullOrEmpty(releaseRaw)) + return Task.FromResult("Linux (unknown release)"); + + var release = releaseRaw + .Replace("PRETTY_NAME=", "") + .Replace("\"", ""); + + if(string.IsNullOrEmpty(release)) + return Task.FromResult("Linux (unknown release)"); + + return Task.FromResult(release); + } + + if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + { + // macOS platform detected + var osVersion = Environment.OSVersion.Version; + return Task.FromResult($"macOS {osVersion.Major}.{osVersion.Minor}.{osVersion.Build}"); + } + + // Unknown platform + return Task.FromResult("N/A"); + } + + public Task GetMemoryUsage() + { + var process = Process.GetCurrentProcess(); + var bytes = process.PrivateMemorySize64; + return Task.FromResult(bytes); + } + + public Task GetUptime() + { + var process = Process.GetCurrentProcess(); + var uptime = DateTime.Now - process.StartTime; + return Task.FromResult(uptime); + } + + public Task GetCpuUsage() + { + var process = Process.GetCurrentProcess(); + var cpuTime = process.TotalProcessorTime; + var wallClockTime = DateTime.UtcNow - process.StartTime.ToUniversalTime(); + + var cpuUsage = (int)(100.0 * cpuTime.TotalMilliseconds / wallClockTime.TotalMilliseconds / Environment.ProcessorCount); + + return Task.FromResult(cpuUsage); + } + + public Task Shutdown() + { + Logger.LogCritical("Restart of api server has been requested"); + + Task.Run(async () => + { + await Task.Delay(TimeSpan.FromSeconds(1)); + await Host.StopAsync(CancellationToken.None); + }); + + return Task.CompletedTask; + } +} \ No newline at end of file diff --git a/Moonlight.ApiServer/Startup.cs b/Moonlight.ApiServer/Startup.cs index 20ff10c6..b07862f6 100644 --- a/Moonlight.ApiServer/Startup.cs +++ b/Moonlight.ApiServer/Startup.cs @@ -288,9 +288,6 @@ public static class Startup // TODO: Make modular configuration.ProcessComplete = async (serviceProvider, accessData) => { - var loggerFactory = serviceProvider.GetRequiredService(); - var logger = loggerFactory.CreateLogger("OAuth2 Handler"); - var oauth2Providers = serviceProvider.GetRequiredService(); // Find oauth2 provider @@ -321,6 +318,9 @@ public static class Startup } catch (Exception e) { + var loggerFactory = serviceProvider.GetRequiredService(); + var logger = loggerFactory.CreateLogger(provider.GetType()); + logger.LogTrace("An error occured while syncing user with oauth2 provider: {e}", e); throw new HttpApiException("Unable to synchronize with oauth2 provider", 400); } diff --git a/Moonlight.Client/Implementations/DefaultSidebarItemProvider.cs b/Moonlight.Client/Implementations/DefaultSidebarItemProvider.cs index f199639f..0a276cf0 100644 --- a/Moonlight.Client/Implementations/DefaultSidebarItemProvider.cs +++ b/Moonlight.Client/Implementations/DefaultSidebarItemProvider.cs @@ -58,7 +58,7 @@ public class DefaultSidebarItemProvider : ISidebarItemProvider Path = "/admin/system", Priority = 3, RequiresExactMatch = false, - Permission = "admin.system.info" + Permission = "admin.system.overview" }, ]; } diff --git a/Moonlight.Client/UI/Views/Admin/Sys/Index.razor b/Moonlight.Client/UI/Views/Admin/Sys/Index.razor index 66774b2a..6c50da5d 100644 --- a/Moonlight.Client/UI/Views/Admin/Sys/Index.razor +++ b/Moonlight.Client/UI/Views/Admin/Sys/Index.razor @@ -1,30 +1,43 @@ @page "/admin/system" @using MoonCore.Attributes +@using MoonCore.Helpers @using Moonlight.Client.UI.Components +@using Moonlight.Shared.Http.Responses.Admin.Sys -@attribute [RequirePermission("admin.system.read")] +@attribute [RequirePermission("admin.system.overview")] -
- - - - - -
-
- - - Restart - +@inject HttpApiClient ApiClient + + +
+ + + + + +
+
+ + + Restart/Shutdown + +
-
+ @code { + private SystemOverviewResponse OverviewData; + + private async Task LoadOverview(LazyLoader arg) + { + OverviewData = await ApiClient.GetJson("api/admin/system"); + } + private async Task Restart(WButton _) { - + await ApiClient.Post("api/admin/system/shutdown"); } } diff --git a/Moonlight.Shared/Http/Responses/Admin/Sys/SystemOverviewResponse.cs b/Moonlight.Shared/Http/Responses/Admin/Sys/SystemOverviewResponse.cs new file mode 100644 index 00000000..e72ae39e --- /dev/null +++ b/Moonlight.Shared/Http/Responses/Admin/Sys/SystemOverviewResponse.cs @@ -0,0 +1,9 @@ +namespace Moonlight.Shared.Http.Responses.Admin.Sys; + +public class SystemOverviewResponse +{ + public int CpuUsage { get; set; } + public long MemoryUsage { get; set; } + public string OperatingSystem { get; set; } + public TimeSpan Uptime { get; set; } +} \ No newline at end of file