Enhance admin listing & metadata extraction, update healthchecks
Build and Push Docker Image / build-and-push (push) Successful in 19s

- Show current admins in RegistryAdmin using new RegistryAdminDisplayModel and backend support
- Improve RegistryMetadataService: better normalization, Amazon-specific extraction for images/prices, and URL normalization
- Use normalized product URLs and improved metadata logic in RegistryAdmin
- Add error handling for Blazored.TextEditor interop issues
- Switch healthchecks in portainer-stack.yml to process checks (pidof)
- Remove appsettings.Development.json contents from source control
- Add RegistryAdminDisplayModel and NormalizedUrl to models
This commit is contained in:
Arne Moerman
2026-05-14 14:05:18 +02:00
parent c1b11603e8
commit b46269bfc0
8 changed files with 302 additions and 57 deletions
@@ -20,6 +20,23 @@ else
</div>
}
<section class="mb-4">
<h2>Current admins</h2>
@if (Admins.Count == 0)
{
<p>No admins assigned yet.</p>
}
else
{
<ul>
@foreach (var admin in Admins)
{
<li>@admin.DisplayName</li>
}
</ul>
}
</section>
<section class="mb-4">
<h2>Settings</h2>
<EditForm Model="SettingsModel" OnValidSubmit="SaveSettingsAsync" FormName="registry-settings-form">
@@ -4,6 +4,7 @@ using BirthList.Web.Services;
using Blazored.TextEditor;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Components;
using Microsoft.JSInterop;
namespace BirthList.Web.Components.Pages;
@@ -23,6 +24,7 @@ public partial class RegistryAdmin : ComponentBase
protected RegistrySettingsEditModel SettingsModel { get; } = new();
protected RegistryItemEditModel ItemModel { get; private set; } = new();
protected IReadOnlyList<RegistryItemEditModel> Items { get; private set; } = [];
protected IReadOnlyList<RegistryAdminDisplayModel> Admins { get; private set; } = [];
protected bool IsAuthorized { get; private set; }
protected bool IsSmtpConfigured { get; private set; }
protected string? InviteEmail { get; set; }
@@ -50,22 +52,41 @@ public partial class RegistryAdmin : ComponentBase
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (!firstRender || TextEditor is null)
if (!firstRender || TextEditor is null || string.IsNullOrWhiteSpace(SettingsModel.HeaderContentHtml))
{
return;
}
if (!string.IsNullOrWhiteSpace(SettingsModel.HeaderContentHtml))
try
{
await TextEditor.LoadHTMLContent(SettingsModel.HeaderContentHtml).ConfigureAwait(false);
}
catch (JSException)
{
// Editor scripts may still be initializing on first render in some environments.
}
catch (InvalidOperationException)
{
// Ignore transient prerender/circuit timing issues.
}
}
protected async Task SaveSettingsAsync()
{
if (TextEditor is not null)
{
SettingsModel.HeaderContentHtml = await TextEditor.GetHTML().ConfigureAwait(false);
try
{
SettingsModel.HeaderContentHtml = await TextEditor.GetHTML().ConfigureAwait(false);
}
catch (JSException)
{
// Preserve existing content when editor JS isn't ready yet.
}
catch (InvalidOperationException)
{
// Preserve existing content when interop isn't available.
}
}
await RegistryService.UpdateRegistrySettingsAsync(RegistryId, SettingsModel, CancellationToken.None).ConfigureAwait(false);
@@ -125,12 +146,17 @@ public partial class RegistryAdmin : ComponentBase
return;
}
if (string.IsNullOrWhiteSpace(ItemModel.Name) && !string.IsNullOrWhiteSpace(metadata.Title))
if (!string.IsNullOrWhiteSpace(metadata.NormalizedUrl))
{
ItemModel.ProductUrl = metadata.NormalizedUrl;
}
if ((string.IsNullOrWhiteSpace(ItemModel.Name) || string.Equals(ItemModel.Name, "Amazon", StringComparison.OrdinalIgnoreCase)) && !string.IsNullOrWhiteSpace(metadata.Title))
{
ItemModel.Name = metadata.Title;
}
if (string.IsNullOrWhiteSpace(ItemModel.Description) && !string.IsNullOrWhiteSpace(metadata.Description))
if ((string.IsNullOrWhiteSpace(ItemModel.Description) || string.Equals(ItemModel.Description, "Amazon", StringComparison.OrdinalIgnoreCase)) && !string.IsNullOrWhiteSpace(metadata.Description))
{
ItemModel.Description = metadata.Description;
}
@@ -145,7 +171,7 @@ public partial class RegistryAdmin : ComponentBase
ItemModel.PriceAmount = metadata.PriceAmount;
}
if (string.IsNullOrWhiteSpace(ItemModel.CurrencyCode) && !string.IsNullOrWhiteSpace(metadata.CurrencyCode))
if (!string.IsNullOrWhiteSpace(metadata.CurrencyCode))
{
ItemModel.CurrencyCode = metadata.CurrencyCode;
}
@@ -165,6 +191,8 @@ public partial class RegistryAdmin : ComponentBase
{
await EmailSender.SendInviteAsync(InviteEmail, InviteLink).ConfigureAwait(false);
}
Admins = await RegistryService.GetRegistryAdminsAsync(RegistryId, CancellationToken.None).ConfigureAwait(false);
}
private async Task LoadAsync()
@@ -183,6 +211,7 @@ public partial class RegistryAdmin : ComponentBase
SettingsModel.BankAccountBic = settings.BankAccountBic;
}
Admins = await RegistryService.GetRegistryAdminsAsync(RegistryId, CancellationToken.None).ConfigureAwait(false);
Items = await RegistryService.GetRegistryItemsAsync(RegistryId, CancellationToken.None).ConfigureAwait(false);
ItemModel = new RegistryItemEditModel
{