diff --git a/.claude/features-done/blazor-ui-overhaul.md b/.claude/features-done/blazor-ui-overhaul.md new file mode 100644 index 0000000..0a634d4 --- /dev/null +++ b/.claude/features-done/blazor-ui-overhaul.md @@ -0,0 +1,25 @@ +# Feature: blazor-ui-overhaul + +## Goal +Overhaul the Blazor monitoring UI: slim ListView columns, move details to a dialog showing all metadata + JSON content, show persist type per cache type, and make Clear Cache refresh the ListView. + +## Scope +- `CacheTypeInfo` — add `PersistType` field +- `CacheMonitor.Set/Track` — populate `PersistType` +- `ListView.razor` — slim columns to Key, Expires, Size, Load Time, Stale; add persist type to the type-level grid; add chevron button per row; subscribe to monitor events for live refresh +- `SummaryView.razor` — subscribe to monitor events for live refresh +- New `DetailView.razor` — dialog component with all metadata + JSON content via `PeekAsync` + reflection + `CopyButton` + +## Acceptance Criteria +- ListView shows slim columns with chevron to open detail dialog +- Detail dialog shows all metadata (Created, Updated, Fresh Span, Last Accessed, Access Count) plus cached data as JSON +- Copy JSON button works +- Persist type column appears on the type-level grid +- Clear Cache button refreshes the ListView without reload +- All tests still pass + +## Done Condition +User confirms the UI works as expected. + +## Originating Branch +develop diff --git a/Sample/Tharga.Cache.BlazorServer/Components/App.razor b/Sample/Tharga.Cache.BlazorServer/Components/App.razor index 03393c5..b1e5a7a 100644 --- a/Sample/Tharga.Cache.BlazorServer/Components/App.razor +++ b/Sample/Tharga.Cache.BlazorServer/Components/App.razor @@ -1,4 +1,4 @@ - + @@ -11,14 +11,15 @@ - - + + - + + diff --git a/Sample/Tharga.Cache.BlazorServer/Components/Layout/MainLayout.razor b/Sample/Tharga.Cache.BlazorServer/Components/Layout/MainLayout.razor index 78624f3..2edd86c 100644 --- a/Sample/Tharga.Cache.BlazorServer/Components/Layout/MainLayout.razor +++ b/Sample/Tharga.Cache.BlazorServer/Components/Layout/MainLayout.razor @@ -21,3 +21,5 @@ Reload 🗙 + + diff --git a/Sample/Tharga.Cache.BlazorServer/Program.cs b/Sample/Tharga.Cache.BlazorServer/Program.cs index b480836..d23fa36 100644 --- a/Sample/Tharga.Cache.BlazorServer/Program.cs +++ b/Sample/Tharga.Cache.BlazorServer/Program.cs @@ -1,3 +1,4 @@ +using Blazored.LocalStorage; using Radzen; using Tharga.Cache; using Tharga.Cache.BlazorServer.Components; @@ -13,6 +14,7 @@ .AddInteractiveServerComponents(); builder.Services.AddRadzenComponents(); +builder.Services.AddBlazoredLocalStorage(); builder.AddMongoDB(); builder.Services.AddCache(o => { diff --git a/Tharga.Cache.Blazor/CacheContent.razor b/Tharga.Cache.Blazor/CacheContent.razor new file mode 100644 index 0000000..cbf3047 --- /dev/null +++ b/Tharga.Cache.Blazor/CacheContent.razor @@ -0,0 +1,84 @@ +@using System.Reflection +@using System.Text.Json +@using Microsoft.JSInterop +@inject IEternalCache Cache +@inject IJSRuntime Js +@inject NotificationService NotificationService + +
+
+ +
+ @if (_loading) + { + + } + else + { +
@_json
+ } +
+ +@code { + [Parameter] public Type CacheType { get; set; } + [Parameter] public string CacheKey { get; set; } + + private string _json; + private bool _loading = true; + + protected override async Task OnInitializedAsync() + { + await LoadContentAsync(); + } + + private async Task LoadContentAsync() + { + _loading = true; + try + { + if (CacheType == null || string.IsNullOrEmpty(CacheKey)) + { + _json = "(no key/type)"; + return; + } + + var method = typeof(ICache).GetMethod(nameof(ICache.PeekAsync), BindingFlags.Public | BindingFlags.Instance); + if (method == null) + { + _json = "(PeekAsync method not found)"; + return; + } + + var generic = method.MakeGenericMethod(CacheType); + Key key = CacheKey; + var task = (Task)generic.Invoke(Cache, [key]); + await task.ConfigureAwait(false); + var resultProperty = task.GetType().GetProperty("Result"); + var data = resultProperty?.GetValue(task); + + _json = data == null + ? "(no data)" + : JsonSerializer.Serialize(data, new JsonSerializerOptions { WriteIndented = true }); + } + catch (Exception ex) + { + _json = $"(error: {ex.Message})"; + } + finally + { + _loading = false; + } + } + + private async Task CopyAction() + { + if (string.IsNullOrWhiteSpace(_json)) return; + await Js.InvokeVoidAsync("clipboard.copyText", _json); + NotificationService.Notify(new NotificationMessage + { + Summary = "Content copied.", + Severity = NotificationSeverity.Info + }); + } +} diff --git a/Tharga.Cache.Blazor/DetailView.razor b/Tharga.Cache.Blazor/DetailView.razor new file mode 100644 index 0000000..852c4fc --- /dev/null +++ b/Tharga.Cache.Blazor/DetailView.razor @@ -0,0 +1,66 @@ + + + + + Key + @CacheKey + + + Type + @CacheType?.Name + + + + + Created +
+
+ + Updated +
+
+ + Last Accessed +
+
+
+ + + Fresh Span +
+
+ + Expires +
+
+ + Load Time +
+
+
+ + + Size + @(Info?.Size.ToReadableByteSize() ?? "-") + + + Access Count + @(Info?.AccessCount.ToString() ?? "-") + + + Stale + @(Info?.IsStale.ToString() ?? "-") + + + + + + + +
+ +@code { + [Parameter] public Type CacheType { get; set; } + [Parameter] public string CacheKey { get; set; } + [Parameter] public CacheItemInfo Info { get; set; } +} diff --git a/Tharga.Cache.Blazor/ListView.razor b/Tharga.Cache.Blazor/ListView.razor index ded970c..0907590 100644 --- a/Tharga.Cache.Blazor/ListView.razor +++ b/Tharga.Cache.Blazor/ListView.razor @@ -1,4 +1,6 @@ -@inject ICacheMonitor CacheMonitor +@implements IDisposable +@inject ICacheMonitor CacheMonitor +@inject DialogService DialogService @if (_model == null) { @@ -23,31 +25,11 @@ else @item.Key - - - - - - - - - - - - - - @@ -73,6 +56,11 @@ else @context.Type.Name + + +