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.Exceptions;
using Newtonsoft.Json;
using RestSharp;
@@ -14,26 +13,13 @@ public class DaemonApiHelper
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)
{
RestRequest request = new(GetApiUrl(node) + resource);
var request = await CreateRequest(node, resource);
request.AddHeader("Content-Type", "application/json");
request.AddHeader("Accept", "application/json");
request.AddHeader("Authorization", node.Token);
var response = await Client.GetAsync(request);
request.Method = Method.Get;
var response = await Client.ExecuteAsync(request);
if (!response.IsSuccessful)
{
@@ -52,4 +38,69 @@ public class DaemonApiHelper
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";
}
}
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)
{
case "jwt error":
await WebSocket.CloseAsync(WebSocketCloseStatus.Empty, "Jwt error detected",
CancellationToken.None);
if (WebSocket != null)
{
if (WebSocket.State == WebSocketState.Connecting || WebSocket.State == WebSocketState.Open)
await WebSocket.CloseAsync(WebSocketCloseStatus.Empty, null, CancellationToken.None);
WebSocket.Dispose();
}
await UpdateServerState(ServerState.Offline);
await UpdateConsoleState(ConsoleState.Disconnected);
await SaveMessage("Received a jwt error", true);
await SaveMessage("Received a jwt error. Disconnected", true);
break;
case "token expired":
await WebSocket.CloseAsync(WebSocketCloseStatus.Empty, "Jwt error detected",
CancellationToken.None);
if (WebSocket != null)
{
if (WebSocket.State == WebSocketState.Connecting || WebSocket.State == WebSocketState.Open)
await WebSocket.CloseAsync(WebSocketCloseStatus.Empty, null, CancellationToken.None);
WebSocket.Dispose();
}
await UpdateServerState(ServerState.Offline);
await UpdateConsoleState(ConsoleState.Disconnected);
@@ -346,6 +356,7 @@ public class WingsConsole : IDisposable
public async Task Disconnect()
{
Disconnecting = true;
Messages.Clear();
if (WebSocket != null)
{
@@ -362,6 +373,7 @@ public class WingsConsole : IDisposable
public void Dispose()
{
Disconnecting = true;
Messages.Clear();
if (WebSocket != null)
{

View File

@@ -16,14 +16,12 @@ public class ResourcesController : Controller
{
private readonly SecurityLogService SecurityLogService;
private readonly BucketService BucketService;
private readonly BundleService BundleService;
public ResourcesController(SecurityLogService securityLogService,
BucketService bucketService, BundleService bundleService)
BucketService bucketService)
{
SecurityLogService = securityLogService;
BucketService = bucketService;
BundleService = bundleService;
}
[HttpGet("images/{name}")]
@@ -77,34 +75,4 @@ public class ResourcesController : Controller
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 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.Database.Entities;
using Moonlight.App.Events;
using Moonlight.App.Helpers;
using Moonlight.App.Repositories;
using Moonlight.App.Repositories.Servers;
using Newtonsoft.Json;
@@ -81,12 +82,12 @@ public class CleanupService
{
try
{
var cpuStats = await nodeService.GetCpuStats(node);
var memoryStats = await nodeService.GetMemoryStats(node);
var cpuMetrics = await nodeService.GetCpuMetrics(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 imageRepository = scope.ServiceProvider.GetRequiredService<ImageRepository>();
@@ -101,9 +102,9 @@ public class CleanupService
)
.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))
{

View File

@@ -1,4 +1,5 @@
using Moonlight.App.ApiClients.Daemon;
using Moonlight.App.ApiClients.Daemon.Requests;
using Moonlight.App.ApiClients.Daemon.Resources;
using Moonlight.App.ApiClients.Wings;
using Moonlight.App.ApiClients.Wings.Resources;
@@ -24,35 +25,56 @@ public class NodeService
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)
{
try
{
//TODO: Implement status caching
var data = await GetStatus(node);
await GetSystemMetrics(node);
if (data != null)
return true;
return true;
}
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 WebSpaceService WebSpaceService;
private readonly NodeService NodeService;
private readonly ConfigService ConfigService;
public SmartDeployService(
NodeRepository nodeRepository,
NodeService nodeService,
WebSpaceService webSpaceService,
Repository<CloudPanel> cloudPanelRepository)
Repository<CloudPanel> cloudPanelRepository,
ConfigService configService)
{
NodeRepository = nodeRepository;
NodeService = nodeService;
WebSpaceService = webSpaceService;
CloudPanelRepository = cloudPanelRepository;
ConfigService = configService;
}
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>();
foreach (var node in NodeRepository.Get().ToArray())
@@ -59,17 +74,17 @@ public class SmartDeployService
try
{
var cpuStats = await NodeService.GetCpuStats(node);
var memoryStats = await NodeService.GetMemoryStats(node);
var diskStats = await NodeService.GetDiskStats(node);
var cpuMetrics = await NodeService.GetCpuMetrics(node);
var memoryMetrics = await NodeService.GetMemoryMetrics(node);
var diskMetrics = await NodeService.GetDiskMetrics(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 cpuScore = (1 - cpuMetrics.CpuUsage) * cpuWeight; // CPU score is based on the inverse of CPU usage
var memoryScore = (1 - (memoryMetrics.Used / 1024)) * memoryWeight; // Memory score is based on the percentage of free memory
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;

View File

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

View File

@@ -7,7 +7,6 @@
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@inject ConfigService ConfigService
@inject BundleService BundleService
@inject LoadingMessageRepository LoadingMessageRepository
@{
@@ -38,29 +37,20 @@
<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/style.bundle.css"/>
<link rel="stylesheet" type="text/css" href="/assets/css/flashbang.css"/>
<link rel="stylesheet" type="text/css" href="/assets/css/snow.css"/>
<link rel="stylesheet" type="text/css" href="/assets/css/utils.css"/>
<link rel="stylesheet" type="text/css" href="/assets/css/boxicons.min.css"/>
@if (BundleService.BundledFinished)
{
<link rel="stylesheet" type="text/css" href="/api/moonlight/resources/bundle/css?idontwannabecached=@(BundleService.CacheId)"/>
}
else
{
foreach (var cssFile in BundleService.CssFiles)
{
if (cssFile.StartsWith("http"))
{
<link rel="stylesheet" type="text/css" href="@(cssFile)">
}
else
{
<style>
@cssFile
</style>
}
}
}
<link rel="stylesheet" type="text/css" href="/assets/css/blazor.css"/>
<link rel="stylesheet" type="text/css" href="/_content/XtermBlazor/XtermBlazor.css"/>
<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 href="/assets/plugins/global/plugins.bundle.css" rel="stylesheet" type="text/css"/>
<meta name="viewport" content="width=device-width, initial-scale=1"/>
<base href="~/"/>
@@ -106,24 +96,33 @@
</div>
</div>
@if (BundleService.BundledFinished)
{
<script src="/api/moonlight/resources/bundle/js?idontwannabecached=@(BundleService.CacheId)">
</script>
}
else
{
foreach (var jsFile in BundleService.JsFiles)
{
if (jsFile.StartsWith("http"))
{
<script src="@(jsFile)"></script>
}
else
{
@Html.Raw("<script>" + jsFile +"</script>")
}
}
}
<script src="/_framework/blazor.server.js"></script>
<script src="/assets/plugins/global/plugins.bundle.js"></script>
<script src="/_content/XtermBlazor/XtermBlazor.min.js"></script>
<script src="/_content/BlazorTable/BlazorTable.min.js"></script>
<script src="/_content/CurrieTechnologies.Razor.SweetAlert2/sweetAlert2.min.js"></script>
<script src="/_content/Blazor.ContextMenu/blazorContextMenu.min.js"></script>
<script src="https://www.google.com/recaptcha/api.js"></script>
<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="https://cdn.jsdelivr.net/npm/xterm-addon-web-links@0.5.0/lib/xterm-addon-web-links.min.js"></script>
<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>
<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>
</html>

View File

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

View File

@@ -185,6 +185,8 @@ else
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 class="col fs-5">
<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 class="col fs-5">
<span class="fw-bold"><TL>Memory</TL>:</span>

View File

@@ -5,9 +5,11 @@
@using Moonlight.App.Repositories
@using Moonlight.App.Repositories.Servers
@using Logging.Net
@using Moonlight.App.ApiClients.Wings
@using Moonlight.App.Database.Entities
@inject ServerRepository ServerRepository
@inject ServerService ServerService
@inject SmartTranslateService TranslationService
<div class="col">
@@ -28,7 +30,8 @@
OnClick="Save"
Text="@(TranslationService.Translate("Change"))"
WorkingText="@(TranslationService.Translate("Changing"))"
CssClasses="btn-primary"></WButton>
CssClasses="btn-primary">
</WButton>
</td>
</tr>
</table>
@@ -55,9 +58,23 @@
private async Task Save()
{
CurrentServer.Variables.First(x => x.Key == "J2S").Value = Value ? "1" : "0";
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();
}
}

View File

@@ -41,7 +41,7 @@ else
</div>
<div class="m-0">
<span class="fw-bolder d-block fs-2qx lh-1 ls-n1 mb-1">
@if (CpuStats == null)
@if (CpuMetrics == null)
{
<span class="text-muted">
<TL>Loading</TL>
@@ -50,12 +50,12 @@ else
else
{
<span>
@(CpuStats.Usage)% <TL>of</TL> @(CpuStats.Cores) <TL>Cores used</TL>
@(CpuMetrics.CpuUsage)% <TL>of CPU used</TL>
</span>
}
</span>
<span class="fw-semibold fs-6">
@if (CpuStats == null)
@if (CpuMetrics == null)
{
<span class="text-muted">
<TL>Loading</TL>
@@ -63,7 +63,7 @@ else
}
else
{
<span>@(CpuStats.Model)</span>
<span>@(CpuMetrics.CpuModel)</span>
}
</span>
</div>
@@ -78,7 +78,7 @@ else
</div>
<div class="m-0">
<span class="fw-bolder d-block fs-2qx lh-1 ls-n1 mb-1">
@if (MemoryStats == null)
@if (MemoryMetrics == null)
{
<span class="text-muted">
<TL>Loading</TL>
@@ -87,32 +87,12 @@ else
else
{
<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 class="fw-semibold fs-6">
@if (MemoryStats == null)
{
<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>
}
}
@*IDK what to put here*@
</span>
</div>
</div>
@@ -126,7 +106,7 @@ else
</div>
<div class="m-0">
<span class="fw-bolder d-block fs-2qx lh-1 ls-n1 mb-1">
@if (DiskStats == null)
@if (DiskMetrics == null)
{
<span class="text-muted">
<TL>Loading</TL>
@@ -135,21 +115,12 @@ else
else
{
<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 class="fw-semibold fs-6">
@if (DiskStats == null)
{
<span class="text-muted">
<TL>Loading</TL>
</span>
}
else
{
<span>@(DiskStats.Name) - @(DiskStats.DriveFormat)</span>
}
@*IDK what to put here*@
</span>
</div>
</div>
@@ -239,7 +210,7 @@ else
</div>
<div class="m-0">
<span class="fw-bolder d-block fs-2qx lh-1 ls-n1 mb-1">
@if (ContainerStats == null)
@if (DockerMetrics == null)
{
<span class="text-muted">
<TL>Loading</TL>
@@ -248,12 +219,12 @@ else
else
{
<span>
<TL>@(ContainerStats.Containers.Count)</TL>
<TL>@(DockerMetrics.Containers.Length)</TL>
</span>
}
</span>
<span class="fw-semibold fs-6">
@if (ContainerStats == null)
@if (DockerMetrics == null)
{
<span class="text-muted">
<TL>Loading</TL>
@@ -290,11 +261,11 @@ else
private Node? Node;
private CpuStats CpuStats;
private MemoryStats MemoryStats;
private DiskStats DiskStats;
private CpuMetrics CpuMetrics;
private MemoryMetrics MemoryMetrics;
private DiskMetrics DiskMetrics;
private DockerMetrics DockerMetrics;
private SystemStatus SystemStatus;
private ContainerStats ContainerStats;
private async Task Load(LazyLoader arg)
{
@@ -311,16 +282,16 @@ else
SystemStatus = await NodeService.GetStatus(Node);
await InvokeAsync(StateHasChanged);
CpuStats = await NodeService.GetCpuStats(Node);
CpuMetrics = await NodeService.GetCpuMetrics(Node);
await InvokeAsync(StateHasChanged);
MemoryStats = await NodeService.GetMemoryStats(Node);
MemoryMetrics = await NodeService.GetMemoryMetrics(Node);
await InvokeAsync(StateHasChanged);
DiskStats = await NodeService.GetDiskStats(Node);
DiskMetrics = await NodeService.GetDiskMetrics(Node);
await InvokeAsync(StateHasChanged);
ContainerStats = await NodeService.GetContainerStats(Node);
DockerMetrics = await NodeService.GetDockerMetrics(Node);
await InvokeAsync(StateHasChanged);
}
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
{
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))
{

View File

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