diff --git a/Data_Coupler/Pages/DataCoupler.razor b/Data_Coupler/Pages/DataCoupler.razor index 2d1718c..330035a 100644 --- a/Data_Coupler/Pages/DataCoupler.razor +++ b/Data_Coupler/Pages/DataCoupler.razor @@ -11,6 +11,9 @@ @using System.Text @using System.Data @using ExcelDataReader +@using Microsoft.AspNetCore.Components +@using Microsoft.AspNetCore.Components.Web +@using Components @inject IDataConnectionCredentialService CredentialService @inject IDataConnectionFactory ConnectionFactory @inject IJSRuntime JSRuntime diff --git a/Data_Coupler/Pages/ProfilesManagement.razor b/Data_Coupler/Pages/ProfilesManagement.razor new file mode 100644 index 0000000..50956e3 --- /dev/null +++ b/Data_Coupler/Pages/ProfilesManagement.razor @@ -0,0 +1,413 @@ +@page "/profiles" +@using CredentialManager.Models +@using CredentialManager.Services +@using Microsoft.AspNetCore.Components +@using Microsoft.AspNetCore.Components.Forms +@using Microsoft.AspNetCore.Components.Web +@using Microsoft.JSInterop +@using System.Text.Json + +Gestione Profili - Data Coupler + +
+ +
+
+
+
+

Gestione Profili

+

Gestisci e monitora tutti i profili di configurazione Data Coupler

+
+
+ +
+
+
+
+ + +
+
+
+
+
+
+

@totalProfiles

+

Profili Totali

+
+
+ +
+
+
+
+
+
+
+
+
+
+

@activeProfiles

+

Profili Attivi

+
+
+ +
+
+
+
+
+
+
+
+
+
+

@profilesThisWeek

+

Creati Questa Settimana

+
+
+ +
+
+
+
+
+
+
+
+
+
+

@unusedProfiles

+

Non Utilizzati

