21 Commits
v1b2 ... v1b4

Author SHA1 Message Date
Marcel Baumgartner
0c4fc942b0 Merge pull request #148 from Moonlight-Panel/AddNewDaemonCommunication
Add new daemon communication
2023-06-07 03:34:26 +02:00
Marcel Baumgartner
94b8f07d92 Did some testing. Now able to finish new daemon communication 2023-06-07 03:29:36 +02:00
Marcel Baumgartner
f11eef2734 Merge pull request #147 from Moonlight-Panel/main
Update AddNewDaemonCommunication with latest commits
2023-06-07 02:49:53 +02:00
Marcel Baumgartner
0f8946fe27 Switched to new daemon communication 2023-06-07 02:46:26 +02:00
Marcel Baumgartner
a8cb1392e8 Merge pull request #145 from Moonlight-Panel/ImproveUserExperienceJ2S
Improved user experience for enabling and disabling join2start
2023-06-07 02:39:00 +02:00
Marcel Baumgartner
4241debc3b Improved user experience for enabling and disabling join2start 2023-06-07 02:38:21 +02:00
Marcel Baumgartner
a99959bd2b Merge pull request #144 from Moonlight-Panel/AddDeployNodeOverride
Added smart deploy node override option
2023-06-07 02:23:56 +02:00
Marcel Baumgartner
23644eb93f Added smart deploy node override option 2023-06-07 02:23:30 +02:00
Marcel Baumgartner
ce0016fa3f Merge pull request #143 from Moonlight-Panel/AddFolderDownloadHandler
Added error handler for folder download
2023-06-05 22:01:40 +02:00
Marcel Baumgartner
15d8f49ce9 Added error handler for folder download 2023-06-05 22:01:21 +02:00
Marcel Baumgartner
98d8e5b755 Merge pull request #142 from Moonlight-Panel/ImproveCpuUsageCalculation
Improved cpu usage calculation
2023-06-05 21:48:15 +02:00
Marcel Baumgartner
bfa1a09aab Improved cpu usage calculation 2023-06-05 21:47:33 +02:00
Marcel Baumgartner
84396c34e6 Merge pull request #140 from Moonlight-Panel/RemoveBundleService
Removed bundle service
2023-06-05 21:34:57 +02:00
Marcel Baumgartner
4fb4a2415b Removed bundle service 2023-06-05 21:34:09 +02:00
Marcel Baumgartner
c6cf11626e Merge pull request #139 from Moonlight-Panel/ImproveConsoleStreaming
Improve console streaming
2023-06-04 21:42:13 +02:00
Marcel Baumgartner
233c304b3c Fixed error when closing a failed websocket connection 2023-06-04 21:41:15 +02:00
Marcel Baumgartner
343e527fb6 Added message cache clear for console streaming 2023-06-04 20:56:47 +02:00
Daniel Balk
25da3c233e Merge pull request #138 from Dannyx1604/main
Ein paar kleine Änderungen ;-)
2023-06-03 12:48:43 +02:00
Dannyx
d7fb3382f7 Ein paar kleine Änderungen ;-) 2023-06-03 09:06:22 +02:00
Marcel Baumgartner
88c9f5372d Merge pull request #137 from Moonlight-Panel/AddConsoleStreamingDispose
Add console streaming dispose
2023-06-01 00:44:56 +02:00
Marcel Baumgartner
7128a7f8a7 Add console streaming dispose 2023-06-01 00:44:14 +02:00
31 changed files with 309 additions and 382 deletions

View File

@@ -1,5 +1,4 @@
using Moonlight.App.Database.Entities; using Moonlight.App.Database.Entities;
using Moonlight.App.Exceptions;
using Newtonsoft.Json; using Newtonsoft.Json;
using RestSharp; using RestSharp;
@@ -14,26 +13,13 @@ public class DaemonApiHelper
Client = new(); Client = new();
} }
private string GetApiUrl(Node node)
{
/* SSL not implemented in moonlight daemon
if(node.Ssl)
return $"https://{node.Fqdn}:{node.MoonlightDaemonPort}/";
else
return $"http://{node.Fqdn}:{node.MoonlightDaemonPort}/";*/
return $"http://{node.Fqdn}:{node.MoonlightDaemonPort}/";
}
public async Task<T> Get<T>(Node node, string resource) public async Task<T> Get<T>(Node node, string resource)
{ {
RestRequest request = new(GetApiUrl(node) + resource); var request = await CreateRequest(node, resource);
request.AddHeader("Content-Type", "application/json"); request.Method = Method.Get;
request.AddHeader("Accept", "application/json");
request.AddHeader("Authorization", node.Token);
var response = await Client.GetAsync(request); var response = await Client.ExecuteAsync(request);
if (!response.IsSuccessful) if (!response.IsSuccessful)
{ {
@@ -52,4 +38,69 @@ public class DaemonApiHelper
return JsonConvert.DeserializeObject<T>(response.Content!)!; return JsonConvert.DeserializeObject<T>(response.Content!)!;
} }
public async Task Post(Node node, string resource, object body)
{
var request = await CreateRequest(node, resource);
request.Method = Method.Post;
request.AddParameter("text/plain", JsonConvert.SerializeObject(body), ParameterType.RequestBody);
var response = await Client.ExecuteAsync(request);
if (!response.IsSuccessful)
{
if (response.StatusCode != 0)
{
throw new DaemonException(
$"An error occured: ({response.StatusCode}) {response.Content}",
(int)response.StatusCode
);
}
else
{
throw new Exception($"An internal error occured: {response.ErrorMessage}");
}
}
}
public async Task Delete(Node node, string resource, object body)
{
var request = await CreateRequest(node, resource);
request.Method = Method.Delete;
request.AddParameter("text/plain", JsonConvert.SerializeObject(body), ParameterType.RequestBody);
var response = await Client.ExecuteAsync(request);
if (!response.IsSuccessful)
{
if (response.StatusCode != 0)
{
throw new DaemonException(
$"An error occured: ({response.StatusCode}) {response.Content}",
(int)response.StatusCode
);
}
else
{
throw new Exception($"An internal error occured: {response.ErrorMessage}");
}
}
}
private Task<RestRequest> CreateRequest(Node node, string resource)
{
var url = $"http://{node.Fqdn}:{node.MoonlightDaemonPort}/";
RestRequest request = new(url + resource);
request.AddHeader("Content-Type", "application/json");
request.AddHeader("Accept", "application/json");
request.AddHeader("Authorization", node.Token);
return Task.FromResult(request);
}
} }

