From 2298bab71e98cc3ae60bd8e04a22f60284db4c9a Mon Sep 17 00:00:00 2001 From: Marcel Baumgartner Date: Mon, 3 Apr 2023 21:23:27 +0200 Subject: [PATCH] Base implementation of smart deploy for servers, order screen, subscription service --- .../App/Models/Forms/ServerOrderDataModel.cs | 14 ++ .../App/Models/Misc/SubscriptionLimit.cs | 6 + Moonlight/App/Services/SmartDeployService.cs | 64 +++++++ Moonlight/App/Services/SubscriptionService.cs | 22 ++- Moonlight/Program.cs | 1 + Moonlight/Shared/Layouts/MainLayout.razor | 3 +- .../{Servers.razor => Servers/Index.razor} | 0 Moonlight/Shared/Views/Servers/New.razor | 174 ++++++++++++++++++ Moonlight/resources/lang/de_de.lang | 12 ++ 9 files changed, 287 insertions(+), 9 deletions(-) create mode 100644 Moonlight/App/Models/Forms/ServerOrderDataModel.cs create mode 100644 Moonlight/App/Services/SmartDeployService.cs rename Moonlight/Shared/Views/{Servers.razor => Servers/Index.razor} (100%) create mode 100644 Moonlight/Shared/Views/Servers/New.razor diff --git a/Moonlight/App/Models/Forms/ServerOrderDataModel.cs b/Moonlight/App/Models/Forms/ServerOrderDataModel.cs new file mode 100644 index 0000000..778096f --- /dev/null +++ b/Moonlight/App/Models/Forms/ServerOrderDataModel.cs @@ -0,0 +1,14 @@ +using System.ComponentModel.DataAnnotations; +using Moonlight.App.Database.Entities; + +namespace Moonlight.App.Models.Forms; + +public class ServerOrderDataModel +{ + [Required(ErrorMessage = "You need to enter a name")] + [MaxLength(32, ErrorMessage = "The name cannot be longer that 32 characters")] + public string Name { get; set; } = ""; + + [Required(ErrorMessage = "You need to specify a server image")] + public Image Image { get; set; } +} \ No newline at end of file diff --git a/Moonlight/App/Models/Misc/SubscriptionLimit.cs b/Moonlight/App/Models/Misc/SubscriptionLimit.cs index 6e77aba..21d5910 100644 --- a/Moonlight/App/Models/Misc/SubscriptionLimit.cs +++ b/Moonlight/App/Models/Misc/SubscriptionLimit.cs @@ -5,6 +5,12 @@ public class SubscriptionLimit public string Identifier { get; set; } = ""; public int Amount { get; set; } public List Options { get; set; } = new(); + + public string? ReadValue(string key) + { + var d = Options.FirstOrDefault(x => string.Equals(x.Key, key, StringComparison.InvariantCultureIgnoreCase)); + return d?.Value; + } public class LimitOption { diff --git a/Moonlight/App/Services/SmartDeployService.cs b/Moonlight/App/Services/SmartDeployService.cs new file mode 100644 index 0000000..0585c1d --- /dev/null +++ b/Moonlight/App/Services/SmartDeployService.cs @@ -0,0 +1,64 @@ +using Moonlight.App.Database.Entities; +using Moonlight.App.Repositories; + +namespace Moonlight.App.Services; + +public class SmartDeployService +{ + private readonly NodeRepository NodeRepository; + private readonly NodeService NodeService; + + public SmartDeployService(NodeRepository nodeRepository, NodeService nodeService) + { + NodeRepository = nodeRepository; + NodeService = nodeService; + } + + public async Task GetNode() + { + var data = new Dictionary(); + + foreach (var node in NodeRepository.Get().ToArray()) + { + var u = await GetUsageScore(node); + + if(u != 0) + data.Add(node, u); + } + + if (!data.Any()) + return null; + + return data.MaxBy(x => x.Value).Key; + } + + private async Task GetUsageScore(Node node) + { + var score = 0; + + try + { + var cpuStats = await NodeService.GetCpuStats(node); + var memoryStats = await NodeService.GetMemoryStats(node); + var diskStats = await NodeService.GetDiskStats(node); + + var cpuWeight = 0.5; // Weight of CPU usage in the final score + var memoryWeight = 0.3; // Weight of memory usage in the final score + var diskSpaceWeight = 0.2; // Weight of free disk space in the final score + + var cpuScore = (1 - cpuStats.Usage) * cpuWeight; // CPU score is based on the inverse of CPU usage + var memoryScore = (1 - (memoryStats.Used / 1024)) * memoryWeight; // Memory score is based on the percentage of free memory + var diskSpaceScore = (double) diskStats.FreeBytes / 1000000000 * diskSpaceWeight; // Disk space score is based on the amount of free disk space in GB + + var finalScore = cpuScore + memoryScore + diskSpaceScore; + + return finalScore; + } + catch (Exception e) + { + // ignored + } + + return score; + } +} \ No newline at end of file diff --git a/Moonlight/App/Services/SubscriptionService.cs b/Moonlight/App/Services/SubscriptionService.cs index f5e77c9..9d35fe4 100644 --- a/Moonlight/App/Services/SubscriptionService.cs +++ b/Moonlight/App/Services/SubscriptionService.cs @@ -82,16 +82,19 @@ public class SubscriptionService { var configSection = ConfigService.GetSection("Moonlight").GetSection("Subscriptions"); - var defaultLimits = configSection.GetValue("defaultLimits"); + var defaultLimits = configSection.GetValue("DefaultLimits"); var subscription = await GetCurrent(); if (subscription == null) { - var foundDefault = defaultLimits.FirstOrDefault(x => x.Identifier == identifier); + if (defaultLimits != null) + { + var foundDefault = defaultLimits.FirstOrDefault(x => x.Identifier == identifier); - if (foundDefault != null) - return foundDefault; + if (foundDefault != null) + return foundDefault; + } return new() { @@ -109,11 +112,14 @@ public class SubscriptionService if (foundLimit != null) return foundLimit; - - var foundDefault = defaultLimits.FirstOrDefault(x => x.Identifier == identifier); - if (foundDefault != null) - return foundDefault; + if (defaultLimits != null) + { + var foundDefault = defaultLimits.FirstOrDefault(x => x.Identifier == identifier); + + if (foundDefault != null) + return foundDefault; + } return new() { diff --git a/Moonlight/Program.cs b/Moonlight/Program.cs index d762971..cb4fdcf 100644 --- a/Moonlight/Program.cs +++ b/Moonlight/Program.cs @@ -91,6 +91,7 @@ namespace Moonlight builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); + builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); diff --git a/Moonlight/Shared/Layouts/MainLayout.razor b/Moonlight/Shared/Layouts/MainLayout.razor index b2de036..f4f29aa 100644 --- a/Moonlight/Shared/Layouts/MainLayout.razor +++ b/Moonlight/Shared/Layouts/MainLayout.razor @@ -153,7 +153,8 @@ await JsRuntime.InvokeVoidAsync("document.body.removeAttribute", "data-kt-app-page-loading"); await JsRuntime.InvokeVoidAsync("KTMenu.createInstances"); await JsRuntime.InvokeVoidAsync("KTDrawer.createInstances"); - await JsRuntime.InvokeVoidAsync("createSnow"); + + //await JsRuntime.InvokeVoidAsync("createSnow"); await SessionService.Register(); diff --git a/Moonlight/Shared/Views/Servers.razor b/Moonlight/Shared/Views/Servers/Index.razor similarity index 100% rename from Moonlight/Shared/Views/Servers.razor rename to Moonlight/Shared/Views/Servers/Index.razor diff --git a/Moonlight/Shared/Views/Servers/New.razor b/Moonlight/Shared/Views/Servers/New.razor new file mode 100644 index 0000000..5fb01f0 --- /dev/null +++ b/Moonlight/Shared/Views/Servers/New.razor @@ -0,0 +1,174 @@ +@page "/servers/new" +@using Moonlight.App.Services +@using Moonlight.App.Database.Entities +@using Moonlight.App.Models.Forms +@using Moonlight.App.Models.Misc +@using Moonlight.App.Repositories + +@inject SubscriptionService SubscriptionService +@inject ImageRepository ImageRepository +@inject SmartTranslateService SmartTranslateService +@inject SmartDeployService SmartDeployService + + + @if (DeployNode == null) + { +
+
+ Not found image +
+

+ No node found +

+

+ No node found to deploy to found +

+
+
+
+ } + else + { +
+
+
+
+
+

+ Server details +

+
+
+
+
+
+ +
@(DeployNode.Name)
+
+ @if (Model.Image != null) + { + var limit = Images[Model.Image]; + +
+ +
@(Model.Image.Name)
+
+ +
+ +
+ @{ + var cpu = limit.ReadValue("cpu"); + + if (cpu == null) + cpu = "N/A"; + else + cpu = (int.Parse(cpu) / 100).ToString(); + } + @(cpu) Cores +
+
+ +
+ +
@(limit.ReadValue("memory")) MB
+
+ +
+ +
@(limit.ReadValue("disk")) MB
+
+ } +
+
+
+
+
+
+
+
+

+ Configure your server +

+
+
+
+ + +
+ +
+ @if (Images.Any()) + { + + + + + + } + else + { +
+ + You reached the maximum amount of servers for every image of your subscription: @(Subscription == null ? SmartTranslateService.Translate("Default") : Subscription.Name) + +
+ } +
+
+
+
+
+ } +
+ +@code +{ + private Node? DeployNode; + private Subscription? Subscription; + + private Dictionary Images = new(); + + private ServerOrderDataModel Model = new(); + + private async Task Load(LazyLoader lazyLoader) + { + // Reset state + Images.Clear(); + Model = new(); + + await lazyLoader.SetText(SmartTranslateService.Translate("Loading your subscription")); + Subscription = await SubscriptionService.GetCurrent(); + + await lazyLoader.SetText(SmartTranslateService.Translate("Searching for deploy node")); + + DeployNode = await SmartDeployService.GetNode(); + + await lazyLoader.SetText(SmartTranslateService.Translate("Searching for available images")); + + var images = ImageRepository.Get().ToArray(); + + foreach (var image in images) + { + var limit = await SubscriptionService.GetLimit("image." + image.Id); + + if (limit.Amount > 0) + { + Images.Add(image, limit); + } + } + } + + private async Task OnValidSubmit() + { + } +} \ No newline at end of file diff --git a/Moonlight/resources/lang/de_de.lang b/Moonlight/resources/lang/de_de.lang index b56f294..bcb775a 100644 --- a/Moonlight/resources/lang/de_de.lang +++ b/Moonlight/resources/lang/de_de.lang @@ -463,3 +463,15 @@ Options;Options Amount;Amount Do you really want to delete it?;Do you really want to delete it? Save subscription;Save subscription +Loading your subscription;Loading your subscription +Searching for deploy node;Searching for deploy node +Searching for available images;Searching for available images +Server details;Server details +Configure your server;Configure your server +Default;Default +No images available;No images available +You reached the maximum amount of servers for every image of your subscription;You reached the maximum amount of servers for every image of your subscription +No node found;No node found +No node found to deploy to found;No node found to deploy to found +You need to specify a server image;You need to specify a server image +CPU;CPU