Renamed SharedSerializationContext to SerializationContext. Added error handling and no build cache functionality

This commit is contained in:
2026-01-29 13:59:24 +01:00
parent 8181404f0c
commit 660319afec
10 changed files with 109 additions and 37 deletions

View File

@@ -4,6 +4,7 @@ using Microsoft.Extensions.Options;
using Moonlight.Api.Configuration; using Moonlight.Api.Configuration;
using Moonlight.Api.Mappers; using Moonlight.Api.Mappers;
using Moonlight.Api.Services; using Moonlight.Api.Services;
using Moonlight.Shared.Http.Events;
using Moonlight.Shared.Http.Requests.Admin.ContainerHelper; using Moonlight.Shared.Http.Requests.Admin.ContainerHelper;
using Moonlight.Shared.Http.Responses.Admin; using Moonlight.Shared.Http.Responses.Admin;
@@ -34,9 +35,9 @@ public class ContainerHelperController : Controller
} }
[HttpPost("rebuild")] [HttpPost("rebuild")]
public Task<IResult> RebuildAsync() public Task<IResult> RebuildAsync([FromBody] RequestRebuildDto request)
{ {
var result = ContainerHelperService.RebuildAsync(); var result = ContainerHelperService.RebuildAsync(request.NoBuildCache);
var mappedResult = result.Select(ContainerHelperMapper.ToDto); var mappedResult = result.Select(ContainerHelperMapper.ToDto);
return Task.FromResult<IResult>( return Task.FromResult<IResult>(

View File

@@ -0,0 +1,3 @@
namespace Moonlight.Api.Http.Services.ContainerHelper.Requests;
public record RequestRebuildDto(bool NoBuildCache);

View File

@@ -8,6 +8,7 @@ namespace Moonlight.Api.Http.Services.ContainerHelper;
[JsonSerializable(typeof(SetVersionDto))] [JsonSerializable(typeof(SetVersionDto))]
[JsonSerializable(typeof(ProblemDetails))] [JsonSerializable(typeof(ProblemDetails))]
[JsonSerializable(typeof(RebuildEventDto))] [JsonSerializable(typeof(RebuildEventDto))]
[JsonSerializable(typeof(RequestRebuildDto))]
public partial class SerializationContext : JsonSerializerContext public partial class SerializationContext : JsonSerializerContext
{ {
private static JsonSerializerOptions? InternalTunedOptions; private static JsonSerializerOptions? InternalTunedOptions;

View File

@@ -1,4 +1,5 @@
using System.Net.Http.Json; using System.Net.Http.Headers;
using System.Net.Http.Json;
using System.Text.Json; using System.Text.Json;
using Moonlight.Api.Http.Services.ContainerHelper; using Moonlight.Api.Http.Services.ContainerHelper;
using Moonlight.Api.Http.Services.ContainerHelper.Requests; using Moonlight.Api.Http.Services.ContainerHelper.Requests;
@@ -32,11 +33,22 @@ public class ContainerHelperService
} }
} }
public async IAsyncEnumerable<RebuildEventDto> RebuildAsync() public async IAsyncEnumerable<RebuildEventDto> RebuildAsync(bool noBuildCache)
{ {
var client = HttpClientFactory.CreateClient("ContainerHelper"); var client = HttpClientFactory.CreateClient("ContainerHelper");
var response = await client.GetAsync("api/rebuild", HttpCompletionOption.ResponseHeadersRead); 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) if (!response.IsSuccessStatusCode)
{ {
@@ -90,15 +102,16 @@ public class ContainerHelperService
new SetVersionDto(version), new SetVersionDto(version),
SerializationContext.TunedOptions SerializationContext.TunedOptions
); );
if(response.IsSuccessStatusCode) if (response.IsSuccessStatusCode)
return; return;
var problemDetails = await response.Content.ReadFromJsonAsync<ProblemDetails>(SerializationContext.TunedOptions); var problemDetails =
await response.Content.ReadFromJsonAsync<ProblemDetails>(SerializationContext.TunedOptions);
if(problemDetails == null)
if (problemDetails == null)
throw new HttpRequestException($"Failed to set version: {response.ReasonPhrase}"); throw new HttpRequestException($"Failed to set version: {response.ReasonPhrase}");
throw new HttpRequestException($"Failed to set version: {problemDetails.Detail ?? problemDetails.Title}"); throw new HttpRequestException($"Failed to set version: {problemDetails.Detail ?? problemDetails.Title}");
} }
} }

