d042863a56
- Aggiunto supporto schedulazione con intervalli flessibili (secondi/minuti/ore/giorni/settimane/mesi) - Esteso modello ProfileSchedule con campi IntervalValue e IntervalUnit - Ottimizzato ScheduledJobService per controlli ogni 30s con esecuzione parallela - Implementata interfaccia UI completa con anteprima real-time in italiano - Aggiunta migrazione database AddIntervalSchedulingFields - Implementati metodi calcolo NextExecutionTime per intervalli - Aggiunta gestione tracking anti-duplicati e cleanup automatico - Creata documentazione completa (6 file, 2500+ righe) Modifiche tecniche: - ProfileSchedule.cs: Nuovi campi e metodi CalculateNextInterval/GetScheduleDescription - ScheduledJobService.cs: Ridotto check interval a 30s, aggiunto parallel processing - ProfileScheduleService.cs: Supporto calcolo intervalli in UpdateNextExecutionTimeAsync - Scheduling.razor: Aggiunta sezione UI per configurazione intervalli - Scheduling.razor.cs: Implementato GetIntervalPreview() e gestione stato campi
256 lines
14 KiB
Plaintext
256 lines
14 KiB
Plaintext
@page "/scheduling/history"
|
|
@using CredentialManager.Models
|
|
@using CredentialManager.Services
|
|
|
|
<PageTitle>Storico Esecuzioni</PageTitle>
|
|
|
|
<div class="container-fluid">
|
|
<div class="row">
|
|
<div class="col-12">
|
|
<div class="d-flex justify-content-between align-items-center mb-4">
|
|
<h3><i class="fas fa-history"></i> Storico Esecuzioni Schedulazioni</h3>
|
|
<div>
|
|
<button class="btn btn-outline-secondary me-2" @onclick="LoadHistory">
|
|
<i class="fas fa-sync-alt"></i> Aggiorna
|
|
</button>
|
|
<a href="/scheduling" class="btn btn-primary">
|
|
<i class="fas fa-arrow-left"></i> Torna alle Schedulazioni
|
|
</a>
|
|
</div>
|
|
</div>
|
|
|
|
@if (executionHistory == null)
|
|
{
|
|
<div class="d-flex justify-content-center">
|
|
<div class="spinner-border" role="status">
|
|
<span class="visually-hidden">Caricamento...</span>
|
|
</div>
|
|
</div>
|
|
}
|
|
else if (!executionHistory.Any())
|
|
{
|
|
<div class="alert alert-info">
|
|
<i class="fas fa-info-circle"></i> Nessuna esecuzione trovata nello storico.
|
|
</div>
|
|
}
|
|
else
|
|
{
|
|
<div class="row mb-3">
|
|
<div class="col-md-6">
|
|
<div class="card bg-light">
|
|
<div class="card-body">
|
|
<h6 class="card-title">
|
|
<i class="fas fa-chart-bar"></i> Statistiche
|
|
</h6>
|
|
<div class="row text-center">
|
|
<div class="col-4">
|
|
<div class="fs-5 text-success">@executionHistory.Count(e => e.Status == "success")</div>
|
|
<small class="text-muted">Successo</small>
|
|
</div>
|
|
<div class="col-4">
|
|
<div class="fs-5 text-danger">@executionHistory.Count(e => e.Status == "failed")</div>
|
|
<small class="text-muted">Fallite</small>
|
|
</div>
|
|
<div class="col-4">
|
|
<div class="fs-5 text-primary">@executionHistory.Count(e => e.Status == "running")</div>
|
|
<small class="text-muted">In corso</small>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<div class="card bg-light">
|
|
<div class="card-body">
|
|
<h6 class="card-title">
|
|
<i class="fas fa-database"></i> Record Processati
|
|
</h6>
|
|
<div class="text-center">
|
|
<div class="fs-4 text-info">@executionHistory.Where(e => e.Status == "success").Sum(e => e.RecordsProcessed)</div>
|
|
<small class="text-muted">Totale record elaborati con successo</small>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="table-responsive">
|
|
<table class="table table-striped table-hover">
|
|
<thead class="table-dark">
|
|
<tr>
|
|
<th>Data/Ora Inizio</th>
|
|
<th>Profilo</th>
|
|
<th>Schedulazione</th>
|
|
<th>Durata</th>
|
|
<th>Status</th>
|
|
<th>Record</th>
|
|
<th>Trigger</th>
|
|
<th>Azioni</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
@foreach (var execution in executionHistory.OrderByDescending(e => e.StartTime))
|
|
{
|
|
<tr class="@(execution.Status == "success" ? "table-success" : execution.Status == "failed" ? "table-danger" : execution.Status == "running" ? "table-info" : "")">
|
|
<td>
|
|
<div>@execution.StartTime.ToString("dd/MM/yyyy HH:mm:ss")</div>
|
|
@if (execution.EndTime.HasValue)
|
|
{
|
|
<small class="text-muted">Fine: @execution.EndTime.Value.ToString("HH:mm:ss")</small>
|
|
}
|
|
</td>
|
|
<td>
|
|
<div class="fw-bold">@execution.ProfileName</div>
|
|
@if (!string.IsNullOrEmpty(execution.SourceInfo) || !string.IsNullOrEmpty(execution.DestinationInfo))
|
|
{
|
|
<small class="text-muted">
|
|
@if (!string.IsNullOrEmpty(execution.SourceInfo))
|
|
{
|
|
<div>S: @execution.SourceInfo</div>
|
|
}
|
|
@if (!string.IsNullOrEmpty(execution.DestinationInfo))
|
|
{
|
|
<div>D: @execution.DestinationInfo</div>
|
|
}
|
|
</small>
|
|
}
|
|
</td>
|
|
<td>
|
|
<div>@execution.Schedule?.Name</div>
|
|
<small class="text-muted">ID: @execution.ScheduleId</small>
|
|
</td>
|
|
<td>
|
|
@if (execution.Duration.HasValue)
|
|
{
|
|
<span class="badge bg-secondary">@FormatDuration(execution.Duration.Value)</span>
|
|
}
|
|
else if (execution.Status == "running")
|
|
{
|
|
<span class="badge bg-primary">In corso...</span>
|
|
}
|
|
else
|
|
{
|
|
<span class="text-muted">-</span>
|
|
}
|
|
</td>
|
|
<td>
|
|
<span class="badge bg-@(execution.Status switch { "success" => "success", "failed" => "danger", "running" => "primary", "cancelled" => "warning", _ => "secondary" })">
|
|
@execution.GetStatusDisplayText()
|
|
</span>
|
|
</td>
|
|
<td>
|
|
<div class="fw-bold">@execution.RecordsProcessed</div>
|
|
@if (execution.RecordsWithErrors.HasValue && execution.RecordsWithErrors.Value > 0)
|
|
{
|
|
<small class="text-warning">@execution.RecordsWithErrors errori</small>
|
|
}
|
|
</td>
|
|
<td>
|
|
<div>
|
|
<span class="badge bg-@(execution.TriggerType == "manual" ? "info" : "success")">
|
|
@execution.TriggerType.ToUpper()
|
|
</span>
|
|
</div>
|
|
@if (!string.IsNullOrEmpty(execution.TriggeredBy))
|
|
{
|
|
<small class="text-muted">@execution.TriggeredBy</small>
|
|
}
|
|
</td>
|
|
<td>
|
|
<button class="btn btn-sm btn-outline-info" @onclick="() => ShowExecutionDetails(execution)">
|
|
<i class="fas fa-eye"></i>
|
|
</button>
|
|
</td>
|
|
</tr>
|
|
}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Modal per dettagli esecuzione -->
|
|
<div class="modal fade" id="executionDetailModal" tabindex="-1">
|
|
<div class="modal-dialog modal-xl">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h5 class="modal-title">
|
|
<i class="fas fa-info-circle"></i> Dettagli Esecuzione
|
|
@if (selectedExecution != null)
|
|
{
|
|
<span> - @selectedExecution.ProfileName</span>
|
|
}
|
|
</h5>
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
|
</div>
|
|
<div class="modal-body">
|
|
@if (selectedExecution != null)
|
|
{
|
|
<div class="row">
|
|
<div class="col-md-6">
|
|
<h6>Informazioni Generali</h6>
|
|
<table class="table table-sm">
|
|
<tr><th>Data Inizio:</th><td>@selectedExecution.StartTime.ToString("dd/MM/yyyy HH:mm:ss")</td></tr>
|
|
<tr><th>Data Fine:</th><td>@(selectedExecution.EndTime?.ToString("dd/MM/yyyy HH:mm:ss") ?? "In corso")</td></tr>
|
|
<tr><th>Durata:</th><td>@(selectedExecution.Duration?.ToString(@"hh\:mm\:ss") ?? "N/A")</td></tr>
|
|
<tr><th>Status:</th><td><span class="badge bg-@(selectedExecution.Status switch { "success" => "success", "failed" => "danger", "running" => "primary", "cancelled" => "warning", _ => "secondary" })">@selectedExecution.GetStatusDisplayText()</span></td></tr>
|
|
<tr><th>Trigger:</th><td>@selectedExecution.TriggerType (@selectedExecution.TriggeredBy)</td></tr>
|
|
<tr><th>Record Processati:</th><td>@selectedExecution.RecordsProcessed</td></tr>
|
|
@if (selectedExecution.RecordsWithErrors.HasValue)
|
|
{
|
|
<tr><th>Record con Errori:</th><td class="text-warning">@selectedExecution.RecordsWithErrors</td></tr>
|
|
}
|
|
</table>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<h6>Configurazione</h6>
|
|
<table class="table table-sm">
|
|
<tr><th>Schedulazione:</th><td>@selectedExecution.Schedule?.Name</td></tr>
|
|
<tr><th>Profilo:</th><td>@selectedExecution.ProfileName (ID: @selectedExecution.ProfileId)</td></tr>
|
|
<tr><th>Tipo Sorgente:</th><td>@selectedExecution.SourceType</td></tr>
|
|
<tr><th>Tipo Destinazione:</th><td>@selectedExecution.DestinationType</td></tr>
|
|
@if (!string.IsNullOrEmpty(selectedExecution.SourceInfo))
|
|
{
|
|
<tr><th>Info Sorgente:</th><td>@selectedExecution.SourceInfo</td></tr>
|
|
}
|
|
@if (!string.IsNullOrEmpty(selectedExecution.DestinationInfo))
|
|
{
|
|
<tr><th>Info Destinazione:</th><td>@selectedExecution.DestinationInfo</td></tr>
|
|
}
|
|
</table>
|
|
</div>
|
|
</div>
|
|
|
|
@if (!string.IsNullOrEmpty(selectedExecution.Message))
|
|
{
|
|
<h6>Messaggio</h6>
|
|
<div class="alert alert-@(selectedExecution.Status == "success" ? "success" : selectedExecution.Status == "failed" ? "danger" : "info")">
|
|
@selectedExecution.Message
|
|
</div>
|
|
}
|
|
|
|
@if (!string.IsNullOrEmpty(selectedExecution.ErrorDetails))
|
|
{
|
|
<h6>Dettagli Errori</h6>
|
|
<div class="alert alert-danger">
|
|
<pre style="white-space: pre-wrap; font-size: 0.85em;">@selectedExecution.ErrorDetails</pre>
|
|
</div>
|
|
}
|
|
|
|
@if (!string.IsNullOrEmpty(selectedExecution.AdditionalInfo))
|
|
{
|
|
<h6>Informazioni Aggiuntive</h6>
|
|
<div class="alert alert-light">
|
|
<pre style="white-space: pre-wrap; font-size: 0.85em;">@selectedExecution.AdditionalInfo</pre>
|
|
</div>
|
|
}
|
|
}
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Chiudi</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div> |