04f0403f12
BREAKING CHANGE: Rimosso completamente il vecchio sistema RecordAssociation Modifiche principali: - Sostituito RecordAssociation con KeyAssociation basato sui valori delle chiavi - Implementata logica robusta di UPDATE vs INSERT basata su associazioni esistenti - Aggiunta normalizzazione delle chiavi (.Trim()) per consistenza - Implementato fallback nella ricerca associazioni per maggiore affidabilità - Sostituita verifica pre-UPDATE con tentativo diretto più efficiente Componenti modificati: - Nuovo modello: KeyAssociation.cs con campi ottimizzati - Nuovo servizio: KeyAssociationService.cs con metodi completi - Aggiornato: DataCoupler.razor con logica migliorata di gestione associazioni - Aggiornato: CredentialDbContext per gestire solo KeyAssociations - Aggiornati: tutti i servizi di interfaccia per supportare il nuovo sistema - Creata: pagina KeyAssociations.razor per gestione associazioni - Aggiornato: NavMenu.razor con link alla gestione associazioni Miglioramenti tecnici: - Logica di UPDATE più robusta: tenta direttamente l'aggiornamento invece di verificare prima l'esistenza - Gestione errori migliorata con cleanup automatico delle associazioni non valide - Debug logging estensivo per troubleshooting - Fallback nella ricerca associazioni se parametri specifici falliscono - Normalizzazione valori chiave per prevenire problemi di whitespace Risultato: Il sistema ora previene correttamente i duplicati utilizzando le associazioni per decidere se fare INSERT (nuovo record) o UPDATE (record esistente) basandosi sui valori delle chiavi. Database: - Creata migrazione EF per rimuovere RecordAssociations e aggiungere KeyAssociations - Eliminati file e codice legacy non più necessari
708 lines
29 KiB
Plaintext
708 lines
29 KiB
Plaintext
@page "/key-associations"
|
|
@using CredentialManager.Models
|
|
@using CredentialManager.Services
|
|
@using DataConnection.CredentialManagement.Interfaces
|
|
@using Microsoft.AspNetCore.Components.Forms
|
|
@using Microsoft.JSInterop
|
|
@inject IDataConnectionCredentialService CredentialService
|
|
@inject IJSRuntime JSRuntime
|
|
@inject ILogger<KeyAssociations> Logger
|
|
|
|
<PageTitle>Gestione Associazioni Chiavi</PageTitle>
|
|
|
|
<div class="container-fluid">
|
|
<div class="row">
|
|
<div class="col-12">
|
|
<h3><i class="fas fa-key"></i> Gestione Associazioni Chiavi</h3>
|
|
<p class="text-muted">
|
|
Visualizza e gestisci le associazioni basate sui valori delle chiavi.
|
|
Ogni associazione lega un valore di chiave univoco a un record di destinazione,
|
|
indipendentemente dalla sorgente che ha generato quel valore.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Statistiche -->
|
|
@if (statistics != null)
|
|
{
|
|
<div class="row mb-4">
|
|
<div class="col-md-2">
|
|
<div class="card bg-primary text-white">
|
|
<div class="card-body">
|
|
<div class="d-flex justify-content-between">
|
|
<div>
|
|
<h4 class="card-title">@statistics.TotalAssociations</h4>
|
|
<p class="card-text">Totali</p>
|
|
</div>
|
|
<div class="align-self-center">
|
|
<i class="fas fa-link fa-2x"></i>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-2">
|
|
<div class="card bg-success text-white">
|
|
<div class="card-body">
|
|
<div class="d-flex justify-content-between">
|
|
<div>
|
|
<h4 class="card-title">@statistics.ActiveAssociations</h4>
|
|
<p class="card-text">Attive</p>
|
|
</div>
|
|
<div class="align-self-center">
|
|
<i class="fas fa-check-circle fa-2x"></i>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-2">
|
|
<div class="card bg-warning text-white">
|
|
<div class="card-body">
|
|
<div class="d-flex justify-content-between">
|
|
<div>
|
|
<h4 class="card-title">@statistics.InactiveAssociations</h4>
|
|
<p class="card-text">Disattive</p>
|
|
</div>
|
|
<div class="align-self-center">
|
|
<i class="fas fa-pause-circle fa-2x"></i>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-2">
|
|
<div class="card bg-info text-white">
|
|
<div class="card-body">
|
|
<div class="d-flex justify-content-between">
|
|
<div>
|
|
<h4 class="card-title">@statistics.UniqueKeyValues</h4>
|
|
<p class="card-text">Chiavi Uniche</p>
|
|
</div>
|
|
<div class="align-self-center">
|
|
<i class="fas fa-key fa-2x"></i>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-2">
|
|
<div class="card bg-secondary text-white">
|
|
<div class="card-body">
|
|
<div class="d-flex justify-content-between">
|
|
<div>
|
|
<h4 class="card-title">@statistics.UniqueDestinationEntities</h4>
|
|
<p class="card-text">Entità</p>
|
|
</div>
|
|
<div class="align-self-center">
|
|
<i class="fas fa-database fa-2x"></i>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-2">
|
|
<div class="card bg-dark text-white">
|
|
<div class="card-body">
|
|
<div class="d-flex justify-content-between">
|
|
<div>
|
|
<h6 class="card-title">@(statistics.OldestAssociation?.ToString("dd/MM/yy") ?? "N/A")</h6>
|
|
<p class="card-text">Più Vecchia</p>
|
|
</div>
|
|
<div class="align-self-center">
|
|
<i class="fas fa-calendar fa-2x"></i>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
}
|
|
|
|
<!-- Filtri -->
|
|
<div class="row mb-4">
|
|
<div class="col-md-3">
|
|
<label class="form-label">Filtra per Valore Chiave:</label>
|
|
<input class="form-control" @bind="keyValueFilter" @bind:event="oninput" @onkeyup="ApplyFilters" placeholder="Valore chiave..." />
|
|
</div>
|
|
<div class="col-md-3">
|
|
<label class="form-label">Filtra per Entità:</label>
|
|
<input class="form-control" @bind="entityFilter" @bind:event="oninput" @onkeyup="ApplyFilters" placeholder="Nome entità..." />
|
|
</div>
|
|
<div class="col-md-3">
|
|
<label class="form-label">Filtra per Credenziale:</label>
|
|
<input class="form-control" @bind="credentialFilter" @bind:event="oninput" @onkeyup="ApplyFilters" placeholder="Nome credenziale..." />
|
|
</div>
|
|
<div class="col-md-3 d-flex align-items-end">
|
|
<button class="btn btn-outline-secondary me-2" @onclick="ClearFilters">
|
|
<i class="fas fa-times"></i> Pulisci Filtri
|
|
</button>
|
|
<button class="btn btn-primary" @onclick="RefreshAssociations">
|
|
<i class="fas fa-sync-alt"></i> Aggiorna
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Azioni di Gestione -->
|
|
<div class="row mb-4">
|
|
<div class="col-12">
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<h5 class="mb-0"><i class="fas fa-tools"></i> Gestione Associazioni</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="row">
|
|
<div class="col-md-6">
|
|
<h6>Operazioni di Pulizia</h6>
|
|
<div class="btn-group me-3">
|
|
<button class="btn btn-warning" @onclick="ValidateAssociations" disabled="@isProcessing">
|
|
<i class="fas fa-check-double"></i> Valida Associazioni
|
|
</button>
|
|
<button class="btn btn-danger" @onclick="CleanupInvalidAssociations" disabled="@isProcessing">
|
|
<i class="fas fa-broom"></i> Pulisci Non Valide
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<h6>Operazioni Avanzate</h6>
|
|
<div class="btn-group">
|
|
<button class="btn btn-info" @onclick="ExportAssociations" disabled="@isProcessing">
|
|
<i class="fas fa-download"></i> Esporta CSV
|
|
</button>
|
|
<button class="btn btn-danger" @onclick="ClearAllAssociations" disabled="@isProcessing">
|
|
<i class="fas fa-trash-alt"></i> Elimina Tutte
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
@if (isProcessing)
|
|
{
|
|
<div class="mt-3">
|
|
<div class="progress">
|
|
<div class="progress-bar progress-bar-striped progress-bar-animated" style="width: 100%"></div>
|
|
</div>
|
|
<small class="text-muted">@processingMessage</small>
|
|
</div>
|
|
}
|
|
|
|
@if (!string.IsNullOrEmpty(operationMessage))
|
|
{
|
|
<div class="alert alert-@operationMessageType alert-dismissible fade show mt-3" role="alert">
|
|
@operationMessage
|
|
<button type="button" class="btn-close" @onclick="() => operationMessage = string.Empty"></button>
|
|
</div>
|
|
}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Tabella Associazioni -->
|
|
@if (isLoading)
|
|
{
|
|
<div class="text-center">
|
|
<div class="spinner-border" role="status">
|
|
<span class="visually-hidden">Caricamento...</span>
|
|
</div>
|
|
<p>Caricamento associazioni...</p>
|
|
</div>
|
|
}
|
|
else if (!filteredAssociations.Any())
|
|
{
|
|
<div class="alert alert-info">
|
|
<i class="fas fa-info-circle"></i>
|
|
@if (!allAssociations.Any())
|
|
{
|
|
<span>Nessuna associazione trovata. Le associazioni vengono create automaticamente durante il trasferimento dati quando il sistema di associazioni è abilitato.</span>
|
|
}
|
|
else
|
|
{
|
|
<span>Nessuna associazione corrisponde ai filtri applicati. Prova a modificare i criteri di ricerca.</span>
|
|
}
|
|
</div>
|
|
}
|
|
else
|
|
{
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<h5 class="mb-0">
|
|
<i class="fas fa-table"></i> Associazioni Chiavi
|
|
<span class="badge bg-primary ms-2">@filteredAssociations.Count</span>
|
|
</h5>
|
|
</div>
|
|
<div class="card-body p-0">
|
|
<div class="table-responsive">
|
|
<table class="table table-striped table-hover mb-0">
|
|
<thead class="table-dark">
|
|
<tr>
|
|
<th>Valore Chiave</th>
|
|
<th>Campo Sorgente</th>
|
|
<th>Campo Destinazione</th>
|
|
<th>Entità Destinazione</th>
|
|
<th>ID Destinazione</th>
|
|
<th>Credenziale</th>
|
|
<th>Stato</th>
|
|
<th>Creata</th>
|
|
<th>Verificata</th>
|
|
<th>Azioni</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
@foreach (var association in pagedAssociations)
|
|
{
|
|
<tr class="@(association.IsActive ? "" : "table-secondary")">
|
|
<td>
|
|
<code class="small">@association.KeyValue</code>
|
|
</td>
|
|
<td>
|
|
<span class="badge bg-info">@association.SourceKeyField</span>
|
|
</td>
|
|
<td>
|
|
<span class="badge bg-secondary">@association.DestinationKeyField</span>
|
|
</td>
|
|
<td>
|
|
<strong>@association.DestinationEntity</strong>
|
|
</td>
|
|
<td>
|
|
<code class="small">@association.DestinationId</code>
|
|
</td>
|
|
<td>
|
|
<span class="badge bg-secondary">@association.RestCredentialName</span>
|
|
</td>
|
|
<td>
|
|
@if (association.IsActive)
|
|
{
|
|
<span class="badge bg-success">
|
|
<i class="fas fa-check"></i> Attiva
|
|
</span>
|
|
}
|
|
else
|
|
{
|
|
<span class="badge bg-warning">
|
|
<i class="fas fa-pause"></i> Disattivata
|
|
</span>
|
|
}
|
|
</td>
|
|
<td>
|
|
<small class="text-muted">
|
|
@association.CreatedAt.ToString("dd/MM/yyyy HH:mm")
|
|
</small>
|
|
</td>
|
|
<td>
|
|
<small class="text-muted">
|
|
@(association.LastVerifiedAt?.ToString("dd/MM/yyyy HH:mm") ?? "Mai")
|
|
</small>
|
|
</td>
|
|
<td>
|
|
<div class="btn-group btn-group-sm">
|
|
@if (association.IsActive)
|
|
{
|
|
<button class="btn btn-outline-warning" @onclick="() => DeactivateAssociation(association.Id)" title="Disattiva">
|
|
<i class="fas fa-pause"></i>
|
|
</button>
|
|
}
|
|
else
|
|
{
|
|
<button class="btn btn-outline-success" @onclick="() => ActivateAssociation(association.Id)" title="Riattiva">
|
|
<i class="fas fa-play"></i>
|
|
</button>
|
|
}
|
|
<button class="btn btn-outline-info" @onclick="() => ShowAssociationDetails(association)" title="Dettagli">
|
|
<i class="fas fa-info"></i>
|
|
</button>
|
|
<button class="btn btn-outline-danger" @onclick="() => DeleteAssociation(association.Id)" title="Elimina">
|
|
<i class="fas fa-trash"></i>
|
|
</button>
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Paginazione -->
|
|
@if (totalPages > 1)
|
|
{
|
|
<nav aria-label="Paginazione associazioni" class="mt-3">
|
|
<ul class="pagination justify-content-center">
|
|
<li class="page-item @(currentPage == 1 ? "disabled" : "")">
|
|
<a class="page-link" @onclick="() => ChangePage(currentPage - 1)">Precedente</a>
|
|
</li>
|
|
|
|
@for (int i = Math.Max(1, currentPage - 2); i <= Math.Min(totalPages, currentPage + 2); i++)
|
|
{
|
|
<li class="page-item @(i == currentPage ? "active" : "")">
|
|
<a class="page-link" @onclick="() => ChangePage(i)">@i</a>
|
|
</li>
|
|
}
|
|
|
|
<li class="page-item @(currentPage == totalPages ? "disabled" : "")">
|
|
<a class="page-link" @onclick="() => ChangePage(currentPage + 1)">Successiva</a>
|
|
</li>
|
|
</ul>
|
|
</nav>
|
|
}
|
|
}
|
|
</div>
|
|
|
|
@code {
|
|
// Dati
|
|
private List<KeyAssociation> allAssociations = new();
|
|
private List<KeyAssociation> filteredAssociations = new();
|
|
private List<KeyAssociation> pagedAssociations = new();
|
|
private AssociationStatistics? statistics;
|
|
|
|
// Filtri
|
|
private string keyValueFilter = "";
|
|
private string entityFilter = "";
|
|
private string credentialFilter = "";
|
|
|
|
// Paginazione
|
|
private int currentPage = 1;
|
|
private int pageSize = 25;
|
|
private int totalPages = 1;
|
|
|
|
// Stato
|
|
private bool isLoading = true;
|
|
private bool isProcessing = false;
|
|
private string processingMessage = "";
|
|
private string operationMessage = "";
|
|
private string operationMessageType = "";
|
|
|
|
protected override async Task OnInitializedAsync()
|
|
{
|
|
await RefreshAssociations();
|
|
}
|
|
|
|
private async Task RefreshAssociations()
|
|
{
|
|
isLoading = true;
|
|
operationMessage = "";
|
|
StateHasChanged();
|
|
|
|
try
|
|
{
|
|
allAssociations = await CredentialService.GetAllKeyAssociationsAsync();
|
|
statistics = await CredentialService.GetKeyAssociationStatisticsAsync();
|
|
ApplyFilters();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Logger.LogError(ex, "Errore nel caricamento delle associazioni");
|
|
SetOperationMessage($"Errore nel caricamento: {ex.Message}", "danger");
|
|
}
|
|
finally
|
|
{
|
|
isLoading = false;
|
|
StateHasChanged();
|
|
}
|
|
}
|
|
|
|
private void ApplyFilters()
|
|
{
|
|
filteredAssociations = allAssociations.Where(a =>
|
|
(string.IsNullOrEmpty(keyValueFilter) || a.KeyValue.Contains(keyValueFilter, StringComparison.OrdinalIgnoreCase)) &&
|
|
(string.IsNullOrEmpty(entityFilter) || a.DestinationEntity.Contains(entityFilter, StringComparison.OrdinalIgnoreCase)) &&
|
|
(string.IsNullOrEmpty(credentialFilter) || a.RestCredentialName.Contains(credentialFilter, StringComparison.OrdinalIgnoreCase))
|
|
).OrderByDescending(a => a.CreatedAt).ToList();
|
|
|
|
currentPage = 1;
|
|
UpdatePagedAssociations();
|
|
StateHasChanged();
|
|
}
|
|
|
|
private void ClearFilters()
|
|
{
|
|
keyValueFilter = "";
|
|
entityFilter = "";
|
|
credentialFilter = "";
|
|
ApplyFilters();
|
|
}
|
|
|
|
private void ChangePage(int page)
|
|
{
|
|
if (page >= 1 && page <= totalPages)
|
|
{
|
|
currentPage = page;
|
|
UpdatePagedAssociations();
|
|
}
|
|
}
|
|
|
|
private void UpdatePagedAssociations()
|
|
{
|
|
totalPages = (int)Math.Ceiling((double)filteredAssociations.Count / pageSize);
|
|
var startIndex = (currentPage - 1) * pageSize;
|
|
pagedAssociations = filteredAssociations.Skip(startIndex).Take(pageSize).ToList();
|
|
}
|
|
|
|
private async Task DeactivateAssociation(int id)
|
|
{
|
|
if (await JSRuntime.InvokeAsync<bool>("confirm", "Sei sicuro di voler disattivare questa associazione?"))
|
|
{
|
|
try
|
|
{
|
|
var success = await CredentialService.DeactivateKeyAssociationAsync(id);
|
|
if (success)
|
|
{
|
|
SetOperationMessage("Associazione disattivata con successo!", "success");
|
|
await RefreshAssociations();
|
|
}
|
|
else
|
|
{
|
|
SetOperationMessage("Errore nella disattivazione dell'associazione.", "danger");
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Logger.LogError(ex, "Errore nella disattivazione dell'associazione {Id}", id);
|
|
SetOperationMessage($"Errore: {ex.Message}", "danger");
|
|
}
|
|
}
|
|
}
|
|
|
|
private async Task ActivateAssociation(int id)
|
|
{
|
|
try
|
|
{
|
|
var association = allAssociations.FirstOrDefault(a => a.Id == id);
|
|
if (association != null)
|
|
{
|
|
association.IsActive = true;
|
|
association.UpdatedAt = DateTime.UtcNow;
|
|
|
|
var success = await CredentialService.UpdateKeyAssociationAsync(association);
|
|
if (success)
|
|
{
|
|
SetOperationMessage("Associazione riattivata con successo!", "success");
|
|
await RefreshAssociations();
|
|
}
|
|
else
|
|
{
|
|
SetOperationMessage("Errore nella riattivazione dell'associazione.", "danger");
|
|
}
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Logger.LogError(ex, "Errore nella riattivazione dell'associazione {Id}", id);
|
|
SetOperationMessage($"Errore: {ex.Message}", "danger");
|
|
}
|
|
}
|
|
|
|
private async Task DeleteAssociation(int id)
|
|
{
|
|
if (await JSRuntime.InvokeAsync<bool>("confirm", "Sei sicuro di voler eliminare questa associazione? Questa operazione non può essere annullata."))
|
|
{
|
|
try
|
|
{
|
|
var success = await CredentialService.DeleteKeyAssociationAsync(id);
|
|
if (success)
|
|
{
|
|
SetOperationMessage("Associazione eliminata con successo!", "success");
|
|
await RefreshAssociations();
|
|
}
|
|
else
|
|
{
|
|
SetOperationMessage("Errore nell'eliminazione dell'associazione.", "danger");
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Logger.LogError(ex, "Errore nell'eliminazione dell'associazione {Id}", id);
|
|
SetOperationMessage($"Errore: {ex.Message}", "danger");
|
|
}
|
|
}
|
|
}
|
|
|
|
private async Task ShowAssociationDetails(KeyAssociation association)
|
|
{
|
|
var info = $"Dettagli associazione:\n\n";
|
|
info += $"ID: {association.Id}\n";
|
|
info += $"Valore Chiave: {association.KeyValue}\n";
|
|
info += $"Campo Sorgente: {association.SourceKeyField}\n";
|
|
info += $"Campo Destinazione: {association.DestinationKeyField}\n";
|
|
info += $"Entità: {association.DestinationEntity}\n";
|
|
info += $"ID Destinazione: {association.DestinationId}\n";
|
|
info += $"Credenziale: {association.RestCredentialName}\n";
|
|
info += $"Creata: {association.CreatedAt:dd/MM/yyyy HH:mm}\n";
|
|
if (association.UpdatedAt.HasValue)
|
|
info += $"Aggiornata: {association.UpdatedAt:dd/MM/yyyy HH:mm}\n";
|
|
if (association.LastVerifiedAt.HasValue)
|
|
info += $"Verificata: {association.LastVerifiedAt:dd/MM/yyyy HH:mm}\n";
|
|
info += $"Stato: {(association.IsActive ? "Attiva" : "Disattivata")}\n";
|
|
|
|
if (!string.IsNullOrEmpty(association.SourcesInfo))
|
|
info += $"\nSorgenti:\n{association.SourcesInfo}\n";
|
|
|
|
if (!string.IsNullOrEmpty(association.AdditionalInfo))
|
|
info += $"\nInformazioni aggiuntive:\n{association.AdditionalInfo}";
|
|
|
|
await JSRuntime.InvokeVoidAsync("alert", info);
|
|
}
|
|
|
|
private async Task ValidateAssociations()
|
|
{
|
|
isProcessing = true;
|
|
processingMessage = "Validazione associazioni in corso...";
|
|
StateHasChanged();
|
|
|
|
try
|
|
{
|
|
int invalidCount = 0;
|
|
var uniqueDestinations = allAssociations
|
|
.GroupBy(a => new { a.DestinationEntity, a.RestCredentialName })
|
|
.ToList();
|
|
|
|
foreach (var group in uniqueDestinations)
|
|
{
|
|
var invalidAssociations = await CredentialService.GetInvalidKeyAssociationsAsync(
|
|
group.Key.DestinationEntity,
|
|
group.Key.RestCredentialName);
|
|
invalidCount += invalidAssociations.Count;
|
|
}
|
|
|
|
if (invalidCount == 0)
|
|
{
|
|
SetOperationMessage("Tutte le associazioni sono valide! Non sono stati trovati ID di destinazione non più esistenti.", "success");
|
|
}
|
|
else
|
|
{
|
|
SetOperationMessage($"Trovate {invalidCount} associazioni con ID di destinazione non più validi. Usa 'Pulisci Non Valide' per rimuoverle.", "warning");
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Logger.LogError(ex, "Errore nella validazione delle associazioni");
|
|
SetOperationMessage($"Errore nella validazione: {ex.Message}", "danger");
|
|
}
|
|
finally
|
|
{
|
|
isProcessing = false;
|
|
processingMessage = "";
|
|
StateHasChanged();
|
|
}
|
|
}
|
|
|
|
private async Task CleanupInvalidAssociations()
|
|
{
|
|
isProcessing = true;
|
|
processingMessage = "Pulizia associazioni non valide...";
|
|
StateHasChanged();
|
|
|
|
try
|
|
{
|
|
int totalCleaned = 0;
|
|
var uniqueDestinations = allAssociations
|
|
.GroupBy(a => new { a.DestinationEntity, a.RestCredentialName })
|
|
.ToList();
|
|
|
|
foreach (var group in uniqueDestinations)
|
|
{
|
|
var cleanedCount = await CredentialService.CleanupInvalidKeyAssociationsAsync(
|
|
group.Key.DestinationEntity,
|
|
group.Key.RestCredentialName);
|
|
totalCleaned += cleanedCount;
|
|
}
|
|
|
|
if (totalCleaned == 0)
|
|
{
|
|
SetOperationMessage("Nessuna associazione non valida trovata da pulire.", "info");
|
|
}
|
|
else
|
|
{
|
|
SetOperationMessage($"Pulite {totalCleaned} associazioni non valide!", "success");
|
|
await RefreshAssociations();
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Logger.LogError(ex, "Errore nella pulizia delle associazioni");
|
|
SetOperationMessage($"Errore nella pulizia: {ex.Message}", "danger");
|
|
}
|
|
finally
|
|
{
|
|
isProcessing = false;
|
|
processingMessage = "";
|
|
StateHasChanged();
|
|
}
|
|
}
|
|
|
|
private async Task ExportAssociations()
|
|
{
|
|
try
|
|
{
|
|
var csv = "Valore Chiave,Campo Sorgente,Campo Destinazione,Entità Destinazione,ID Destinazione,Credenziale,Stato,Creata,Aggiornata,Verificata\n";
|
|
|
|
foreach (var association in filteredAssociations)
|
|
{
|
|
csv += $"\"{association.KeyValue}\",\"{association.SourceKeyField}\",\"{association.DestinationKeyField}\",";
|
|
csv += $"\"{association.DestinationEntity}\",\"{association.DestinationId}\",\"{association.RestCredentialName}\",";
|
|
csv += $"\"{(association.IsActive ? "Attiva" : "Disattivata")}\",\"{association.CreatedAt:dd/MM/yyyy HH:mm}\",";
|
|
csv += $"\"{(association.UpdatedAt?.ToString("dd/MM/yyyy HH:mm") ?? "")}\",";
|
|
csv += $"\"{(association.LastVerifiedAt?.ToString("dd/MM/yyyy HH:mm") ?? "")}\"\n";
|
|
}
|
|
|
|
var bytes = System.Text.Encoding.UTF8.GetBytes(csv);
|
|
var fileName = $"associazioni_chiavi_{DateTime.Now:yyyyMMdd_HHmmss}.csv";
|
|
|
|
await JSRuntime.InvokeVoidAsync("downloadFile", fileName, "text/csv", System.Convert.ToBase64String(bytes));
|
|
SetOperationMessage($"File {fileName} esportato con successo!", "success");
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Logger.LogError(ex, "Errore nell'esportazione");
|
|
SetOperationMessage($"Errore nell'esportazione: {ex.Message}", "danger");
|
|
}
|
|
}
|
|
|
|
private async Task ClearAllAssociations()
|
|
{
|
|
if (await JSRuntime.InvokeAsync<bool>("confirm", "ATTENZIONE: Questa operazione eliminerà TUTTE le associazioni dal sistema. Sei assolutamente sicuro di voler procedere?"))
|
|
{
|
|
try
|
|
{
|
|
isProcessing = true;
|
|
processingMessage = "Eliminazione di tutte le associazioni...";
|
|
StateHasChanged();
|
|
|
|
var deletedCount = await CredentialService.ClearAllKeyAssociationsAsync();
|
|
|
|
SetOperationMessage($"Eliminate {deletedCount} associazioni dal sistema!", "success");
|
|
await RefreshAssociations();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Logger.LogError(ex, "Errore nell'eliminazione di massa");
|
|
SetOperationMessage($"Errore: {ex.Message}", "danger");
|
|
}
|
|
finally
|
|
{
|
|
isProcessing = false;
|
|
processingMessage = "";
|
|
StateHasChanged();
|
|
}
|
|
}
|
|
}
|
|
|
|
private void SetOperationMessage(string message, string type)
|
|
{
|
|
operationMessage = message;
|
|
operationMessageType = type;
|
|
}
|
|
}
|
|
|
|
<script>
|
|
window.downloadFile = function (fileName, contentType, data) {
|
|
const link = document.createElement('a');
|
|
link.download = fileName;
|
|
link.href = 'data:' + contentType + ';base64,' + data;
|
|
document.body.appendChild(link);
|
|
link.click();
|
|
document.body.removeChild(link);
|
|
}
|
|
</script>
|