From d45de8d8702e2704efe256b2ed51426400c40b56 Mon Sep 17 00:00:00 2001 From: Marcel Baumgartner Date: Mon, 20 Mar 2023 15:55:44 +0100 Subject: [PATCH] Added ml daemon communication and node stats --- Moonlight/App/Helpers/DaemonApiHelper.cs | 53 +++ Moonlight/App/Helpers/Formatter.cs | 21 ++ .../Models/Daemon/Resources/ContainerStats.cs | 15 + .../App/Models/Daemon/Resources/CpuStats.cs | 8 + .../App/Models/Daemon/Resources/DiskStats.cs | 9 + .../Models/Daemon/Resources/MemoryStats.cs | 15 + Moonlight/App/Models/Node/CpuStats.cs | 6 - Moonlight/App/Models/Node/DiskStats.cs | 6 - Moonlight/App/Models/Node/DockerStats.cs | 17 - Moonlight/App/Models/Node/MemoryStats.cs | 8 - Moonlight/App/Services/NodeService.cs | 27 +- Moonlight/Moonlight.csproj | 2 +- Moonlight/Program.cs | 1 + .../Shared/Views/Admin/Nodes/Index.razor | 234 ++++-------- Moonlight/Shared/Views/Admin/Nodes/View.razor | 357 ++++++++++++++++++ .../Shared/Views/Admin/Servers/Index.razor | 6 +- Moonlight/resources/lang/de_de.lang | 12 + 17 files changed, 590 insertions(+), 207 deletions(-) create mode 100644 Moonlight/App/Helpers/DaemonApiHelper.cs create mode 100644 Moonlight/App/Models/Daemon/Resources/ContainerStats.cs create mode 100644 Moonlight/App/Models/Daemon/Resources/CpuStats.cs create mode 100644 Moonlight/App/Models/Daemon/Resources/DiskStats.cs create mode 100644 Moonlight/App/Models/Daemon/Resources/MemoryStats.cs delete mode 100644 Moonlight/App/Models/Node/CpuStats.cs delete mode 100644 Moonlight/App/Models/Node/DiskStats.cs delete mode 100644 Moonlight/App/Models/Node/DockerStats.cs delete mode 100644 Moonlight/App/Models/Node/MemoryStats.cs create mode 100644 Moonlight/Shared/Views/Admin/Nodes/View.razor diff --git a/Moonlight/App/Helpers/DaemonApiHelper.cs b/Moonlight/App/Helpers/DaemonApiHelper.cs new file mode 100644 index 0000000..45d91a2 --- /dev/null +++ b/Moonlight/App/Helpers/DaemonApiHelper.cs @@ -0,0 +1,53 @@ +using Moonlight.App.Database.Entities; +using Moonlight.App.Exceptions; +using Newtonsoft.Json; +using RestSharp; + +namespace Moonlight.App.Helpers; + +public class DaemonApiHelper +{ + private readonly RestClient Client; + + public DaemonApiHelper() + { + Client = new(); + } + + private string GetApiUrl(Node node) + { + if(node.Ssl) + return $"https://{node.Fqdn}:{node.MoonlightDaemonPort}/"; + else + return $"http://{node.Fqdn}:{node.MoonlightDaemonPort}/"; + //return $"https://{node.Fqdn}:{node.HttpPort}/"; + } + + public async Task Get(Node node, string resource) + { + RestRequest request = new(GetApiUrl(node) + resource); + + request.AddHeader("Content-Type", "application/json"); + request.AddHeader("Accept", "application/json"); + request.AddHeader("Authorization", node.Token); + + var response = await Client.GetAsync(request); + + if (!response.IsSuccessful) + { + if (response.StatusCode != 0) + { + throw new WingsException( + $"An error occured: ({response.StatusCode}) {response.Content}", + (int)response.StatusCode + ); + } + else + { + throw new Exception($"An internal error occured: {response.ErrorMessage}"); + } + } + + return JsonConvert.DeserializeObject(response.Content!)!; + } +} \ No newline at end of file diff --git a/Moonlight/App/Helpers/Formatter.cs b/Moonlight/App/Helpers/Formatter.cs index 1725df9..78fbfc5 100644 --- a/Moonlight/App/Helpers/Formatter.cs +++ b/Moonlight/App/Helpers/Formatter.cs @@ -88,4 +88,25 @@ public static class Formatter return $"{i2s(e.Day)}.{i2s(e.Month)}.{e.Year}"; } + + public static string FormatSize(double bytes) + { + var i = Math.Abs(bytes) / 1024D; + if (i < 1) + { + return bytes + " B"; + } + else if (i / 1024D < 1) + { + return i.Round(2) + " KB"; + } + else if (i / (1024D * 1024D) < 1) + { + return (i / 1024D).Round(2) + " MB"; + } + else + { + return (i / (1024D * 1024D)).Round(2) + " GB"; + } + } } \ No newline at end of file diff --git a/Moonlight/App/Models/Daemon/Resources/ContainerStats.cs b/Moonlight/App/Models/Daemon/Resources/ContainerStats.cs new file mode 100644 index 0000000..5c2e057 --- /dev/null +++ b/Moonlight/App/Models/Daemon/Resources/ContainerStats.cs @@ -0,0 +1,15 @@ +namespace Moonlight.App.Models.Daemon.Resources; + +public class ContainerStats +{ + public List Containers { get; set; } = new(); + + public class Container + { + public Guid Name { get; set; } + public long Memory { get; set; } + public double Cpu { get; set; } + public long NetworkIn { get; set; } + public long NetworkOut { get; set; } + } +} \ No newline at end of file diff --git a/Moonlight/App/Models/Daemon/Resources/CpuStats.cs b/Moonlight/App/Models/Daemon/Resources/CpuStats.cs new file mode 100644 index 0000000..9282627 --- /dev/null +++ b/Moonlight/App/Models/Daemon/Resources/CpuStats.cs @@ -0,0 +1,8 @@ +namespace Moonlight.App.Models.Daemon.Resources; + +public class CpuStats +{ + public double Usage { get; set; } + public int Cores { get; set; } + public string Model { get; set; } = ""; +} \ No newline at end of file diff --git a/Moonlight/App/Models/Daemon/Resources/DiskStats.cs b/Moonlight/App/Models/Daemon/Resources/DiskStats.cs new file mode 100644 index 0000000..ea19ae9 --- /dev/null +++ b/Moonlight/App/Models/Daemon/Resources/DiskStats.cs @@ -0,0 +1,9 @@ +namespace Moonlight.App.Models.Daemon.Resources; + +public class DiskStats +{ + public long FreeBytes { get; set; } + public string DriveFormat { get; set; } + public string Name { get; set; } + public long TotalSize { get; set; } +} \ No newline at end of file diff --git a/Moonlight/App/Models/Daemon/Resources/MemoryStats.cs b/Moonlight/App/Models/Daemon/Resources/MemoryStats.cs new file mode 100644 index 0000000..2682309 --- /dev/null +++ b/Moonlight/App/Models/Daemon/Resources/MemoryStats.cs @@ -0,0 +1,15 @@ +namespace Moonlight.App.Models.Daemon.Resources; + +public class MemoryStats +{ + public List Sticks { get; set; } = new(); + public double Free { get; set; } + public double Used { get; set; } + public double Total { get; set; } + + public class MemoryStick + { + public int Size { get; set; } + public string Type { get; set; } = ""; + } +} \ No newline at end of file diff --git a/Moonlight/App/Models/Node/CpuStats.cs b/Moonlight/App/Models/Node/CpuStats.cs deleted file mode 100644 index 5a12f6e..0000000 --- a/Moonlight/App/Models/Node/CpuStats.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Moonlight.App.Models.Node; - -public class CpuStats -{ - public double CpuUsage { get; set; } -} \ No newline at end of file diff --git a/Moonlight/App/Models/Node/DiskStats.cs b/Moonlight/App/Models/Node/DiskStats.cs deleted file mode 100644 index d562f98..0000000 --- a/Moonlight/App/Models/Node/DiskStats.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Moonlight.App.Models.Node; - -public class DiskStats -{ - public long FreeBytes { get; set; } -} \ No newline at end of file diff --git a/Moonlight/App/Models/Node/DockerStats.cs b/Moonlight/App/Models/Node/DockerStats.cs deleted file mode 100644 index 791e4e0..0000000 --- a/Moonlight/App/Models/Node/DockerStats.cs +++ /dev/null @@ -1,17 +0,0 @@ -namespace Moonlight.App.Models.Node; - -public class DockerStats -{ - public ContainerStats[] Containers { get; set; } - public int NodeId { get; set; } -} - -public class ContainerStats -{ - public Guid Name { get; set; } - public long Memory { get; set; } - public double Cpu { get; set; } - public long NetworkIn { get; set; } - public long NetworkOut { get; set; } - public int NodeId { get; set; } -} \ No newline at end of file diff --git a/Moonlight/App/Models/Node/MemoryStats.cs b/Moonlight/App/Models/Node/MemoryStats.cs deleted file mode 100644 index 335c9fe..0000000 --- a/Moonlight/App/Models/Node/MemoryStats.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Moonlight.App.Models.Node; - -public class MemoryStats -{ - public long Free { get; set; } - public long Used { get; set; } - public long Total { get; set; } -} \ No newline at end of file diff --git a/Moonlight/App/Services/NodeService.cs b/Moonlight/App/Services/NodeService.cs index 4a0afba..577214e 100644 --- a/Moonlight/App/Services/NodeService.cs +++ b/Moonlight/App/Services/NodeService.cs @@ -1,5 +1,6 @@ using Moonlight.App.Database.Entities; using Moonlight.App.Helpers; +using Moonlight.App.Models.Daemon.Resources; using Moonlight.App.Models.Wings.Resources; using Moonlight.App.Repositories; @@ -7,17 +8,37 @@ namespace Moonlight.App.Services; public class NodeService { - private readonly NodeRepository NodeRepository; private readonly WingsApiHelper WingsApiHelper; + private readonly DaemonApiHelper DaemonApiHelper; - public NodeService(NodeRepository nodeRepository, WingsApiHelper wingsApiHelper) + public NodeService(WingsApiHelper wingsApiHelper, DaemonApiHelper daemonApiHelper) { - NodeRepository = nodeRepository; WingsApiHelper = wingsApiHelper; + DaemonApiHelper = daemonApiHelper; } public async Task GetStatus(Node node) { return await WingsApiHelper.Get(node, "api/system"); } + + public async Task GetCpuStats(Node node) + { + return await DaemonApiHelper.Get(node, "stats/cpu"); + } + + public async Task GetMemoryStats(Node node) + { + return await DaemonApiHelper.Get(node, "stats/memory"); + } + + public async Task GetDiskStats(Node node) + { + return await DaemonApiHelper.Get(node, "stats/disk"); + } + + public async Task GetContainerStats(Node node) + { + return await DaemonApiHelper.Get(node, "stats/container"); + } } \ No newline at end of file diff --git a/Moonlight/Moonlight.csproj b/Moonlight/Moonlight.csproj index 57522d5..fa894e5 100644 --- a/Moonlight/Moonlight.csproj +++ b/Moonlight/Moonlight.csproj @@ -62,7 +62,7 @@ - + diff --git a/Moonlight/Program.cs b/Moonlight/Program.cs index 64635f1..c43cde5 100644 --- a/Moonlight/Program.cs +++ b/Moonlight/Program.cs @@ -118,6 +118,7 @@ namespace Moonlight builder.Services.AddScoped(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); + builder.Services.AddScoped(); // Background services builder.Services.AddSingleton(); diff --git a/Moonlight/Shared/Views/Admin/Nodes/Index.razor b/Moonlight/Shared/Views/Admin/Nodes/Index.razor index 4b168c4..e939447 100644 --- a/Moonlight/Shared/Views/Admin/Nodes/Index.razor +++ b/Moonlight/Shared/Views/Admin/Nodes/Index.razor @@ -1,12 +1,11 @@ @page "/admin/nodes" @using Moonlight.App.Repositories @using Moonlight.App.Database.Entities -@using Moonlight.App.Helpers -@using Moonlight.App.Models.Node @using Moonlight.App.Models.Wings.Resources @using Moonlight.App.Services @using Moonlight.App.Services.Interop @using Logging.Net +@using BlazorTable @inject NodeRepository NodeRepository @inject AlertService AlertService @@ -14,168 +13,85 @@ @inject SmartTranslateService SmartTranslateService -
- @if (!Nodes.Any()) - { -
-
- No nodes found. Start with adding a new node +
+
+

+ + Nodes + +

+
- } - else - { - foreach (var node in Nodes) - { -
-
-
-

- @(node.Name) - (ID: @(node.Id)) -

-
-
-
-
-
- - Status - -
-
-
- - @{ - var ss = StatusCache.ContainsKey(node) ? StatusCache[node] : null; - } +
+ @if (Nodes.Any()) + { +
+ + + + + + + + + + + + + + + + + + + +
-
- } - } + } + else + { +
+ No nodes found +
+ } +
+
@@ -186,17 +102,10 @@ private LazyLoader LazyLoader; - private Dictionary CpuCache = new(); - private Dictionary MemoryCache = new(); - private Dictionary DiskCache = new(); private Dictionary StatusCache = new(); private Task Load(LazyLoader lazyLoader) { - CpuCache.Clear(); - MemoryCache.Clear(); - DiskCache.Clear(); - lock (StatusCache) { StatusCache.Clear(); @@ -222,11 +131,6 @@ catch (Exception e) { Logger.Debug(e.Message); - - lock (StatusCache) - { - StatusCache.Add(node, null); - } } await InvokeAsync(StateHasChanged); diff --git a/Moonlight/Shared/Views/Admin/Nodes/View.razor b/Moonlight/Shared/Views/Admin/Nodes/View.razor new file mode 100644 index 0000000..4b64b6d --- /dev/null +++ b/Moonlight/Shared/Views/Admin/Nodes/View.razor @@ -0,0 +1,357 @@ +@page "/admin/nodes/view/{id:int}" + +@using Moonlight.App.Repositories +@using Moonlight.App.Database.Entities +@using Moonlight.App.Helpers +@using Moonlight.App.Models.Daemon.Resources +@using Moonlight.App.Models.Wings.Resources +@using Moonlight.App.Services + +@inject NodeRepository NodeRepository +@inject NodeService NodeService + + + +@if (Node == null) +{ +
+ No node with this id found +
+} +else +{ +
+
+
+
+

+ + @(Node.Name) details + +

+
+
+
+
+
+
+ + + +
+
+ + @if (CpuStats == null) + { + + Loading + + } + else + { + + @(CpuStats.Usage)% of @(CpuStats.Cores) Cores used + + } + + + @if (CpuStats == null) + { + + Loading + + } + else + { + @(CpuStats.Model) + } + +
+
+
+
+
+
+ + + +
+
+ + @if (MemoryStats == null) + { + + Loading + + } + else + { + + @(Formatter.FormatSize(MemoryStats.Used * 1024D * 1024D)) of @(Formatter.FormatSize(MemoryStats.Total * 1024D * 1024D)) used + + } + + + @if (MemoryStats == null) + { + + Loading + + } + else + { + if (MemoryStats.Sticks.Any()) + { + foreach (var stick in SortMemorySticks(MemoryStats.Sticks)) + { + @(stick) +
+ } + } + else + { + No memory sticks detected + } + } +
+
+
+
+
+
+
+ + + +
+
+ + @if (DiskStats == null) + { + + Loading + + } + else + { + + @(Formatter.FormatSize(DiskStats.TotalSize - DiskStats.FreeBytes)) of @(Formatter.FormatSize(DiskStats.TotalSize)) used + + } + + + @if (DiskStats == null) + { + + Loading + + } + else + { + @(DiskStats.Name) - @(DiskStats.DriveFormat) + } + +
+
+
+
+
+
+
+
+ + + +
+
+ + @if (SystemStatus == null) + { + + Loading + + } + else + { + + Online + + } + + + @if (SystemStatus == null) + { + + Loading + + } + else + { + @(SystemStatus.Version) + } + +
+
+
+
+
+
+ + + +
+
+ + @if (SystemStatus == null) + { + + Loading + + } + else + { + + @(SystemStatus.KernelVersion) - @(SystemStatus.Architecture) + + } + + + @if (SystemStatus == null) + { + + Loading + + } + else + { + Host system information + } + +
+
+
+
+
+
+ + + +
+
+ + @if (ContainerStats == null) + { + + Loading + + } + else + { + + @(ContainerStats.Containers.Count) + + } + + + @if (ContainerStats == null) + { + + Loading + + } + else + { + Docker containers running + } + +
+
+
+
+
+
+
+ +
+
+
+} +
+
+ +@code +{ + [Parameter] + public int Id { get; set; } + + private Node? Node; + + private CpuStats CpuStats; + private MemoryStats MemoryStats; + private DiskStats DiskStats; + private SystemStatus SystemStatus; + private ContainerStats ContainerStats; + + private async Task Load(LazyLoader arg) + { + Node = NodeRepository + .Get() + .FirstOrDefault(x => x.Id == Id); + + if (Node != null) + { + Task.Run(async () => + { + try + { + SystemStatus = await NodeService.GetStatus(Node); + await InvokeAsync(StateHasChanged); + + CpuStats = await NodeService.GetCpuStats(Node); + await InvokeAsync(StateHasChanged); + + MemoryStats = await NodeService.GetMemoryStats(Node); + await InvokeAsync(StateHasChanged); + + DiskStats = await NodeService.GetDiskStats(Node); + await InvokeAsync(StateHasChanged); + + ContainerStats = await NodeService.GetContainerStats(Node); + await InvokeAsync(StateHasChanged); + } + catch (Exception e) + { + // ignored + } + }); + } + } + + private List SortMemorySticks(List sticks) + { + // Thank you ChatGPT <3 + + var groupedMemory = sticks.GroupBy(memory => new { memory.Type, memory.Size }) + .Select(group => new + { + Type = group.Key.Type, + Size = group.Key.Size, + Count = group.Count() + }); + + var sortedMemory = groupedMemory.OrderBy(memory => memory.Type) + .ThenBy(memory => memory.Size); + + List sortedList = sortedMemory.Select(memory => + { + string sizeString = $"{memory.Size}GB"; + return $"{memory.Count}x {memory.Type} {sizeString}"; + }).ToList(); + + return sortedList; + } +} \ No newline at end of file diff --git a/Moonlight/Shared/Views/Admin/Servers/Index.razor b/Moonlight/Shared/Views/Admin/Servers/Index.razor index f60b9b1..c4da20a 100644 --- a/Moonlight/Shared/Views/Admin/Servers/Index.razor +++ b/Moonlight/Shared/Views/Admin/Servers/Index.razor @@ -29,7 +29,11 @@
- + + + diff --git a/Moonlight/resources/lang/de_de.lang b/Moonlight/resources/lang/de_de.lang index cd9e6a0..8721e8b 100644 --- a/Moonlight/resources/lang/de_de.lang +++ b/Moonlight/resources/lang/de_de.lang @@ -354,3 +354,15 @@ Allocations;Allocations No variables found;No variables found Successfully added image;Successfully added image Password change for;Password change for +of;of +New node;New node +Fqdn;Fqdn +Cores used;Cores used +used;used +5.15.90.1-microsoft-standard-WSL2 - amd64;5.15.90.1-microsoft-standard-WSL2 - amd64 +Host system information;Host system information +0;0 +Docker containers running;Docker containers running +details;details +1;1 +2;2