View File

@@ -0,0 +1,8 @@
namespace Moonlight.App.ApiClients.Daemon.Requests;
public class Mount
{
public string Server { get; set; } = "";
public string ServerPath { get; set; } = "";
public string Path { get; set; } = "";
}

View File

@@ -0,0 +1,6 @@
namespace Moonlight.App.ApiClients.Daemon.Requests;
public class Unmount
{
public string Path { get; set; } = "";
}

View File

@@ -0,0 +1,10 @@
namespace Moonlight.App.ApiClients.Daemon.Resources;
public class Container
{
public string Name { get; set; } = "";
public long Memory { get; set; }
public double Cpu { get; set; }
public long NetworkIn { get; set; }
public long NetworkOut { get; set; }
}

View File

@@ -1,15 +0,0 @@
namespace Moonlight.App.ApiClients.Daemon.Resources;
public class ContainerStats
{
public List<Container> Containers { get; set; } = new();
public class Container
{
public string Name { get; set; }
public long Memory { get; set; }
public double Cpu { get; set; }
public long NetworkIn { get; set; }
public long NetworkOut { get; set; }
}
}

View File

@@ -0,0 +1,7 @@
namespace Moonlight.App.ApiClients.Daemon.Resources;
public class CpuMetrics
{
public string CpuModel { get; set; } = "";
public double CpuUsage { get; set; }
}

View File

@@ -1,8 +0,0 @@
namespace Moonlight.App.ApiClients.Daemon.Resources;
public class CpuStats
{
public double Usage { get; set; }
public int Cores { get; set; }
public string Model { get; set; } = "";
}

View File

@@ -0,0 +1,7 @@
namespace Moonlight.App.ApiClients.Daemon.Resources;
public class DiskMetrics
{
public long Used { get; set; }
public long Total { get; set; }
}

View File

@@ -1,9 +0,0 @@
namespace Moonlight.App.ApiClients.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; }
}

View File

@@ -0,0 +1,6 @@
namespace Moonlight.App.ApiClients.Daemon.Resources;
public class DockerMetrics
{
public Container[] Containers { get; set; } = Array.Empty<Container>();
}

View File

@@ -0,0 +1,7 @@
namespace Moonlight.App.ApiClients.Daemon.Resources;
public class MemoryMetrics
{
public long Used { get; set; }
public long Total { get; set; }
}

View File

@@ -1,15 +0,0 @@
namespace Moonlight.App.ApiClients.Daemon.Resources;
public class MemoryStats
{
public List<MemoryStick> 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; } = "";
}
}

View File

@@ -0,0 +1,7 @@
namespace Moonlight.App.ApiClients.Daemon.Resources;
public class SystemMetrics
{
public string OsName { get; set; } = "";
public long Uptime { get; set; }
}

View File

@@ -116,4 +116,12 @@ public static class Formatter
return (i / (1024D * 1024D)).Round(2) + " GB"; return (i / (1024D * 1024D)).Round(2) + " GB";
} }
} }
public static double BytesToGb(long bytes)
{
const double gbMultiplier = 1024 * 1024 * 1024; // 1 GB = 1024 MB * 1024 KB * 1024 B
double gigabytes = (double)bytes / gbMultiplier;
return gigabytes;
}
} }

View File

