9fab99112b
- SchedulingHistory.razor / .cs: iniettato IWebHostEnvironment per nascondere lo stack trace (con percorsi di file) in produzione; in produzione viene mostrato solo il messaggio di errore sanitizzato e un avviso che invita a consultare i log dell'applicazione; in sviluppo il dettaglio completo resta visibile invariato. - Scheduling.razor.cs (ExecuteScheduleManually): isolata la notifica JS (ShowSuccessMessage / ShowErrorMessage) in un blocco try-catch separato per TaskCanceledException / OperationCanceledException. In questo modo una disconnessione del browser durante un'esecuzione lunga non sovrascrive più il risultato già salvato correttamente come 'success' con uno stato 'failed' e lo stack trace di un'eccezione JSInterop. L'evento viene registrato come avviso di log senza impatto sul record storico. - ScheduledJobService.cs: aggiunto commento esplicativo sul motivo per cui il dettaglio completo (ex.ToString) è salvato nel DB ma la UI ne mostra solo la versione sanitizzata in produzione.
176 lines
6.2 KiB
C#
176 lines
6.2 KiB
C#
using CredentialManager.Models;
|
|
using CredentialManager.Services;
|
|
using Microsoft.AspNetCore.Components;
|
|
using Microsoft.AspNetCore.Hosting;
|
|
using Microsoft.Extensions.Logging;
|
|
using Microsoft.JSInterop;
|
|
|
|
namespace Data_Coupler.Pages;
|
|
|
|
public partial class SchedulingHistory : ComponentBase
|
|
{
|
|
[Inject] private IProfileScheduleService ScheduleService { get; set; } = null!;
|
|
[Inject] private IJSRuntime JSRuntime { get; set; } = null!;
|
|
[Inject] private ILogger<SchedulingHistory> Logger { get; set; } = null!;
|
|
[Inject] private IWebHostEnvironment WebHostEnvironment { get; set; } = null!;
|
|
|
|
protected bool IsDevelopment => WebHostEnvironment.IsDevelopment();
|
|
|
|
/// <summary>
|
|
/// Restituisce solo il messaggio dell'eccezione (senza stack trace) per la visualizzazione in produzione.
|
|
/// </summary>
|
|
protected static string GetSanitizedErrorMessage(string errorDetails)
|
|
{
|
|
if (string.IsNullOrEmpty(errorDetails))
|
|
return string.Empty;
|
|
|
|
// Prende solo le righe fino al primo stack frame (riga che inizia con " at")
|
|
var lines = errorDetails.Split('\n');
|
|
var messageLines = new System.Collections.Generic.List<string>();
|
|
foreach (var line in lines)
|
|
{
|
|
if (line.TrimStart().StartsWith("at ", StringComparison.Ordinal))
|
|
break;
|
|
messageLines.Add(line.TrimEnd());
|
|
}
|
|
return string.Join("\n", messageLines).Trim();
|
|
}
|
|
|
|
protected List<ScheduleExecutionHistory>? executionHistory;
|
|
protected ScheduleExecutionHistory? selectedExecution;
|
|
protected bool isLoading = false;
|
|
|
|
protected override async Task OnInitializedAsync()
|
|
{
|
|
await LoadHistory();
|
|
}
|
|
|
|
protected async Task LoadHistory()
|
|
{
|
|
if (isLoading) return;
|
|
|
|
isLoading = true;
|
|
StateHasChanged();
|
|
|
|
try
|
|
{
|
|
// Carica le ultime 100 esecuzioni
|
|
executionHistory = await ScheduleService.GetRecentExecutionsAsync(100);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Logger.LogError(ex, "Errore nel caricamento dello storico delle esecuzioni");
|
|
await ShowErrorMessage("Errore nel caricamento dello storico: " + ex.Message);
|
|
}
|
|
finally
|
|
{
|
|
isLoading = false;
|
|
StateHasChanged();
|
|
}
|
|
}
|
|
|
|
protected async Task ShowExecutionDetails(ScheduleExecutionHistory execution)
|
|
{
|
|
try
|
|
{
|
|
// Carica i dettagli completi dell'esecuzione
|
|
selectedExecution = await ScheduleService.GetExecutionByIdAsync(execution.Id);
|
|
|
|
if (selectedExecution != null)
|
|
{
|
|
await ShowModal();
|
|
}
|
|
else
|
|
{
|
|
await ShowErrorMessage("Dettagli dell'esecuzione non trovati.");
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Logger.LogError(ex, "Errore nel caricamento dei dettagli esecuzione {ExecutionId}", execution.Id);
|
|
await ShowErrorMessage("Errore nel caricamento dei dettagli: " + ex.Message);
|
|
}
|
|
}
|
|
|
|
protected async Task ShowModal()
|
|
{
|
|
StateHasChanged();
|
|
await Task.Delay(100);
|
|
|
|
try
|
|
{
|
|
// Proviamo prima con l'approccio Bootstrap standard
|
|
await JSRuntime.InvokeVoidAsync("eval",
|
|
"if (typeof bootstrap !== 'undefined' && bootstrap.Modal) { " +
|
|
"var modal = new bootstrap.Modal(document.getElementById('executionDetailModal')); " +
|
|
"modal.show(); " +
|
|
"} else { " +
|
|
"document.getElementById('executionDetailModal').style.display = 'block'; " +
|
|
"document.getElementById('executionDetailModal').classList.add('show'); " +
|
|
"}");
|
|
}
|
|
catch (Exception)
|
|
{
|
|
// Fallback: mostra il modal manualmente
|
|
await JSRuntime.InvokeVoidAsync("eval",
|
|
"var modal = document.getElementById('executionDetailModal');" +
|
|
"modal.style.display = 'block';" +
|
|
"modal.classList.add('show');" +
|
|
"document.body.classList.add('modal-open');" +
|
|
"var backdrop = document.createElement('div');" +
|
|
"backdrop.className = 'modal-backdrop fade show';" +
|
|
"document.body.appendChild(backdrop);");
|
|
}
|
|
}
|
|
|
|
protected string FormatDuration(TimeSpan duration)
|
|
{
|
|
if (duration.TotalHours >= 1)
|
|
{
|
|
return duration.ToString(@"h\:mm\:ss");
|
|
}
|
|
else if (duration.TotalMinutes >= 1)
|
|
{
|
|
return duration.ToString(@"m\:ss");
|
|
}
|
|
else
|
|
{
|
|
return $"{duration.TotalSeconds:F1}s";
|
|
}
|
|
}
|
|
|
|
protected async Task HideModal()
|
|
{
|
|
try
|
|
{
|
|
await JSRuntime.InvokeVoidAsync("eval",
|
|
"if (typeof bootstrap !== 'undefined' && bootstrap.Modal) { " +
|
|
"var modalElement = document.getElementById('executionDetailModal'); " +
|
|
"var modal = bootstrap.Modal.getInstance(modalElement); " +
|
|
"if (modal) modal.hide(); " +
|
|
"} else { " +
|
|
"document.getElementById('executionDetailModal').style.display = 'none'; " +
|
|
"document.getElementById('executionDetailModal').classList.remove('show'); " +
|
|
"document.body.classList.remove('modal-open'); " +
|
|
"var backdrop = document.querySelector('.modal-backdrop'); " +
|
|
"if (backdrop) backdrop.remove(); " +
|
|
"}");
|
|
}
|
|
catch (Exception)
|
|
{
|
|
// Fallback: nascondi il modal manualmente
|
|
await JSRuntime.InvokeVoidAsync("eval",
|
|
"var modal = document.getElementById('executionDetailModal');" +
|
|
"modal.style.display = 'none';" +
|
|
"modal.classList.remove('show');" +
|
|
"document.body.classList.remove('modal-open');" +
|
|
"var backdrop = document.querySelector('.modal-backdrop');" +
|
|
"if (backdrop) backdrop.remove();");
|
|
}
|
|
}
|
|
|
|
private async Task ShowErrorMessage(string message)
|
|
{
|
|
await JSRuntime.InvokeVoidAsync("alert", "Errore: " + message);
|
|
}
|
|
} |