View File

@@ -19,7 +19,7 @@ public partial class Startup
{ {
builder.Services.AddControllers().AddJsonOptions(options => builder.Services.AddControllers().AddJsonOptions(options =>
{ {
options.JsonSerializerOptions.TypeInfoResolverChain.Add(SharedSerializationContext.Default); options.JsonSerializerOptions.TypeInfoResolverChain.Add(SerializationContext.Default);
}); });
builder.Logging.ClearProviders(); builder.Logging.ClearProviders();

View File

@@ -18,7 +18,7 @@ public static class Constants
}; };
// Add source generated options from shared project // Add source generated options from shared project
InternalOptions.TypeInfoResolverChain.Add(SharedSerializationContext.Default); InternalOptions.TypeInfoResolverChain.Add(SerializationContext.Default);
return InternalOptions; return InternalOptions;
} }

View File

@@ -2,6 +2,7 @@
@using System.Text.Json @using System.Text.Json
@using LucideBlazor @using LucideBlazor
@using Moonlight.Shared.Http
@using Moonlight.Shared.Http.Events @using Moonlight.Shared.Http.Events
@using Moonlight.Shared.Http.Requests.Admin.ContainerHelper @using Moonlight.Shared.Http.Requests.Admin.ContainerHelper
@using ShadcnBlazor.Buttons @using ShadcnBlazor.Buttons
@@ -18,13 +19,20 @@
</DialogHeader> </DialogHeader>
<div class="grid grid-cols-1 xl:grid-cols-2 w-full gap-5"> <div class="grid grid-cols-1 xl:grid-cols-2 w-full gap-5">
<div class="text-base flex flex-col p-2 gap-y-0.5"> <div class="text-base flex flex-col p-2 gap-y-1">
@for (var i = 0; i < Steps.Length; i++) @for (var i = 0; i < Steps.Length; i++)
{ {
if (CurrentStep == i) if (CurrentStep == i)
{ {
<div class="flex flex-row items-center gap-x-2"> <div class="flex flex-row items-center gap-x-1">
<Spinner ClassName="size-4"/> @if (IsFailed)
{
<CircleXIcon ClassName="text-red-500 size-5"/>
}
else
{
<Spinner ClassName="size-5"/>
}
<span> <span>
@Steps[i] @Steps[i]
</span> </span>
@@ -34,8 +42,8 @@
{ {
if (i < CurrentStep) if (i < CurrentStep)
{ {
<div class="flex flex-row items-center gap-x-2"> <div class="flex flex-row items-center gap-x-1">
<CheckIcon ClassName="text-green-500 size-4"/> <CircleCheckIcon ClassName="text-green-500 size-5"/>
<span> <span>
@Steps[i] @Steps[i]
</span> </span>
@@ -43,8 +51,8 @@
} }
else else
{ {
<div class="text-muted-foreground flex flex-row items-center gap-x-2"> <div class="text-muted-foreground flex flex-row items-center gap-x-1">
<span class="size-4"></span> <span class="size-5"></span>
@Steps[i] @Steps[i]
</div> </div>
} }
@@ -61,7 +69,7 @@
</div> </div>
</div> </div>
@if (CurrentStep == Steps.Length) @if (CurrentStep == Steps.Length || IsFailed)
{ {
<DialogFooter ClassName="justify-end"> <DialogFooter ClassName="justify-end">
<Button Variant="ButtonVariant.Outline" @onclick="CloseAsync">Close</Button> <Button Variant="ButtonVariant.Outline" @onclick="CloseAsync">Close</Button>
@@ -77,7 +85,9 @@ else
@code @code
{ {
[Parameter] public string Version { get; set; } [Parameter] public string Version { get; set; }
[Parameter] public bool NoBuildCache { get; set; }
private bool IsFailed;
private int Progress; private int Progress;
private int CurrentStep; private int CurrentStep;
@@ -114,15 +124,23 @@ else
await HttpClient.PostAsJsonAsync("api/admin/ch/version", new SetVersionDto() await HttpClient.PostAsJsonAsync("api/admin/ch/version", new SetVersionDto()
{ {
Version = Version Version = Version
}); }, SerializationContext.TunedOptions);
// Starting rebuild task // Starting rebuild task
CurrentStep = 2; CurrentStep = 2;
Progress = 30; Progress = 30;
await InvokeAsync(StateHasChanged); await InvokeAsync(StateHasChanged);
var request = new HttpRequestMessage(HttpMethod.Post, "api/admin/ch/rebuild");
request.Content = JsonContent.Create(
new RequestRebuildDto(NoBuildCache),
null,
SerializationContext.TunedOptions
);
var response = await HttpClient.SendAsync( var response = await HttpClient.SendAsync(
new HttpRequestMessage(HttpMethod.Post, "api/admin/ch/rebuild"), request,
HttpCompletionOption.ResponseHeadersRead HttpCompletionOption.ResponseHeadersRead
); );
@@ -176,6 +194,13 @@ else
} }
break; break;
case RebuildEventType.Failed:
IsFailed = true;
await InvokeAsync(StateHasChanged);
return;
} }
await InvokeAsync(StateHasChanged); await InvokeAsync(StateHasChanged);
@@ -187,7 +212,7 @@ else
} while (true); } while (true);
// Waiting for container instance to start up // Waiting for container instance to start up
CurrentStep = 5; CurrentStep = 5;
Progress = 90; Progress = 90;
await InvokeAsync(StateHasChanged); await InvokeAsync(StateHasChanged);

View File

@@ -10,6 +10,7 @@
@using ShadcnBlazor.Extras.Dialogs @using ShadcnBlazor.Extras.Dialogs
@using ShadcnBlazor.Fields @using ShadcnBlazor.Fields
@using ShadcnBlazor.Selects @using ShadcnBlazor.Selects
@using ShadcnBlazor.Switches
@inject HttpClient HttpClient @inject HttpClient HttpClient
@inject DialogService DialogService @inject DialogService DialogService
@@ -30,9 +31,7 @@
<FieldGroup> <FieldGroup>
<FieldSet> <FieldSet>
<Field> <Field>
<FieldLabel> <FieldLabel>Version / Branch</FieldLabel>
Version
</FieldLabel>
<FieldContent> <FieldContent>
<Select DefaultValue="@SelectedVersion" @bind-Value="SelectedVersion"> <Select DefaultValue="@SelectedVersion" @bind-Value="SelectedVersion">
<SelectTrigger ClassName="w-64"> <SelectTrigger ClassName="w-64">
@@ -40,12 +39,19 @@
</SelectTrigger> </SelectTrigger>
<SelectContent ClassName="w-64"> <SelectContent ClassName="w-64">
<SelectItem Value="v2.1">v2.1</SelectItem> <SelectItem Value="v2.1">v2.1</SelectItem>
<SelectItem Value="v2.1.1">v2.1.1</SelectItem>
<SelectItem Value="feat/ContainerHelper">feat/ContainerHelper <SelectItem Value="feat/ContainerHelper">feat/ContainerHelper
</SelectItem> </SelectItem>
</SelectContent> </SelectContent>
</Select> </Select>
</FieldContent> </FieldContent>
</Field> </Field>
<Field>
<FieldLabel>Bypass Build Cache</FieldLabel>
<FieldContent>
<Switch @bind-Value="NoBuildCache" />
</FieldContent>
</Field>
</FieldSet> </FieldSet>
<Field Orientation="FieldOrientation.Horizontal"> <Field Orientation="FieldOrientation.Horizontal">
<Button @onclick="AskApplyAsync">Apply</Button> <Button @onclick="AskApplyAsync">Apply</Button>
@@ -110,6 +116,7 @@
{ {
private ContainerHelperStatusDto StatusDto; private ContainerHelperStatusDto StatusDto;
private string SelectedVersion = "v2.1"; private string SelectedVersion = "v2.1";
private bool NoBuildCache;
private async Task LoadAsync(LazyLoader _) private async Task LoadAsync(LazyLoader _)
{ {
@@ -119,7 +126,11 @@
private async Task ApplyAsync() private async Task ApplyAsync()
{ {
await DialogService.LaunchAsync<UpdateInstanceModal>( await DialogService.LaunchAsync<UpdateInstanceModal>(
parameters => { parameters[nameof(UpdateInstanceModal.Version)] = SelectedVersion; }, parameters =>
{
parameters[nameof(UpdateInstanceModal.Version)] = SelectedVersion;
parameters[nameof(UpdateInstanceModal.NoBuildCache)] = NoBuildCache;
},
onConfigure: model => onConfigure: model =>
{ {
model.ShowCloseButton = false; model.ShowCloseButton = false;
@@ -130,17 +141,17 @@
private async Task AskApplyAsync() private async Task AskApplyAsync()
{ {
if(string.IsNullOrWhiteSpace(SelectedVersion)) if (string.IsNullOrWhiteSpace(SelectedVersion))
return; return;
var shouldContinue = await ConfirmRiskyVersionAsync( var shouldContinue = await ConfirmRiskyVersionAsync(
"Moonlight Rebuild", "Moonlight Rebuild",
"If you continue the moonlight instance will become unavailable during the rebuild process. This will impact users on this instance" "If you continue the moonlight instance will become unavailable during the rebuild process. This will impact users on this instance"
); );
if(!shouldContinue) if (!shouldContinue)
return; return;
if (!Regex.IsMatch(SelectedVersion, @"^v\d+(\.\d+)*b?$")) if (!Regex.IsMatch(SelectedVersion, @"^v\d+(\.\d+)*b?$"))
{ {
shouldContinue = await ConfirmRiskyVersionAsync( shouldContinue = await ConfirmRiskyVersionAsync(
@@ -163,7 +174,7 @@
if (!shouldContinue) if (!shouldContinue)
return; return;
await ApplyAsync(); await ApplyAsync();
} }

View File

@@ -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;
}
}

View File

@@ -2,6 +2,7 @@
using System.Text.Json.Serialization; using System.Text.Json.Serialization;
using Moonlight.Shared.Http.Events; using Moonlight.Shared.Http.Events;
using Moonlight.Shared.Http.Requests.Admin.ApiKeys; 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.Roles;
using Moonlight.Shared.Http.Requests.Admin.Themes; using Moonlight.Shared.Http.Requests.Admin.Themes;
using Moonlight.Shared.Http.Requests.Admin.Users; using Moonlight.Shared.Http.Requests.Admin.Users;
@@ -51,10 +52,12 @@ namespace Moonlight.Shared.Http;
// Container Helper // Container Helper
[JsonSerializable(typeof(ContainerHelperStatusDto))] [JsonSerializable(typeof(ContainerHelperStatusDto))]
[JsonSerializable(typeof(RequestRebuildDto))]
[JsonSerializable(typeof(SetVersionDto))]
// Misc // Misc
[JsonSerializable(typeof(ProblemDetails))] [JsonSerializable(typeof(ProblemDetails))]
public partial class SharedSerializationContext : JsonSerializerContext public partial class SerializationContext : JsonSerializerContext
{ {
private static JsonSerializerOptions? InternalTunedOptions; private static JsonSerializerOptions? InternalTunedOptions;