@@ -142,18 +142,28 @@ public class WingsConsole : IDisposable
switch (eventData.Event) switch (eventData.Event)
{ {
case "jwt error": case "jwt error":
await WebSocket.CloseAsync(WebSocketCloseStatus.Empty, "Jwt error detected", if (WebSocket != null)
CancellationToken.None); {
if (WebSocket.State == WebSocketState.Connecting || WebSocket.State == WebSocketState.Open)
await WebSocket.CloseAsync(WebSocketCloseStatus.Empty, null, CancellationToken.None);
WebSocket.Dispose();
}
await UpdateServerState(ServerState.Offline); await UpdateServerState(ServerState.Offline);
await UpdateConsoleState(ConsoleState.Disconnected); await UpdateConsoleState(ConsoleState.Disconnected);
await SaveMessage("Received a jwt error", true); await SaveMessage("Received a jwt error. Disconnected", true);
break; break;
case "token expired": case "token expired":
await WebSocket.CloseAsync(WebSocketCloseStatus.Empty, "Jwt error detected", if (WebSocket != null)
CancellationToken.None); {
if (WebSocket.State == WebSocketState.Connecting || WebSocket.State == WebSocketState.Open)
await WebSocket.CloseAsync(WebSocketCloseStatus.Empty, null, CancellationToken.None);
WebSocket.Dispose();
}
await UpdateServerState(ServerState.Offline); await UpdateServerState(ServerState.Offline);
await UpdateConsoleState(ConsoleState.Disconnected); await UpdateConsoleState(ConsoleState.Disconnected);
@@ -346,6 +356,7 @@ public class WingsConsole : IDisposable
public async Task Disconnect() public async Task Disconnect()
{ {
Disconnecting = true; Disconnecting = true;
Messages.Clear();
if (WebSocket != null) if (WebSocket != null)
{ {
@@ -362,6 +373,7 @@ public class WingsConsole : IDisposable
public void Dispose() public void Dispose()
{ {
Disconnecting = true; Disconnecting = true;
Messages.Clear();
if (WebSocket != null) if (WebSocket != null)
{ {

View File

@@ -16,14 +16,12 @@ public class ResourcesController : Controller
{ {
private readonly SecurityLogService SecurityLogService; private readonly SecurityLogService SecurityLogService;
private readonly BucketService BucketService; private readonly BucketService BucketService;
private readonly BundleService BundleService;
public ResourcesController(SecurityLogService securityLogService, public ResourcesController(SecurityLogService securityLogService,
BucketService bucketService, BundleService bundleService) BucketService bucketService)
{ {
SecurityLogService = securityLogService; SecurityLogService = securityLogService;
BucketService = bucketService; BucketService = bucketService;
BundleService = bundleService;
} }
[HttpGet("images/{name}")] [HttpGet("images/{name}")]
@@ -77,34 +75,4 @@ public class ResourcesController : Controller
return Problem(); return Problem();
} }
} }
[HttpGet("bundle/js")]
public Task<ActionResult> GetJs()
{
if (BundleService.BundledFinished)
{
return Task.FromResult<ActionResult>(
File(Encoding.ASCII.GetBytes(BundleService.BundledJs), "text/javascript")
);
}
return Task.FromResult<ActionResult>(
NotFound()
);
}
[HttpGet("bundle/css")]
public Task<ActionResult> GetCss()
{
if (BundleService.BundledFinished)
{
return Task.FromResult<ActionResult>(
File(Encoding.ASCII.GetBytes(BundleService.BundledCss), "text/css")
);
}
return Task.FromResult<ActionResult>(
NotFound()
);
}
} }

View File

@@ -6,5 +6,5 @@ namespace Moonlight.App.Models.Misc;
public class RunningServer public class RunningServer
{ {
public Server Server { get; set; } public Server Server { get; set; }
public ContainerStats.Container Container { get; set; } public Container Container { get; set; }
} }

View File

@@ -5,6 +5,7 @@ using Moonlight.App.ApiClients.Daemon.Resources;
using Moonlight.App.ApiClients.Wings; using Moonlight.App.ApiClients.Wings;
using Moonlight.App.Database.Entities; using Moonlight.App.Database.Entities;
using Moonlight.App.Events; using Moonlight.App.Events;
using Moonlight.App.Helpers;
using Moonlight.App.Repositories; using Moonlight.App.Repositories;
using Moonlight.App.Repositories.Servers; using Moonlight.App.Repositories.Servers;
using Newtonsoft.Json; using Newtonsoft.Json;
@@ -81,12 +82,12 @@ public class CleanupService
{ {
try try
{ {
var cpuStats = await nodeService.GetCpuStats(node); var cpuMetrics = await nodeService.GetCpuMetrics(node);
var memoryStats = await nodeService.GetMemoryStats(node); var memoryMetrics = await nodeService.GetMemoryMetrics(node);
if (cpuStats.Usage > maxCpu || memoryStats.Free < minMemory) if (cpuMetrics.CpuUsage > maxCpu || (Formatter.BytesToGb(memoryMetrics.Total) - (Formatter.BytesToGb(memoryMetrics.Used))) < minMemory)
{ {
var containerStats = await nodeService.GetContainerStats(node); var dockerMetrics = await nodeService.GetDockerMetrics(node);
var serverRepository = scope.ServiceProvider.GetRequiredService<ServerRepository>(); var serverRepository = scope.ServiceProvider.GetRequiredService<ServerRepository>();
var imageRepository = scope.ServiceProvider.GetRequiredService<ImageRepository>(); var imageRepository = scope.ServiceProvider.GetRequiredService<ImageRepository>();
@@ -101,9 +102,9 @@ public class CleanupService
) )
.ToArray(); .ToArray();
var containerMappedToServers = new Dictionary<ContainerStats.Container, Server>(); var containerMappedToServers = new Dictionary<Container, Server>();
foreach (var container in containerStats.Containers) foreach (var container in dockerMetrics.Containers)
{ {
if (Guid.TryParse(container.Name, out Guid uuid)) if (Guid.TryParse(container.Name, out Guid uuid))
{ {

View File

@@ -1,4 +1,5 @@
using Moonlight.App.ApiClients.Daemon; using Moonlight.App.ApiClients.Daemon;
using Moonlight.App.ApiClients.Daemon.Requests;
using Moonlight.App.ApiClients.Daemon.Resources; using Moonlight.App.ApiClients.Daemon.Resources;
using Moonlight.App.ApiClients.Wings; using Moonlight.App.ApiClients.Wings;
using Moonlight.App.ApiClients.Wings.Resources; using Moonlight.App.ApiClients.Wings.Resources;
@@ -24,35 +25,56 @@ public class NodeService
return await WingsApiHelper.Get<SystemStatus>(node, "api/system"); return await WingsApiHelper.Get<SystemStatus>(node, "api/system");
} }
public async Task<CpuStats> GetCpuStats(Node node) public async Task<CpuMetrics> GetCpuMetrics(Node node)
{ {
return await DaemonApiHelper.Get<CpuStats>(node, "stats/cpu"); return await DaemonApiHelper.Get<CpuMetrics>(node, "metrics/cpu");
} }
public async Task<MemoryStats> GetMemoryStats(Node node) public async Task<MemoryMetrics> GetMemoryMetrics(Node node)
{ {
return await DaemonApiHelper.Get<MemoryStats>(node, "stats/memory"); return await DaemonApiHelper.Get<MemoryMetrics>(node, "metrics/memory");
} }
public async Task<DiskStats> GetDiskStats(Node node) public async Task<DiskMetrics> GetDiskMetrics(Node node)
{ {
return await DaemonApiHelper.Get<DiskStats>(node, "stats/disk"); return await DaemonApiHelper.Get<DiskMetrics>(node, "metrics/disk");
} }
public async Task<ContainerStats> GetContainerStats(Node node) public async Task<SystemMetrics> GetSystemMetrics(Node node)
{ {
return await DaemonApiHelper.Get<ContainerStats>(node, "stats/container"); return await DaemonApiHelper.Get<SystemMetrics>(node, "metrics/system");
}
public async Task<DockerMetrics> GetDockerMetrics(Node node)
{
return await DaemonApiHelper.Get<DockerMetrics>(node, "metrics/docker");
}
public async Task Mount(Node node, string server, string serverPath, string path)
{
await DaemonApiHelper.Post(node, "mount", new Mount()
{
Server = server,
ServerPath = serverPath,
Path = path
});
}
public async Task Unmount(Node node, string path)
{
await DaemonApiHelper.Delete(node, "mount", new Unmount()
{
Path = path
});
} }
public async Task<bool> IsHostUp(Node node) public async Task<bool> IsHostUp(Node node)
{ {
try try
{ {
//TODO: Implement status caching await GetSystemMetrics(node);
var data = await GetStatus(node);
if (data != null) return true;
return true;
} }
catch (Exception) catch (Exception)
{ {

View File

@@ -1,129 +0,0 @@
using Logging.Net;
namespace Moonlight.App.Services.Sessions;
public class BundleService
{
public BundleService(ConfigService configService)
{
var url = configService
.GetSection("Moonlight")
.GetValue<string>("AppUrl");
#region JS
JsFiles = new();
JsFiles.AddRange(new[]
{
url + "/_framework/blazor.server.js",
url + "/assets/plugins/global/plugins.bundle.js",
url + "/_content/XtermBlazor/XtermBlazor.min.js",
url + "/_content/BlazorTable/BlazorTable.min.js",
url + "/_content/CurrieTechnologies.Razor.SweetAlert2/sweetAlert2.min.js",
url + "/_content/Blazor.ContextMenu/blazorContextMenu.min.js",
"https://www.google.com/recaptcha/api.js",
"https://cdn.jsdelivr.net/npm/xterm-addon-fit@0.5.0/lib/xterm-addon-fit.min.js",
"https://cdn.jsdelivr.net/npm/xterm-addon-search@0.8.2/lib/xterm-addon-search.min.js",
"https://cdn.jsdelivr.net/npm/xterm-addon-web-links@0.5.0/lib/xterm-addon-web-links.min.js",
url + "/_content/BlazorMonaco/lib/monaco-editor/min/vs/loader.js",
"require.config({ paths: { 'vs': '/_content/BlazorMonaco/lib/monaco-editor/min/vs' } });",
url + "/_content/BlazorMonaco/lib/monaco-editor/min/vs/editor/editor.main.js",
url + "/_content/BlazorMonaco/jsInterop.js",
url + "/assets/js/scripts.bundle.js",
url + "/assets/js/moonlight.js",
"moonlight.loading.registerXterm();",
url + "/_content/Blazor-ApexCharts/js/apex-charts.min.js",
url + "/_content/Blazor-ApexCharts/js/blazor-apex-charts.js"
});
#endregion
#region CSS
CssFiles = new();
CssFiles.AddRange(new[]
{
"https://fonts.googleapis.com/css?family=Poppins:300,400,500,600,700",
url + "/assets/css/style.bundle.css",
url + "/assets/css/flashbang.css",
url + "/assets/css/snow.css",
url + "/assets/css/utils.css",
url + "/assets/css/blazor.css",
url + "/_content/XtermBlazor/XtermBlazor.css",
url + "/_content/BlazorMonaco/lib/monaco-editor/min/vs/editor/editor.main.css",
url + "/_content/Blazor.ContextMenu/blazorContextMenu.min.css",
url + "/assets/plugins/global/plugins.bundle.css"
});
#endregion
CacheId = Guid.NewGuid().ToString();
Task.Run(Bundle);
}
// Javascript
public string BundledJs { get; private set; }
public readonly List<string> JsFiles;
// CSS
public string BundledCss { get; private set; }
public readonly List<string> CssFiles;
// States
public string CacheId { get; private set; }
public bool BundledFinished { get; set; } = false;
private bool IsBundling { get; set; } = false;
private async Task Bundle()
{
if (!IsBundling)
IsBundling = true;
Logger.Info("Bundling js and css files");
BundledJs = "";
BundledCss = "";
BundledJs = await BundleFiles(
JsFiles
);
BundledCss = await BundleFiles(
CssFiles
);
Logger.Info("Successfully bundled");
BundledFinished = true;
}
private async Task<string> BundleFiles(IEnumerable<string> items)
{
var bundled = "";
using HttpClient client = new HttpClient();
foreach (string item in items)
{
// Item is a url, fetch it
if (item.StartsWith("http"))
{
try
{
var jsCode = await client.GetStringAsync(item);
bundled += jsCode + "\n";
}
catch (Exception e)
{
Logger.Warn($"Error fetching '{item}' while bundling");
Logger.Warn(e);
}
}
else // If not, it is probably a manual addition, so add it
bundled += item + "\n";
}
return bundled;
}
}

View File

@@ -9,21 +9,36 @@ public class SmartDeployService
private readonly Repository<CloudPanel> CloudPanelRepository; private readonly Repository<CloudPanel> CloudPanelRepository;
private readonly WebSpaceService WebSpaceService; private readonly WebSpaceService WebSpaceService;
private readonly NodeService NodeService; private readonly NodeService NodeService;
private readonly ConfigService ConfigService;
public SmartDeployService( public SmartDeployService(
NodeRepository nodeRepository, NodeRepository nodeRepository,
NodeService nodeService, NodeService nodeService,
WebSpaceService webSpaceService, WebSpaceService webSpaceService,
Repository<CloudPanel> cloudPanelRepository) Repository<CloudPanel> cloudPanelRepository,
ConfigService configService)
{ {
NodeRepository = nodeRepository; NodeRepository = nodeRepository;
NodeService = nodeService; NodeService = nodeService;
WebSpaceService = webSpaceService; WebSpaceService = webSpaceService;
CloudPanelRepository = cloudPanelRepository; CloudPanelRepository = cloudPanelRepository;
ConfigService = configService;
} }
public async Task<Node?> GetNode() public async Task<Node?> GetNode()
{ {
var config = ConfigService
.GetSection("Moonlight")
.GetSection("SmartDeploy")
.GetSection("Server");
if (config.GetValue<bool>("EnableOverride"))
{
var nodeId = config.GetValue<int>("OverrideNode");
return NodeRepository.Get().FirstOrDefault(x => x.Id == nodeId);
}
var data = new Dictionary<Node, double>(); var data = new Dictionary<Node, double>();
foreach (var node in NodeRepository.Get().ToArray()) foreach (var node in NodeRepository.Get().ToArray())
@@ -59,17 +74,17 @@ public class SmartDeployService
try try
{ {
var cpuStats = await NodeService.GetCpuStats(node); var cpuMetrics = await NodeService.GetCpuMetrics(node);
var memoryStats = await NodeService.GetMemoryStats(node); var memoryMetrics = await NodeService.GetMemoryMetrics(node);
var diskStats = await NodeService.GetDiskStats(node); var diskMetrics = await NodeService.GetDiskMetrics(node);
var cpuWeight = 0.5; // Weight of CPU usage in the final score 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 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 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 cpuScore = (1 - cpuMetrics.CpuUsage) * 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 memoryScore = (1 - (memoryMetrics.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 diskSpaceScore = (double) (diskMetrics.Total - diskMetrics.Used) / 1000000000 * diskSpaceWeight; // Disk space score is based on the amount of free disk space in GB
var finalScore = cpuScore + memoryScore + diskSpaceScore; var finalScore = cpuScore + memoryScore + diskSpaceScore;

View File

@@ -71,7 +71,6 @@
<ItemGroup> <ItemGroup>
<Folder Include="App\ApiClients\CloudPanel\Resources\" /> <Folder Include="App\ApiClients\CloudPanel\Resources\" />
<Folder Include="App\ApiClients\Daemon\Requests\" />
<Folder Include="App\Http\Middleware" /> <Folder Include="App\Http\Middleware" />
<Folder Include="storage\backups\" /> <Folder Include="storage\backups\" />
</ItemGroup> </ItemGroup>

View File

@@ -7,7 +7,6 @@
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@inject ConfigService ConfigService @inject ConfigService ConfigService
@inject BundleService BundleService
@inject LoadingMessageRepository LoadingMessageRepository @inject LoadingMessageRepository LoadingMessageRepository
@{ @{
@@ -38,29 +37,20 @@
<link rel="shortcut icon" href="@(moonlightConfig.GetValue<string>("AppUrl"))/api/moonlight/resources/images/logo.svg"/> <link rel="shortcut icon" href="@(moonlightConfig.GetValue<string>("AppUrl"))/api/moonlight/resources/images/logo.svg"/>
@*This import is not in the bundle because the files it references are linked relative to the current lath*@ <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Poppins:300,400,500,600,700"/>
<link rel="stylesheet" type="text/css" href="/assets/css/boxicons.min.css"/>
@if (BundleService.BundledFinished) <link rel="stylesheet" type="text/css" href="/assets/css/style.bundle.css"/>
{ <link rel="stylesheet" type="text/css" href="/assets/css/flashbang.css"/>
<link rel="stylesheet" type="text/css" href="/api/moonlight/resources/bundle/css?idontwannabecached=@(BundleService.CacheId)"/> <link rel="stylesheet" type="text/css" href="/assets/css/snow.css"/>
} <link rel="stylesheet" type="text/css" href="/assets/css/utils.css"/>
else <link rel="stylesheet" type="text/css" href="/assets/css/boxicons.min.css"/>
{ <link rel="stylesheet" type="text/css" href="/assets/css/blazor.css"/>
foreach (var cssFile in BundleService.CssFiles)
{ <link rel="stylesheet" type="text/css" href="/_content/XtermBlazor/XtermBlazor.css"/>
if (cssFile.StartsWith("http")) <link rel="stylesheet" type="text/css" href="/_content/BlazorMonaco/lib/monaco-editor/min/vs/editor/editor.main.css"/>
{ <link rel="stylesheet" type="text/css" href="/_content/Blazor.ContextMenu/blazorContextMenu.min.css"/>
<link rel="stylesheet" type="text/css" href="@(cssFile)">
} <link href="/assets/plugins/global/plugins.bundle.css" rel="stylesheet" type="text/css"/>
else
{
<style>
@cssFile
</style>
}
}
}
<meta name="viewport" content="width=device-width, initial-scale=1"/> <meta name="viewport" content="width=device-width, initial-scale=1"/>
<base href="~/"/> <base href="~/"/>
@@ -106,24 +96,33 @@
</div> </div>
</div> </div>
@if (BundleService.BundledFinished) <script src="/_framework/blazor.server.js"></script>
{ <script src="/assets/plugins/global/plugins.bundle.js"></script>
<script src="/api/moonlight/resources/bundle/js?idontwannabecached=@(BundleService.CacheId)"> <script src="/_content/XtermBlazor/XtermBlazor.min.js"></script>
</script> <script src="/_content/BlazorTable/BlazorTable.min.js"></script>
} <script src="/_content/CurrieTechnologies.Razor.SweetAlert2/sweetAlert2.min.js"></script>
else <script src="/_content/Blazor.ContextMenu/blazorContextMenu.min.js"></script>
{
foreach (var jsFile in BundleService.JsFiles) <script src="https://www.google.com/recaptcha/api.js"></script>
{
if (jsFile.StartsWith("http")) <script src="https://cdn.jsdelivr.net/npm/xterm-addon-fit@0.5.0/lib/xterm-addon-fit.min.js"></script>
{ <script src="https://cdn.jsdelivr.net/npm/xterm-addon-search@0.8.2/lib/xterm-addon-search.min.js"></script>
<script src="@(jsFile)"></script> <script src="https://cdn.jsdelivr.net/npm/xterm-addon-web-links@0.5.0/lib/xterm-addon-web-links.min.js"></script>
}
else <script src="/_content/BlazorMonaco/lib/monaco-editor/min/vs/loader.js"></script>
{ <script>require.config({ paths: { 'vs': '/_content/BlazorMonaco/lib/monaco-editor/min/vs' } });</script>
@Html.Raw("<script>" + jsFile +"</script>") <script src="/_content/BlazorMonaco/lib/monaco-editor/min/vs/editor/editor.main.js"></script>
} <script src="/_content/BlazorMonaco/jsInterop.js"></script>
}
} <script src="/assets/js/scripts.bundle.js"></script>
<script src="/assets/js/moonlight.js"></script>
<script>
moonlight.loading.registerXterm();
</script>
<script src="_content/Blazor-ApexCharts/js/apex-charts.min.js"></script>
<script src="_content/Blazor-ApexCharts/js/blazor-apex-charts.js"></script>
</body> </body>
</html> </html>

View File

@@ -128,7 +128,6 @@ namespace Moonlight
builder.Services.AddScoped<ReCaptchaService>(); builder.Services.AddScoped<ReCaptchaService>();
builder.Services.AddScoped<IpBanService>(); builder.Services.AddScoped<IpBanService>();
builder.Services.AddSingleton<OAuth2Service>(); builder.Services.AddSingleton<OAuth2Service>();
builder.Services.AddSingleton<BundleService>();
builder.Services.AddScoped<SubscriptionService>(); builder.Services.AddScoped<SubscriptionService>();
builder.Services.AddScoped<SubscriptionAdminService>(); builder.Services.AddScoped<SubscriptionAdminService>();

View File

@@ -185,6 +185,8 @@ else
await ToastService.Info(SmartTranslateService.Translate("Starting download")); await ToastService.Info(SmartTranslateService.Translate("Starting download"));
} }
} }
else
await ToastService.Error(SmartTranslateService.Translate("You are not able to download folders using the moonlight file manager"));
} }
}); });

View File

@@ -93,7 +93,7 @@
</div> </div>
<div class="col fs-5"> <div class="col fs-5">
<span class="fw-bold"><TL>Cpu</TL>:</span> <span class="fw-bold"><TL>Cpu</TL>:</span>
<span class="ms-1 text-muted">@(Math.Round(Console.Resource.CpuAbsolute, 2))%</span> <span class="ms-1 text-muted">@(Math.Round(Console.Resource.CpuAbsolute / (CurrentServer.Cpu / 100f), 2))%</span>
</div> </div>
<div class="col fs-5"> <div class="col fs-5">
<span class="fw-bold"><TL>Memory</TL>:</span> <span class="fw-bold"><TL>Memory</TL>:</span>

View File

@@ -5,9 +5,11 @@
@using Moonlight.App.Repositories @using Moonlight.App.Repositories
@using Moonlight.App.Repositories.Servers @using Moonlight.App.Repositories.Servers
@using Logging.Net @using Logging.Net
@using Moonlight.App.ApiClients.Wings
@using Moonlight.App.Database.Entities @using Moonlight.App.Database.Entities
@inject ServerRepository ServerRepository @inject ServerRepository ServerRepository
@inject ServerService ServerService
@inject SmartTranslateService TranslationService @inject SmartTranslateService TranslationService
<div class="col"> <div class="col">
@@ -28,7 +30,8 @@
OnClick="Save" OnClick="Save"
Text="@(TranslationService.Translate("Change"))" Text="@(TranslationService.Translate("Change"))"
WorkingText="@(TranslationService.Translate("Changing"))" WorkingText="@(TranslationService.Translate("Changing"))"
CssClasses="btn-primary"></WButton> CssClasses="btn-primary">
</WButton>
</td> </td>
</tr> </tr>
</table> </table>
@@ -58,6 +61,20 @@
ServerRepository.Update(CurrentServer); ServerRepository.Update(CurrentServer);
var details = await ServerService.GetDetails(CurrentServer);
// For better user experience, we start the j2s server right away when the user enables j2s
if (details.State == "offline")
{
await ServerService.SetPowerState(CurrentServer, PowerSignal.Start);
}
// For better user experience, we kill the j2s server right away when the user disables j2s and the server is starting
if (details.State == "starting")
{
await ServerService.SetPowerState(CurrentServer, PowerSignal.Kill);
}
await Loader.Reload(); await Loader.Reload();
} }
} }

View File

@@ -41,7 +41,7 @@ else
</div> </div>
<div class="m-0"> <div class="m-0">
<span class="fw-bolder d-block fs-2qx lh-1 ls-n1 mb-1"> <span class="fw-bolder d-block fs-2qx lh-1 ls-n1 mb-1">
@if (CpuStats == null) @if (CpuMetrics == null)
{ {
<span class="text-muted"> <span class="text-muted">
<TL>Loading</TL> <TL>Loading</TL>
@@ -50,12 +50,12 @@ else
else else
{ {
<span> <span>
@(CpuStats.Usage)% <TL>of</TL> @(CpuStats.Cores) <TL>Cores used</TL> @(CpuMetrics.CpuUsage)% <TL>of CPU used</TL>
</span> </span>
} }
</span> </span>
<span class="fw-semibold fs-6"> <span class="fw-semibold fs-6">
@if (CpuStats == null) @if (CpuMetrics == null)
{ {
<span class="text-muted"> <span class="text-muted">
<TL>Loading</TL> <TL>Loading</TL>
@@ -63,7 +63,7 @@ else
} }
else else
{ {
<span>@(CpuStats.Model)</span> <span>@(CpuMetrics.CpuModel)</span>
} }
</span> </span>
</div> </div>
@@ -78,7 +78,7 @@ else
</div> </div>
<div class="m-0"> <div class="m-0">
<span class="fw-bolder d-block fs-2qx lh-1 ls-n1 mb-1"> <span class="fw-bolder d-block fs-2qx lh-1 ls-n1 mb-1">
@if (MemoryStats == null) @if (MemoryMetrics == null)
{ {
<span class="text-muted"> <span class="text-muted">
<TL>Loading</TL> <TL>Loading</TL>
@@ -87,32 +87,12 @@ else
else else
{ {
<span> <span>
@(Formatter.FormatSize(MemoryStats.Used * 1024D * 1024D)) <TL>of</TL> @(Formatter.FormatSize(MemoryStats.Total * 1024D * 1024D)) <TL>used</TL> @(Formatter.FormatSize(MemoryMetrics.Used)) <TL>of</TL> @(Formatter.FormatSize(MemoryMetrics.Total)) <TL>memory used</TL>
</span> </span>
} }
</span> </span>
<span class="fw-semibold fs-6"> <span class="fw-semibold fs-6">
@if (MemoryStats == null) @*IDK what to put here*@
{
<span class="text-muted">
<TL>Loading</TL>
</span>
}
else
{
if (MemoryStats.Sticks.Any())
{
foreach (var stick in SortMemorySticks(MemoryStats.Sticks))
{
<span>@(stick)</span>
<br/>
}
}
else
{
<span>No memory sticks detected</span>
}
}
</span> </span>
</div> </div>
</div> </div>
@@ -126,7 +106,7 @@ else
</div> </div>
<div class="m-0"> <div class="m-0">
<span class="fw-bolder d-block fs-2qx lh-1 ls-n1 mb-1"> <span class="fw-bolder d-block fs-2qx lh-1 ls-n1 mb-1">
@if (DiskStats == null) @if (DiskMetrics == null)
{ {
<span class="text-muted"> <span class="text-muted">
<TL>Loading</TL> <TL>Loading</TL>
@@ -135,21 +115,12 @@ else
else else
{ {
<span> <span>
@(Formatter.FormatSize(DiskStats.TotalSize - DiskStats.FreeBytes)) <TL>of</TL> @(Formatter.FormatSize(DiskStats.TotalSize)) <TL>used</TL> @(Formatter.FormatSize(DiskMetrics.Used)) <TL>of</TL> @(Formatter.FormatSize(DiskMetrics.Total)) <TL>used</TL>
</span> </span>
} }
</span> </span>
<span class="fw-semibold fs-6"> <span class="fw-semibold fs-6">
@if (DiskStats == null) @*IDK what to put here*@
{
<span class="text-muted">
<TL>Loading</TL>
</span>
}
else
{
<span>@(DiskStats.Name) - @(DiskStats.DriveFormat)</span>
}
</span> </span>
</div> </div>
</div> </div>
@@ -239,7 +210,7 @@ else
</div> </div>
<div class="m-0"> <div class="m-0">
<span class="fw-bolder d-block fs-2qx lh-1 ls-n1 mb-1"> <span class="fw-bolder d-block fs-2qx lh-1 ls-n1 mb-1">
@if (ContainerStats == null) @if (DockerMetrics == null)
{ {
<span class="text-muted"> <span class="text-muted">
<TL>Loading</TL> <TL>Loading</TL>
@@ -248,12 +219,12 @@ else
else else
{ {
<span> <span>
<TL>@(ContainerStats.Containers.Count)</TL> <TL>@(DockerMetrics.Containers.Length)</TL>
</span> </span>
} }
</span> </span>
<span class="fw-semibold fs-6"> <span class="fw-semibold fs-6">
@if (ContainerStats == null) @if (DockerMetrics == null)
{ {
<span class="text-muted"> <span class="text-muted">
<TL>Loading</TL> <TL>Loading</TL>
@@ -290,11 +261,11 @@ else
private Node? Node; private Node? Node;
private CpuStats CpuStats; private CpuMetrics CpuMetrics;
private MemoryStats MemoryStats; private MemoryMetrics MemoryMetrics;
private DiskStats DiskStats; private DiskMetrics DiskMetrics;
private DockerMetrics DockerMetrics;
private SystemStatus SystemStatus; private SystemStatus SystemStatus;
private ContainerStats ContainerStats;
private async Task Load(LazyLoader arg) private async Task Load(LazyLoader arg)
{ {
@@ -311,16 +282,16 @@ else
SystemStatus = await NodeService.GetStatus(Node); SystemStatus = await NodeService.GetStatus(Node);
await InvokeAsync(StateHasChanged); await InvokeAsync(StateHasChanged);
CpuStats = await NodeService.GetCpuStats(Node); CpuMetrics = await NodeService.GetCpuMetrics(Node);
await InvokeAsync(StateHasChanged); await InvokeAsync(StateHasChanged);
MemoryStats = await NodeService.GetMemoryStats(Node); MemoryMetrics = await NodeService.GetMemoryMetrics(Node);
await InvokeAsync(StateHasChanged); await InvokeAsync(StateHasChanged);
DiskStats = await NodeService.GetDiskStats(Node); DiskMetrics = await NodeService.GetDiskMetrics(Node);
await InvokeAsync(StateHasChanged); await InvokeAsync(StateHasChanged);
ContainerStats = await NodeService.GetContainerStats(Node); DockerMetrics = await NodeService.GetDockerMetrics(Node);
await InvokeAsync(StateHasChanged); await InvokeAsync(StateHasChanged);
} }
catch (Exception e) catch (Exception e)
@@ -330,28 +301,4 @@ else
}); });
} }
} }
private List<string> SortMemorySticks(List<MemoryStats.MemoryStick> 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<string> sortedList = sortedMemory.Select(memory =>
{
string sizeString = $"{memory.Size}GB";
return $"{memory.Count}x {memory.Type} {sizeString}";
}).ToList();
return sortedList;
}
} }

View File

@@ -132,9 +132,9 @@
try try
{ {
var containerStats = await NodeService.GetContainerStats(node); var dockerMetrics = await NodeService.GetDockerMetrics(node);
foreach (var container in containerStats.Containers) foreach (var container in dockerMetrics.Containers)
{ {
if (Guid.TryParse(container.Name, out Guid uuid)) if (Guid.TryParse(container.Name, out Guid uuid))
{ {

View File

@@ -311,5 +311,10 @@
{ {
await Event.Off($"server.{CurrentServer.Uuid}.installComplete", this); await Event.Off($"server.{CurrentServer.Uuid}.installComplete", this);
} }
if (Console != null)
{
Console.Dispose();
}
} }
} }