feat: Implementazione completa sistema schedulazione con intervalli personalizzati
- 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
This commit is contained in:
@@ -0,0 +1,558 @@
|
||||
@using Data_Coupler.Services
|
||||
@using Data_Coupler.Models
|
||||
@inject IBackupService BackupService
|
||||
@inject IJSRuntime JSRuntime
|
||||
@inject ILogger<BackupTab> Logger
|
||||
|
||||
<div class="settings-section">
|
||||
<h4>
|
||||
<i class="fas fa-download text-primary me-2"></i>
|
||||
Backup e Ripristino Dati
|
||||
</h4>
|
||||
<p class="text-muted">
|
||||
Gestisci il backup e il ripristino di profili, credenziali, associazioni e schedule.
|
||||
I backup non includono password o API keys per motivi di sicurezza.
|
||||
</p>
|
||||
|
||||
<div class="row">
|
||||
<!-- Export Section -->
|
||||
<div class="col-md-6">
|
||||
<div class="card h-100">
|
||||
<div class="card-header">
|
||||
<h5 class="card-title mb-0">
|
||||
<i class="fas fa-upload text-success me-2"></i>
|
||||
Esporta Backup
|
||||
</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<p class="card-text">
|
||||
Crea un backup completo del sistema con tutti i dati configurati.
|
||||
</p>
|
||||
|
||||
<!-- Opzioni Export -->
|
||||
<div class="mb-3">
|
||||
<label class="form-label fw-bold">Componenti da includere:</label>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" @bind="exportOptions.IncludeProfiles" id="exportProfiles">
|
||||
<label class="form-check-label" for="exportProfiles">
|
||||
<i class="fas fa-user-cog me-1"></i> Profili Data Coupler
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" @bind="exportOptions.IncludeCredentials" id="exportCredentials">
|
||||
<label class="form-check-label" for="exportCredentials">
|
||||
<i class="fas fa-key me-1"></i> Credenziali (senza password)
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" @bind="exportOptions.IncludeKeyAssociations" id="exportAssociations">
|
||||
<label class="form-check-label" for="exportAssociations">
|
||||
<i class="fas fa-link me-1"></i> Associazioni Chiavi
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" @bind="exportOptions.IncludeProfileSchedules" id="exportSchedules">
|
||||
<label class="form-check-label" for="exportSchedules">
|
||||
<i class="fas fa-clock me-1"></i> Schedule Profili
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" @bind="exportOptions.IncludeOnlyActiveRecords" id="exportOnlyActive">
|
||||
<label class="form-check-label" for="exportOnlyActive">
|
||||
Solo record attivi
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="exportDescription" class="form-label">Descrizione (opzionale):</label>
|
||||
<input type="text" class="form-control" id="exportDescription" @bind="exportOptions.Description"
|
||||
placeholder="Backup settimanale, Configurazione produzione, etc.">
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="exportCreatedBy" class="form-label">Creato da:</label>
|
||||
<input type="text" class="form-control" id="exportCreatedBy" @bind="exportOptions.CreatedBy"
|
||||
placeholder="Nome utente o sistema">
|
||||
</div>
|
||||
|
||||
<button class="btn btn-success" @onclick="ExportBackup" disabled="@isExporting">
|
||||
@if (isExporting)
|
||||
{
|
||||
<div class="spinner-border spinner-border-sm me-2" role="status">
|
||||
<span class="visually-hidden">Loading...</span>
|
||||
</div>
|
||||
<text>Esportazione...</text>
|
||||
}
|
||||
else
|
||||
{
|
||||
<i class="fas fa-download me-2"></i>
|
||||
<text>Crea Backup</text>
|
||||
}
|
||||
</button>
|
||||
|
||||
@if (lastExportResult != null)
|
||||
{
|
||||
<div class="mt-3">
|
||||
<div class="alert alert-@(lastExportResult.Success ? "success" : "danger") alert-sm">
|
||||
<div class="d-flex justify-content-between align-items-start">
|
||||
<div>
|
||||
<strong>@(lastExportResult.Success ? "Successo" : "Errore"):</strong>
|
||||
<div>@lastExportResult.Message</div>
|
||||
@if (lastExportResult.Success)
|
||||
{
|
||||
<small class="text-muted">
|
||||
Durata: @lastExportResult.Duration.TotalSeconds.ToString("F1")s |
|
||||
Record: @(lastExportResult.ProcessedCounts.Profiles + lastExportResult.ProcessedCounts.Credentials + lastExportResult.ProcessedCounts.KeyAssociations + lastExportResult.ProcessedCounts.ProfileSchedules)
|
||||
</small>
|
||||
}
|
||||
</div>
|
||||
<button type="button" class="btn-close btn-close-sm" @onclick="() => lastExportResult = null"></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Import Section -->
|
||||
<div class="col-md-6">
|
||||
<div class="card h-100">
|
||||
<div class="card-header">
|
||||
<h5 class="card-title mb-0">
|
||||
<i class="fas fa-download text-primary me-2"></i>
|
||||
Importa Backup
|
||||
</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<p class="card-text">
|
||||
Ripristina i dati da un file di backup precedentemente creato.
|
||||
</p>
|
||||
|
||||
<!-- File Upload -->
|
||||
<div class="mb-3">
|
||||
<label for="backupFile" class="form-label fw-bold">Seleziona file backup:</label>
|
||||
<InputFile class="form-control" id="backupFile"
|
||||
accept=".json" OnChange="OnFileSelected" />
|
||||
<div class="form-text">
|
||||
Seleziona un file .json di backup di Data Coupler
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if (selectedBackupInfo != null)
|
||||
{
|
||||
<div class="mb-3">
|
||||
<div class="card border-info">
|
||||
<div class="card-header bg-info text-white">
|
||||
<h6 class="mb-0">
|
||||
<i class="fas fa-info-circle me-2"></i>
|
||||
Informazioni Backup
|
||||
</h6>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="row">
|
||||
<div class="col-6">
|
||||
<strong>Versione:</strong> @selectedBackupInfo.Metadata.Version<br>
|
||||
<strong>Creato:</strong> @selectedBackupInfo.Metadata.CreatedAt.ToString("dd/MM/yyyy HH:mm")<br>
|
||||
@if (!string.IsNullOrEmpty(selectedBackupInfo.Metadata.CreatedBy))
|
||||
{
|
||||
<strong>Da:</strong> @selectedBackupInfo.Metadata.CreatedBy<br>
|
||||
}
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<strong>Profili:</strong> @selectedBackupInfo.Profiles.Count<br>
|
||||
<strong>Credenziali:</strong> @selectedBackupInfo.Credentials.Count<br>
|
||||
<strong>Associazioni:</strong> @selectedBackupInfo.KeyAssociations.Count<br>
|
||||
<strong>Schedule:</strong> @selectedBackupInfo.ProfileSchedules.Count<br>
|
||||
</div>
|
||||
@if (!string.IsNullOrEmpty(selectedBackupInfo.Metadata.Description))
|
||||
{
|
||||
<div class="col-12 mt-2">
|
||||
<strong>Descrizione:</strong> @selectedBackupInfo.Metadata.Description
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Opzioni Import -->
|
||||
<div class="mb-3">
|
||||
<label class="form-label fw-bold">Componenti da ripristinare:</label>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" @bind="restoreOptions.RestoreProfiles" id="restoreProfiles">
|
||||
<label class="form-check-label" for="restoreProfiles">
|
||||
<i class="fas fa-user-cog me-1"></i> Profili (@selectedBackupInfo.Profiles.Count)
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" @bind="restoreOptions.RestoreCredentials" id="restoreCredentials">
|
||||
<label class="form-check-label" for="restoreCredentials">
|
||||
<i class="fas fa-key me-1"></i> Credenziali (@selectedBackupInfo.Credentials.Count)
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" @bind="restoreOptions.RestoreKeyAssociations" id="restoreAssociations">
|
||||
<label class="form-check-label" for="restoreAssociations">
|
||||
<i class="fas fa-link me-1"></i> Associazioni (@selectedBackupInfo.KeyAssociations.Count)
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" @bind="restoreOptions.RestoreProfileSchedules" id="restoreSchedules">
|
||||
<label class="form-check-label" for="restoreSchedules">
|
||||
<i class="fas fa-clock me-1"></i> Schedule (@selectedBackupInfo.ProfileSchedules.Count)
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" @bind="restoreOptions.OverwriteExisting" id="overwriteExisting">
|
||||
<label class="form-check-label" for="overwriteExisting">
|
||||
<i class="fas fa-exclamation-triangle text-warning me-1"></i>
|
||||
Sovrascrivi dati esistenti
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" @bind="restoreOptions.CreateBackupBeforeRestore" id="createBackupBefore">
|
||||
<label class="form-check-label" for="createBackupBefore">
|
||||
<i class="fas fa-shield-alt text-info me-1"></i>
|
||||
Crea backup di sicurezza prima del ripristino
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="importedBy" class="form-label">Importato da:</label>
|
||||
<input type="text" class="form-control" id="importedBy" @bind="restoreOptions.ImportedBy"
|
||||
placeholder="Nome utente">
|
||||
</div>
|
||||
}
|
||||
|
||||
<button class="btn btn-primary" @onclick="ImportBackup"
|
||||
disabled="@(isImporting || selectedBackupInfo == null)">
|
||||
@if (isImporting)
|
||||
{
|
||||
<div class="spinner-border spinner-border-sm me-2" role="status">
|
||||
<span class="visually-hidden">Loading...</span>
|
||||
</div>
|
||||
<text>Importazione...</text>
|
||||
}
|
||||
else
|
||||
{
|
||||
<i class="fas fa-upload me-2"></i>
|
||||
<text>Importa Backup</text>
|
||||
}
|
||||
</button>
|
||||
|
||||
@if (lastImportResult != null)
|
||||
{
|
||||
<div class="mt-3">
|
||||
<div class="alert alert-@(lastImportResult.Success ? "success" : "danger") alert-sm">
|
||||
<div class="d-flex justify-content-between align-items-start">
|
||||
<div>
|
||||
<strong>@(lastImportResult.Success ? "Successo" : "Errore"):</strong>
|
||||
<div>@lastImportResult.Message</div>
|
||||
@if (lastImportResult.Success)
|
||||
{
|
||||
<small class="text-muted">
|
||||
Durata: @lastImportResult.Duration.TotalSeconds.ToString("F1")s |
|
||||
Record importati: @(lastImportResult.ProcessedCounts.Profiles + lastImportResult.ProcessedCounts.Credentials + lastImportResult.ProcessedCounts.KeyAssociations + lastImportResult.ProcessedCounts.ProfileSchedules)
|
||||
</small>
|
||||
}
|
||||
@if (lastImportResult.Warnings.Any())
|
||||
{
|
||||
<div class="mt-2">
|
||||
<strong>Avvisi:</strong>
|
||||
<ul class="mb-0">
|
||||
@foreach (var warning in lastImportResult.Warnings)
|
||||
{
|
||||
<li><small>@warning</small></li>
|
||||
}
|
||||
</ul>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
<button type="button" class="btn-close btn-close-sm" @onclick="() => lastImportResult = null"></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Backup History Section -->
|
||||
<div class="row mt-4">
|
||||
<div class="col-12">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h5 class="card-title mb-0">
|
||||
<i class="fas fa-history text-secondary me-2"></i>
|
||||
Backup Recenti
|
||||
</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="d-flex justify-content-between align-items-center mb-3">
|
||||
<p class="text-muted mb-0">
|
||||
Elenco dei file di backup nella cartella Documenti/DataCoupler/Backups
|
||||
</p>
|
||||
<button class="btn btn-outline-secondary btn-sm" @onclick="RefreshBackupList">
|
||||
<i class="fas fa-refresh me-1"></i>
|
||||
Aggiorna
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@if (recentBackups.Any())
|
||||
{
|
||||
<div class="table-responsive">
|
||||
<table class="table table-sm table-hover">
|
||||
<thead class="table-light">
|
||||
<tr>
|
||||
<th>Nome File</th>
|
||||
<th>Data Creazione</th>
|
||||
<th>Dimensione</th>
|
||||
<th>Azioni</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach (var backup in recentBackups.Take(10))
|
||||
{
|
||||
<tr>
|
||||
<td>
|
||||
<i class="fas fa-file-code text-primary me-2"></i>
|
||||
@backup.Name
|
||||
</td>
|
||||
<td>
|
||||
<small>@backup.CreationTime.ToString("dd/MM/yyyy HH:mm")</small>
|
||||
</td>
|
||||
<td>
|
||||
<small>@FormatFileSize(backup.Length)</small>
|
||||
</td>
|
||||
<td>
|
||||
<div class="btn-group btn-group-sm">
|
||||
<button class="btn btn-outline-primary"
|
||||
@onclick="() => DownloadBackup(backup.FullName)"
|
||||
title="Scarica">
|
||||
<i class="fas fa-download"></i>
|
||||
</button>
|
||||
<button class="btn btn-outline-info"
|
||||
@onclick='() => OnShowToast.InvokeAsync(("Funzione non disponibile", "info"))'
|
||||
title="Carica per import">
|
||||
<i class="fas fa-upload"></i>
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="text-center py-4">
|
||||
<i class="fas fa-folder-open text-muted fa-3x mb-3"></i>
|
||||
<p class="text-muted">Nessun backup trovato</p>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@code {
|
||||
[Parameter] public EventCallback<(string message, string type)> OnShowToast { get; set; }
|
||||
|
||||
private BackupOptions exportOptions = new()
|
||||
{
|
||||
IncludeProfiles = true,
|
||||
IncludeCredentials = true,
|
||||
IncludeKeyAssociations = true,
|
||||
IncludeProfileSchedules = true,
|
||||
IncludeOnlyActiveRecords = true
|
||||
};
|
||||
|
||||
private RestoreOptions restoreOptions = new()
|
||||
{
|
||||
RestoreProfiles = true,
|
||||
RestoreCredentials = false, // Default false per sicurezza
|
||||
RestoreKeyAssociations = true,
|
||||
RestoreProfileSchedules = true,
|
||||
OverwriteExisting = false,
|
||||
CreateBackupBeforeRestore = true
|
||||
};
|
||||
|
||||
private bool isExporting = false;
|
||||
private bool isImporting = false;
|
||||
private BackupOperationResult? lastExportResult;
|
||||
private BackupOperationResult? lastImportResult;
|
||||
private SystemBackupData? selectedBackupInfo;
|
||||
private List<FileInfo> recentBackups = new();
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
await RefreshBackupList();
|
||||
}
|
||||
|
||||
private async Task ExportBackup()
|
||||
{
|
||||
try
|
||||
{
|
||||
isExporting = true;
|
||||
lastExportResult = null;
|
||||
StateHasChanged();
|
||||
|
||||
lastExportResult = await BackupService.ExportBackupAsync(exportOptions);
|
||||
|
||||
if (lastExportResult.Success)
|
||||
{
|
||||
await OnShowToast.InvokeAsync(($"Backup creato con successo", "success"));
|
||||
await RefreshBackupList();
|
||||
}
|
||||
else
|
||||
{
|
||||
await OnShowToast.InvokeAsync(("Errore durante la creazione del backup", "error"));
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError(ex, "Errore esportazione backup");
|
||||
await OnShowToast.InvokeAsync(($"Errore: {ex.Message}", "error"));
|
||||
}
|
||||
finally
|
||||
{
|
||||
isExporting = false;
|
||||
StateHasChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task OnFileSelected(InputFileChangeEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
var file = e.File;
|
||||
if (file != null)
|
||||
{
|
||||
using var stream = file.OpenReadStream(maxAllowedSize: 10 * 1024 * 1024); // 10MB max
|
||||
using var reader = new StreamReader(stream);
|
||||
var content = await reader.ReadToEndAsync();
|
||||
|
||||
selectedBackupInfo = await BackupService.GetBackupInfoAsync(content);
|
||||
StateHasChanged();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError(ex, "Errore lettura file backup");
|
||||
await OnShowToast.InvokeAsync(($"Errore lettura file: {ex.Message}", "error"));
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ImportBackup()
|
||||
{
|
||||
try
|
||||
{
|
||||
isImporting = true;
|
||||
lastImportResult = null;
|
||||
StateHasChanged();
|
||||
|
||||
if (selectedBackupInfo != null)
|
||||
{
|
||||
// Serializza il backup selezionato per l'import
|
||||
var backupContent = System.Text.Json.JsonSerializer.Serialize(selectedBackupInfo);
|
||||
|
||||
lastImportResult = await BackupService.ImportBackupFromJsonAsync(backupContent, restoreOptions);
|
||||
|
||||
if (lastImportResult.Success)
|
||||
{
|
||||
await OnShowToast.InvokeAsync(("Backup importato con successo", "success"));
|
||||
}
|
||||
else
|
||||
{
|
||||
await OnShowToast.InvokeAsync(("Errore durante l'importazione", "error"));
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError(ex, "Errore importazione backup");
|
||||
await OnShowToast.InvokeAsync(($"Errore: {ex.Message}", "error"));
|
||||
}
|
||||
finally
|
||||
{
|
||||
isImporting = false;
|
||||
StateHasChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private Task RefreshBackupList()
|
||||
{
|
||||
try
|
||||
{
|
||||
var documentsPath = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
|
||||
var backupFolder = Path.Combine(documentsPath, "DataCoupler", "Backups");
|
||||
|
||||
if (Directory.Exists(backupFolder))
|
||||
{
|
||||
var files = new DirectoryInfo(backupFolder)
|
||||
.GetFiles("*.json")
|
||||
.OrderByDescending(f => f.CreationTime)
|
||||
.ToList();
|
||||
|
||||
recentBackups = files;
|
||||
}
|
||||
else
|
||||
{
|
||||
recentBackups = new List<FileInfo>();
|
||||
}
|
||||
|
||||
StateHasChanged();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogWarning(ex, "Errore refresh lista backup");
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private async Task DownloadBackup(string filePath)
|
||||
{
|
||||
try
|
||||
{
|
||||
var fileName = Path.GetFileName(filePath);
|
||||
var bytes = await File.ReadAllBytesAsync(filePath);
|
||||
var stream = new MemoryStream(bytes);
|
||||
|
||||
using var streamRef = new DotNetStreamReference(stream);
|
||||
await JSRuntime.InvokeVoidAsync("downloadFileFromStream", fileName, streamRef);
|
||||
|
||||
await OnShowToast.InvokeAsync(($"Download di {fileName} avviato", "info"));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError(ex, "Errore download backup");
|
||||
await OnShowToast.InvokeAsync(($"Errore download: {ex.Message}", "error"));
|
||||
}
|
||||
}
|
||||
|
||||
private string FormatFileSize(long bytes)
|
||||
{
|
||||
string[] sizes = { "B", "KB", "MB", "GB" };
|
||||
double len = bytes;
|
||||
int order = 0;
|
||||
while (len >= 1024 && order < sizes.Length - 1)
|
||||
{
|
||||
order++;
|
||||
len = len / 1024;
|
||||
}
|
||||
return $"{len:0.##} {sizes[order]}";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,573 @@
|
||||
@inject ILogger<MaintenanceTab> Logger
|
||||
|
||||
<div class="settings-section">
|
||||
<h4>
|
||||
<i class="fas fa-tools text-success me-2"></i>
|
||||
Manutenzione Sistema
|
||||
</h4>
|
||||
<p class="text-muted">
|
||||
Strumenti per la manutenzione e l'ottimizzazione del sistema.
|
||||
</p>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h5 class="card-title mb-0">
|
||||
<i class="fas fa-database text-primary me-2"></i>
|
||||
Database
|
||||
</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<p class="card-text">Operazioni di manutenzione del database.</p>
|
||||
|
||||
<div class="mb-3">
|
||||
<h6>Stato Database</h6>
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<span>Dimensione:</span>
|
||||
<span class="badge bg-info">@databaseStats.SizeMB MB</span>
|
||||
</div>
|
||||
<div class="d-flex justify-content-between align-items-center mt-1">
|
||||
<span>Ultima ottimizzazione:</span>
|
||||
<span class="text-muted">@databaseStats.LastOptimization.ToString("dd/MM/yyyy HH:mm")</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="d-grid gap-2">
|
||||
<button class="btn btn-outline-primary" @onclick="OptimizeDatabase" disabled="@isOptimizing">
|
||||
@if (isOptimizing)
|
||||
{
|
||||
<div class="spinner-border spinner-border-sm me-2" role="status"></div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<i class="fas fa-cogs me-1"></i>
|
||||
}
|
||||
Ottimizza Database
|
||||
</button>
|
||||
|
||||
<button class="btn btn-outline-secondary" @onclick="AnalyzeDatabase" disabled="@isAnalyzing">
|
||||
@if (isAnalyzing)
|
||||
{
|
||||
<div class="spinner-border spinner-border-sm me-2" role="status"></div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<i class="fas fa-search me-1"></i>
|
||||
}
|
||||
Analizza Integrità
|
||||
</button>
|
||||
|
||||
<button class="btn btn-outline-warning" @onclick="CompactDatabase" disabled="@isCompacting">
|
||||
@if (isCompacting)
|
||||
{
|
||||
<div class="spinner-border spinner-border-sm me-2" role="status"></div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<i class="fas fa-compress-alt me-1"></i>
|
||||
}
|
||||
Compatta Database
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h5 class="card-title mb-0">
|
||||
<i class="fas fa-trash-alt text-warning me-2"></i>
|
||||
Pulizia Sistema
|
||||
</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<p class="card-text">Strumenti per la pulizia dei dati temporanei.</p>
|
||||
|
||||
<div class="mb-3">
|
||||
<h6>File temporanei</h6>
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<span>Dimensione cache:</span>
|
||||
<span class="badge bg-secondary">@cleanupStats.CacheSizeMB MB</span>
|
||||
</div>
|
||||
<div class="d-flex justify-content-between align-items-center mt-1">
|
||||
<span>File di log:</span>
|
||||
<span class="badge bg-secondary">@cleanupStats.LogFileCount file</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="d-grid gap-2">
|
||||
<button class="btn btn-outline-warning" @onclick="ClearTempFiles" disabled="@isClearingTemp">
|
||||
@if (isClearingTemp)
|
||||
{
|
||||
<div class="spinner-border spinner-border-sm me-2" role="status"></div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<i class="fas fa-broom me-1"></i>
|
||||
}
|
||||
Pulisci File Temporanei
|
||||
</button>
|
||||
|
||||
<button class="btn btn-outline-secondary" @onclick="ClearOldLogs" disabled="@isClearingLogs">
|
||||
@if (isClearingLogs)
|
||||
{
|
||||
<div class="spinner-border spinner-border-sm me-2" role="status"></div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<i class="fas fa-file-alt me-1"></i>
|
||||
}
|
||||
Pulisci Log Vecchi
|
||||
</button>
|
||||
|
||||
<button class="btn btn-outline-info" @onclick="RefreshStats">
|
||||
<i class="fas fa-sync-alt me-1"></i>
|
||||
Aggiorna Statistiche
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mt-4">
|
||||
<div class="col-md-12">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h5 class="card-title mb-0">
|
||||
<i class="fas fa-chart-line text-info me-2"></i>
|
||||
Monitoraggio Performance
|
||||
</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="row">
|
||||
<div class="col-md-3">
|
||||
<div class="text-center">
|
||||
<h6>Memoria Utilizzata</h6>
|
||||
<div class="progress mb-2">
|
||||
<div class="progress-bar" role="progressbar" style="width: @(performanceStats.MemoryUsagePercent)%"></div>
|
||||
</div>
|
||||
<small class="text-muted">@performanceStats.MemoryUsageMB MB / @performanceStats.TotalMemoryMB MB</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="text-center">
|
||||
<h6>CPU</h6>
|
||||
<div class="progress mb-2">
|
||||
<div class="progress-bar bg-warning" role="progressbar" style="width: @(performanceStats.CpuUsagePercent)%"></div>
|
||||
</div>
|
||||
<small class="text-muted">@performanceStats.CpuUsagePercent%</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="text-center">
|
||||
<h6>Connessioni Attive</h6>
|
||||
<h4 class="text-primary">@performanceStats.ActiveConnections</h4>
|
||||
<small class="text-muted">di @performanceStats.MaxConnections max</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="text-center">
|
||||
<h6>Uptime</h6>
|
||||
<h4 class="text-success">@performanceStats.UptimeHours h</h4>
|
||||
<small class="text-muted">@performanceStats.StartTime.ToString("dd/MM HH:mm")</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-3">
|
||||
<button class="btn btn-outline-primary" @onclick="GeneratePerformanceReport">
|
||||
<i class="fas fa-file-chart-column me-1"></i>
|
||||
Genera Report Performance
|
||||
</button>
|
||||
<button class="btn btn-outline-secondary ms-2" @onclick="RefreshPerformanceStats">
|
||||
<i class="fas fa-sync-alt me-1"></i>
|
||||
Aggiorna
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mt-4">
|
||||
<div class="col-md-6">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h5 class="card-title mb-0">
|
||||
<i class="fas fa-calendar-check text-primary me-2"></i>
|
||||
Manutenzione Programmata
|
||||
</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<p class="card-text">Configurazione delle attività automatiche.</p>
|
||||
|
||||
<div class="form-check mb-3">
|
||||
<input class="form-check-input" type="checkbox" @bind="maintenanceSettings.AutoOptimizeEnabled" id="autoOptimize">
|
||||
<label class="form-check-label" for="autoOptimize">
|
||||
Ottimizzazione automatica database
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="form-check mb-3">
|
||||
<input class="form-check-input" type="checkbox" @bind="maintenanceSettings.AutoCleanupEnabled" id="autoCleanup">
|
||||
<label class="form-check-label" for="autoCleanup">
|
||||
Pulizia automatica file temporanei
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="maintenanceTime" class="form-label">Orario manutenzione:</label>
|
||||
<input type="time" class="form-control" id="maintenanceTime" @bind="maintenanceSettings.ScheduledTime">
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="maintenanceFrequency" class="form-label">Frequenza:</label>
|
||||
<select class="form-select" id="maintenanceFrequency" @bind="maintenanceSettings.Frequency">
|
||||
<option value="Daily">Giornaliera</option>
|
||||
<option value="Weekly">Settimanale</option>
|
||||
<option value="Monthly">Mensile</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<button class="btn btn-primary" @onclick="SaveMaintenanceSettings">
|
||||
<i class="fas fa-save me-1"></i>
|
||||
Salva Configurazione
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h5 class="card-title mb-0">
|
||||
<i class="fas fa-history text-info me-2"></i>
|
||||
Storico Attività
|
||||
</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<p class="card-text">Ultime operazioni di manutenzione.</p>
|
||||
|
||||
<div class="maintenance-history">
|
||||
@foreach (var activity in maintenanceHistory)
|
||||
{
|
||||
<div class="d-flex justify-content-between align-items-center mb-2">
|
||||
<div>
|
||||
<i class="@GetActivityIcon(activity.Type) me-2"></i>
|
||||
@activity.Description
|
||||
</div>
|
||||
<small class="text-muted">@activity.Timestamp.ToString("dd/MM HH:mm")</small>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
@if (!maintenanceHistory.Any())
|
||||
{
|
||||
<div class="text-muted text-center py-3">
|
||||
<i class="fas fa-info-circle me-2"></i>
|
||||
Nessuna attività di manutenzione recente
|
||||
</div>
|
||||
}
|
||||
|
||||
<button class="btn btn-outline-secondary btn-sm mt-2" @onclick="ClearMaintenanceHistory">
|
||||
<i class="fas fa-trash-alt me-1"></i>
|
||||
Pulisci Storico
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@code {
|
||||
[Parameter] public EventCallback<(string message, string type)> OnShowToast { get; set; }
|
||||
|
||||
private bool isOptimizing = false;
|
||||
private bool isAnalyzing = false;
|
||||
private bool isCompacting = false;
|
||||
private bool isClearingTemp = false;
|
||||
private bool isClearingLogs = false;
|
||||
|
||||
private DatabaseStats databaseStats = new()
|
||||
{
|
||||
SizeMB = 12.5,
|
||||
LastOptimization = DateTime.Now.AddDays(-3)
|
||||
};
|
||||
|
||||
private CleanupStats cleanupStats = new()
|
||||
{
|
||||
CacheSizeMB = 45.2,
|
||||
LogFileCount = 127
|
||||
};
|
||||
|
||||
private PerformanceStats performanceStats = new()
|
||||
{
|
||||
MemoryUsageMB = 234,
|
||||
TotalMemoryMB = 512,
|
||||
MemoryUsagePercent = 45,
|
||||
CpuUsagePercent = 12,
|
||||
ActiveConnections = 3,
|
||||
MaxConnections = 100,
|
||||
UptimeHours = 72,
|
||||
StartTime = DateTime.Now.AddHours(-72)
|
||||
};
|
||||
|
||||
private MaintenanceSettings maintenanceSettings = new()
|
||||
{
|
||||
AutoOptimizeEnabled = true,
|
||||
AutoCleanupEnabled = true,
|
||||
ScheduledTime = new TimeOnly(2, 0),
|
||||
Frequency = "Weekly"
|
||||
};
|
||||
|
||||
private List<MaintenanceActivity> maintenanceHistory = new()
|
||||
{
|
||||
new MaintenanceActivity { Type = "Optimization", Description = "Database ottimizzato", Timestamp = DateTime.Now.AddHours(-6) },
|
||||
new MaintenanceActivity { Type = "Cleanup", Description = "File temporanei rimossi", Timestamp = DateTime.Now.AddDays(-1) },
|
||||
new MaintenanceActivity { Type = "Backup", Description = "Backup automatico completato", Timestamp = DateTime.Now.AddDays(-2) }
|
||||
};
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
await RefreshStats();
|
||||
}
|
||||
|
||||
private async Task OptimizeDatabase()
|
||||
{
|
||||
try
|
||||
{
|
||||
isOptimizing = true;
|
||||
StateHasChanged();
|
||||
|
||||
await Task.Delay(3000); // Simula ottimizzazione
|
||||
|
||||
databaseStats.LastOptimization = DateTime.Now;
|
||||
AddMaintenanceActivity("Optimization", "Database ottimizzato");
|
||||
|
||||
await OnShowToast.InvokeAsync(("Database ottimizzato con successo", "success"));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError(ex, "Errore ottimizzazione database");
|
||||
await OnShowToast.InvokeAsync(("Errore durante l'ottimizzazione", "error"));
|
||||
}
|
||||
finally
|
||||
{
|
||||
isOptimizing = false;
|
||||
StateHasChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task AnalyzeDatabase()
|
||||
{
|
||||
try
|
||||
{
|
||||
isAnalyzing = true;
|
||||
StateHasChanged();
|
||||
|
||||
await Task.Delay(2000);
|
||||
await OnShowToast.InvokeAsync(("Analisi completata: nessun problema rilevato", "success"));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError(ex, "Errore analisi database");
|
||||
await OnShowToast.InvokeAsync(("Errore durante l'analisi", "error"));
|
||||
}
|
||||
finally
|
||||
{
|
||||
isAnalyzing = false;
|
||||
StateHasChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task CompactDatabase()
|
||||
{
|
||||
try
|
||||
{
|
||||
isCompacting = true;
|
||||
StateHasChanged();
|
||||
|
||||
await Task.Delay(4000);
|
||||
|
||||
databaseStats.SizeMB *= 0.8; // Simula riduzione dimensioni
|
||||
AddMaintenanceActivity("Compact", "Database compattato");
|
||||
|
||||
await OnShowToast.InvokeAsync(("Database compattato con successo", "success"));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError(ex, "Errore compattazione database");
|
||||
await OnShowToast.InvokeAsync(("Errore durante la compattazione", "error"));
|
||||
}
|
||||
finally
|
||||
{
|
||||
isCompacting = false;
|
||||
StateHasChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ClearTempFiles()
|
||||
{
|
||||
try
|
||||
{
|
||||
isClearingTemp = true;
|
||||
StateHasChanged();
|
||||
|
||||
await Task.Delay(1500);
|
||||
|
||||
cleanupStats.CacheSizeMB = 0;
|
||||
AddMaintenanceActivity("Cleanup", "File temporanei rimossi");
|
||||
|
||||
await OnShowToast.InvokeAsync(("File temporanei rimossi", "success"));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError(ex, "Errore pulizia file temporanei");
|
||||
await OnShowToast.InvokeAsync(("Errore durante la pulizia", "error"));
|
||||
}
|
||||
finally
|
||||
{
|
||||
isClearingTemp = false;
|
||||
StateHasChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ClearOldLogs()
|
||||
{
|
||||
try
|
||||
{
|
||||
isClearingLogs = true;
|
||||
StateHasChanged();
|
||||
|
||||
await Task.Delay(1000);
|
||||
|
||||
cleanupStats.LogFileCount = Math.Max(0, cleanupStats.LogFileCount - 50);
|
||||
AddMaintenanceActivity("LogCleanup", "Log vecchi rimossi");
|
||||
|
||||
await OnShowToast.InvokeAsync(("Log vecchi rimossi", "success"));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError(ex, "Errore pulizia log");
|
||||
await OnShowToast.InvokeAsync(("Errore durante la pulizia log", "error"));
|
||||
}
|
||||
finally
|
||||
{
|
||||
isClearingLogs = false;
|
||||
StateHasChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task RefreshStats()
|
||||
{
|
||||
await Task.Delay(300);
|
||||
// Simula aggiornamento statistiche
|
||||
cleanupStats.CacheSizeMB = Random.Shared.Next(20, 80);
|
||||
cleanupStats.LogFileCount = Random.Shared.Next(50, 200);
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
private async Task RefreshPerformanceStats()
|
||||
{
|
||||
await Task.Delay(500);
|
||||
// Simula aggiornamento performance
|
||||
performanceStats.MemoryUsagePercent = Random.Shared.Next(30, 70);
|
||||
performanceStats.CpuUsagePercent = Random.Shared.Next(5, 25);
|
||||
performanceStats.ActiveConnections = Random.Shared.Next(1, 10);
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
private async Task GeneratePerformanceReport()
|
||||
{
|
||||
await OnShowToast.InvokeAsync(("Report performance generato", "info"));
|
||||
}
|
||||
|
||||
private async Task SaveMaintenanceSettings()
|
||||
{
|
||||
try
|
||||
{
|
||||
await Task.Delay(300);
|
||||
await OnShowToast.InvokeAsync(("Configurazione manutenzione salvata", "success"));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError(ex, "Errore salvataggio configurazione");
|
||||
await OnShowToast.InvokeAsync(("Errore salvataggio configurazione", "error"));
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ClearMaintenanceHistory()
|
||||
{
|
||||
maintenanceHistory.Clear();
|
||||
await OnShowToast.InvokeAsync(("Storico attività pulito", "info"));
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
private void AddMaintenanceActivity(string type, string description)
|
||||
{
|
||||
maintenanceHistory.Insert(0, new MaintenanceActivity
|
||||
{
|
||||
Type = type,
|
||||
Description = description,
|
||||
Timestamp = DateTime.Now
|
||||
});
|
||||
|
||||
// Mantieni solo le ultime 10 attività
|
||||
if (maintenanceHistory.Count > 10)
|
||||
{
|
||||
maintenanceHistory.RemoveAt(maintenanceHistory.Count - 1);
|
||||
}
|
||||
}
|
||||
|
||||
private string GetActivityIcon(string type) => type switch
|
||||
{
|
||||
"Optimization" => "fas fa-cogs text-primary",
|
||||
"Cleanup" => "fas fa-broom text-warning",
|
||||
"Backup" => "fas fa-download text-success",
|
||||
"Compact" => "fas fa-compress-alt text-info",
|
||||
"LogCleanup" => "fas fa-file-alt text-secondary",
|
||||
_ => "fas fa-cog text-muted"
|
||||
};
|
||||
|
||||
private class DatabaseStats
|
||||
{
|
||||
public double SizeMB { get; set; }
|
||||
public DateTime LastOptimization { get; set; }
|
||||
}
|
||||
|
||||
private class CleanupStats
|
||||
{
|
||||
public double CacheSizeMB { get; set; }
|
||||
public int LogFileCount { get; set; }
|
||||
}
|
||||
|
||||
private class PerformanceStats
|
||||
{
|
||||
public int MemoryUsageMB { get; set; }
|
||||
public int TotalMemoryMB { get; set; }
|
||||
public int MemoryUsagePercent { get; set; }
|
||||
public int CpuUsagePercent { get; set; }
|
||||
public int ActiveConnections { get; set; }
|
||||
public int MaxConnections { get; set; }
|
||||
public int UptimeHours { get; set; }
|
||||
public DateTime StartTime { get; set; }
|
||||
}
|
||||
|
||||
private class MaintenanceSettings
|
||||
{
|
||||
public bool AutoOptimizeEnabled { get; set; }
|
||||
public bool AutoCleanupEnabled { get; set; }
|
||||
public TimeOnly ScheduledTime { get; set; }
|
||||
public string Frequency { get; set; } = "Weekly";
|
||||
}
|
||||
|
||||
private class MaintenanceActivity
|
||||
{
|
||||
public string Type { get; set; } = "";
|
||||
public string Description { get; set; } = "";
|
||||
public DateTime Timestamp { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,346 @@
|
||||
@inject ILogger<SecurityTab> Logger
|
||||
|
||||
<div class="settings-section">
|
||||
<h4>
|
||||
<i class="fas fa-shield-alt text-primary me-2"></i>
|
||||
Sicurezza e Privacy
|
||||
</h4>
|
||||
<p class="text-muted">
|
||||
Configurazioni per la sicurezza dei dati e la gestione delle credenziali.
|
||||
</p>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h5 class="card-title mb-0">
|
||||
<i class="fas fa-lock text-danger me-2"></i>
|
||||
Crittografia
|
||||
</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<p class="card-text">Gestione della crittografia delle credenziali.</p>
|
||||
|
||||
<div class="alert alert-info">
|
||||
<i class="fas fa-info-circle me-2"></i>
|
||||
<strong>Data Protection API Attiva</strong><br>
|
||||
Le credenziali sono crittografate utilizzando la Data Protection API di .NET.
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<span>Stato Crittografia:</span>
|
||||
<span class="badge bg-success">
|
||||
<i class="fas fa-check-circle me-1"></i>
|
||||
Attiva
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<span>Algoritmo:</span>
|
||||
<span class="badge bg-info">AES-256</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button class="btn btn-outline-warning btn-sm" @onclick="RegenerateKeys" disabled="@isRegeneratingKeys">
|
||||
@if (isRegeneratingKeys)
|
||||
{
|
||||
<div class="spinner-border spinner-border-sm me-2" role="status"></div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<i class="fas fa-key me-1"></i>
|
||||
}
|
||||
Rigenera Chiavi
|
||||
</button>
|
||||
<div class="form-text">
|
||||
<i class="fas fa-exclamation-triangle text-warning me-1"></i>
|
||||
Attenzione: La rigenerazione delle chiavi renderà inaccessibili le credenziali esistenti
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h5 class="card-title mb-0">
|
||||
<i class="fas fa-user-shield text-success me-2"></i>
|
||||
Audit e Logging
|
||||
</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<p class="card-text">Configurazione del logging di sicurezza.</p>
|
||||
|
||||
<div class="form-check mb-3">
|
||||
<input class="form-check-input" type="checkbox" @bind="securitySettings.LogCredentialAccess" id="logCredentialAccess">
|
||||
<label class="form-check-label" for="logCredentialAccess">
|
||||
Log accesso alle credenziali
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="form-check mb-3">
|
||||
<input class="form-check-input" type="checkbox" @bind="securitySettings.LogDataTransfers" id="logDataTransfers">
|
||||
<label class="form-check-label" for="logDataTransfers">
|
||||
Log trasferimenti dati
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="form-check mb-3">
|
||||
<input class="form-check-input" type="checkbox" @bind="securitySettings.LogFailedOperations" id="logFailedOperations">
|
||||
<label class="form-check-label" for="logFailedOperations">
|
||||
Log operazioni fallite
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="logRetentionDays" class="form-label">Giorni di ritenzione log:</label>
|
||||
<input type="number" class="form-control" id="logRetentionDays" @bind="securitySettings.LogRetentionDays"
|
||||
min="1" max="365" step="1">
|
||||
</div>
|
||||
|
||||
<button class="btn btn-primary btn-sm" @onclick="SaveSecuritySettings">
|
||||
<i class="fas fa-save me-1"></i>
|
||||
Salva Impostazioni
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mt-4">
|
||||
<div class="col-md-6">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h5 class="card-title mb-0">
|
||||
<i class="fas fa-network-wired text-primary me-2"></i>
|
||||
Connessioni Sicure
|
||||
</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<p class="card-text">Impostazioni per le connessioni di rete.</p>
|
||||
|
||||
<div class="form-check mb-3">
|
||||
<input class="form-check-input" type="checkbox" @bind="securitySettings.EnforceHttps" id="enforceHttps">
|
||||
<label class="form-check-label" for="enforceHttps">
|
||||
Forza HTTPS per API REST
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="form-check mb-3">
|
||||
<input class="form-check-input" type="checkbox" @bind="securitySettings.ValidateSslCertificates" id="validateSsl">
|
||||
<label class="form-check-label" for="validateSsl">
|
||||
Valida certificati SSL/TLS
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="connectionTimeout" class="form-label">Timeout connessioni (secondi):</label>
|
||||
<input type="number" class="form-control" id="connectionTimeout" @bind="securitySettings.ConnectionTimeoutSeconds"
|
||||
min="5" max="300" step="5">
|
||||
</div>
|
||||
|
||||
<div class="alert alert-warning">
|
||||
<i class="fas fa-exclamation-triangle me-2"></i>
|
||||
<strong>Nota:</strong> Disabilitare la validazione SSL solo in ambienti di sviluppo sicuri.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h5 class="card-title mb-0">
|
||||
<i class="fas fa-database text-warning me-2"></i>
|
||||
Backup Sicurezza
|
||||
</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<p class="card-text">Configurazione dei backup di sicurezza.</p>
|
||||
|
||||
<div class="form-check mb-3">
|
||||
<input class="form-check-input" type="checkbox" @bind="securitySettings.AutoBackupEnabled" id="autoBackup">
|
||||
<label class="form-check-label" for="autoBackup">
|
||||
Backup automatico giornaliero
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="form-check mb-3">
|
||||
<input class="form-check-input" type="checkbox" @bind="securitySettings.EncryptBackups" id="encryptBackups">
|
||||
<label class="form-check-label" for="encryptBackups">
|
||||
Cripta file di backup
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="backupRetentionDays" class="form-label">Giorni di ritenzione backup:</label>
|
||||
<input type="number" class="form-control" id="backupRetentionDays" @bind="securitySettings.BackupRetentionDays"
|
||||
min="1" max="365" step="1">
|
||||
</div>
|
||||
|
||||
@if (securitySettings.AutoBackupEnabled)
|
||||
{
|
||||
<div class="alert alert-info">
|
||||
<i class="fas fa-clock me-2"></i>
|
||||
Prossimo backup automatico: <strong>Domani alle 02:00</strong>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mt-4">
|
||||
<div class="col-12">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h5 class="card-title mb-0">
|
||||
<i class="fas fa-exclamation-triangle text-danger me-2"></i>
|
||||
Azioni Critiche
|
||||
</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<h6>Pulizia Cache Credenziali</h6>
|
||||
<p class="text-muted">Pulisce la cache delle credenziali in memoria.</p>
|
||||
<button class="btn btn-outline-warning" @onclick="ClearCredentialCache">
|
||||
<i class="fas fa-broom me-1"></i>
|
||||
Pulisci Cache
|
||||
</button>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<h6>Reset Configurazione Sicurezza</h6>
|
||||
<p class="text-muted">Ripristina le impostazioni di sicurezza ai valori predefiniti.</p>
|
||||
<button class="btn btn-outline-danger" @onclick="ResetSecuritySettings">
|
||||
<i class="fas fa-undo me-1"></i>
|
||||
Reset Impostazioni
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@code {
|
||||
[Parameter] public EventCallback<(string message, string type)> OnShowToast { get; set; }
|
||||
|
||||
private bool isRegeneratingKeys = false;
|
||||
private SecuritySettings securitySettings = new()
|
||||
{
|
||||
LogCredentialAccess = true,
|
||||
LogDataTransfers = true,
|
||||
LogFailedOperations = true,
|
||||
LogRetentionDays = 30,
|
||||
EnforceHttps = true,
|
||||
ValidateSslCertificates = true,
|
||||
ConnectionTimeoutSeconds = 30,
|
||||
AutoBackupEnabled = false,
|
||||
EncryptBackups = true,
|
||||
BackupRetentionDays = 30
|
||||
};
|
||||
|
||||
private async Task SaveSecuritySettings()
|
||||
{
|
||||
try
|
||||
{
|
||||
// Implementazione semplificata - in produzione salvare nel database o file di configurazione
|
||||
await Task.Delay(500);
|
||||
|
||||
await OnShowToast.InvokeAsync(("Impostazioni di sicurezza salvate", "success"));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError(ex, "Errore salvataggio impostazioni sicurezza");
|
||||
await OnShowToast.InvokeAsync(("Errore salvataggio impostazioni", "error"));
|
||||
}
|
||||
}
|
||||
|
||||
private async Task RegenerateKeys()
|
||||
{
|
||||
try
|
||||
{
|
||||
isRegeneratingKeys = true;
|
||||
StateHasChanged();
|
||||
|
||||
// Simula rigenerazione chiavi
|
||||
await Task.Delay(2000);
|
||||
|
||||
await OnShowToast.InvokeAsync(("Chiavi di crittografia rigenerate", "success"));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError(ex, "Errore rigenerazione chiavi");
|
||||
await OnShowToast.InvokeAsync(("Errore rigenerazione chiavi", "error"));
|
||||
}
|
||||
finally
|
||||
{
|
||||
isRegeneratingKeys = false;
|
||||
StateHasChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ClearCredentialCache()
|
||||
{
|
||||
try
|
||||
{
|
||||
// Implementazione semplificata
|
||||
await Task.Delay(500);
|
||||
|
||||
await OnShowToast.InvokeAsync(("Cache credenziali pulita", "success"));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError(ex, "Errore pulizia cache");
|
||||
await OnShowToast.InvokeAsync(("Errore pulizia cache", "error"));
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ResetSecuritySettings()
|
||||
{
|
||||
try
|
||||
{
|
||||
securitySettings = new SecuritySettings
|
||||
{
|
||||
LogCredentialAccess = true,
|
||||
LogDataTransfers = true,
|
||||
LogFailedOperations = true,
|
||||
LogRetentionDays = 30,
|
||||
EnforceHttps = true,
|
||||
ValidateSslCertificates = true,
|
||||
ConnectionTimeoutSeconds = 30,
|
||||
AutoBackupEnabled = false,
|
||||
EncryptBackups = true,
|
||||
BackupRetentionDays = 30
|
||||
};
|
||||
|
||||
await Task.Delay(300);
|
||||
await OnShowToast.InvokeAsync(("Impostazioni di sicurezza ripristinate", "info"));
|
||||
StateHasChanged();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError(ex, "Errore reset impostazioni");
|
||||
await OnShowToast.InvokeAsync(("Errore reset impostazioni", "error"));
|
||||
}
|
||||
}
|
||||
|
||||
private class SecuritySettings
|
||||
{
|
||||
public bool LogCredentialAccess { get; set; }
|
||||
public bool LogDataTransfers { get; set; }
|
||||
public bool LogFailedOperations { get; set; }
|
||||
public int LogRetentionDays { get; set; }
|
||||
public bool EnforceHttps { get; set; }
|
||||
public bool ValidateSslCertificates { get; set; }
|
||||
public int ConnectionTimeoutSeconds { get; set; }
|
||||
public bool AutoBackupEnabled { get; set; }
|
||||
public bool EncryptBackups { get; set; }
|
||||
public int BackupRetentionDays { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,200 @@
|
||||
@inject ILogger<SystemTab> Logger
|
||||
|
||||
<div class="settings-section">
|
||||
<h4>
|
||||
<i class="fas fa-server text-primary me-2"></i>
|
||||
Configurazione Sistema
|
||||
</h4>
|
||||
<p class="text-muted">
|
||||
Impostazioni generali del sistema Data Coupler.
|
||||
</p>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h5 class="card-title mb-0">
|
||||
<i class="fas fa-database text-info me-2"></i>
|
||||
Database
|
||||
</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<p class="card-text">Configurazione del database principale.</p>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Percorso Database:</label>
|
||||
<input type="text" class="form-control" value="@GetDatabasePath()" readonly>
|
||||
<div class="form-text">Percorso del database SQLite di sistema</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Statistiche Database:</label>
|
||||
<div class="row">
|
||||
<div class="col-6">
|
||||
<div class="text-center p-2 bg-light rounded">
|
||||
<div class="h5 mb-1">@databaseStats.TotalProfiles</div>
|
||||
<small class="text-muted">Profili</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<div class="text-center p-2 bg-light rounded">
|
||||
<div class="h5 mb-1">@databaseStats.TotalCredentials</div>
|
||||
<small class="text-muted">Credenziali</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button class="btn btn-outline-primary btn-sm" @onclick="RefreshStats">
|
||||
<i class="fas fa-refresh me-1"></i>
|
||||
Aggiorna Statistiche
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h5 class="card-title mb-0">
|
||||
<i class="fas fa-cogs text-warning me-2"></i>
|
||||
Performance
|
||||
</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<p class="card-text">Impostazioni per ottimizzare le performance.</p>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="batchSize" class="form-label">Dimensione Batch Predefinita:</label>
|
||||
<input type="number" class="form-control" id="batchSize" @bind="systemSettings.DefaultBatchSize"
|
||||
min="1" max="1000" step="1">
|
||||
<div class="form-text">Numero di record da processare per volta</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="timeout" class="form-label">Timeout Connessioni (secondi):</label>
|
||||
<input type="number" class="form-control" id="timeout" @bind="systemSettings.DefaultTimeout"
|
||||
min="10" max="300" step="5">
|
||||
</div>
|
||||
|
||||
<button class="btn btn-primary btn-sm" @onclick="SaveSystemSettings">
|
||||
<i class="fas fa-save me-1"></i>
|
||||
Salva Impostazioni
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mt-4">
|
||||
<div class="col-12">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h5 class="card-title mb-0">
|
||||
<i class="fas fa-info-circle text-success me-2"></i>
|
||||
Informazioni Sistema
|
||||
</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="row">
|
||||
<div class="col-md-3">
|
||||
<strong>Versione Applicazione:</strong><br>
|
||||
<span class="badge bg-primary">1.0.0</span>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<strong>Framework:</strong><br>
|
||||
<span class="badge bg-info">.NET 9.0</span>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<strong>Sistema Operativo:</strong><br>
|
||||
<span class="badge bg-secondary">@Environment.OSVersion.Platform</span>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<strong>Memoria Utilizzata:</strong><br>
|
||||
<span class="badge bg-warning">@GetMemoryUsage()</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@code {
|
||||
[Parameter] public EventCallback<(string message, string type)> OnShowToast { get; set; }
|
||||
|
||||
private DatabaseStats databaseStats = new();
|
||||
private SystemSettings systemSettings = new()
|
||||
{
|
||||
DefaultBatchSize = 200,
|
||||
DefaultTimeout = 30
|
||||
};
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
await RefreshStats();
|
||||
}
|
||||
|
||||
private string GetDatabasePath()
|
||||
{
|
||||
// Implementazione semplificata - in un'implementazione reale
|
||||
// questo dovrebbe venire dalla configurazione
|
||||
var documentsPath = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
|
||||
return Path.Combine(documentsPath, "DataCoupler", "credentials.db");
|
||||
}
|
||||
|
||||
private string GetMemoryUsage()
|
||||
{
|
||||
var workingSet = Environment.WorkingSet;
|
||||
return $"{workingSet / 1024 / 1024:F1} MB";
|
||||
}
|
||||
|
||||
private async Task RefreshStats()
|
||||
{
|
||||
try
|
||||
{
|
||||
// Implementazione semplificata - in produzione collegarsi al database
|
||||
databaseStats = new DatabaseStats
|
||||
{
|
||||
TotalProfiles = Random.Shared.Next(5, 50),
|
||||
TotalCredentials = Random.Shared.Next(3, 20)
|
||||
};
|
||||
|
||||
await OnShowToast.InvokeAsync(("Statistiche aggiornate", "success"));
|
||||
StateHasChanged();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError(ex, "Errore aggiornamento statistiche");
|
||||
await OnShowToast.InvokeAsync(("Errore aggiornamento statistiche", "error"));
|
||||
}
|
||||
}
|
||||
|
||||
private async Task SaveSystemSettings()
|
||||
{
|
||||
try
|
||||
{
|
||||
// Implementazione semplificata - in produzione salvare nel database o file di configurazione
|
||||
await Task.Delay(500); // Simula operazione di salvataggio
|
||||
|
||||
await OnShowToast.InvokeAsync(("Impostazioni salvate con successo", "success"));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError(ex, "Errore salvataggio impostazioni");
|
||||
await OnShowToast.InvokeAsync(("Errore salvataggio impostazioni", "error"));
|
||||
}
|
||||
}
|
||||
|
||||
private class DatabaseStats
|
||||
{
|
||||
public int TotalProfiles { get; set; }
|
||||
public int TotalCredentials { get; set; }
|
||||
}
|
||||
|
||||
private class SystemSettings
|
||||
{
|
||||
public int DefaultBatchSize { get; set; }
|
||||
public int DefaultTimeout { get; set; }
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user