Files
Data-Coupler/Data_Coupler/Pages/Scheduling.razor
T
Alessio Dal Santo a5f8943c72
Build and Push Docker Images / Build Linux Container (push) Successful in 8m59s
Build and Push Docker Images / Build Windows Container (push) Successful in 9m35s
Build and Push Docker Images / Create Multi-Platform Manifest (push) Failing after 25s
[Feature] Implementata schedulazione completa per file CSV/Excel
- Aggiunta validazione percorsi file prima del salvataggio profili
- Implementati metodi di lettura file CSV e Excel per schedulazioni
- Supporto doppia modalità: caricamento browser (preview) e percorso manuale (schedulazione)
- Gestione completa deletion sync anche per file CSV/Excel
- Rilevamento automatico separatori CSV (virgola, punto e virgola, tab, pipe)
- Supporto formati Excel legacy (.xls) e moderni (.xlsx)
- Abilitati profili file nella UI di schedulazione
- Logging dettagliato per troubleshooting
- Documentazione completa in CSV_SCHEDULING_IMPLEMENTATION.md
- Aggiornati README.md e copilot-instructions.md con nuove feature
- Rimosso testo 'TEST' dalla pagina di login
2026-01-25 12:45:32 +01:00

375 lines
22 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
@page "/scheduling"
@using CredentialManager.Models
@using CredentialManager.Services
@using Data_Coupler.Services
<PageTitle>Schedulazione Profili</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-clock"></i> Schedulazione Profili</h3>
<div>
<a href="/scheduling/history" class="btn btn-outline-info me-2">
<i class="fas fa-history"></i> Storico Esecuzioni
</a>
<button class="btn btn-success" @onclick="ShowCreateModal">
<i class="fas fa-plus"></i> Nuova Schedulazione
</button>
</div>
</div>
@if (schedules == null)
{
<div class="d-flex justify-content-center">
<div class="spinner-border" role="status">
<span class="visually-hidden">Caricamento...</span>
</div>
</div>
}
else if (!schedules.Any())
{
<div class="alert alert-info">
<i class="fas fa-info-circle"></i> Nessuna schedulazione configurata.
<button class="btn btn-link p-0 ms-2" @onclick="ShowCreateModal">
Crea la prima schedulazione
</button>
</div>
}
else
{
<div class="row">
@foreach (var schedule in schedules.OrderBy(s => s.NextExecutionTime))
{
<div class="col-md-6 col-lg-4 mb-3">
<div class="card h-100 @(schedule.IsEnabled ? "border-success" : "border-secondary")">
<div class="card-header d-flex justify-content-between align-items-center">
<h6 class="mb-0">
<i class="fas fa-@(schedule.ScheduleType switch { "once" => "clock", "interval" => "redo", "daily" => "calendar-day", "weekly" => "calendar-week", "monthly" => "calendar", _ => "clock" })"></i>
@schedule.Name
</h6>
<div class="dropdown">
<button class="btn btn-sm btn-outline-secondary dropdown-toggle" data-bs-toggle="dropdown">
<i class="fas fa-ellipsis-v"></i>
</button>
<ul class="dropdown-menu">
<li><button class="dropdown-item" @onclick="() => ShowEditModal(schedule)">
<i class="fas fa-edit"></i> Modifica
</button></li>
<li><button class="dropdown-item" @onclick="() => ExecuteScheduleManually(schedule.Id)">
<i class="fas fa-play"></i> Esegui Ora
</button></li>
<li><hr class="dropdown-divider"></li>
<li><button class="dropdown-item text-danger" @onclick="() => DeleteSchedule(schedule.Id)">
<i class="fas fa-trash"></i> Elimina
</button></li>
</ul>
</div>
</div>
<div class="card-body">
<p class="card-text text-muted mb-2">
<strong>Profilo:</strong> @schedule.Profile?.Name
</p>
@if (!string.IsNullOrEmpty(schedule.Description))
{
<p class="card-text small">@schedule.Description</p>
}
<div class="mb-2">
<small class="text-muted">
<strong>Tipo:</strong> @schedule.GetScheduleDescription()
</small>
</div>
<!-- Prossima esecuzione -->
@if (schedule.NextExecutionTime.HasValue)
{
<div class="mb-2">
<small class="text-info">
<i class="fas fa-clock"></i>
<strong>Prossima esecuzione:</strong><br>
@schedule.NextExecutionTime.Value.ToString("dd/MM/yyyy HH:mm")
</small>
</div>
}
<!-- Ultima esecuzione -->
@if (schedule.LastExecutionTime.HasValue)
{
<div class="mb-2">
<small class="text-muted">
<i class="fas fa-history"></i>
<strong>Ultima esecuzione:</strong><br>
@schedule.LastExecutionTime.Value.ToString("dd/MM/yyyy HH:mm")
</small>
</div>
}
<!-- Status ultima esecuzione -->
@if (!string.IsNullOrEmpty(schedule.LastExecutionStatus))
{
<div class="mb-2">
<span class="badge bg-@(schedule.LastExecutionStatus switch { "success" => "success", "failed" => "danger", "running" => "primary", _ => "secondary" })">
@schedule.LastExecutionStatus.ToUpper()
@if (schedule.LastExecutionRecordCount.HasValue)
{
<text> (@schedule.LastExecutionRecordCount record)</text>
}
</span>
</div>
}
</div>
<div class="card-footer d-flex justify-content-between align-items-center">
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox"
checked="@schedule.IsEnabled"
@onchange="(e) => ToggleScheduleEnabled(schedule.Id, (bool)e.Value!)">
<label class="form-check-label small">
@(schedule.IsEnabled ? "Attiva" : "Disattivata")
</label>
</div>
<small class="text-muted">
Esecuzioni: @schedule.ExecutionCount
</small>
</div>
</div>
</div>
}
</div>
}
</div>
</div>
</div>
<!-- Modal per creazione/modifica schedulazione -->
<div class="modal fade" id="scheduleModal" tabindex="-1">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">
@(editingSchedule?.Id > 0 ? "Modifica Schedulazione" : "Nuova Schedulazione")
</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
@if (editingSchedule != null)
{
<EditForm Model="editingSchedule" OnValidSubmit="SaveSchedule">
<DataAnnotationsValidator />
<div class="row">
<div class="col-md-6">
<div class="mb-3">
<label class="form-label">Nome *</label>
<InputText @bind-Value="editingSchedule.Name" class="form-control" />
<ValidationMessage For="() => editingSchedule.Name" />
</div>
</div>
<div class="col-md-6">
<div class="mb-3">
<label class="form-label">Profilo da eseguire *</label>
<InputSelect @bind-Value="editingSchedule.ProfileId" class="form-select" @onchange="OnProfileSelectionChanged">
<option value="0">-- Seleziona Profilo --</option>
@if (availableProfiles != null)
{
@foreach (var profile in availableProfiles)
{
<option value="@profile.Id">@profile.Name @(profile.SourceType == "file" ? "(File)" : "")</option>
}
}
</InputSelect>
<ValidationMessage For="() => editingSchedule.ProfileId" />
@if (availableProfiles?.Any(p => p.SourceType == "file") == true)
{
<small class="form-text text-muted">
️ I profili con file CSV/Excel come sorgente sono ora supportati per le schedulazioni.
Il file specificato nel profilo verrà letto ad ogni esecuzione.
</small>
}
</div>
</div>
</div>
<div class="mb-3">
<label class="form-label">Descrizione</label>
<InputTextArea @bind-Value="editingSchedule.Description" class="form-control" rows="2" />
</div>
@* Sezione override database *@
@if (selectedProfile != null && (selectedProfile.SourceType == "database" || selectedProfile.DestinationType == "database"))
{
<div class="card mb-3">
<div class="card-header">
<h6 class="mb-0">
<i class="fas fa-database"></i> Override Database
</h6>
</div>
<div class="card-body">
<div class="row">
@if (selectedProfile.SourceType == "database")
{
<div class="col-md-6">
<div class="mb-3">
<label class="form-label">Database Sorgente (Opzionale)</label>
<InputText @bind-Value="editingSchedule.SourceDatabaseOverride" class="form-control" />
<small class="form-text text-muted">
Lascia vuoto per usare il database specificato nella credenziale.
</small>
</div>
</div>
}
@if (selectedProfile.DestinationType == "database")
{
<div class="col-md-6">
<div class="mb-3">
<label class="form-label">Database Destinazione (Opzionale)</label>
<InputText @bind-Value="editingSchedule.DestinationDatabaseOverride" class="form-control" />
<small class="form-text text-muted">
Lascia vuoto per usare il database specificato nella credenziale.
</small>
</div>
</div>
}
</div>
</div>
</div>
}
<div class="mb-3">
<label class="form-label">Tipo di Schedulazione *</label>
<InputSelect @bind-Value="editingSchedule.ScheduleType" class="form-select" @onchange="OnScheduleTypeChanged">
<option value="">-- Seleziona Tipo --</option>
<option value="once">Una volta</option>
<option value="interval">Intervallo Personalizzato</option>
<option value="daily">Giornaliera</option>
<option value="weekly">Settimanale</option>
<option value="monthly">Mensile</option>
</InputSelect>
<ValidationMessage For="() => editingSchedule.ScheduleType" />
</div>
@if (editingSchedule.ScheduleType == "once")
{
<div class="mb-3">
<label class="form-label">Data e Ora di Esecuzione *</label>
<input type="datetime-local" @bind="editingSchedule.ScheduledDateTime" class="form-control" />
</div>
}
else if (editingSchedule.ScheduleType == "interval")
{
<div class="row">
<div class="col-md-6">
<div class="mb-3">
<label class="form-label">Intervallo *</label>
<InputNumber @bind-Value="editingSchedule.IntervalValue" class="form-control" min="1" />
<small class="form-text text-muted">Numero di unità temporali</small>
</div>
</div>
<div class="col-md-6">
<div class="mb-3">
<label class="form-label">Unità di Tempo *</label>
<InputSelect @bind-Value="editingSchedule.IntervalUnit" class="form-select">
<option value="">-- Seleziona Unità --</option>
<option value="seconds">Secondi</option>
<option value="minutes">Minuti</option>
<option value="hours">Ore</option>
<option value="days">Giorni</option>
<option value="weeks">Settimane</option>
<option value="months">Mesi</option>
</InputSelect>
<small class="form-text text-muted">Frequenza di ripetizione</small>
</div>
</div>
</div>
<div class="alert alert-info">
<i class="fas fa-info-circle"></i>
<strong>Anteprima:</strong> @GetIntervalPreview()
</div>
}
else if (!string.IsNullOrEmpty(editingSchedule.ScheduleType) && editingSchedule.ScheduleType != "once" && editingSchedule.ScheduleType != "interval")
{
<div class="row">
<div class="col-md-6">
<div class="mb-3">
<label class="form-label">Ora di Esecuzione *</label>
<InputText @bind-Value="editingSchedule.DailyTime" class="form-control" placeholder="HH:mm" />
<small class="form-text text-muted">Formato 24 ore (es: 14:30)</small>
</div>
</div>
@if (editingSchedule.ScheduleType == "weekly")
{
<div class="col-md-6">
<div class="mb-3">
<label class="form-label">Giorno della Settimana *</label>
<InputSelect @bind-Value="editingSchedule.DayOfWeek" class="form-select">
<option value="">-- Seleziona --</option>
<option value="0">Domenica</option>
<option value="1">Lunedì</option>
<option value="2">Martedì</option>
<option value="3">Mercoledì</option>
<option value="4">Giovedì</option>
<option value="5">Venerdì</option>
<option value="6">Sabato</option>
</InputSelect>
</div>
</div>
}
else if (editingSchedule.ScheduleType == "monthly")
{
<div class="col-md-6">
<div class="mb-3">
<label class="form-label">Giorno del Mese *</label>
<InputNumber @bind-Value="editingSchedule.DayOfMonth" class="form-control" min="1" max="31" />
<small class="form-text text-muted">1-31</small>
</div>
</div>
}
</div>
}
<div class="form-check mb-3">
<InputCheckbox @bind-Value="editingSchedule.IsEnabled" class="form-check-input" />
<label class="form-check-label">
Schedulazione attiva
</label>
</div>
<div class="card mb-3">
<div class="card-header bg-warning text-dark">
<h6 class="mb-0">
<i class="fas fa-exclamation-triangle"></i> Opzioni Avanzate
</h6>
</div>
<div class="card-body">
<div class="form-check mb-2">
<InputCheckbox @bind-Value="editingSchedule.EnableDeletionSync" class="form-check-input" id="enableDeletionSyncCheckbox" />
<label class="form-check-label" for="enableDeletionSyncCheckbox">
<strong>Abilita sincronizzazione eliminazioni</strong>
</label>
</div>
<div class="alert alert-warning mb-0">
<small>
<i class="fas fa-info-circle"></i>
<strong>Attenzione:</strong> Se abilitata, i record eliminati dalla sorgente saranno automaticamente eliminati anche dalla destinazione durante l'esecuzione schedulata.
Questa opzione è <strong>disabilitata di default</strong> per motivi di sicurezza.
Usare con cautela!
</small>
</div>
</div>
</div>
<div class="d-flex justify-content-end gap-2">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Annulla</button>
<button type="submit" class="btn btn-primary">
<i class="fas fa-save"></i> Salva
</button>
</div>
</EditForm>
}
</div>
</div>
</div>
</div>