diff --git a/Moonlight.ApiServer/Extensions/ZipArchiveExtensions.cs b/Moonlight.ApiServer/Extensions/ZipArchiveExtensions.cs new file mode 100644 index 00000000..95c928d7 --- /dev/null +++ b/Moonlight.ApiServer/Extensions/ZipArchiveExtensions.cs @@ -0,0 +1,33 @@ +using System.IO.Compression; +using System.Text; + +namespace Moonlight.ApiServer.Extensions; + +public static class ZipArchiveExtensions +{ + public static async Task AddBinary(this ZipArchive archive, string name, byte[] bytes) + { + var entry = archive.CreateEntry(name); + await using var dataStream = entry.Open(); + + await dataStream.WriteAsync(bytes); + await dataStream.FlushAsync(); + } + + public static async Task AddText(this ZipArchive archive, string name, string content) + { + var data = Encoding.UTF8.GetBytes(content); + await archive.AddBinary(name, data); + } + + public static async Task AddFile(this ZipArchive archive, string name, string path) + { + var fs = File.Open(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); + + var entry = archive.CreateEntry(name); + await using var dataStream = entry.Open(); + + await fs.CopyToAsync(dataStream); + await dataStream.FlushAsync(); + } +} \ No newline at end of file diff --git a/Moonlight.ApiServer/Http/Controllers/Admin/Sys/DiagnoseController.cs b/Moonlight.ApiServer/Http/Controllers/Admin/Sys/DiagnoseController.cs index b4ecd92c..c541d877 100644 --- a/Moonlight.ApiServer/Http/Controllers/Admin/Sys/DiagnoseController.cs +++ b/Moonlight.ApiServer/Http/Controllers/Admin/Sys/DiagnoseController.cs @@ -32,13 +32,8 @@ public class DiagnoseController : Controller [HttpGet("available")] [RequirePermission("admin.system.diagnose")] - public async Task GetAvailable() + public async Task GetAvailable() { - var availableProviders = await DiagnoseService.GetAvailable(); - - return new SystemAvailableDiagnoseProviderResponse() - { - AvailableProviders = availableProviders - }; + return await DiagnoseService.GetAvailable(); } } \ No newline at end of file diff --git a/Moonlight.ApiServer/Implementations/Diagnose/CoreConfigDiagnoseProvider.cs b/Moonlight.ApiServer/Implementations/Diagnose/CoreConfigDiagnoseProvider.cs new file mode 100644 index 00000000..b1c8d3f8 --- /dev/null +++ b/Moonlight.ApiServer/Implementations/Diagnose/CoreConfigDiagnoseProvider.cs @@ -0,0 +1,52 @@ +using System.IO.Compression; +using System.Text; +using System.Text.Json; +using MoonCore.Helpers; +using Moonlight.ApiServer.Configuration; +using Moonlight.ApiServer.Extensions; +using Moonlight.ApiServer.Interfaces; +using Moonlight.ApiServer.Models.Diagnose; + +namespace Moonlight.ApiServer.Implementations.Diagnose; + +public class CoreConfigDiagnoseProvider : IDiagnoseProvider +{ + private readonly AppConfiguration Config; + + public CoreConfigDiagnoseProvider(AppConfiguration config) + { + Config = config; + } + + private string CheckForNullOrEmpty(string? content) + { + return string.IsNullOrEmpty(content) + ? "ISEMPTY" + : "ISNOTEMPTY"; + } + + public async Task ModifyZipArchive(ZipArchive archive) + { + + var json = JsonSerializer.Serialize(Config); + var config = JsonSerializer.Deserialize(json); + + if (config == null) + { + await archive.AddText("core/config.txt","Could not fetch config."); + + return; + } + + config.Database.Password = CheckForNullOrEmpty(config.Database.Password); + + config.Authentication.OAuth2.ClientSecret = CheckForNullOrEmpty(config.Authentication.OAuth2.ClientSecret); + + config.Authentication.OAuth2.Secret = CheckForNullOrEmpty(config.Authentication.OAuth2.Secret); + + config.Authentication.Secret = CheckForNullOrEmpty(config.Authentication.Secret); + + await archive.AddText("core/config.txt", + JsonSerializer.Serialize(config, new JsonSerializerOptions() { WriteIndented = true })); + } +} \ No newline at end of file diff --git a/Moonlight.ApiServer/Implementations/Diagnose/CoreDiagnoseProvider.cs b/Moonlight.ApiServer/Implementations/Diagnose/CoreDiagnoseProvider.cs deleted file mode 100644 index aff19bfd..00000000 --- a/Moonlight.ApiServer/Implementations/Diagnose/CoreDiagnoseProvider.cs +++ /dev/null @@ -1,78 +0,0 @@ -using System.Text; -using System.Text.Json; -using MoonCore.Helpers; -using Moonlight.ApiServer.Configuration; -using Moonlight.ApiServer.Interfaces; -using Moonlight.ApiServer.Models.Diagnose; - -namespace Moonlight.ApiServer.Implementations.Diagnose; - -public class CoreDiagnoseProvider : IDiagnoseProvider -{ - private readonly AppConfiguration Config; - - public CoreDiagnoseProvider(AppConfiguration config) - { - Config = config; - } - - public DiagnoseEntry[] GetFiles() - { - return - [ - new DiagnoseDirectory() - { - Name = "core", - Children = [ - - new DiagnoseFile() - { - Name = "logs.txt", - GetContent = () => - { - var logs = File.ReadAllText(PathBuilder.File("storage", "logs", "latest.log")); - - if (string.IsNullOrEmpty(logs)) - return Encoding.UTF8.GetBytes("Could not get the latest logs."); - - return Encoding.UTF8.GetBytes(logs); - } - }, - - new DiagnoseFile() - { - Name = "config.txt", - GetContent = () => - { - var json = JsonSerializer.Serialize(Config); - var config = JsonSerializer.Deserialize(json); - - if (config == null) - { - return Encoding.UTF8.GetBytes("Could not fetch config."); - } - - config.Database.Password = CheckForNullOrEmpty(config.Database.Password); - - config.Authentication.OAuth2.ClientSecret = CheckForNullOrEmpty(config.Authentication.OAuth2.ClientSecret); - - config.Authentication.OAuth2.Secret = CheckForNullOrEmpty(config.Authentication.OAuth2.Secret); - - config.Authentication.Secret = CheckForNullOrEmpty(config.Authentication.Secret); - - return Encoding.UTF8.GetBytes(JsonSerializer.Serialize(config, new JsonSerializerOptions() { WriteIndented = true})); - } - } - - ] - } - ]; - } - - private string CheckForNullOrEmpty(string? content) - { - return string.IsNullOrEmpty(content) - ? "ISEMPTY" - : "ISNOTEMPTY"; - } -} \ No newline at end of file diff --git a/Moonlight.ApiServer/Implementations/Diagnose/LogsDiagnoseProvider.cs b/Moonlight.ApiServer/Implementations/Diagnose/LogsDiagnoseProvider.cs new file mode 100644 index 00000000..c731dd3b --- /dev/null +++ b/Moonlight.ApiServer/Implementations/Diagnose/LogsDiagnoseProvider.cs @@ -0,0 +1,24 @@ +using System.IO.Compression; +using System.Text; +using MoonCore.Helpers; +using Moonlight.ApiServer.Extensions; +using Moonlight.ApiServer.Interfaces; + +namespace Moonlight.ApiServer.Implementations.Diagnose; + +public class LogsDiagnoseProvider : IDiagnoseProvider +{ + public async Task ModifyZipArchive(ZipArchive archive) + { + + var logs = await File.ReadAllTextAsync(PathBuilder.File("storage", "logs", "latest.log")); + + if (string.IsNullOrEmpty(logs)) + { + await archive.AddText("logs.txt", "Could not read the logs"); + return; + } + + await archive.AddText("logs.txt", logs); + } +} \ No newline at end of file diff --git a/Moonlight.ApiServer/Implementations/Startup/CoreStartup.cs b/Moonlight.ApiServer/Implementations/Startup/CoreStartup.cs index 19782349..14324f0d 100644 --- a/Moonlight.ApiServer/Implementations/Startup/CoreStartup.cs +++ b/Moonlight.ApiServer/Implementations/Startup/CoreStartup.cs @@ -54,7 +54,8 @@ public class CoreStartup : IPluginStartup #region Diagnose - builder.Services.AddSingleton(); + builder.Services.AddSingleton(); + builder.Services.AddSingleton(); #endregion diff --git a/Moonlight.ApiServer/Interfaces/IDiagnoseProvider.cs b/Moonlight.ApiServer/Interfaces/IDiagnoseProvider.cs index d2f1b95c..daf6d875 100644 --- a/Moonlight.ApiServer/Interfaces/IDiagnoseProvider.cs +++ b/Moonlight.ApiServer/Interfaces/IDiagnoseProvider.cs @@ -1,8 +1,9 @@ +using System.IO.Compression; using Moonlight.ApiServer.Models.Diagnose; namespace Moonlight.ApiServer.Interfaces; public interface IDiagnoseProvider { - public DiagnoseEntry[] GetFiles(); + public Task ModifyZipArchive(ZipArchive archive); } \ No newline at end of file diff --git a/Moonlight.ApiServer/Services/DiagnoseService.cs b/Moonlight.ApiServer/Services/DiagnoseService.cs index 5d3024af..635a3a1a 100644 --- a/Moonlight.ApiServer/Services/DiagnoseService.cs +++ b/Moonlight.ApiServer/Services/DiagnoseService.cs @@ -2,6 +2,7 @@ using MoonCore.Helpers; using Moonlight.ApiServer.Interfaces; using Moonlight.ApiServer.Models.Diagnose; using System.IO.Compression; +using System.Text; using MoonCore.Attributes; using Moonlight.Shared.Http.Responses.Admin.Sys; using Moonlight.Shared.Misc; @@ -18,9 +19,9 @@ public class DiagnoseService DiagnoseProviders = diagnoseProviders; } - public async Task GetAvailable() + public async Task GetAvailable() { - var availableProviders = new List(); + var availableProviders = new List(); foreach (var diagnoseProvider in DiagnoseProviders) { @@ -30,7 +31,7 @@ public class DiagnoseService // The type name is null if the type is a generic type, unlikely, but still could happen if (type != null) - availableProviders.Add(new DiagnoseProvider() + availableProviders.Add(new DiagnoseProvideResponse() { Name = name, Type = type @@ -42,53 +43,34 @@ public class DiagnoseService public async Task GenerateDiagnose(Stream outputStream, string[]? requestedProviders) { - List providers; + IDiagnoseProvider[] providers; if (requestedProviders != null && requestedProviders.Length > 0) { - providers = new List(); + var requesteDiagnoseProviders = new List(); foreach (var requestedProvider in requestedProviders) { var provider = DiagnoseProviders.FirstOrDefault(x => x.GetType().FullName == requestedProvider); if (provider != null) - providers.Add(provider); + requesteDiagnoseProviders.Add(provider); } + + providers = requesteDiagnoseProviders.ToArray(); } else { - providers = DiagnoseProviders.ToList(); + providers = DiagnoseProviders.ToArray(); } - - var tasks = providers - .SelectMany(x => x.GetFiles()) - .Select(async x => await ProcessDiagnoseEntry(null, x)); - - - var fileEntries = (await Task.WhenAll(tasks)) - .SelectMany(x => x) - .ToDictionary(); - - try { using (var zip = new ZipArchive(outputStream, ZipArchiveMode.Create, leaveOpen: true)) { - foreach (var kv in fileEntries) + foreach (var provider in providers) { - string entryName = kv.Key.Replace('\\', '/'); - byte[] data = kv.Value; - - // Optionally pick CompressionLevel.Optimal or NoCompression - var entry = zip.CreateEntry(entryName, CompressionLevel.Fastest); - - using (var entryStream = entry.Open()) - using (var ms = new MemoryStream(data)) - { - await ms.CopyToAsync(entryStream); - } + await provider.ModifyZipArchive(zip); } } @@ -99,62 +81,4 @@ public class DiagnoseService throw new Exception($"An unknown error occured while building the Diagnose: {ex.Message}"); } } - - private async Task> ProcessDiagnoseEntry(string? path, DiagnoseEntry entry) - { - var fileEntries = new Dictionary(); - - switch (entry) - { - case DiagnoseDirectory diagnoseDirectory: - { - var files = await ProcessDiagnoseDirectory(path, diagnoseDirectory); - - foreach (var file in files) - { - fileEntries.Add(file.Key, file.Value); - } - - break; - } - case DiagnoseFile diagnoseFile: - { - var file = await ProcessDiagnoseFile(path, diagnoseFile); - - fileEntries.Add(file.Key, file.Value); - - break; - } - } - - return fileEntries; - } - - private async Task> ProcessDiagnoseDirectory(string? path, DiagnoseDirectory directory) - { - var result = new Dictionary(); - - var directoryPath = path != null ? string.Join("/", path, directory.Name) : directory.Name; - - foreach (var entry in directory.Children) - { - var files = await ProcessDiagnoseEntry(directoryPath, entry); - - foreach (var file in files) - { - result.Add(file.Key, file.Value); - } - } - - return result; - } - - private async Task> ProcessDiagnoseFile(string? path, DiagnoseFile file) - { - var filePath = path != null ? string.Join("/", path, file.Name) : file.Name; - - var bytes = file.GetContent(); - - return new KeyValuePair(filePath, bytes); - } } \ No newline at end of file diff --git a/Moonlight.Client/UI/Views/Admin/Sys/Diagnose.razor b/Moonlight.Client/UI/Views/Admin/Sys/Diagnose.razor index 596da604..8b84853b 100644 --- a/Moonlight.Client/UI/Views/Admin/Sys/Diagnose.razor +++ b/Moonlight.Client/UI/Views/Admin/Sys/Diagnose.razor @@ -29,27 +29,25 @@ Generate diagnose - @*
*@ - @* Advanced *@ - @* *@ - @*
*@ - @* *@ - @* *@ - @* *@ - @* *@ - @* @for (int i = 0; i < AvailableProviders.Length; i++) *@ - @* { *@ - @*
*@ - @* @Formatter.ConvertCamelCaseToSpaces(AvailableProviders[i].Name) *@ - @*
*@ - @* } *@ - @* *@ - @*
*@ - @* *@ - @* *@ - @*
*@ - @* *@ - @*
*@ +
+ Advanced +
+ +
+ + +
+ + @foreach (var item in AvailableProviders) + { +
+ + +
+ } +
+
+
@@ -59,19 +57,51 @@ private async Task GenerateDiagnose(WButton _) { - var stream = await ApiClient.PostStream("api/admin/system/diagnose"); + string[] payload = []; + + if (!SelectAll) + { + // filter the providers which have been selected if not all providers have been selected + payload = AvailableProviders + .Where(x => x.Value) + .Select(x => x.Key) + .Select(x => x.Type) + .ToArray(); + } + var stream = await ApiClient.PostStream("api/admin/system/diagnose", payload); + await DownloadService.DownloadStream("diagnose.zip", stream); } - - private async Task Load(LazyLoader arg) + private bool DropdownOpen = false; + private bool AllSelected = true; + private Dictionary AvailableProviders; + + private async Task ToggleDropDown() { - // AvailableProviders = (await ApiClient.GetJson("api/admin/system/diagnose/available")).AvailableProviders; + DropdownOpen = !DropdownOpen; + + await InvokeAsync(StateHasChanged); } + private async Task Load(LazyLoader arg) + { + AvailableProviders = (await ApiClient.GetJson("api/admin/system/diagnose/available")) + .ToDictionary(x => x, _ => true); + } - - - + private bool SelectAll + { + get => AvailableProviders.Values.All(v => v); + set + { + // flip every entry to the new value + var keys = AvailableProviders.Keys.ToList(); + foreach (var k in keys) + { + AvailableProviders[k] = value; + } + } + } } \ No newline at end of file diff --git a/Moonlight.Shared/Http/Responses/Admin/Sys/DiagnoseProvideResponse.cs b/Moonlight.Shared/Http/Responses/Admin/Sys/DiagnoseProvideResponse.cs new file mode 100644 index 00000000..e1882266 --- /dev/null +++ b/Moonlight.Shared/Http/Responses/Admin/Sys/DiagnoseProvideResponse.cs @@ -0,0 +1,8 @@ +namespace Moonlight.Shared.Http.Responses.Admin.Sys; + +public class DiagnoseProvideResponse +{ + public string Name { get; set; } + + public string Type { get; set; } +} \ No newline at end of file diff --git a/Moonlight.Shared/Http/Responses/Admin/Sys/SystemAvailableDiagnoseProviderResponse.cs b/Moonlight.Shared/Http/Responses/Admin/Sys/SystemAvailableDiagnoseProviderResponse.cs deleted file mode 100644 index a32fe302..00000000 --- a/Moonlight.Shared/Http/Responses/Admin/Sys/SystemAvailableDiagnoseProviderResponse.cs +++ /dev/null @@ -1,8 +0,0 @@ -using Moonlight.Shared.Misc; - -namespace Moonlight.Shared.Http.Responses.Admin.Sys; - -public class SystemAvailableDiagnoseProviderResponse -{ - public DiagnoseProvider[] AvailableProviders { get; set; }= []; -} diff --git a/Moonlight.Shared/Misc/DiagnoseProvider.cs b/Moonlight.Shared/Misc/DiagnoseProvider.cs deleted file mode 100644 index dd552700..00000000 --- a/Moonlight.Shared/Misc/DiagnoseProvider.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Moonlight.Shared.Misc; - -public class DiagnoseProvider -{ - public string Name { get; set; } - - public string Type { get; set; } -} \ No newline at end of file