diff --git a/Moonlight.Api/Configuration/ContainerHelperOptions.cs b/Moonlight.Api/Configuration/ContainerHelperOptions.cs new file mode 100644 index 00000000..42f89bd7 --- /dev/null +++ b/Moonlight.Api/Configuration/ContainerHelperOptions.cs @@ -0,0 +1,7 @@ +namespace Moonlight.Api.Configuration; + +public class ContainerHelperOptions +{ + public bool IsEnabled { get; set; } + public string Url { get; set; } = "http://helper:8080"; +} \ No newline at end of file diff --git a/Moonlight.Api/Http/Controllers/Admin/ApiKeyController.cs b/Moonlight.Api/Http/Controllers/Admin/ApiKeyController.cs index e1c27afd..23c47e95 100644 --- a/Moonlight.Api/Http/Controllers/Admin/ApiKeyController.cs +++ b/Moonlight.Api/Http/Controllers/Admin/ApiKeyController.cs @@ -6,9 +6,9 @@ using Moonlight.Api.Database.Entities; using Moonlight.Api.Mappers; using Moonlight.Shared; using Moonlight.Shared.Http.Requests; -using Moonlight.Shared.Http.Requests.ApiKeys; +using Moonlight.Shared.Http.Requests.Admin.ApiKeys; using Moonlight.Shared.Http.Responses; -using Moonlight.Shared.Http.Responses.ApiKeys; +using Moonlight.Shared.Http.Responses.Admin.ApiKeys; namespace Moonlight.Api.Http.Controllers.Admin; diff --git a/Moonlight.Api/Http/Controllers/Admin/ContainerHelperController.cs b/Moonlight.Api/Http/Controllers/Admin/ContainerHelperController.cs new file mode 100644 index 00000000..7fed23c7 --- /dev/null +++ b/Moonlight.Api/Http/Controllers/Admin/ContainerHelperController.cs @@ -0,0 +1,54 @@ +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Options; +using Moonlight.Api.Configuration; +using Moonlight.Api.Mappers; +using Moonlight.Api.Services; +using Moonlight.Shared.Http.Events; +using Moonlight.Shared.Http.Requests.Admin.ContainerHelper; +using Moonlight.Shared.Http.Responses.Admin; + +namespace Moonlight.Api.Http.Controllers.Admin; + +[ApiController] +[Route("api/admin/ch")] +public class ContainerHelperController : Controller +{ + private readonly ContainerHelperService ContainerHelperService; + private readonly IOptions Options; + + public ContainerHelperController(ContainerHelperService containerHelperService, IOptions options) + { + ContainerHelperService = containerHelperService; + Options = options; + } + + [HttpGet("status")] + public async Task> GetStatusAsync() + { + if (!Options.Value.IsEnabled) + return new ContainerHelperStatusDto(false, false); + + var status = await ContainerHelperService.CheckConnectionAsync(); + + return new ContainerHelperStatusDto(true, status); + } + + [HttpPost("rebuild")] + public Task RebuildAsync([FromBody] RequestRebuildDto request) + { + var result = ContainerHelperService.RebuildAsync(request.NoBuildCache); + var mappedResult = result.Select(ContainerHelperMapper.ToDto); + + return Task.FromResult( + TypedResults.ServerSentEvents(mappedResult) + ); + } + + [HttpPost("version")] + public async Task SetVersionAsync([FromBody] SetVersionDto request) + { + await ContainerHelperService.SetVersionAsync(request.Version); + return NoContent(); + } +} \ No newline at end of file diff --git a/Moonlight.Api/Http/Controllers/Admin/RoleMembersController.cs b/Moonlight.Api/Http/Controllers/Admin/RoleMembersController.cs index 662516a4..ea2619c0 100644 --- a/Moonlight.Api/Http/Controllers/Admin/RoleMembersController.cs +++ b/Moonlight.Api/Http/Controllers/Admin/RoleMembersController.cs @@ -6,7 +6,7 @@ using Moonlight.Api.Database.Entities; using Moonlight.Api.Mappers; using Moonlight.Shared; using Moonlight.Shared.Http.Responses; -using Moonlight.Shared.Http.Responses.Users; +using Moonlight.Shared.Http.Responses.Admin.Users; namespace Moonlight.Api.Http.Controllers.Admin; diff --git a/Moonlight.Api/Http/Controllers/Admin/RolesController.cs b/Moonlight.Api/Http/Controllers/Admin/RolesController.cs index 9e828f9b..af1bdbff 100644 --- a/Moonlight.Api/Http/Controllers/Admin/RolesController.cs +++ b/Moonlight.Api/Http/Controllers/Admin/RolesController.cs @@ -6,7 +6,7 @@ using Moonlight.Api.Database.Entities; using Moonlight.Api.Mappers; using Moonlight.Shared; using Moonlight.Shared.Http.Requests; -using Moonlight.Shared.Http.Requests.Roles; +using Moonlight.Shared.Http.Requests.Admin.Roles; using Moonlight.Shared.Http.Responses; using Moonlight.Shared.Http.Responses.Admin; diff --git a/Moonlight.Api/Http/Controllers/Admin/ThemesController.cs b/Moonlight.Api/Http/Controllers/Admin/ThemesController.cs index 0c46ae39..8c9b08e9 100644 --- a/Moonlight.Api/Http/Controllers/Admin/ThemesController.cs +++ b/Moonlight.Api/Http/Controllers/Admin/ThemesController.cs @@ -7,9 +7,9 @@ using Moonlight.Api.Mappers; using Moonlight.Api.Services; using Moonlight.Shared; using Moonlight.Shared.Http.Requests; -using Moonlight.Shared.Http.Requests.Themes; +using Moonlight.Shared.Http.Requests.Admin.Themes; using Moonlight.Shared.Http.Responses; -using Moonlight.Shared.Http.Responses.Themes; +using Moonlight.Shared.Http.Responses.Admin.Themes; namespace Moonlight.Api.Http.Controllers.Admin; diff --git a/Moonlight.Api/Http/Controllers/Admin/UsersController.cs b/Moonlight.Api/Http/Controllers/Admin/UsersController.cs index 79110804..f4500f3d 100644 --- a/Moonlight.Api/Http/Controllers/Admin/UsersController.cs +++ b/Moonlight.Api/Http/Controllers/Admin/UsersController.cs @@ -6,9 +6,9 @@ using Moonlight.Api.Database.Entities; using Moonlight.Api.Mappers; using Moonlight.Shared; using Moonlight.Shared.Http.Requests; -using Moonlight.Shared.Http.Requests.Users; +using Moonlight.Shared.Http.Requests.Admin.Users; using Moonlight.Shared.Http.Responses; -using Moonlight.Shared.Http.Responses.Users; +using Moonlight.Shared.Http.Responses.Admin.Users; namespace Moonlight.Api.Http.Controllers.Admin; diff --git a/Moonlight.Api/Http/Controllers/AuthController.cs b/Moonlight.Api/Http/Controllers/AuthController.cs index a5ff547a..34dc18e7 100644 --- a/Moonlight.Api/Http/Controllers/AuthController.cs +++ b/Moonlight.Api/Http/Controllers/AuthController.cs @@ -1,7 +1,7 @@ using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; -using Moonlight.Shared.Http.Responses.Auth; +using Moonlight.Shared.Http.Responses.Admin.Auth; namespace Moonlight.Api.Http.Controllers; diff --git a/Moonlight.Api/Http/Controllers/FrontendController.cs b/Moonlight.Api/Http/Controllers/FrontendController.cs index cdf201ee..6fffb0aa 100644 --- a/Moonlight.Api/Http/Controllers/FrontendController.cs +++ b/Moonlight.Api/Http/Controllers/FrontendController.cs @@ -1,7 +1,7 @@ using Microsoft.AspNetCore.Mvc; using Moonlight.Api.Mappers; using Moonlight.Api.Services; -using Moonlight.Shared.Http.Responses.Frontend; +using Moonlight.Shared.Http.Responses.Admin.Frontend; namespace Moonlight.Api.Http.Controllers; diff --git a/Moonlight.Api/Http/Controllers/PingController.cs b/Moonlight.Api/Http/Controllers/PingController.cs new file mode 100644 index 00000000..8de46960 --- /dev/null +++ b/Moonlight.Api/Http/Controllers/PingController.cs @@ -0,0 +1,13 @@ +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; + +namespace Moonlight.Api.Http.Controllers; + +[ApiController] +[Route("api/ping")] +public class PingController : Controller +{ + [HttpGet] + [AllowAnonymous] + public IActionResult Get() => Ok("Pong"); +} \ No newline at end of file diff --git a/Moonlight.Api/Http/Services/ContainerHelper/Events/RebuildEventDto.cs b/Moonlight.Api/Http/Services/ContainerHelper/Events/RebuildEventDto.cs new file mode 100644 index 00000000..1742b643 --- /dev/null +++ b/Moonlight.Api/Http/Services/ContainerHelper/Events/RebuildEventDto.cs @@ -0,0 +1,20 @@ +using System.Text.Json.Serialization; + +namespace Moonlight.Api.Http.Services.ContainerHelper.Events; + +public struct RebuildEventDto +{ + [JsonPropertyName("type")] + public RebuildEventType Type { get; set; } + + [JsonPropertyName("data")] + public string Data { get; set; } +} + +public enum RebuildEventType +{ + Log = 0, + Failed = 1, + Succeeded = 2, + Step = 3 +} \ No newline at end of file diff --git a/Moonlight.Api/Http/Services/ContainerHelper/ProblemDetails.cs b/Moonlight.Api/Http/Services/ContainerHelper/ProblemDetails.cs new file mode 100644 index 00000000..925672c6 --- /dev/null +++ b/Moonlight.Api/Http/Services/ContainerHelper/ProblemDetails.cs @@ -0,0 +1,10 @@ +namespace Moonlight.Api.Http.Services.ContainerHelper; + +public class ProblemDetails +{ + public string Type { get; set; } + public string Title { get; set; } + public int Status { get; set; } + public string? Detail { get; set; } + public Dictionary? Errors { get; set; } +} \ No newline at end of file diff --git a/Moonlight.Api/Http/Services/ContainerHelper/Requests/RequestRebuildDto.cs b/Moonlight.Api/Http/Services/ContainerHelper/Requests/RequestRebuildDto.cs new file mode 100644 index 00000000..c7dda283 --- /dev/null +++ b/Moonlight.Api/Http/Services/ContainerHelper/Requests/RequestRebuildDto.cs @@ -0,0 +1,3 @@ +namespace Moonlight.Api.Http.Services.ContainerHelper.Requests; + +public record RequestRebuildDto(bool NoBuildCache); \ No newline at end of file diff --git a/Moonlight.Api/Http/Services/ContainerHelper/Requests/SetVersionDto.cs b/Moonlight.Api/Http/Services/ContainerHelper/Requests/SetVersionDto.cs new file mode 100644 index 00000000..4c462fbc --- /dev/null +++ b/Moonlight.Api/Http/Services/ContainerHelper/Requests/SetVersionDto.cs @@ -0,0 +1,3 @@ +namespace Moonlight.Api.Http.Services.ContainerHelper.Requests; + +public record SetVersionDto(string Version); \ No newline at end of file diff --git a/Moonlight.Api/Http/Services/ContainerHelper/SerializationContext.cs b/Moonlight.Api/Http/Services/ContainerHelper/SerializationContext.cs new file mode 100644 index 00000000..371fb761 --- /dev/null +++ b/Moonlight.Api/Http/Services/ContainerHelper/SerializationContext.cs @@ -0,0 +1,29 @@ +using System.Text.Json; +using System.Text.Json.Serialization; +using Moonlight.Api.Http.Services.ContainerHelper.Events; +using Moonlight.Api.Http.Services.ContainerHelper.Requests; + +namespace Moonlight.Api.Http.Services.ContainerHelper; + +[JsonSerializable(typeof(SetVersionDto))] +[JsonSerializable(typeof(ProblemDetails))] +[JsonSerializable(typeof(RebuildEventDto))] +[JsonSerializable(typeof(RequestRebuildDto))] +public partial class SerializationContext : JsonSerializerContext +{ + private static JsonSerializerOptions? InternalTunedOptions; + + public static JsonSerializerOptions TunedOptions + { + get + { + if (InternalTunedOptions != null) + return InternalTunedOptions; + + InternalTunedOptions = new JsonSerializerOptions(JsonSerializerDefaults.Web); + InternalTunedOptions.TypeInfoResolverChain.Add(Default); + + return InternalTunedOptions; + } + } +} \ No newline at end of file diff --git a/Moonlight.Api/Mappers/ApiKeyMapper.cs b/Moonlight.Api/Mappers/ApiKeyMapper.cs index f4f694ad..55dd62e1 100644 --- a/Moonlight.Api/Mappers/ApiKeyMapper.cs +++ b/Moonlight.Api/Mappers/ApiKeyMapper.cs @@ -1,7 +1,7 @@ using System.Diagnostics.CodeAnalysis; using Moonlight.Api.Database.Entities; -using Moonlight.Shared.Http.Requests.ApiKeys; -using Moonlight.Shared.Http.Responses.ApiKeys; +using Moonlight.Shared.Http.Requests.Admin.ApiKeys; +using Moonlight.Shared.Http.Responses.Admin.ApiKeys; using Riok.Mapperly.Abstractions; namespace Moonlight.Api.Mappers; diff --git a/Moonlight.Api/Mappers/ContainerHelperMapper.cs b/Moonlight.Api/Mappers/ContainerHelperMapper.cs new file mode 100644 index 00000000..26cc39b4 --- /dev/null +++ b/Moonlight.Api/Mappers/ContainerHelperMapper.cs @@ -0,0 +1,13 @@ +using System.Diagnostics.CodeAnalysis; +using Moonlight.Shared.Http.Events; +using Riok.Mapperly.Abstractions; + +namespace Moonlight.Api.Mappers; + +[Mapper] +[SuppressMessage("Mapper", "RMG020:No members are mapped in an object mapping")] +[SuppressMessage("Mapper", "RMG012:No members are mapped in an object mapping")] +public static partial class ContainerHelperMapper +{ + public static partial RebuildEventDto ToDto(Http.Services.ContainerHelper.Events.RebuildEventDto rebuildEventDto); +} \ No newline at end of file diff --git a/Moonlight.Api/Mappers/FrontendConfigMapper.cs b/Moonlight.Api/Mappers/FrontendConfigMapper.cs index 85118685..e731d714 100644 --- a/Moonlight.Api/Mappers/FrontendConfigMapper.cs +++ b/Moonlight.Api/Mappers/FrontendConfigMapper.cs @@ -1,6 +1,6 @@ using System.Diagnostics.CodeAnalysis; using Moonlight.Api.Models; -using Moonlight.Shared.Http.Responses.Frontend; +using Moonlight.Shared.Http.Responses.Admin.Frontend; using Riok.Mapperly.Abstractions; namespace Moonlight.Api.Mappers; diff --git a/Moonlight.Api/Mappers/RoleMapper.cs b/Moonlight.Api/Mappers/RoleMapper.cs index 7ac52dad..b4a4e0ad 100644 --- a/Moonlight.Api/Mappers/RoleMapper.cs +++ b/Moonlight.Api/Mappers/RoleMapper.cs @@ -1,6 +1,6 @@ using System.Diagnostics.CodeAnalysis; using Moonlight.Api.Database.Entities; -using Moonlight.Shared.Http.Requests.Roles; +using Moonlight.Shared.Http.Requests.Admin.Roles; using Moonlight.Shared.Http.Responses.Admin; using Riok.Mapperly.Abstractions; diff --git a/Moonlight.Api/Mappers/ThemeMapper.cs b/Moonlight.Api/Mappers/ThemeMapper.cs index ad266092..4496a329 100644 --- a/Moonlight.Api/Mappers/ThemeMapper.cs +++ b/Moonlight.Api/Mappers/ThemeMapper.cs @@ -1,7 +1,7 @@ using System.Diagnostics.CodeAnalysis; using Moonlight.Api.Database.Entities; -using Moonlight.Shared.Http.Requests.Themes; -using Moonlight.Shared.Http.Responses.Themes; +using Moonlight.Shared.Http.Requests.Admin.Themes; +using Moonlight.Shared.Http.Responses.Admin.Themes; using Riok.Mapperly.Abstractions; namespace Moonlight.Api.Mappers; diff --git a/Moonlight.Api/Mappers/UserMapper.cs b/Moonlight.Api/Mappers/UserMapper.cs index 2d150bc5..ef9673d3 100644 --- a/Moonlight.Api/Mappers/UserMapper.cs +++ b/Moonlight.Api/Mappers/UserMapper.cs @@ -1,8 +1,8 @@ using System.Diagnostics.CodeAnalysis; using Riok.Mapperly.Abstractions; -using Moonlight.Shared.Http.Requests.Users; -using Moonlight.Shared.Http.Responses.Users; using Moonlight.Api.Database.Entities; +using Moonlight.Shared.Http.Requests.Admin.Users; +using Moonlight.Shared.Http.Responses.Admin.Users; namespace Moonlight.Api.Mappers; diff --git a/Moonlight.Api/Moonlight.Api.csproj b/Moonlight.Api/Moonlight.Api.csproj index dcf0ef3e..10a62a6c 100644 --- a/Moonlight.Api/Moonlight.Api.csproj +++ b/Moonlight.Api/Moonlight.Api.csproj @@ -35,4 +35,8 @@ false + + + + \ No newline at end of file diff --git a/Moonlight.Api/Services/ContainerHelperService.cs b/Moonlight.Api/Services/ContainerHelperService.cs new file mode 100644 index 00000000..434857bd --- /dev/null +++ b/Moonlight.Api/Services/ContainerHelperService.cs @@ -0,0 +1,117 @@ +using System.Net.Http.Headers; +using System.Net.Http.Json; +using System.Text.Json; +using Moonlight.Api.Http.Services.ContainerHelper; +using Moonlight.Api.Http.Services.ContainerHelper.Requests; +using Moonlight.Api.Http.Services.ContainerHelper.Events; + +namespace Moonlight.Api.Services; + +public class ContainerHelperService +{ + private readonly IHttpClientFactory HttpClientFactory; + + public ContainerHelperService(IHttpClientFactory httpClientFactory) + { + HttpClientFactory = httpClientFactory; + } + + public async Task CheckConnectionAsync() + { + var client = HttpClientFactory.CreateClient("ContainerHelper"); + + try + { + var response = await client.GetAsync("api/ping"); + response.EnsureSuccessStatusCode(); + + return true; + } + catch (Exception) + { + return false; + } + } + + public async IAsyncEnumerable RebuildAsync(bool noBuildCache) + { + var client = HttpClientFactory.CreateClient("ContainerHelper"); + + var request = new HttpRequestMessage(HttpMethod.Post, "api/rebuild"); + + request.Content = JsonContent.Create( + new RequestRebuildDto(noBuildCache), + null, + SerializationContext.TunedOptions + ); + + var response = await client.SendAsync( + request, + HttpCompletionOption.ResponseHeadersRead + ); + + if (!response.IsSuccessStatusCode) + { + var responseText = await response.Content.ReadAsStringAsync(); + + yield return new RebuildEventDto() + { + Type = RebuildEventType.Failed, + Data = responseText + }; + + yield break; + } + + await using var responseStream = await response.Content.ReadAsStreamAsync(); + using var streamReader = new StreamReader(responseStream); + + do + { + var line = await streamReader.ReadLineAsync(); + + if (line == null) + break; + + if (string.IsNullOrWhiteSpace(line)) + continue; + + var data = line.Trim("data: "); + var deserializedData = JsonSerializer.Deserialize(data, SerializationContext.TunedOptions); + + yield return deserializedData; + + // Exit if service will go down for a clean exit + if (deserializedData is { Type: RebuildEventType.Step, Data: "ServiceDown" }) + yield break; + } while (true); + + yield return new RebuildEventDto() + { + Type = RebuildEventType.Succeeded, + Data = string.Empty + }; + } + + public async Task SetVersionAsync(string version) + { + var client = HttpClientFactory.CreateClient("ContainerHelper"); + + var response = await client.PostAsJsonAsync( + "api/configuration/version", + new SetVersionDto(version), + SerializationContext.TunedOptions + ); + + if (response.IsSuccessStatusCode) + return; + + var problemDetails = + await response.Content.ReadFromJsonAsync(SerializationContext.TunedOptions); + + if (problemDetails == null) + throw new HttpRequestException($"Failed to set version: {response.ReasonPhrase}"); + + throw new HttpRequestException($"Failed to set version: {problemDetails.Detail ?? problemDetails.Title}"); + } +} \ No newline at end of file diff --git a/Moonlight.Api/Startup/Startup.Base.cs b/Moonlight.Api/Startup/Startup.Base.cs index ebb3c95f..f21aa407 100644 --- a/Moonlight.Api/Startup/Startup.Base.cs +++ b/Moonlight.Api/Startup/Startup.Base.cs @@ -9,7 +9,7 @@ using Moonlight.Api.Helpers; using Moonlight.Api.Implementations; using Moonlight.Api.Interfaces; using Moonlight.Api.Services; -using SessionOptions = Moonlight.Api.Configuration.SessionOptions; +using SessionOptions = Microsoft.AspNetCore.Builder.SessionOptions; namespace Moonlight.Api.Startup; @@ -38,11 +38,20 @@ public partial class Startup builder.Services.AddOptions().BindConfiguration("Moonlight:Frontend"); builder.Services.AddScoped(); - + builder.Services.AddHttpClient(); builder.Services.AddOptions().BindConfiguration("Moonlight:Version"); builder.Services.AddSingleton(); + + builder.Services.AddOptions().BindConfiguration("Moonlight:ContainerHelper"); + builder.Services.AddSingleton(); + + builder.Services.AddHttpClient("ContainerHelper", (provider, client) => + { + var options = provider.GetRequiredService>(); + client.BaseAddress = new Uri(options.Value.IsEnabled ? options.Value.Url : "http://you-should-fail.invalid"); + }); } private static void UseBase(WebApplication application) diff --git a/Moonlight.Frontend/Helpers/ProblemDetailsHelper.cs b/Moonlight.Frontend/Helpers/ProblemDetailsHelper.cs new file mode 100644 index 00000000..0f04d8ba --- /dev/null +++ b/Moonlight.Frontend/Helpers/ProblemDetailsHelper.cs @@ -0,0 +1,30 @@ +using System.Net.Http.Json; +using Microsoft.AspNetCore.Components.Forms; +using Moonlight.Shared.Http.Responses; + +namespace Moonlight.Frontend.Helpers; + +public static class ProblemDetailsHelper +{ + public static async Task HandleProblemDetailsAsync(HttpResponseMessage response, object model, ValidationMessageStore validationMessageStore) + { + var problemDetails = await response.Content.ReadFromJsonAsync(); + + if (problemDetails == null) + response.EnsureSuccessStatusCode(); // Trigger exception when unable to parse + else + { + if(!string.IsNullOrEmpty(problemDetails.Detail)) + validationMessageStore.Add(new FieldIdentifier(model, string.Empty), problemDetails.Detail); + + if (problemDetails.Errors != null) + { + foreach (var error in problemDetails.Errors) + { + foreach (var message in error.Value) + validationMessageStore.Add(new FieldIdentifier(model, error.Key), message); + } + } + } + } +} \ No newline at end of file diff --git a/Moonlight.Frontend/Mappers/ApiKeyMapper.cs b/Moonlight.Frontend/Mappers/ApiKeyMapper.cs index 77dde21e..b1bb2ad6 100644 --- a/Moonlight.Frontend/Mappers/ApiKeyMapper.cs +++ b/Moonlight.Frontend/Mappers/ApiKeyMapper.cs @@ -1,6 +1,6 @@ using System.Diagnostics.CodeAnalysis; -using Moonlight.Shared.Http.Requests.ApiKeys; -using Moonlight.Shared.Http.Responses.ApiKeys; +using Moonlight.Shared.Http.Requests.Admin.ApiKeys; +using Moonlight.Shared.Http.Responses.Admin.ApiKeys; using Riok.Mapperly.Abstractions; namespace Moonlight.Frontend.Mappers; diff --git a/Moonlight.Frontend/Mappers/RoleMapper.cs b/Moonlight.Frontend/Mappers/RoleMapper.cs index 3495f872..4c4861f1 100644 --- a/Moonlight.Frontend/Mappers/RoleMapper.cs +++ b/Moonlight.Frontend/Mappers/RoleMapper.cs @@ -1,5 +1,5 @@ using System.Diagnostics.CodeAnalysis; -using Moonlight.Shared.Http.Requests.Roles; +using Moonlight.Shared.Http.Requests.Admin.Roles; using Moonlight.Shared.Http.Responses.Admin; using Riok.Mapperly.Abstractions; diff --git a/Moonlight.Frontend/Mappers/ThemeMapper.cs b/Moonlight.Frontend/Mappers/ThemeMapper.cs index 8ddff698..2e1747e7 100644 --- a/Moonlight.Frontend/Mappers/ThemeMapper.cs +++ b/Moonlight.Frontend/Mappers/ThemeMapper.cs @@ -1,6 +1,6 @@ using System.Diagnostics.CodeAnalysis; -using Moonlight.Shared.Http.Requests.Themes; -using Moonlight.Shared.Http.Responses.Themes; +using Moonlight.Shared.Http.Requests.Admin.Themes; +using Moonlight.Shared.Http.Responses.Admin.Themes; using Riok.Mapperly.Abstractions; namespace Moonlight.Frontend.Mappers; diff --git a/Moonlight.Frontend/Mappers/UserMapper.cs b/Moonlight.Frontend/Mappers/UserMapper.cs index 0762ba58..c20a8c8f 100644 --- a/Moonlight.Frontend/Mappers/UserMapper.cs +++ b/Moonlight.Frontend/Mappers/UserMapper.cs @@ -1,7 +1,7 @@ using System.Diagnostics.CodeAnalysis; +using Moonlight.Shared.Http.Requests.Admin.Users; +using Moonlight.Shared.Http.Responses.Admin.Users; using Riok.Mapperly.Abstractions; -using Moonlight.Shared.Http.Requests.Users; -using Moonlight.Shared.Http.Responses.Users; namespace Moonlight.Frontend.Mappers; diff --git a/Moonlight.Frontend/Moonlight.Frontend.csproj b/Moonlight.Frontend/Moonlight.Frontend.csproj index 73eb2f4e..2cfb72cf 100644 --- a/Moonlight.Frontend/Moonlight.Frontend.csproj +++ b/Moonlight.Frontend/Moonlight.Frontend.csproj @@ -24,8 +24,8 @@ - - + + diff --git a/Moonlight.Frontend/Services/RemoteAuthProvider.cs b/Moonlight.Frontend/Services/RemoteAuthProvider.cs index 22cdee35..8e17d6e4 100644 --- a/Moonlight.Frontend/Services/RemoteAuthProvider.cs +++ b/Moonlight.Frontend/Services/RemoteAuthProvider.cs @@ -3,7 +3,7 @@ using System.Net.Http.Json; using System.Security.Claims; using Microsoft.AspNetCore.Components.Authorization; using Microsoft.Extensions.Logging; -using Moonlight.Shared.Http.Responses.Auth; +using Moonlight.Shared.Http.Responses.Admin.Auth; namespace Moonlight.Frontend.Services; diff --git a/Moonlight.Frontend/UI/Admin/Modals/CreateApiKeyDialog.razor b/Moonlight.Frontend/UI/Admin/Modals/CreateApiKeyDialog.razor index 24a3b735..459164c5 100644 --- a/Moonlight.Frontend/UI/Admin/Modals/CreateApiKeyDialog.razor +++ b/Moonlight.Frontend/UI/Admin/Modals/CreateApiKeyDialog.razor @@ -1,13 +1,18 @@ -@using Moonlight.Frontend.UI.Admin.Components -@using Moonlight.Shared.Http.Requests.ApiKeys +@using Moonlight.Frontend.Helpers +@using Moonlight.Frontend.UI.Admin.Components +@using Moonlight.Shared.Http.Requests.Admin.ApiKeys +@using Moonlight.Shared.Http.Responses @using ShadcnBlazor.Dialogs -@using ShadcnBlazor.Extras.Common -@using ShadcnBlazor.Extras.FormHandlers +@using ShadcnBlazor.Extras.Forms +@using ShadcnBlazor.Extras.Toasts +@using ShadcnBlazor.Fields @using ShadcnBlazor.Inputs -@using ShadcnBlazor.Labels @inherits ShadcnBlazor.Extras.Dialogs.DialogBase +@inject HttpClient HttpClient +@inject ToastService ToastService + Create new API key @@ -15,56 +20,74 @@ - -
- + -
- - -
- -
- - -
- -
- - -
-
-
- - - Save changes - + + + + +
+ + Name + + + + Description + + + + Permissions + + + + +
+ + Save changes + +
+ @code { - [Parameter] public Func OnSubmit { get; set; } + [Parameter] public Func OnSubmit { get; set; } private CreateApiKeyDto Request; - private FormHandler FormHandler; - private List Permissions = new(); + private List Permissions = new(); protected override void OnInitialized() { - Request = new(); + Request = new() + { + Permissions = [] + }; } - private async Task SubmitAsync() + private async Task OnSubmitAsync(EditContext editContext, ValidationMessageStore validationMessageStore) { Request.Permissions = Permissions.ToArray(); - await OnSubmit.Invoke(Request); + + var response = await HttpClient.PostAsJsonAsync( + "/api/admin/apiKeys", + Request, + Constants.SerializerOptions + ); + + if (!response.IsSuccessStatusCode) + { + await ProblemDetailsHelper.HandleProblemDetailsAsync(response, Request, validationMessageStore); + return false; + } + + await ToastService.SuccessAsync( + "API Key creation", + $"Successfully created API key {Request.Name}" + ); + + await OnSubmit.Invoke(); + await CloseAsync(); + return true; } } \ No newline at end of file diff --git a/Moonlight.Frontend/UI/Admin/Modals/CreateRoleDialog.razor b/Moonlight.Frontend/UI/Admin/Modals/CreateRoleDialog.razor index 7d1d1248..942e79b8 100644 --- a/Moonlight.Frontend/UI/Admin/Modals/CreateRoleDialog.razor +++ b/Moonlight.Frontend/UI/Admin/Modals/CreateRoleDialog.razor @@ -1,14 +1,17 @@ +@using Moonlight.Frontend.Helpers @using Moonlight.Frontend.UI.Admin.Components -@using Moonlight.Shared.Http.Requests.Roles -@using ShadcnBlazor.Buttons +@using Moonlight.Shared.Http.Requests.Admin.Roles @using ShadcnBlazor.Dialogs -@using ShadcnBlazor.Extras.Common -@using ShadcnBlazor.Extras.FormHandlers +@using ShadcnBlazor.Extras.Forms +@using ShadcnBlazor.Extras.Toasts +@using ShadcnBlazor.Fields @using ShadcnBlazor.Inputs -@using ShadcnBlazor.Labels @inherits ShadcnBlazor.Extras.Dialogs.DialogBase +@inject HttpClient HttpClient +@inject ToastService ToastService + Create new role @@ -18,49 +21,43 @@ - -
- + + + -
- - -
- -
- - -
- -
- - -
-
-
- - - Save changes - +
+ + Name + + + + Description + + + + Permissions + + + + +
+ + Save changes + + + @code { - [Parameter] public Func OnSubmit { get; set; } + [Parameter] public Func OnSubmit { get; set; } private CreateRoleDto Request; private List Permissions; - private FormHandler FormHandler; protected override void OnInitialized() { @@ -68,19 +65,31 @@ { Permissions = [] }; - + Permissions = new(); } - private async Task SubmitAsync() + private async Task OnSubmitAsync(EditContext editContext, ValidationMessageStore validationMessageStore) { Request.Permissions = Permissions.ToArray(); - await FormHandler.SubmitAsync(); - } + + var response = await HttpClient.PostAsJsonAsync( + "api/admin/roles", + Request, + Constants.SerializerOptions + ); + + if (!response.IsSuccessStatusCode) + { + await ProblemDetailsHelper.HandleProblemDetailsAsync(response, Request, validationMessageStore); + return false; + } - private async Task OnSubmitAsync() - { - await OnSubmit.Invoke(Request); + await ToastService.SuccessAsync("Role creation", $"Role {Request.Name} has been successfully created"); + + await OnSubmit.Invoke(); await CloseAsync(); + + return true; } } diff --git a/Moonlight.Frontend/UI/Admin/Modals/CreateUserDialog.razor b/Moonlight.Frontend/UI/Admin/Modals/CreateUserDialog.razor index 55597eb5..ff9113de 100644 --- a/Moonlight.Frontend/UI/Admin/Modals/CreateUserDialog.razor +++ b/Moonlight.Frontend/UI/Admin/Modals/CreateUserDialog.razor @@ -1,12 +1,17 @@ -@using Moonlight.Shared.Http.Requests.Users +@using Moonlight.Frontend.Helpers +@using Moonlight.Shared.Http.Requests.Admin.Users +@using Moonlight.Shared.Http.Responses @using ShadcnBlazor.Dialogs -@using ShadcnBlazor.Extras.Common -@using ShadcnBlazor.Extras.FormHandlers +@using ShadcnBlazor.Extras.Forms +@using ShadcnBlazor.Extras.Toasts +@using ShadcnBlazor.Fields @using ShadcnBlazor.Inputs -@using ShadcnBlazor.Labels @inherits ShadcnBlazor.Extras.Dialogs.DialogBase +@inject HttpClient HttpClient +@inject ToastService ToastService + Create new user @@ -16,50 +21,67 @@ - -
+ + + -
- - -
- -
- - -
-
-
- - - Save changes - +
+ + Username + + + + Email Address + + +
+ + Save changes + + + @code { - [Parameter] public Func OnSubmit { get; set; } + [Parameter] public Func OnCompleted { get; set; } private CreateUserDto Request; - private FormHandler FormHandler; protected override void OnInitialized() { Request = new(); } - private async Task SubmitAsync() + private async Task OnSubmitAsync(EditContext editContext, ValidationMessageStore validationMessageStore) { - await OnSubmit.Invoke(Request); + var response = await HttpClient.PostAsJsonAsync( + "/api/admin/users", + Request, + Constants.SerializerOptions + ); + if (!response.IsSuccessStatusCode) + { + await ProblemDetailsHelper.HandleProblemDetailsAsync(response, Request, validationMessageStore); + return false; + } + + await ToastService.SuccessAsync( + "User creation", + $"Successfully created user {Request.Username}" + ); + + await OnCompleted.Invoke(); await CloseAsync(); + + return true; } } diff --git a/Moonlight.Frontend/UI/Admin/Modals/ManageRoleMembersDialog.razor b/Moonlight.Frontend/UI/Admin/Modals/ManageRoleMembersDialog.razor index 6abb099a..403d9c3f 100644 --- a/Moonlight.Frontend/UI/Admin/Modals/ManageRoleMembersDialog.razor +++ b/Moonlight.Frontend/UI/Admin/Modals/ManageRoleMembersDialog.razor @@ -1,7 +1,7 @@ @using LucideBlazor @using Moonlight.Shared.Http.Responses @using Moonlight.Shared.Http.Responses.Admin -@using Moonlight.Shared.Http.Responses.Users +@using Moonlight.Shared.Http.Responses.Admin.Users @using ShadcnBlazor.Buttons @using ShadcnBlazor.DataGrids @using ShadcnBlazor.Dialogs @@ -32,9 +32,9 @@ SearchPlaceholder="Search user" ValueSelector="dto => dto.Username" Source="LoadUsersAsync"/> - + - +
@@ -50,9 +50,9 @@
- + - +
diff --git a/Moonlight.Frontend/UI/Admin/Modals/UpdateApiKeyDialog.razor b/Moonlight.Frontend/UI/Admin/Modals/UpdateApiKeyDialog.razor index 10696f83..f9e4aef9 100644 --- a/Moonlight.Frontend/UI/Admin/Modals/UpdateApiKeyDialog.razor +++ b/Moonlight.Frontend/UI/Admin/Modals/UpdateApiKeyDialog.razor @@ -1,15 +1,19 @@ -@using Moonlight.Frontend.Mappers +@using Moonlight.Frontend.Helpers +@using Moonlight.Frontend.Mappers @using Moonlight.Frontend.UI.Admin.Components -@using Moonlight.Shared.Http.Requests.ApiKeys -@using Moonlight.Shared.Http.Responses.ApiKeys +@using Moonlight.Shared.Http.Requests.Admin.ApiKeys +@using Moonlight.Shared.Http.Responses.Admin.ApiKeys @using ShadcnBlazor.Dialogs -@using ShadcnBlazor.Extras.Common -@using ShadcnBlazor.Extras.FormHandlers +@using ShadcnBlazor.Extras.Forms +@using ShadcnBlazor.Extras.Toasts +@using ShadcnBlazor.Fields @using ShadcnBlazor.Inputs -@using ShadcnBlazor.Labels @inherits ShadcnBlazor.Extras.Dialogs.DialogBase +@inject HttpClient HttpClient +@inject ToastService ToastService + Update API key @@ -17,45 +21,39 @@ - -
- + + + + -
- - -
- -
- - -
- -
- - -
-
-
- - - Save changes - +
+ + Name + + + + Description + + + + Permissions + + + + +
+ + Save changes + + + @code { - [Parameter] public Func OnSubmit { get; set; } + [Parameter] public Func OnSubmit { get; set; } [Parameter] public ApiKeyDto Key { get; set; } private UpdateApiKeyDto Request; - private FormHandler FormHandler; private List Permissions = new(); protected override void OnInitialized() @@ -64,10 +62,30 @@ Permissions = Key.Permissions.ToList(); } - private async Task SubmitAsync() + private async Task OnSubmitAsync(EditContext editContext, ValidationMessageStore validationMessageStore) { Request.Permissions = Permissions.ToArray(); - await OnSubmit.Invoke(Request); + + var response = await HttpClient.PatchAsJsonAsync( + $"/api/admin/apiKeys/{Key.Id}", + Request, + Constants.SerializerOptions + ); + + if (!response.IsSuccessStatusCode) + { + await ProblemDetailsHelper.HandleProblemDetailsAsync(response, Request, validationMessageStore); + return false; + } + + await ToastService.SuccessAsync( + "API Key update", + $"Successfully updated API key {Request.Name}" + ); + + await OnSubmit.Invoke(); await CloseAsync(); + + return true; } } \ No newline at end of file diff --git a/Moonlight.Frontend/UI/Admin/Modals/UpdateInstanceModal.razor b/Moonlight.Frontend/UI/Admin/Modals/UpdateInstanceModal.razor index ba8e6dab..8a79aa15 100644 --- a/Moonlight.Frontend/UI/Admin/Modals/UpdateInstanceModal.razor +++ b/Moonlight.Frontend/UI/Admin/Modals/UpdateInstanceModal.razor @@ -1,37 +1,38 @@ @inherits ShadcnBlazor.Extras.Dialogs.DialogBase +@using System.Text.Json @using LucideBlazor +@using Moonlight.Shared.Http +@using Moonlight.Shared.Http.Events +@using Moonlight.Shared.Http.Requests.Admin.ContainerHelper +@using ShadcnBlazor.Buttons @using ShadcnBlazor.Dialogs -@using ShadcnBlazor.Extras.AlertDialogs @using ShadcnBlazor.Progresses @using ShadcnBlazor.Spinners -@inject AlertDialogService AlertService +@inject HttpClient HttpClient - Updating... + Updating instance to @Version... -
- @for (var i = 0; i < Steps.Length; i++) - { - if (CurrentStep == i) +
+
+ @for (var i = 0; i < Steps.Length; i++) { -
- - - @Steps[i] - -
- } - else - { - if (i < CurrentStep) + if (CurrentStep == i) { -
- +
+ @if (IsFailed) + { + + } + else + { + + } @Steps[i] @@ -39,81 +40,205 @@ } else { -
- - @Steps[i] -
+ if (i < CurrentStep) + { +
+ + + @Steps[i] + +
+ } + else + { +
+ + @Steps[i] +
+ } } } - } +
+
+ @for (var i = LogLines.Count - 1; i >= 0; i--) + { +
+ @LogLines[i] +
+ } +
- - - +@if (CurrentStep == Steps.Length || IsFailed) +{ + + + +} +else +{ + + + +} @code { - private int Progress = 0; + [Parameter] public string Version { get; set; } + [Parameter] public bool NoBuildCache { get; set; } + private bool IsFailed; + private int Progress; private int CurrentStep; - private string[] Steps = + private readonly string[] Steps = [ - "Preparing", - "Updating configuration files", - "Building docker image", - "Redeploying container instance", - "Waiting for container instance to start up", - "Update complete" + "Checking", // 0 + "Updating configuration files", // 1 + "Starting rebuild task", // 2 + "Building docker image", // 3 + "Redeploying container instance", // 4 + "Waiting for container instance to start up", // 5 + "Update complete" // 6 ]; + private readonly List LogLines = new(); + protected override async Task OnAfterRenderAsync(bool firstRender) { if (!firstRender) return; + // Checking CurrentStep = 0; Progress = 0; await InvokeAsync(StateHasChanged); await Task.Delay(2000); + // Update configuration CurrentStep = 1; Progress = 20; await InvokeAsync(StateHasChanged); - await Task.Delay(6000); + await HttpClient.PostAsJsonAsync("api/admin/ch/version", new SetVersionDto() + { + Version = Version + }, SerializationContext.TunedOptions); + // Starting rebuild task CurrentStep = 2; - Progress = 40; + Progress = 30; await InvokeAsync(StateHasChanged); - await Task.Delay(2000); + var request = new HttpRequestMessage(HttpMethod.Post, "api/admin/ch/rebuild"); - CurrentStep = 3; - Progress = 60; - await InvokeAsync(StateHasChanged); - - await Task.Delay(4000); - - CurrentStep = 4; - Progress = 80; - await InvokeAsync(StateHasChanged); - - await Task.Delay(4000); - - CurrentStep = 5; - Progress = 100; - await InvokeAsync(StateHasChanged); - - await Task.Delay(1000); - - await AlertService.SuccessAsync( - "Update completed", - "Update successfully completed. Please refresh the page to load new frontend changes" + request.Content = JsonContent.Create( + new RequestRebuildDto(NoBuildCache), + null, + SerializationContext.TunedOptions ); - await CloseAsync(); + var response = await HttpClient.SendAsync( + request, + HttpCompletionOption.ResponseHeadersRead + ); + + await using var responseStream = await response.Content.ReadAsStreamAsync(); + using var streamReader = new StreamReader(responseStream); + + do + { + try + { + var line = await streamReader.ReadLineAsync(); + + if (line == null) + break; + + if (string.IsNullOrWhiteSpace(line)) + continue; + + var data = line.Trim("data: "); + var deserializedData = JsonSerializer.Deserialize(data, Constants.SerializerOptions); + + switch (deserializedData.Type) + { + case RebuildEventType.Log: + LogLines.Add(deserializedData.Data); + break; + + case RebuildEventType.Step: + + switch (deserializedData.Data) + { + case "BuildImage": + + // Building docker image + + CurrentStep = 3; + Progress = 40; + + await InvokeAsync(StateHasChanged); + break; + + case "ServiceDown": + + // Redeploying container instance + + CurrentStep = 4; + Progress = 60; + + await InvokeAsync(StateHasChanged); + break; + } + + break; + + case RebuildEventType.Failed: + + IsFailed = true; + await InvokeAsync(StateHasChanged); + + return; + } + + await InvokeAsync(StateHasChanged); + } + catch (Exception) + { + break; + } + } while (true); + + // Waiting for container instance to start up + + CurrentStep = 5; + Progress = 90; + await InvokeAsync(StateHasChanged); + + // Wait some time for instance to shut down + await Task.Delay(TimeSpan.FromSeconds(5)); + + // Ping instance until its reachable again + while (true) + { + try + { + await HttpClient.GetStringAsync("api/ping"); + break; + } + catch (Exception) + { + // Ignored + } + + await Task.Delay(3000); + } + + // Update complete + CurrentStep = 7; + Progress = 100; + await InvokeAsync(StateHasChanged); } } diff --git a/Moonlight.Frontend/UI/Admin/Modals/UpdateRoleDialog.razor b/Moonlight.Frontend/UI/Admin/Modals/UpdateRoleDialog.razor index 8878320f..288d8a71 100644 --- a/Moonlight.Frontend/UI/Admin/Modals/UpdateRoleDialog.razor +++ b/Moonlight.Frontend/UI/Admin/Modals/UpdateRoleDialog.razor @@ -1,16 +1,19 @@ +@using Moonlight.Frontend.Helpers @using Moonlight.Frontend.Mappers @using Moonlight.Frontend.UI.Admin.Components -@using Moonlight.Shared.Http.Requests.Roles +@using Moonlight.Shared.Http.Requests.Admin.Roles @using Moonlight.Shared.Http.Responses.Admin -@using ShadcnBlazor.Buttons @using ShadcnBlazor.Dialogs -@using ShadcnBlazor.Extras.Common -@using ShadcnBlazor.Extras.FormHandlers +@using ShadcnBlazor.Extras.Forms +@using ShadcnBlazor.Extras.Toasts +@using ShadcnBlazor.Fields @using ShadcnBlazor.Inputs -@using ShadcnBlazor.Labels @inherits ShadcnBlazor.Extras.Dialogs.DialogBase +@inject HttpClient HttpClient +@inject ToastService ToastService + Update @Role.Name @@ -20,50 +23,44 @@ - -
- + + + -
- - -
- -
- - -
- -
- - -
-
-
- - - Save changes - +
+ + Name + + + + Description + + + + Permissions + + + + +
+ + Save changes + + + @code { - [Parameter] public Func OnSubmit { get; set; } + [Parameter] public Func OnSubmit { get; set; } [Parameter] public RoleDto Role { get; set; } - + private UpdateRoleDto Request; private List Permissions; - private FormHandler FormHandler; protected override void OnInitialized() { @@ -71,15 +68,27 @@ Permissions = Role.Permissions.ToList(); } - private async Task SubmitAsync() + private async Task OnSubmitAsync(EditContext editContext, ValidationMessageStore validationMessageStore) { Request.Permissions = Permissions.ToArray(); - await FormHandler.SubmitAsync(); - } + + var response = await HttpClient.PatchAsJsonAsync( + $"api/admin/roles/{Role.Id}", + Request, + Constants.SerializerOptions + ); + + if (!response.IsSuccessStatusCode) + { + await ProblemDetailsHelper.HandleProblemDetailsAsync(response, Request, validationMessageStore); + return false; + } - private async Task OnSubmitAsync() - { - await OnSubmit.Invoke(Request); + await ToastService.SuccessAsync("Role update", $"Role {Request.Name} has been successfully updated"); + + await OnSubmit.Invoke(); await CloseAsync(); + + return true; } } diff --git a/Moonlight.Frontend/UI/Admin/Modals/UpdateUserDialog.razor b/Moonlight.Frontend/UI/Admin/Modals/UpdateUserDialog.razor index fd2e77a8..f23a2ec7 100644 --- a/Moonlight.Frontend/UI/Admin/Modals/UpdateUserDialog.razor +++ b/Moonlight.Frontend/UI/Admin/Modals/UpdateUserDialog.razor @@ -1,14 +1,19 @@ -@using Moonlight.Frontend.Mappers -@using Moonlight.Shared.Http.Requests.Users -@using Moonlight.Shared.Http.Responses.Users +@using Moonlight.Frontend.Helpers +@using Moonlight.Frontend.Mappers +@using Moonlight.Shared.Http.Requests.Admin.Users +@using Moonlight.Shared.Http.Responses +@using Moonlight.Shared.Http.Responses.Admin.Users @using ShadcnBlazor.Dialogs -@using ShadcnBlazor.Extras.Common -@using ShadcnBlazor.Extras.FormHandlers +@using ShadcnBlazor.Extras.Forms +@using ShadcnBlazor.Extras.Toasts +@using ShadcnBlazor.Fields @using ShadcnBlazor.Inputs -@using ShadcnBlazor.Labels @inherits ShadcnBlazor.Extras.Dialogs.DialogBase +@inject HttpClient HttpClient +@inject ToastService ToastService + Update @User.Username @@ -18,51 +23,66 @@ - -
- + + + -
- - -
- -
- - -
-
-
- - - Save changes - +
+ + Username + + + + Email Address + + +
+ + Save changes + + + @code { - [Parameter] public Func OnSubmit { get; set; } + [Parameter] public Func OnCompleted { get; set; } [Parameter] public UserDto User { get; set; } - + private UpdateUserDto Request; - private FormHandler FormHandler; protected override void OnInitialized() { Request = UserMapper.ToUpdate(User); } - private async Task SubmitAsync() + private async Task OnSubmitAsync(EditContext editContext, ValidationMessageStore validationMessageStore) { - await OnSubmit.Invoke(Request); + var response = await HttpClient.PatchAsJsonAsync( + $"/api/admin/users/{User.Id}", + Request + ); + + if (!response.IsSuccessStatusCode) + { + await ProblemDetailsHelper.HandleProblemDetailsAsync(response, Request, validationMessageStore); + return false; + } + await ToastService.SuccessAsync( + "User update", + $"Successfully updated user {Request.Username}" + ); + + await OnCompleted.Invoke(); await CloseAsync(); + + return true; } } diff --git a/Moonlight.Frontend/UI/Admin/Views/Overview.razor b/Moonlight.Frontend/UI/Admin/Views/Overview.razor index ddea7a73..f8e03c48 100644 --- a/Moonlight.Frontend/UI/Admin/Views/Overview.razor +++ b/Moonlight.Frontend/UI/Admin/Views/Overview.razor @@ -133,7 +133,11 @@ { Update available - + } @@ -156,9 +160,4 @@ await InvokeAsync(StateHasChanged); } - - private async Task LaunchUpdateModalAsync() => await DialogService.LaunchAsync(onConfigure: model => - { - model.ShowCloseButton = false; - }); } diff --git a/Moonlight.Frontend/UI/Admin/Views/Sys/ApiKeys.razor b/Moonlight.Frontend/UI/Admin/Views/Sys/ApiKeys.razor index db99b5b2..9b76f917 100644 --- a/Moonlight.Frontend/UI/Admin/Views/Sys/ApiKeys.razor +++ b/Moonlight.Frontend/UI/Admin/Views/Sys/ApiKeys.razor @@ -1,12 +1,11 @@ -@using Moonlight.Shared.Http.Requests.ApiKeys -@using Moonlight.Shared.Http.Responses.ApiKeys -@using LucideBlazor +@using LucideBlazor @using Microsoft.AspNetCore.Authorization @using Microsoft.AspNetCore.Components.Authorization @using Moonlight.Frontend.UI.Admin.Modals @using Moonlight.Shared @using Moonlight.Shared.Http.Requests @using Moonlight.Shared.Http.Responses +@using Moonlight.Shared.Http.Responses.Admin.ApiKeys @using ShadcnBlazor.DataGrids @using ShadcnBlazor.Dropdowns @using ShadcnBlazor.Extras.AlertDialogs @@ -123,19 +122,8 @@ { await DialogService.LaunchAsync(parameters => { - parameters[nameof(CreateApiKeyDialog.OnSubmit)] = async (CreateApiKeyDto dto) => + parameters[nameof(CreateApiKeyDialog.OnSubmit)] = async () => { - await HttpClient.PostAsJsonAsync( - "/api/admin/apiKeys", - dto, - Constants.SerializerOptions - ); - - await ToastService.SuccessAsync( - "API Key creation", - $"Successfully created API key {dto.Name}" - ); - await Grid.RefreshAsync(); }; }); @@ -146,19 +134,8 @@ await DialogService.LaunchAsync(parameters => { parameters[nameof(UpdateApiKeyDialog.Key)] = key; - parameters[nameof(UpdateApiKeyDialog.OnSubmit)] = async (UpdateApiKeyDto dto) => + parameters[nameof(UpdateApiKeyDialog.OnSubmit)] = async () => { - await HttpClient.PatchAsJsonAsync( - $"/api/admin/apiKeys/{key.Id}", - dto, - Constants.SerializerOptions - ); - - await ToastService.SuccessAsync( - "API Key update", - $"Successfully updated API key {dto.Name}" - ); - await Grid.RefreshAsync(); }; }); diff --git a/Moonlight.Frontend/UI/Admin/Views/Sys/Diagnose.razor b/Moonlight.Frontend/UI/Admin/Views/Sys/Diagnose.razor index 0cac7f81..f6433df8 100644 --- a/Moonlight.Frontend/UI/Admin/Views/Sys/Diagnose.razor +++ b/Moonlight.Frontend/UI/Admin/Views/Sys/Diagnose.razor @@ -44,10 +44,10 @@ - + Start diagnostics - +
diff --git a/Moonlight.Frontend/UI/Admin/Views/Sys/Index.razor b/Moonlight.Frontend/UI/Admin/Views/Sys/Index.razor index 6bc70e12..7d764378 100644 --- a/Moonlight.Frontend/UI/Admin/Views/Sys/Index.razor +++ b/Moonlight.Frontend/UI/Admin/Views/Sys/Index.razor @@ -31,17 +31,13 @@ Diagnose + + + Instance + - -
-
- - -
-
-
+ + + +
+ + + Plugins + + + + + + + + No Plugins found + + No plugins found in instance configuration + + + + + +
+ } + else + { + + + + + + Container Helper unreachable + + The container helper is unreachable. No management actions are available + + + + } + } + else + { + + + + + + Container Helper is disabled + + The container helper is disabled on this instance. + This might be due to running a multiple container moonlight setup + + + + } + +
+ +@code +{ + private ContainerHelperStatusDto StatusDto; + private string SelectedVersion = "v2.1"; + private bool NoBuildCache; + + private async Task LoadAsync(LazyLoader _) + { + StatusDto = (await HttpClient.GetFromJsonAsync("api/admin/ch/status"))!; + } + + private async Task ApplyAsync() + { + await DialogService.LaunchAsync( + parameters => + { + parameters[nameof(UpdateInstanceModal.Version)] = SelectedVersion; + parameters[nameof(UpdateInstanceModal.NoBuildCache)] = NoBuildCache; + }, + onConfigure: model => + { + model.ShowCloseButton = false; + model.ClassName = "sm:max-w-4xl!"; + } + ); + } + + private async Task AskApplyAsync() + { + if (string.IsNullOrWhiteSpace(SelectedVersion)) + return; + + var shouldContinue = await ConfirmRiskyVersionAsync( + "Moonlight Rebuild", + "If you continue the moonlight instance will become unavailable during the rebuild process. This will impact users on this instance" + ); + + if (!shouldContinue) + return; + + if (!Regex.IsMatch(SelectedVersion, @"^v\d+(\.\d+)*b?$")) + { + shouldContinue = await ConfirmRiskyVersionAsync( + "Development Version", + "You are about to install development a version. This can break your instance. Continue at your own risk" + ); + } + else + { + if (SelectedVersion.EndsWith('b')) + { + shouldContinue = await ConfirmRiskyVersionAsync( + "Beta / Pre-Release Version", + "You are about to install a version marked as pre-release / beta. This can break your instance. Continue at your own risk" + ); + } + else + shouldContinue = true; + } + + if (!shouldContinue) + return; + + await ApplyAsync(); + } + + private async Task ConfirmRiskyVersionAsync(string title, string message) + { + var tcs = new TaskCompletionSource(); + var confirmed = false; + + await AlertDialogService.ConfirmDangerAsync( + title, + message, + () => + { + confirmed = true; + tcs.SetResult(); + return Task.CompletedTask; + } + ); + + await tcs.Task; + + return confirmed; + } +} diff --git a/Moonlight.Frontend/UI/Admin/Views/Sys/Themes/Create.razor b/Moonlight.Frontend/UI/Admin/Views/Sys/Themes/Create.razor index 9f12a2e5..be2711df 100644 --- a/Moonlight.Frontend/UI/Admin/Views/Sys/Themes/Create.razor +++ b/Moonlight.Frontend/UI/Admin/Views/Sys/Themes/Create.razor @@ -3,14 +3,15 @@ @using Microsoft.AspNetCore.Authorization @using Moonlight.Shared @using LucideBlazor +@using Moonlight.Frontend.Helpers @using Moonlight.Frontend.Services -@using Moonlight.Shared.Http.Requests.Themes +@using Moonlight.Shared.Http.Requests.Admin.Themes @using ShadcnBlazor.Buttons -@using ShadcnBlazor.Labels @using ShadcnBlazor.Cards @using ShadcnBlazor.Extras.Editors -@using ShadcnBlazor.Extras.FormHandlers +@using ShadcnBlazor.Extras.Forms @using ShadcnBlazor.Extras.Toasts +@using ShadcnBlazor.Fields @using ShadcnBlazor.Inputs @using ShadcnBlazor.Switches @@ -21,86 +22,89 @@ @inject ToastService ToastService @inject FrontendService FrontendService -
-
-

Create theme

-
- Create a new theme + +
+
+

Create theme

+
+ Create a new theme +
+
+
+ + + + Continue +
-
- - -
-
-
- - - -
- - - -
-
- - + + + + + + +
+ + Name + -
+ -
- - + Version + -
- -
- - + + + Author + -
- -
- - -
-
- - - -
- - -
-
-
-
-
-
+ + + + Is Enabled + + + + + + + + + CSS Content + + + + + + + +
+ @code { @@ -109,22 +113,23 @@ CssContent = "/* Define your css here */" }; - private FormHandler Form; private Editor Editor; - private async Task SubmitAsync() + private async Task OnSubmitAsync(EditContext editContext, ValidationMessageStore validationMessageStore) { Request.CssContent = await Editor.GetValueAsync(); - await Form.SubmitAsync(); - } - private async Task OnSubmitAsync() - { - await HttpClient.PostAsJsonAsync( + var response = await HttpClient.PostAsJsonAsync( "/api/admin/themes", Request, Constants.SerializerOptions ); + + if (!response.IsSuccessStatusCode) + { + await ProblemDetailsHelper.HandleProblemDetailsAsync(response, Request, validationMessageStore); + return false; + } await ToastService.SuccessAsync( "Theme creation", @@ -134,5 +139,7 @@ await FrontendService.ReloadAsync(); Navigation.NavigateTo("/admin/system?tab=themes"); + + return true; } } \ No newline at end of file diff --git a/Moonlight.Frontend/UI/Admin/Views/Sys/Themes/Index.razor b/Moonlight.Frontend/UI/Admin/Views/Sys/Themes/Index.razor index 17208173..c90c0dfd 100644 --- a/Moonlight.Frontend/UI/Admin/Views/Sys/Themes/Index.razor +++ b/Moonlight.Frontend/UI/Admin/Views/Sys/Themes/Index.razor @@ -4,7 +4,7 @@ @using Moonlight.Shared @using Moonlight.Shared.Http.Requests @using Moonlight.Shared.Http.Responses -@using Moonlight.Shared.Http.Responses.Themes +@using Moonlight.Shared.Http.Responses.Admin.Themes @using ShadcnBlazor.DataGrids @using ShadcnBlazor.Dropdowns @using ShadcnBlazor.Extras.AlertDialogs diff --git a/Moonlight.Frontend/UI/Admin/Views/Sys/Themes/Update.razor b/Moonlight.Frontend/UI/Admin/Views/Sys/Themes/Update.razor index 2de60d7e..4c19541f 100644 --- a/Moonlight.Frontend/UI/Admin/Views/Sys/Themes/Update.razor +++ b/Moonlight.Frontend/UI/Admin/Views/Sys/Themes/Update.razor @@ -3,17 +3,18 @@ @using Microsoft.AspNetCore.Authorization @using Moonlight.Shared @using LucideBlazor +@using Moonlight.Frontend.Helpers @using Moonlight.Frontend.Mappers @using Moonlight.Frontend.Services -@using Moonlight.Shared.Http.Requests.Themes -@using Moonlight.Shared.Http.Responses.Themes +@using Moonlight.Shared.Http.Requests.Admin.Themes +@using Moonlight.Shared.Http.Responses.Admin.Themes @using ShadcnBlazor.Buttons -@using ShadcnBlazor.Labels @using ShadcnBlazor.Cards @using ShadcnBlazor.Extras.Common @using ShadcnBlazor.Extras.Editors -@using ShadcnBlazor.Extras.FormHandlers +@using ShadcnBlazor.Extras.Forms @using ShadcnBlazor.Extras.Toasts +@using ShadcnBlazor.Fields @using ShadcnBlazor.Inputs @using ShadcnBlazor.Switches @@ -24,99 +25,102 @@ @inject ToastService ToastService @inject FrontendService FrontendService -
-
-

Update theme

-
- Update the theme + + +
+
+

Update theme

+
+ Update the theme +
+
+
+ + + + Continue + +
-
-
- - -
-
- -
- - - - -
+
+ + + + -
-
- - + + Name + -
+ -
- - + Version + -
+ -
- - + Author + -
+ -
- - -
-
+ + Is Enabled + + + + + + + - - -
- - -
-
- - - - -
+ CSS Content + + + + + +
+
+
+ + @code { [Parameter] public int Id { get; set; } - + private UpdateThemeDto Request; private ThemeDto Theme; - - private FormHandler Form; + private Editor Editor; - + private async Task LoadAsync(LazyLoader _) { var theme = await HttpClient.GetFromJsonAsync($"api/admin/themes/{Id}"); @@ -125,19 +129,21 @@ Request = ThemeMapper.ToUpdate(Theme); } - private async Task SubmitAsync() + private async Task OnSubmitAsync(EditContext editContext, ValidationMessageStore validationMessageStore) { Request.CssContent = await Editor.GetValueAsync(); - await Form.SubmitAsync(); - } - private async Task OnSubmitAsync() - { - await HttpClient.PatchAsJsonAsync( + var response = await HttpClient.PatchAsJsonAsync( $"/api/admin/themes/{Theme.Id}", Request, Constants.SerializerOptions ); + + if (!response.IsSuccessStatusCode) + { + await ProblemDetailsHelper.HandleProblemDetailsAsync(response, Request, validationMessageStore); + return false; + } await ToastService.SuccessAsync( "Theme update", @@ -147,5 +153,7 @@ await FrontendService.ReloadAsync(); Navigation.NavigateTo("/admin/system?tab=themes"); + + return true; } } \ No newline at end of file diff --git a/Moonlight.Frontend/UI/Admin/Views/Users/Roles.razor b/Moonlight.Frontend/UI/Admin/Views/Users/Roles.razor index 7d7753ce..a84bf8ba 100644 --- a/Moonlight.Frontend/UI/Admin/Views/Users/Roles.razor +++ b/Moonlight.Frontend/UI/Admin/Views/Users/Roles.razor @@ -4,7 +4,6 @@ @using Moonlight.Frontend.UI.Admin.Modals @using Moonlight.Shared @using Moonlight.Shared.Http.Requests -@using Moonlight.Shared.Http.Requests.Roles @using Moonlight.Shared.Http.Responses @using Moonlight.Shared.Http.Responses.Admin @using ShadcnBlazor.DataGrids @@ -134,15 +133,8 @@ { await DialogService.LaunchAsync(parameters => { - parameters[nameof(CreateRoleDialog.OnSubmit)] = async Task (CreateRoleDto request) => + parameters[nameof(CreateRoleDialog.OnSubmit)] = async Task () => { - await HttpClient.PostAsJsonAsync( - "api/admin/roles", - request, - Constants.SerializerOptions - ); - - await ToastService.SuccessAsync("Role creation", $"Role {request.Name} has been successfully created"); await Grid.RefreshAsync(); }; }); @@ -153,15 +145,8 @@ await DialogService.LaunchAsync(parameters => { parameters[nameof(UpdateRoleDialog.Role)] = role; - parameters[nameof(UpdateRoleDialog.OnSubmit)] = async Task (UpdateRoleDto request) => + parameters[nameof(UpdateRoleDialog.OnSubmit)] = async Task () => { - await HttpClient.PatchAsJsonAsync( - $"api/admin/roles/{role.Id}", - request, - Constants.SerializerOptions - ); - - await ToastService.SuccessAsync("Role update", $"Role {request.Name} has been successfully updated"); await Grid.RefreshAsync(); }; }); diff --git a/Moonlight.Frontend/UI/Admin/Views/Users/Users.razor b/Moonlight.Frontend/UI/Admin/Views/Users/Users.razor index 55f49383..c6a4e752 100644 --- a/Moonlight.Frontend/UI/Admin/Views/Users/Users.razor +++ b/Moonlight.Frontend/UI/Admin/Views/Users/Users.razor @@ -10,9 +10,8 @@ @using ShadcnBlazor.Extras.Toasts @using ShadcnBlazor.Tabels @using Moonlight.Shared.Http.Requests -@using Moonlight.Shared.Http.Requests.Users @using Moonlight.Shared.Http.Responses -@using Moonlight.Shared.Http.Responses.Users +@using Moonlight.Shared.Http.Responses.Admin.Users @using ShadcnBlazor.Extras.Dialogs @inject HttpClient HttpClient @@ -124,19 +123,8 @@ { await DialogService.LaunchAsync(parameters => { - parameters[nameof(CreateUserDialog.OnSubmit)] = async (CreateUserDto dto) => + parameters[nameof(CreateUserDialog.OnCompleted)] = async () => { - await HttpClient.PostAsJsonAsync( - "/api/admin/users", - dto, - Constants.SerializerOptions - ); - - await ToastService.SuccessAsync( - "User creation", - $"Successfully created user {dto.Username}" - ); - await Grid.RefreshAsync(); }; }); @@ -147,18 +135,8 @@ await DialogService.LaunchAsync(parameters => { parameters[nameof(UpdateUserDialog.User)] = user; - parameters[nameof(CreateUserDialog.OnSubmit)] = async (UpdateUserDto dto) => + parameters[nameof(UpdateUserDialog.OnCompleted)] = async () => { - await HttpClient.PatchAsJsonAsync( - $"/api/admin/users/{user.Id}", - dto - ); - - await ToastService.SuccessAsync( - "User update", - $"Successfully updated user {dto.Username}" - ); - await Grid.RefreshAsync(); }; }); diff --git a/Moonlight.Frontend/UI/Shared/Components/Auth/Authentication.razor b/Moonlight.Frontend/UI/Shared/Components/Auth/Authentication.razor index 1278c182..22a2f7eb 100644 --- a/Moonlight.Frontend/UI/Shared/Components/Auth/Authentication.razor +++ b/Moonlight.Frontend/UI/Shared/Components/Auth/Authentication.razor @@ -1,7 +1,7 @@ -@using ShadcnBlazor.Cards +@using Moonlight.Shared.Http.Responses.Admin.Auth +@using ShadcnBlazor.Cards @using ShadcnBlazor.Spinners @using ShadcnBlazor.Buttons -@using Moonlight.Shared.Http.Responses.Auth @inject HttpClient HttpClient @inject NavigationManager Navigation diff --git a/Moonlight.Shared/Http/Events/RebuildEventDto.cs b/Moonlight.Shared/Http/Events/RebuildEventDto.cs new file mode 100644 index 00000000..ecfdefb8 --- /dev/null +++ b/Moonlight.Shared/Http/Events/RebuildEventDto.cs @@ -0,0 +1,20 @@ +using System.Text.Json.Serialization; + +namespace Moonlight.Shared.Http.Events; + +public struct RebuildEventDto +{ + [JsonPropertyName("type")] + public RebuildEventType Type { get; set; } + + [JsonPropertyName("data")] + public string Data { get; set; } +} + +public enum RebuildEventType +{ + Log = 0, + Failed = 1, + Succeeded = 2, + Step = 3 +} \ No newline at end of file diff --git a/Moonlight.Shared/Http/Requests/ApiKeys/CreateApiKeyDto.cs b/Moonlight.Shared/Http/Requests/Admin/ApiKeys/CreateApiKeyDto.cs similarity index 72% rename from Moonlight.Shared/Http/Requests/ApiKeys/CreateApiKeyDto.cs rename to Moonlight.Shared/Http/Requests/Admin/ApiKeys/CreateApiKeyDto.cs index 12b28620..2b60a525 100644 --- a/Moonlight.Shared/Http/Requests/ApiKeys/CreateApiKeyDto.cs +++ b/Moonlight.Shared/Http/Requests/Admin/ApiKeys/CreateApiKeyDto.cs @@ -1,13 +1,16 @@ using System.ComponentModel.DataAnnotations; -namespace Moonlight.Shared.Http.Requests.ApiKeys; +namespace Moonlight.Shared.Http.Requests.Admin.ApiKeys; public class CreateApiKeyDto { + [Required] [MaxLength(30)] public string Name { get; set; } - + [MaxLength(300)] public string Description { get; set; } = ""; + + [Required] public string[] Permissions { get; set; } } \ No newline at end of file diff --git a/Moonlight.Shared/Http/Requests/ApiKeys/UpdateApiKeyDto.cs b/Moonlight.Shared/Http/Requests/Admin/ApiKeys/UpdateApiKeyDto.cs similarity index 73% rename from Moonlight.Shared/Http/Requests/ApiKeys/UpdateApiKeyDto.cs rename to Moonlight.Shared/Http/Requests/Admin/ApiKeys/UpdateApiKeyDto.cs index 20f64b56..3c154ec7 100644 --- a/Moonlight.Shared/Http/Requests/ApiKeys/UpdateApiKeyDto.cs +++ b/Moonlight.Shared/Http/Requests/Admin/ApiKeys/UpdateApiKeyDto.cs @@ -1,13 +1,15 @@ using System.ComponentModel.DataAnnotations; -namespace Moonlight.Shared.Http.Requests.ApiKeys; +namespace Moonlight.Shared.Http.Requests.Admin.ApiKeys; public class UpdateApiKeyDto { + [Required] [MaxLength(30)] public string Name { get; set; } - + [MaxLength(300)] public string Description { get; set; } = ""; + [Required] public string[] Permissions { get; set; } } \ No newline at end of file diff --git a/Moonlight.Shared/Http/Requests/Admin/ContainerHelper/RequestRebuildDto.cs b/Moonlight.Shared/Http/Requests/Admin/ContainerHelper/RequestRebuildDto.cs new file mode 100644 index 00000000..335f7be5 --- /dev/null +++ b/Moonlight.Shared/Http/Requests/Admin/ContainerHelper/RequestRebuildDto.cs @@ -0,0 +1,15 @@ +namespace Moonlight.Shared.Http.Requests.Admin.ContainerHelper; + +public class RequestRebuildDto +{ + public bool NoBuildCache { get; set; } + + public RequestRebuildDto() + { + } + + public RequestRebuildDto(bool noBuildCache) + { + NoBuildCache = noBuildCache; + } +} \ No newline at end of file diff --git a/Moonlight.Shared/Http/Requests/Admin/ContainerHelper/SetVersionDto.cs b/Moonlight.Shared/Http/Requests/Admin/ContainerHelper/SetVersionDto.cs new file mode 100644 index 00000000..fab46fd1 --- /dev/null +++ b/Moonlight.Shared/Http/Requests/Admin/ContainerHelper/SetVersionDto.cs @@ -0,0 +1,10 @@ +using System.ComponentModel.DataAnnotations; + +namespace Moonlight.Shared.Http.Requests.Admin.ContainerHelper; + +public class SetVersionDto +{ + [Required] + [RegularExpression(@"^(?!\/|.*\/\/|.*\.\.|.*\/$)[A-Za-z0-9._/-]+$", ErrorMessage = "Invalid version format")] + public string Version { get; set; } +} \ No newline at end of file diff --git a/Moonlight.Shared/Http/Requests/Roles/CreateRoleDto.cs b/Moonlight.Shared/Http/Requests/Admin/Roles/CreateRoleDto.cs similarity index 83% rename from Moonlight.Shared/Http/Requests/Roles/CreateRoleDto.cs rename to Moonlight.Shared/Http/Requests/Admin/Roles/CreateRoleDto.cs index 638673fd..6573aedd 100644 --- a/Moonlight.Shared/Http/Requests/Roles/CreateRoleDto.cs +++ b/Moonlight.Shared/Http/Requests/Admin/Roles/CreateRoleDto.cs @@ -1,6 +1,6 @@ using System.ComponentModel.DataAnnotations; -namespace Moonlight.Shared.Http.Requests.Roles; +namespace Moonlight.Shared.Http.Requests.Admin.Roles; public class CreateRoleDto { diff --git a/Moonlight.Shared/Http/Requests/Roles/UpdateRoleDto.cs b/Moonlight.Shared/Http/Requests/Admin/Roles/UpdateRoleDto.cs similarity index 83% rename from Moonlight.Shared/Http/Requests/Roles/UpdateRoleDto.cs rename to Moonlight.Shared/Http/Requests/Admin/Roles/UpdateRoleDto.cs index d7a03ee6..57538cc2 100644 --- a/Moonlight.Shared/Http/Requests/Roles/UpdateRoleDto.cs +++ b/Moonlight.Shared/Http/Requests/Admin/Roles/UpdateRoleDto.cs @@ -1,6 +1,6 @@ using System.ComponentModel.DataAnnotations; -namespace Moonlight.Shared.Http.Requests.Roles; +namespace Moonlight.Shared.Http.Requests.Admin.Roles; public class UpdateRoleDto { diff --git a/Moonlight.Shared/Http/Requests/Themes/CreateThemeDto.cs b/Moonlight.Shared/Http/Requests/Admin/Themes/CreateThemeDto.cs similarity index 88% rename from Moonlight.Shared/Http/Requests/Themes/CreateThemeDto.cs rename to Moonlight.Shared/Http/Requests/Admin/Themes/CreateThemeDto.cs index 01ebf5b0..36374de0 100644 --- a/Moonlight.Shared/Http/Requests/Themes/CreateThemeDto.cs +++ b/Moonlight.Shared/Http/Requests/Admin/Themes/CreateThemeDto.cs @@ -1,6 +1,6 @@ using System.ComponentModel.DataAnnotations; -namespace Moonlight.Shared.Http.Requests.Themes; +namespace Moonlight.Shared.Http.Requests.Admin.Themes; public class CreateThemeDto { diff --git a/Moonlight.Shared/Http/Requests/Themes/UpdateThemeDto.cs b/Moonlight.Shared/Http/Requests/Admin/Themes/UpdateThemeDto.cs similarity index 88% rename from Moonlight.Shared/Http/Requests/Themes/UpdateThemeDto.cs rename to Moonlight.Shared/Http/Requests/Admin/Themes/UpdateThemeDto.cs index 0d22d223..ead52a32 100644 --- a/Moonlight.Shared/Http/Requests/Themes/UpdateThemeDto.cs +++ b/Moonlight.Shared/Http/Requests/Admin/Themes/UpdateThemeDto.cs @@ -1,6 +1,6 @@ using System.ComponentModel.DataAnnotations; -namespace Moonlight.Shared.Http.Requests.Themes; +namespace Moonlight.Shared.Http.Requests.Admin.Themes; public class UpdateThemeDto { diff --git a/Moonlight.Shared/Http/Requests/Users/CreateUserDto.cs b/Moonlight.Shared/Http/Requests/Admin/Users/CreateUserDto.cs similarity index 81% rename from Moonlight.Shared/Http/Requests/Users/CreateUserDto.cs rename to Moonlight.Shared/Http/Requests/Admin/Users/CreateUserDto.cs index 826b7129..0c3b9d1b 100644 --- a/Moonlight.Shared/Http/Requests/Users/CreateUserDto.cs +++ b/Moonlight.Shared/Http/Requests/Admin/Users/CreateUserDto.cs @@ -1,6 +1,6 @@ using System.ComponentModel.DataAnnotations; -namespace Moonlight.Shared.Http.Requests.Users; +namespace Moonlight.Shared.Http.Requests.Admin.Users; public class CreateUserDto { diff --git a/Moonlight.Shared/Http/Requests/Users/UpdateUserDto.cs b/Moonlight.Shared/Http/Requests/Admin/Users/UpdateUserDto.cs similarity index 81% rename from Moonlight.Shared/Http/Requests/Users/UpdateUserDto.cs rename to Moonlight.Shared/Http/Requests/Admin/Users/UpdateUserDto.cs index c2182bc1..96b33e87 100644 --- a/Moonlight.Shared/Http/Requests/Users/UpdateUserDto.cs +++ b/Moonlight.Shared/Http/Requests/Admin/Users/UpdateUserDto.cs @@ -1,6 +1,6 @@ using System.ComponentModel.DataAnnotations; -namespace Moonlight.Shared.Http.Requests.Users; +namespace Moonlight.Shared.Http.Requests.Admin.Users; public class UpdateUserDto { diff --git a/Moonlight.Shared/Http/Responses/ApiKeys/ApiKeyDto.cs b/Moonlight.Shared/Http/Responses/Admin/ApiKeys/ApiKeyDto.cs similarity index 71% rename from Moonlight.Shared/Http/Responses/ApiKeys/ApiKeyDto.cs rename to Moonlight.Shared/Http/Responses/Admin/ApiKeys/ApiKeyDto.cs index 8561d083..85158f42 100644 --- a/Moonlight.Shared/Http/Responses/ApiKeys/ApiKeyDto.cs +++ b/Moonlight.Shared/Http/Responses/Admin/ApiKeys/ApiKeyDto.cs @@ -1,3 +1,3 @@ -namespace Moonlight.Shared.Http.Responses.ApiKeys; +namespace Moonlight.Shared.Http.Responses.Admin.ApiKeys; public record ApiKeyDto(int Id, string Name, string Description, string[] Permissions, string Key, DateTimeOffset CreatedAt, DateTimeOffset UpdatedAt); \ No newline at end of file diff --git a/Moonlight.Shared/Http/Responses/Admin/Auth/ClaimDto.cs b/Moonlight.Shared/Http/Responses/Admin/Auth/ClaimDto.cs new file mode 100644 index 00000000..7d9ee7c1 --- /dev/null +++ b/Moonlight.Shared/Http/Responses/Admin/Auth/ClaimDto.cs @@ -0,0 +1,3 @@ +namespace Moonlight.Shared.Http.Responses.Admin.Auth; + +public record ClaimDto(string Type, string Value); \ No newline at end of file diff --git a/Moonlight.Shared/Http/Responses/Auth/SchemeDto.cs b/Moonlight.Shared/Http/Responses/Admin/Auth/SchemeDto.cs similarity index 50% rename from Moonlight.Shared/Http/Responses/Auth/SchemeDto.cs rename to Moonlight.Shared/Http/Responses/Admin/Auth/SchemeDto.cs index bd9fa7ce..4c2218bf 100644 --- a/Moonlight.Shared/Http/Responses/Auth/SchemeDto.cs +++ b/Moonlight.Shared/Http/Responses/Admin/Auth/SchemeDto.cs @@ -1,3 +1,3 @@ -namespace Moonlight.Shared.Http.Responses.Auth; +namespace Moonlight.Shared.Http.Responses.Admin.Auth; public record SchemeDto(string Name, string DisplayName); \ No newline at end of file diff --git a/Moonlight.Shared/Http/Responses/Admin/ContainerHelperStatusDto.cs b/Moonlight.Shared/Http/Responses/Admin/ContainerHelperStatusDto.cs new file mode 100644 index 00000000..16bc5866 --- /dev/null +++ b/Moonlight.Shared/Http/Responses/Admin/ContainerHelperStatusDto.cs @@ -0,0 +1,3 @@ +namespace Moonlight.Shared.Http.Responses.Admin; + +public record ContainerHelperStatusDto(bool IsEnabled, bool IsReachable); \ No newline at end of file diff --git a/Moonlight.Shared/Http/Responses/Frontend/FrontendConfigDto.cs b/Moonlight.Shared/Http/Responses/Admin/Frontend/FrontendConfigDto.cs similarity index 52% rename from Moonlight.Shared/Http/Responses/Frontend/FrontendConfigDto.cs rename to Moonlight.Shared/Http/Responses/Admin/Frontend/FrontendConfigDto.cs index 747e5d39..f1c7dd5a 100644 --- a/Moonlight.Shared/Http/Responses/Frontend/FrontendConfigDto.cs +++ b/Moonlight.Shared/Http/Responses/Admin/Frontend/FrontendConfigDto.cs @@ -1,3 +1,3 @@ -namespace Moonlight.Shared.Http.Responses.Frontend; +namespace Moonlight.Shared.Http.Responses.Admin.Frontend; public record FrontendConfigDto(string Name, string? ThemeCss); \ No newline at end of file diff --git a/Moonlight.Shared/Http/Responses/Themes/ThemeDto.cs b/Moonlight.Shared/Http/Responses/Admin/Themes/ThemeDto.cs similarity index 66% rename from Moonlight.Shared/Http/Responses/Themes/ThemeDto.cs rename to Moonlight.Shared/Http/Responses/Admin/Themes/ThemeDto.cs index adc167dd..854bfd6e 100644 --- a/Moonlight.Shared/Http/Responses/Themes/ThemeDto.cs +++ b/Moonlight.Shared/Http/Responses/Admin/Themes/ThemeDto.cs @@ -1,3 +1,3 @@ -namespace Moonlight.Shared.Http.Responses.Themes; +namespace Moonlight.Shared.Http.Responses.Admin.Themes; public record ThemeDto(int Id, string Name, string Author, string Version, string CssContent, bool IsEnabled); \ No newline at end of file diff --git a/Moonlight.Shared/Http/Responses/Users/UserDto.cs b/Moonlight.Shared/Http/Responses/Admin/Users/UserDto.cs similarity index 66% rename from Moonlight.Shared/Http/Responses/Users/UserDto.cs rename to Moonlight.Shared/Http/Responses/Admin/Users/UserDto.cs index 80a824ab..1060b50c 100644 --- a/Moonlight.Shared/Http/Responses/Users/UserDto.cs +++ b/Moonlight.Shared/Http/Responses/Admin/Users/UserDto.cs @@ -1,3 +1,3 @@ -namespace Moonlight.Shared.Http.Responses.Users; +namespace Moonlight.Shared.Http.Responses.Admin.Users; public record UserDto(int Id, string Username, string Email, DateTimeOffset CreatedAt, DateTimeOffset UpdatedAt); \ No newline at end of file diff --git a/Moonlight.Shared/Http/Responses/Auth/ClaimDto.cs b/Moonlight.Shared/Http/Responses/Auth/ClaimDto.cs deleted file mode 100644 index 0b3c73a1..00000000 --- a/Moonlight.Shared/Http/Responses/Auth/ClaimDto.cs +++ /dev/null @@ -1,3 +0,0 @@ -namespace Moonlight.Shared.Http.Responses.Auth; - -public record ClaimDto(string Type, string Value); \ No newline at end of file diff --git a/Moonlight.Shared/Http/Responses/ProblemDetails.cs b/Moonlight.Shared/Http/Responses/ProblemDetails.cs new file mode 100644 index 00000000..0af3bc50 --- /dev/null +++ b/Moonlight.Shared/Http/Responses/ProblemDetails.cs @@ -0,0 +1,10 @@ +namespace Moonlight.Shared.Http.Responses; + +public class ProblemDetails +{ + public string Type { get; set; } + public string Title { get; set; } + public int Status { get; set; } + public string? Detail { get; set; } + public Dictionary? Errors { get; set; } +} \ No newline at end of file diff --git a/Moonlight.Shared/Http/SerializationContext.cs b/Moonlight.Shared/Http/SerializationContext.cs index e306c219..a77737f8 100644 --- a/Moonlight.Shared/Http/SerializationContext.cs +++ b/Moonlight.Shared/Http/SerializationContext.cs @@ -1,14 +1,17 @@ -using System.Text.Json.Serialization; -using Moonlight.Shared.Http.Requests.ApiKeys; -using Moonlight.Shared.Http.Requests.Roles; -using Moonlight.Shared.Http.Requests.Themes; -using Moonlight.Shared.Http.Requests.Users; +using System.Text.Json; +using System.Text.Json.Serialization; +using Moonlight.Shared.Http.Events; +using Moonlight.Shared.Http.Requests.Admin.ApiKeys; +using Moonlight.Shared.Http.Requests.Admin.ContainerHelper; +using Moonlight.Shared.Http.Requests.Admin.Roles; +using Moonlight.Shared.Http.Requests.Admin.Themes; +using Moonlight.Shared.Http.Requests.Admin.Users; using Moonlight.Shared.Http.Responses; using Moonlight.Shared.Http.Responses.Admin; -using Moonlight.Shared.Http.Responses.ApiKeys; -using Moonlight.Shared.Http.Responses.Auth; -using Moonlight.Shared.Http.Responses.Themes; -using Moonlight.Shared.Http.Responses.Users; +using Moonlight.Shared.Http.Responses.Admin.ApiKeys; +using Moonlight.Shared.Http.Responses.Admin.Auth; +using Moonlight.Shared.Http.Responses.Admin.Themes; +using Moonlight.Shared.Http.Responses.Admin.Users; namespace Moonlight.Shared.Http; @@ -44,8 +47,32 @@ namespace Moonlight.Shared.Http; [JsonSerializable(typeof(PagedData))] [JsonSerializable(typeof(ThemeDto))] +// Events +[JsonSerializable(typeof(RebuildEventDto))] + +// Container Helper +[JsonSerializable(typeof(ContainerHelperStatusDto))] +[JsonSerializable(typeof(RequestRebuildDto))] +[JsonSerializable(typeof(SetVersionDto))] + //Misc [JsonSerializable(typeof(VersionDto))] +[JsonSerializable(typeof(ProblemDetails))] public partial class SerializationContext : JsonSerializerContext { + private static JsonSerializerOptions? InternalTunedOptions; + + public static JsonSerializerOptions TunedOptions + { + get + { + if (InternalTunedOptions != null) + return InternalTunedOptions; + + InternalTunedOptions = new JsonSerializerOptions(JsonSerializerDefaults.Web); + InternalTunedOptions.TypeInfoResolverChain.Add(Default); + + return InternalTunedOptions; + } + } } \ No newline at end of file diff --git a/compose.yaml b/compose.yaml index 3f80e03b..f19057b5 100644 --- a/compose.yaml +++ b/compose.yaml @@ -36,4 +36,32 @@ # Logging - "Logging__LogLevel__Default=Information" - - "Logging__LogLevel__Microsoft.AspNetCore=Warning" \ No newline at end of file + - "Logging__LogLevel__Microsoft.AspNetCore=Warning" + - "Logging__LogLevel__System.Net.Http.HttpClient=Warning" + + - "Moonlight__ContainerHelper__IsEnabled=true" + - "Moonlight__ContainerHelper__Url=http://app:8080" + + app: + image: git.battlestati.one/moonlight-panel/container_helper + + group_add: + - "989" + + environment: + # Logging + - "Logging__LogLevel__Default=Information" + - "Logging__LogLevel__Microsoft.AspNetCore=Warning" + + # Compose + - "ContainerHelper__Compose__Directory=${PWD}" + - "ContainerHelper__Compose__Binary=docker-compose" + - "ContainerHelper__Service__Name=api" + + # HTTP Proxy + - "HTTP_PROXY=${HTTP_PROXY}" + - "HTTPS_PROXY=${HTTPS_PROXY}" + + volumes: + - "${PWD}:${PWD}" + - "/var/run/docker.sock:/var/run/docker.sock" \ No newline at end of file