@page "/search/queue" @attribute [Authorize] @using JdeScoping.Core.ApiContracts @using JdeScoping.Client.Extensions @inject ISearchApiClient SearchApi @inject IHubConnectionService HubConnection @implements IDisposable Search Queue - JDE Scoping Tool Search Queue Search Processor Status @if (_isLoading) { } else if (!string.IsNullOrEmpty(_errorMessage)) { @_errorMessage } else { } @code { private List _searches = []; private RadzenDataGrid? _grid; private bool _isLoading = true; private string? _errorMessage; private string _statusMessage = ""; private string _statusUpdateDt = ""; protected override async Task OnInitializedAsync() { await LoadQueueAsync(); await SetupSignalRAsync(); } private async Task LoadQueueAsync() { _isLoading = true; _errorMessage = null; try { var result = await SearchApi.GetQueuedSearchesAsync(); result.Switch( searches => { _searches = searches.ToClientList(); }, notFound => { _errorMessage = "Queue not found."; _searches = []; }, validation => { _errorMessage = string.Join("; ", validation.FieldErrors.SelectMany(e => e.Value)); }, unauthorized => { _errorMessage = "Session expired. Please login again."; }, forbidden => { _errorMessage = "Access denied."; }, error => { _errorMessage = error.Message; } ); } finally { _isLoading = false; } } private async Task SetupSignalRAsync() { HubConnection.OnSearchUpdate += HandleSearchUpdate; HubConnection.OnStatusUpdate += HandleStatusUpdate; await HubConnection.StartAsync(); // Get cached status var cachedStatus = await HubConnection.GetCachedStatusAsync(); if (cachedStatus != null) { HandleStatusUpdate(cachedStatus); } } private void HandleSearchUpdate(SearchUpdateDto update) { InvokeAsync(() => { if (update.Status == "Ended" || update.Status == "Error") { // Remove completed/errored searches from queue var existing = _searches.FirstOrDefault(s => s.Id == update.Id); if (existing != null) { _searches.Remove(existing); } } else { // Update or add the search var existing = _searches.FirstOrDefault(s => s.Id == update.Id); if (existing != null) { existing.Status = update.Status; existing.SubmitDt = update.SubmitDt; existing.StartDt = update.StartDt; existing.EndDt = update.EndDt; } else { _searches.Add(new ClientSearchViewModel { Id = update.Id, Name = update.Name, UserName = update.UserName, Status = update.Status, SubmitDt = update.SubmitDt, StartDt = update.StartDt, EndDt = update.EndDt }); } } StateHasChanged(); }); } private void HandleStatusUpdate(StatusUpdateDto update) { InvokeAsync(() => { _statusMessage = update.Message; _statusUpdateDt = update.Timestamp?.ToString("MM/dd/yyyy hh:mm:ss tt") ?? ""; StateHasChanged(); }); } private static BadgeStyle GetBadgeStyle(string status) => status switch { "Error" => BadgeStyle.Danger, "Ended" => BadgeStyle.Success, "Running" => BadgeStyle.Info, "Queued" => BadgeStyle.Warning, _ => BadgeStyle.Light }; public void Dispose() { HubConnection.OnSearchUpdate -= HandleSearchUpdate; HubConnection.OnStatusUpdate -= HandleStatusUpdate; } }