@page "/jobs/{id:guid}" @using DiunaBI.UI.Shared.Services @using DiunaBI.Domain.Entities @using MudBlazor @inject JobService JobService @inject EntityChangeHubService HubService @inject NavigationManager NavigationManager @inject ISnackbar Snackbar @implements IDisposable Job Details @if (job != null && job.Status == JobStatus.Failed) { Retry } @if (job != null && (job.Status == JobStatus.Pending || job.Status == JobStatus.Retrying)) { Cancel } Back to List @if (isLoading) { } else if (job == null) { Job not found } else { @if (!string.IsNullOrEmpty(job.LastError)) { } View Layer Details } @code { [Parameter] public Guid Id { get; set; } private QueueJob? job; private bool isLoading = true; protected override async Task OnInitializedAsync() { await LoadJob(); // Subscribe to SignalR entity changes HubService.EntityChanged += OnEntityChanged; } private async void OnEntityChanged(string module, string id, string operation) { // Only react if it's a QueueJobs change for this specific job if (module.Equals("QueueJobs", StringComparison.OrdinalIgnoreCase) && Guid.TryParse(id, out var jobId) && jobId == Id) { Console.WriteLine($"📨 Job {jobId} changed, refreshing detail page"); await InvokeAsync(async () => { await LoadJob(); StateHasChanged(); }); } } private async Task LoadJob() { isLoading = true; try { job = await JobService.GetJobByIdAsync(Id); } catch (Exception ex) { Console.WriteLine($"Loading job failed: {ex.Message}"); Snackbar.Add("Failed to load job", Severity.Error); } finally { isLoading = false; } } private async Task RetryJob() { if (job == null) return; var success = await JobService.RetryJobAsync(job.Id); if (success) { Snackbar.Add("Job reset to Pending status", Severity.Success); await LoadJob(); } else { Snackbar.Add("Failed to retry job", Severity.Error); } } private async Task CancelJob() { if (job == null) return; var success = await JobService.CancelJobAsync(job.Id); if (success) { Snackbar.Add("Job cancelled", Severity.Success); await LoadJob(); } else { Snackbar.Add("Failed to cancel job", Severity.Error); } } private void GoBack() { NavigationManager.NavigateTo("/jobs"); } private Color GetStatusColor(JobStatus status) { return status switch { JobStatus.Pending => Color.Default, JobStatus.Running => Color.Info, JobStatus.Completed => Color.Success, JobStatus.Failed => Color.Error, JobStatus.Retrying => Color.Warning, _ => Color.Default }; } private string GetStatusIcon(JobStatus status) { return status switch { JobStatus.Pending => Icons.Material.Filled.HourglassEmpty, JobStatus.Running => Icons.Material.Filled.PlayArrow, JobStatus.Completed => Icons.Material.Filled.CheckCircle, JobStatus.Failed => Icons.Material.Filled.Error, JobStatus.Retrying => Icons.Material.Filled.Refresh, _ => Icons.Material.Filled.Help }; } public void Dispose() { HubService.EntityChanged -= OnEntityChanged; } }