+
+
+ +
+
+
+
+
+
+ + +
+
+
+
+
Filtri e Ricerca
+
+
+
+
+ +
+ + + @if (!string.IsNullOrEmpty(searchTerm)) + { + + } +
+
+
+ + +
+
+ + +
+
+ + +
+
+
+
+
+
+ + +
+
+
+
+
Elenco Profili (@GetFilteredProfiles().Count())
+ +
+
+ @if (isLoading) + { +
+
+ Caricamento... +
+

Caricamento profili in corso...

+
+ } + else if (!GetFilteredProfiles().Any()) + { +
+ +
Nessun profilo trovato
+

Non ci sono profili che corrispondono ai criteri di ricerca.

+
+ } + else + { +
+ + + + + + + + + + + + + + + @foreach (var profile in GetFilteredProfiles()) + { + + + + + + + + + + + } + +
NomeDescrizioneTipoSorgente → DestinazioneMappingsCreatoUltimo UsoAzioni
+ @profile.Name + @if (profile.LastUsedAt.HasValue && profile.LastUsedAt.Value > DateTime.Now.AddDays(-7)) + { + Recente + } + + @if (!string.IsNullOrEmpty(profile.Description)) + { + + @(profile.Description.Length > 50 ? profile.Description.Substring(0, 50) + "..." : profile.Description) + + } + else + { + - + } + + + @GetSourceTypeDisplayName(profile.SourceType) + + + @profile.SourceTable → @profile.DestinationEndpoint + + @GetMappingCount(profile) + + @profile.CreatedAt.ToString("dd/MM/yyyy") + + @if (profile.LastUsedAt.HasValue) + { + @profile.LastUsedAt.Value.ToString("dd/MM/yyyy") + } + else + { + Mai + } + +
+ + +
+
+
+ } +
+
+
+
+
+ + +@if (showDetailsModal && selectedProfile != null) +{ + +} + + +@if (showDeleteModal && profileToDelete != null) +{ + +} + + +@if (!string.IsNullOrEmpty(toastMessage)) +{ +
+ +
+} diff --git a/Data_Coupler/Pages/ProfilesManagement.razor.cs b/Data_Coupler/Pages/ProfilesManagement.razor.cs new file mode 100644 index 0000000..77858f5 --- /dev/null +++ b/Data_Coupler/Pages/ProfilesManagement.razor.cs @@ -0,0 +1,277 @@ +using Microsoft.AspNetCore.Components; +using CredentialManager.Models; +using CredentialManager.Services; +using Microsoft.JSInterop; +using System.Text.Json; + +namespace Data_Coupler.Pages; + +public partial class ProfilesManagement : ComponentBase +{ + [Inject] private IDataCouplerProfileService ProfileService { get; set; } = null!; + [Inject] private IJSRuntime JSRuntime { get; set; } = null!; + [Inject] private ILogger Logger { get; set; } = null!; + + // State delle liste + private List allProfiles = new(); + + // State dei filtri + private string searchTerm = ""; + private string selectedSourceType = ""; + private string selectedUsageFilter = ""; + private string sortOrder = "name"; + + // State delle modali + private bool showDetailsModal = false; + private bool showDeleteModal = false; + private DataCouplerProfile? selectedProfile = null; + private DataCouplerProfile? profileToDelete = null; + + // State operazioni + private bool isLoading = true; + private bool isDeleting = false; + + // Toast notifications + private string toastMessage = ""; + private string toastType = "info"; + + // Statistiche + private int totalProfiles => allProfiles.Count; + private int activeProfiles => allProfiles.Count(p => p.LastUsedAt.HasValue && p.LastUsedAt.Value > DateTime.Now.AddDays(-30)); + private int profilesThisWeek => allProfiles.Count(p => p.CreatedAt > DateTime.Now.AddDays(-7)); + private int unusedProfiles => allProfiles.Count(p => !p.LastUsedAt.HasValue); + + protected override async Task OnInitializedAsync() + { + await LoadProfiles(); + } + + private async Task LoadProfiles() + { + try + { + isLoading = true; + var profiles = await ProfileService.GetAllProfilesAsync(); + allProfiles = profiles.ToList(); + } + catch (Exception ex) + { + Logger.LogError(ex, "Errore nel caricamento dei profili"); + ShowToast("Errore nel caricamento dei profili: " + ex.Message, "error"); + } + finally + { + isLoading = false; + StateHasChanged(); + } + } + + private async Task RefreshProfiles() + { + await LoadProfiles(); + } + + // Filtri e ricerca + private IEnumerable GetFilteredProfiles() + { + var filtered = allProfiles.AsEnumerable(); + + // Filtro per testo + if (!string.IsNullOrEmpty(searchTerm)) + { + filtered = filtered.Where(p => + p.Name.Contains(searchTerm, StringComparison.OrdinalIgnoreCase) || + (p.Description?.Contains(searchTerm, StringComparison.OrdinalIgnoreCase) ?? false)); + } + + // Filtro per tipo sorgente + if (!string.IsNullOrEmpty(selectedSourceType)) + { + filtered = filtered.Where(p => p.SourceType.Equals(selectedSourceType, StringComparison.OrdinalIgnoreCase)); + } + + // Filtro per utilizzo + if (!string.IsNullOrEmpty(selectedUsageFilter)) + { + filtered = selectedUsageFilter switch + { + "used" => filtered.Where(p => p.LastUsedAt.HasValue), + "unused" => filtered.Where(p => !p.LastUsedAt.HasValue), + "recent" => filtered.Where(p => p.LastUsedAt.HasValue && p.LastUsedAt.Value > DateTime.Now.AddDays(-7)), + _ => filtered + }; + } + + // Ordinamento + filtered = sortOrder switch + { + "created" => filtered.OrderByDescending(p => p.CreatedAt), + "lastused" => filtered.OrderByDescending(p => p.LastUsedAt ?? DateTime.MinValue), + _ => filtered.OrderBy(p => p.Name) + }; + + return filtered; + } + + private void FilterProfiles() + { + StateHasChanged(); + } + + private void ClearSearch() + { + searchTerm = ""; + FilterProfiles(); + } + + // Operazioni sui profili + private void ShowDetails(DataCouplerProfile profile) + { + selectedProfile = profile; + showDetailsModal = true; + StateHasChanged(); + } + + private void CloseDetailsModal() + { + showDetailsModal = false; + selectedProfile = null; + StateHasChanged(); + } + + private void ConfirmDelete(DataCouplerProfile profile) + { + profileToDelete = profile; + showDeleteModal = true; + StateHasChanged(); + } + + private void CancelDelete() + { + showDeleteModal = false; + profileToDelete = null; + StateHasChanged(); + } + + private async Task DeleteProfile() + { + if (profileToDelete == null) return; + + try + { + isDeleting = true; + StateHasChanged(); + + await ProfileService.DeleteProfileAsync(profileToDelete.Id); + allProfiles.Remove(profileToDelete); + + ShowToast("Profilo eliminato con successo", "success"); + CancelDelete(); + } + catch (Exception ex) + { + Logger.LogError(ex, "Errore nell'eliminazione del profilo {ProfileId}", profileToDelete.Id); + ShowToast("Errore nell'eliminazione: " + ex.Message, "error"); + } + finally + { + isDeleting = false; + StateHasChanged(); + } + } + + private async Task ExportProfiles() + { + try + { + var profilesToExport = GetFilteredProfiles().ToList(); + var json = JsonSerializer.Serialize(profilesToExport, new JsonSerializerOptions { WriteIndented = true }); + var fileName = $"profiles_export_{DateTime.Now:yyyyMMdd_HHmmss}.json"; + + var bytes = System.Text.Encoding.UTF8.GetBytes(json); + var stream = new MemoryStream(bytes); + + using var streamRef = new DotNetStreamReference(stream); + await JSRuntime.InvokeVoidAsync("downloadFileFromStream", fileName, streamRef); + + ShowToast("Esportazione completata", "success"); + } + catch (Exception ex) + { + Logger.LogError(ex, "Errore nell'esportazione dei profili"); + ShowToast("Errore nell'esportazione: " + ex.Message, "error"); + } + } + + // Helper methods per la UI + private string GetSourceTypeBadgeClass(string sourceType) + { + return sourceType?.ToLower() switch + { + "database" => "primary", + "file" => "success", + "rest" => "info", + _ => "secondary" + }; + } + + private string GetSourceTypeDisplayName(string sourceType) + { + return sourceType?.ToLower() switch + { + "database" => "Database", + "file" => "File", + "rest" => "REST API", + _ => "Sconosciuto" + }; + } + + private int GetMappingCount(DataCouplerProfile profile) + { + if (string.IsNullOrEmpty(profile.FieldMappingJson)) + return 0; + + try + { + var mappings = JsonSerializer.Deserialize>(profile.FieldMappingJson); + return mappings?.Count ?? 0; + } + catch + { + return 0; + } + } + + private List GetProfileMappings(DataCouplerProfile profile) + { + if (string.IsNullOrEmpty(profile.FieldMappingJson)) + return new List(); + + try + { + return JsonSerializer.Deserialize>(profile.FieldMappingJson) ?? new List(); + } + catch + { + return new List(); + } + } + + // Toast notifications + private void ShowToast(string message, string type = "info") + { + toastMessage = message; + toastType = type; + StateHasChanged(); + + // Auto-hide dopo 5 secondi + _ = Task.Delay(5000).ContinueWith(_ => ClearToast()); + } + + private void ClearToast() + { + toastMessage = ""; + toastType = "info"; + InvokeAsync(StateHasChanged); + } +} diff --git a/Data_Coupler/Pages/_Host.cshtml b/Data_Coupler/Pages/_Host.cshtml index 35b9527..2afe5c7 100644 --- a/Data_Coupler/Pages/_Host.cshtml +++ b/Data_Coupler/Pages/_Host.cshtml @@ -32,5 +32,18 @@ + diff --git a/Data_Coupler/Shared/NavMenu.razor b/Data_Coupler/Shared/NavMenu.razor index e0bfde3..28ef4fa 100644 --- a/Data_Coupler/Shared/NavMenu.razor +++ b/Data_Coupler/Shared/NavMenu.razor @@ -39,6 +39,11 @@ Gestione Associazioni Chiave +