Files
Data-Coupler/Data_Coupler/Pages/Scheduling.razor
T
Alessio Dal Santo e35de1614f [Feature] Disabilitata deletion sync nei trasferimenti manuali e aggiunta configurazione nelle schedulazioni
- Disabilitata completamente la sincronizzazione eliminazioni nei trasferimenti manuali (DataCoupler.razor.cs)
- Aggiunto campo EnableDeletionSync al modello ProfileSchedule (default: false)
- Implementata logica condizionale in ScheduledProfileExecutionService per deletion sync
- Aggiunta sezione 'Opzioni Avanzate' nell'interfaccia schedulazione con warning
- Creata migration Entity Framework AddEnableDeletionSyncToProfileSchedule
- Aggiornato BackupModels per supporto backup/restore del nuovo campo
- Aggiornata documentazione README.md e copilot-instructions.md
- La deletion sync è ora disponibile solo per schedulazioni con configurazione esplicita per massima sicurezza
2026-01-23 15:52:15 +01:00

374 lines
22 KiB
Plaintext

@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.Where(p => p.SourceType != "file"))
{
<option value="@profile.Id">@profile.Name</option>
}
}
</InputSelect>
<ValidationMessage For="() => editingSchedule.ProfileId" />
@if (availableProfiles?.Any(p => p.SourceType == "file") == true)
{
<small class="form-text text-muted">
⚠️ I profili con file come sorgente sono esclusi dalle schedulazioni per motivi di sicurezza.
</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>