[Fix] Sicurezza e affidabilità storico esecuzioni schedulazioni
- 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.
This commit is contained in:
@@ -286,6 +286,8 @@ public class ScheduledJobService : BackgroundService
|
|||||||
executionHistory.EndTime = DateTime.Now;
|
executionHistory.EndTime = DateTime.Now;
|
||||||
executionHistory.Status = "failed";
|
executionHistory.Status = "failed";
|
||||||
executionHistory.Message = $"Errore durante l'esecuzione automatica: {ex.Message}";
|
executionHistory.Message = $"Errore durante l'esecuzione automatica: {ex.Message}";
|
||||||
|
// Memorizza il dettaglio completo (stack trace) solo per scopi diagnostici;
|
||||||
|
// la UI in produzione ne mostrerà una versione sanitizzata senza percorsi di file.
|
||||||
executionHistory.ErrorDetails = ex.ToString();
|
executionHistory.ErrorDetails = ex.ToString();
|
||||||
await scheduleService.UpdateExecutionHistoryAsync(executionHistory);
|
await scheduleService.UpdateExecutionHistoryAsync(executionHistory);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -311,14 +311,21 @@ public partial class Scheduling : ComponentBase
|
|||||||
|
|
||||||
await ScheduleService.UpdateExecutionStatusAsync(scheduleId, status, message, result.RecordsProcessed);
|
await ScheduleService.UpdateExecutionStatusAsync(scheduleId, status, message, result.RecordsProcessed);
|
||||||
|
|
||||||
|
// Notifica l'utente (best-effort: la connessione browser potrebbe essere stata interrotta
|
||||||
|
// durante un'esecuzione lunga senza che questo invalidi il risultato già salvato).
|
||||||
|
try
|
||||||
|
{
|
||||||
if (result.IsSuccess)
|
if (result.IsSuccess)
|
||||||
{
|
|
||||||
await ShowSuccessMessage($"Schedulazione eseguita con successo! {result.RecordsProcessed} record elaborati in {result.Duration.TotalSeconds:F2} secondi.");
|
await ShowSuccessMessage($"Schedulazione eseguita con successo! {result.RecordsProcessed} record elaborati in {result.Duration.TotalSeconds:F2} secondi.");
|
||||||
}
|
|
||||||
else
|
else
|
||||||
{
|
|
||||||
await ShowErrorMessage($"Errore durante l'esecuzione: {result.ErrorMessage}");
|
await ShowErrorMessage($"Errore durante l'esecuzione: {result.ErrorMessage}");
|
||||||
}
|
}
|
||||||
|
catch (OperationCanceledException)
|
||||||
|
{
|
||||||
|
// La connessione Blazor è stata interrotta durante l'esecuzione: il risultato è
|
||||||
|
// già stato salvato correttamente, la notifica non può essere recapitata.
|
||||||
|
Logger.LogWarning("Notifica UI non inviata per la schedulazione {ScheduleId}: connessione browser interrotta durante l'esecuzione", scheduleId);
|
||||||
|
}
|
||||||
|
|
||||||
await LoadSchedules();
|
await LoadSchedules();
|
||||||
}
|
}
|
||||||
@@ -326,7 +333,7 @@ public partial class Scheduling : ComponentBase
|
|||||||
{
|
{
|
||||||
Logger.LogError(ex, "Errore nell'esecuzione manuale schedulazione {ScheduleId}", scheduleId);
|
Logger.LogError(ex, "Errore nell'esecuzione manuale schedulazione {ScheduleId}", scheduleId);
|
||||||
|
|
||||||
// Aggiorna lo storico in caso di eccezione
|
// Aggiorna lo storico in caso di eccezione durante l'esecuzione effettiva
|
||||||
if (executionHistory != null)
|
if (executionHistory != null)
|
||||||
{
|
{
|
||||||
executionHistory.EndTime = DateTime.Now;
|
executionHistory.EndTime = DateTime.Now;
|
||||||
@@ -337,8 +344,16 @@ public partial class Scheduling : ComponentBase
|
|||||||
}
|
}
|
||||||
|
|
||||||
await ScheduleService.UpdateExecutionStatusAsync(scheduleId, "failed", $"Errore: {ex.Message}");
|
await ScheduleService.UpdateExecutionStatusAsync(scheduleId, "failed", $"Errore: {ex.Message}");
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
await ShowErrorMessage("Errore nell'esecuzione: " + ex.Message);
|
await ShowErrorMessage("Errore nell'esecuzione: " + ex.Message);
|
||||||
}
|
}
|
||||||
|
catch (OperationCanceledException)
|
||||||
|
{
|
||||||
|
Logger.LogWarning("Notifica UI non inviata per la schedulazione {ScheduleId}: connessione browser non disponibile", scheduleId);
|
||||||
|
}
|
||||||
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
isExecuting = false;
|
isExecuting = false;
|
||||||
|
|||||||
@@ -235,7 +235,15 @@
|
|||||||
{
|
{
|
||||||
<h6>Dettagli Errori</h6>
|
<h6>Dettagli Errori</h6>
|
||||||
<div class="alert alert-danger">
|
<div class="alert alert-danger">
|
||||||
|
@if (IsDevelopment)
|
||||||
|
{
|
||||||
<pre style="white-space: pre-wrap; font-size: 0.85em;">@selectedExecution.ErrorDetails</pre>
|
<pre style="white-space: pre-wrap; font-size: 0.85em;">@selectedExecution.ErrorDetails</pre>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<p class="mb-1">@GetSanitizedErrorMessage(selectedExecution.ErrorDetails)</p>
|
||||||
|
<small class="text-muted"><i class="fas fa-info-circle"></i> Per i dettagli tecnici completi consultare i log dell'applicazione.</small>
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
using CredentialManager.Models;
|
using CredentialManager.Models;
|
||||||
using CredentialManager.Services;
|
using CredentialManager.Services;
|
||||||
using Microsoft.AspNetCore.Components;
|
using Microsoft.AspNetCore.Components;
|
||||||
|
using Microsoft.AspNetCore.Hosting;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Microsoft.JSInterop;
|
using Microsoft.JSInterop;
|
||||||
|
|
||||||
@@ -11,6 +12,29 @@ public partial class SchedulingHistory : ComponentBase
|
|||||||
[Inject] private IProfileScheduleService ScheduleService { get; set; } = null!;
|
[Inject] private IProfileScheduleService ScheduleService { get; set; } = null!;
|
||||||
[Inject] private IJSRuntime JSRuntime { get; set; } = null!;
|
[Inject] private IJSRuntime JSRuntime { get; set; } = null!;
|
||||||
[Inject] private ILogger<SchedulingHistory> Logger { 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 List<ScheduleExecutionHistory>? executionHistory;
|
||||||
protected ScheduleExecutionHistory? selectedExecution;
|
protected ScheduleExecutionHistory? selectedExecution;
|
||||||
|
|||||||
Reference in New Issue
Block a user