feat: Aggiunto sistema completo di gestione profili per Data Coupler
- Creata nuova libreria Components con componenti Blazor riutilizzabili * ProfileSelector: dropdown per selezione profili salvati * ProfileSaver: componente per salvare configurazioni correnti come profili * ProfileManagement: modale per gestione profili salvati * ProfileQuickActions: bottoni azioni rapide per operazioni sui profili - Esteso CredentialManager con entità e servizi per DataCouplerProfile * Aggiunto modello DataCouplerProfile con configurazioni mapping e metadati * Implementata migrazione Entity Framework per memorizzazione profili * Creato DataCouplerProfileService per operazioni CRUD * Aggiunto CredentialDbContextFactory per operazioni database design-time - Migliorato componente principale DataCoupler con integrazione profili * Integrata funzionalità caricamento/salvataggio profili * Aggiunto selettore profili nella parte superiore dell'interfaccia * Mantenuta retrocompatibilità con funzionalità esistenti * Migliorata esperienza utente con gestione configurazioni salvate - Aggiornata struttura progetto e dipendenze * Aggiunto progetto Components alla soluzione * Aggiornati riferimenti progetti e import * Rimosso progetto obsoleto TestDatabaseFix Questo aggiornamento migliora significativamente il flusso di lavoro permettendo agli utenti di salvare, caricare e gestire configurazioni complete di accoppiamento dati come
This commit is contained in:
@@ -0,0 +1,22 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk.Razor">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net9.0</TargetFramework>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<SupportedPlatform Include="browser" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Microsoft.AspNetCore.Components.Web" Version="9.0.6" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\CredentialManager\CredentialManager.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
@@ -0,0 +1,180 @@
|
|||||||
|
@* Componente per la gestione completa dei profili *@
|
||||||
|
|
||||||
|
<!-- Modal per la gestione profili -->
|
||||||
|
@if (ShowModal)
|
||||||
|
{
|
||||||
|
<div class="modal fade show d-block" style="background-color: rgba(0,0,0,0.5);">
|
||||||
|
<div class="modal-dialog modal-lg modal-dialog-centered">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title">
|
||||||
|
<i class="fas fa-cogs"></i> Gestione Profili
|
||||||
|
</h5>
|
||||||
|
<button type="button" class="btn-close" @onclick="CloseModal"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
@if (IsLoading)
|
||||||
|
{
|
||||||
|
<div class="text-center py-4">
|
||||||
|
<div class="spinner-border" role="status">
|
||||||
|
<span class="visually-hidden">Caricamento...</span>
|
||||||
|
</div>
|
||||||
|
<p class="mt-2">Caricamento profili...</p>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
else if (Profiles == null || !Profiles.Any())
|
||||||
|
{
|
||||||
|
<div class="text-center py-4">
|
||||||
|
<i class="fas fa-folder-open fa-3x text-muted mb-3"></i>
|
||||||
|
<h6 class="text-muted">Nessun profilo salvato</h6>
|
||||||
|
<p class="text-muted">Configura una connessione e salva il tuo primo profilo!</p>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<!-- Filtro di ricerca -->
|
||||||
|
<div class="mb-3">
|
||||||
|
<div class="input-group">
|
||||||
|
<span class="input-group-text">
|
||||||
|
<i class="fas fa-search"></i>
|
||||||
|
</span>
|
||||||
|
<input type="text" class="form-control" @bind="SearchTerm" @oninput="FilterProfiles"
|
||||||
|
placeholder="Cerca profili per nome o descrizione..." />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Lista profili -->
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table table-hover">
|
||||||
|
<thead class="table-light">
|
||||||
|
<tr>
|
||||||
|
<th>Nome</th>
|
||||||
|
<th>Fonte → Destinazione</th>
|
||||||
|
<th>Creato</th>
|
||||||
|
<th>Ultimo Uso</th>
|
||||||
|
<th width="120">Azioni</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
@foreach (var profile in GetFilteredProfiles())
|
||||||
|
{
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<strong>@profile.Name</strong>
|
||||||
|
@if (!string.IsNullOrEmpty(profile.Description))
|
||||||
|
{
|
||||||
|
<br><small class="text-muted">@profile.Description</small>
|
||||||
|
}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<span class="badge bg-primary me-1">@GetTypeLabel(profile.SourceType)</span>
|
||||||
|
<i class="fas fa-arrow-right text-muted"></i>
|
||||||
|
<span class="badge bg-success ms-1">@GetTypeLabel(profile.DestinationType)</span>
|
||||||
|
<br>
|
||||||
|
<small class="text-muted">
|
||||||
|
@GetProfileSummary(profile)
|
||||||
|
</small>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<small>
|
||||||
|
@profile.CreatedAt.ToString("dd/MM/yyyy HH:mm")
|
||||||
|
@if (!string.IsNullOrEmpty(profile.CreatedBy))
|
||||||
|
{
|
||||||
|
<br><span class="text-muted">da @profile.CreatedBy</span>
|
||||||
|
}
|
||||||
|
</small>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<small>
|
||||||
|
@(profile.LastUsedAt?.ToString("dd/MM/yyyy HH:mm") ?? "Mai utilizzato")
|
||||||
|
</small>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<div class="btn-group btn-group-sm">
|
||||||
|
<button type="button" class="btn btn-outline-primary"
|
||||||
|
@onclick="() => LoadProfile(profile)"
|
||||||
|
title="Carica questo profilo">
|
||||||
|
<i class="fas fa-download"></i>
|
||||||
|
</button>
|
||||||
|
<button type="button" class="btn btn-outline-danger"
|
||||||
|
@onclick="() => ConfirmDelete(profile)"
|
||||||
|
title="Elimina questo profilo">
|
||||||
|
<i class="fas fa-trash"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@if (!GetFilteredProfiles().Any())
|
||||||
|
{
|
||||||
|
<div class="text-center py-3">
|
||||||
|
<i class="fas fa-search text-muted"></i>
|
||||||
|
<p class="text-muted mb-0">Nessun profilo corrisponde alla ricerca</p>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@if (!string.IsNullOrEmpty(Message))
|
||||||
|
{
|
||||||
|
<div class="alert alert-@(MessageType) mt-3">
|
||||||
|
<i class="fas fa-@(MessageType == "success" ? "check-circle" : "exclamation-triangle")"></i>
|
||||||
|
@Message
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary" @onclick="CloseModal">
|
||||||
|
<i class="fas fa-times"></i> Chiudi
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
<!-- Modal conferma eliminazione -->
|
||||||
|
@if (ShowDeleteConfirm && ProfileToDelete != null)
|
||||||
|
{
|
||||||
|
<div class="modal fade show d-block" style="background-color: rgba(0,0,0,0.7);">
|
||||||
|
<div class="modal-dialog modal-dialog-centered">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title text-danger">
|
||||||
|
<i class="fas fa-exclamation-triangle"></i> Conferma Eliminazione
|
||||||
|
</h5>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<p>Sei sicuro di voler eliminare il profilo <strong>"@ProfileToDelete.Name"</strong>?</p>
|
||||||
|
@if (!string.IsNullOrEmpty(ProfileToDelete.Description))
|
||||||
|
{
|
||||||
|
<p class="text-muted">@ProfileToDelete.Description</p>
|
||||||
|
}
|
||||||
|
<div class="alert alert-warning">
|
||||||
|
<i class="fas fa-info-circle"></i>
|
||||||
|
<strong>Attenzione:</strong> Questa operazione non può essere annullata.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary" @onclick="CancelDelete">
|
||||||
|
<i class="fas fa-times"></i> Annulla
|
||||||
|
</button>
|
||||||
|
<button type="button" class="btn btn-danger" @onclick="DeleteProfile" disabled="@IsDeleting">
|
||||||
|
@if (IsDeleting)
|
||||||
|
{
|
||||||
|
<span class="spinner-border spinner-border-sm" role="status"></span>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<i class="fas fa-trash"></i>
|
||||||
|
}
|
||||||
|
Elimina
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
@@ -0,0 +1,145 @@
|
|||||||
|
using Microsoft.AspNetCore.Components;
|
||||||
|
using CredentialManager.Models;
|
||||||
|
|
||||||
|
namespace Components;
|
||||||
|
|
||||||
|
public partial class ProfileManagement
|
||||||
|
{
|
||||||
|
[Parameter] public bool ShowModal { get; set; }
|
||||||
|
[Parameter] public List<DataCouplerProfile>? Profiles { get; set; }
|
||||||
|
[Parameter] public EventCallback OnCloseModal { get; set; }
|
||||||
|
[Parameter] public EventCallback<DataCouplerProfile> OnProfileLoaded { get; set; }
|
||||||
|
[Parameter] public EventCallback<int> OnProfileDeleted { get; set; }
|
||||||
|
[Parameter] public bool IsLoading { get; set; }
|
||||||
|
|
||||||
|
private string SearchTerm { get; set; } = "";
|
||||||
|
private string Message { get; set; } = "";
|
||||||
|
private string MessageType { get; set; } = "info";
|
||||||
|
private bool ShowDeleteConfirm { get; set; } = false;
|
||||||
|
private bool IsDeleting { get; set; } = false;
|
||||||
|
private DataCouplerProfile? ProfileToDelete { get; set; }
|
||||||
|
|
||||||
|
private void FilterProfiles(ChangeEventArgs e)
|
||||||
|
{
|
||||||
|
SearchTerm = e.Value?.ToString() ?? "";
|
||||||
|
}
|
||||||
|
|
||||||
|
private IEnumerable<DataCouplerProfile> GetFilteredProfiles()
|
||||||
|
{
|
||||||
|
if (Profiles == null)
|
||||||
|
return Enumerable.Empty<DataCouplerProfile>();
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(SearchTerm))
|
||||||
|
return Profiles;
|
||||||
|
|
||||||
|
var searchLower = SearchTerm.ToLower();
|
||||||
|
return Profiles.Where(p =>
|
||||||
|
p.Name.ToLower().Contains(searchLower) ||
|
||||||
|
(!string.IsNullOrEmpty(p.Description) && p.Description.ToLower().Contains(searchLower))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GetTypeLabel(string type)
|
||||||
|
{
|
||||||
|
return type switch
|
||||||
|
{
|
||||||
|
"database" => "DB",
|
||||||
|
"file" => "File",
|
||||||
|
"rest" => "REST",
|
||||||
|
_ => type.ToUpper()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GetProfileSummary(DataCouplerProfile profile)
|
||||||
|
{
|
||||||
|
var parts = new List<string>();
|
||||||
|
|
||||||
|
// Fonte
|
||||||
|
if (!string.IsNullOrEmpty(profile.SourceTable))
|
||||||
|
parts.Add($"da {profile.SourceTable}");
|
||||||
|
else if (!string.IsNullOrEmpty(profile.SourceFilePath))
|
||||||
|
parts.Add($"da {Path.GetFileName(profile.SourceFilePath)}");
|
||||||
|
|
||||||
|
// Destinazione
|
||||||
|
if (!string.IsNullOrEmpty(profile.DestinationTable))
|
||||||
|
parts.Add($"verso {profile.DestinationTable}");
|
||||||
|
else if (!string.IsNullOrEmpty(profile.DestinationEndpoint))
|
||||||
|
parts.Add($"verso {profile.DestinationEndpoint}");
|
||||||
|
|
||||||
|
return string.Join(" ", parts);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task CloseModal()
|
||||||
|
{
|
||||||
|
SearchTerm = "";
|
||||||
|
Message = "";
|
||||||
|
await OnCloseModal.InvokeAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task LoadProfile(DataCouplerProfile profile)
|
||||||
|
{
|
||||||
|
Message = $"Caricamento profilo '{profile.Name}'...";
|
||||||
|
MessageType = "info";
|
||||||
|
|
||||||
|
await OnProfileLoaded.InvokeAsync(profile);
|
||||||
|
|
||||||
|
Message = $"Profilo '{profile.Name}' caricato con successo!";
|
||||||
|
MessageType = "success";
|
||||||
|
|
||||||
|
// Chiudi il modal dopo un breve delay
|
||||||
|
await Task.Delay(1000);
|
||||||
|
await CloseModal();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ConfirmDelete(DataCouplerProfile profile)
|
||||||
|
{
|
||||||
|
ProfileToDelete = profile;
|
||||||
|
ShowDeleteConfirm = true;
|
||||||
|
Message = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CancelDelete()
|
||||||
|
{
|
||||||
|
ProfileToDelete = null;
|
||||||
|
ShowDeleteConfirm = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task DeleteProfile()
|
||||||
|
{
|
||||||
|
if (ProfileToDelete == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
IsDeleting = true;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await OnProfileDeleted.InvokeAsync(ProfileToDelete.Id);
|
||||||
|
|
||||||
|
Message = $"Profilo '{ProfileToDelete.Name}' eliminato con successo.";
|
||||||
|
MessageType = "success";
|
||||||
|
|
||||||
|
ShowDeleteConfirm = false;
|
||||||
|
ProfileToDelete = null;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Message = $"Errore nell'eliminazione: {ex.Message}";
|
||||||
|
MessageType = "danger";
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
IsDeleting = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetMessage(string message, string type = "info")
|
||||||
|
{
|
||||||
|
Message = message;
|
||||||
|
MessageType = type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ClearMessage()
|
||||||
|
{
|
||||||
|
Message = "";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,115 @@
|
|||||||
|
@* Componente per salvare la configurazione corrente come profilo *@
|
||||||
|
<div class="card mb-3">
|
||||||
|
<div class="card-header bg-success text-white">
|
||||||
|
<h6 class="mb-0">
|
||||||
|
<i class="fas fa-save"></i> Salva Configurazione Corrente
|
||||||
|
</h6>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
@if (!ShowSaveForm)
|
||||||
|
{
|
||||||
|
<button type="button" class="btn btn-success" @onclick="ShowSaveDialog" disabled="@(!CanSave)">
|
||||||
|
<i class="fas fa-plus"></i> Salva come Nuovo Profilo
|
||||||
|
</button>
|
||||||
|
@if (!CanSave)
|
||||||
|
{
|
||||||
|
<small class="text-muted d-block mt-1">
|
||||||
|
<i class="fas fa-info-circle"></i>
|
||||||
|
Configura fonte e destinazione per abilitare il salvataggio
|
||||||
|
</small>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<EditForm Model="ProfileData" OnValidSubmit="SaveProfile">
|
||||||
|
<DataAnnotationsValidator />
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label">Nome Profilo <span class="text-danger">*</span></label>
|
||||||
|
<InputText @bind-Value="ProfileData.Name" class="form-control"
|
||||||
|
placeholder="Es: Export Clienti a CRM" />
|
||||||
|
<ValidationMessage For="@(() => ProfileData.Name)" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label">Descrizione</label>
|
||||||
|
<InputText @bind-Value="ProfileData.Description" class="form-control"
|
||||||
|
placeholder="Descrizione opzionale del profilo" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@if (!string.IsNullOrEmpty(SaveMessage))
|
||||||
|
{
|
||||||
|
<div class="alert alert-@(SaveMessageType) mb-3">
|
||||||
|
<i class="fas fa-@(SaveMessageType == "success" ? "check-circle" : "exclamation-triangle")"></i>
|
||||||
|
@SaveMessage
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
<!-- Anteprima Configurazione -->
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label">Configurazione da salvare:</label>
|
||||||
|
<div class="bg-light p-3 rounded small">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<strong>Fonte:</strong> @GetSourceSummary()<br />
|
||||||
|
@if (!string.IsNullOrEmpty(SourceSchema))
|
||||||
|
{
|
||||||
|
<span class="text-muted">Schema: @SourceSchema</span><br />
|
||||||
|
}
|
||||||
|
@if (!string.IsNullOrEmpty(SourceTable))
|
||||||
|
{
|
||||||
|
<span class="text-muted">Tabella: @SourceTable</span>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<strong>Destinazione:</strong> @GetDestinationSummary()<br />
|
||||||
|
@if (!string.IsNullOrEmpty(DestinationSchema))
|
||||||
|
{
|
||||||
|
<span class="text-muted">Schema: @DestinationSchema</span><br />
|
||||||
|
}
|
||||||
|
@if (!string.IsNullOrEmpty(DestinationTable))
|
||||||
|
{
|
||||||
|
<span class="text-muted">Tabella: @DestinationTable</span>
|
||||||
|
}
|
||||||
|
@if (!string.IsNullOrEmpty(DestinationEndpoint))
|
||||||
|
{
|
||||||
|
<span class="text-muted">Endpoint: @DestinationEndpoint</span>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@if (FieldMappings != null && FieldMappings.Any())
|
||||||
|
{
|
||||||
|
<hr class="my-2" />
|
||||||
|
<small class="text-muted">
|
||||||
|
<i class="fas fa-exchange-alt"></i>
|
||||||
|
@FieldMappings.Count mapping dei campi configurati
|
||||||
|
</small>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="text-end">
|
||||||
|
<button type="button" class="btn btn-secondary me-2" @onclick="CancelSave">
|
||||||
|
<i class="fas fa-times"></i> Annulla
|
||||||
|
</button>
|
||||||
|
<button type="submit" class="btn btn-success" disabled="@IsSaving">
|
||||||
|
@if (IsSaving)
|
||||||
|
{
|
||||||
|
<span class="spinner-border spinner-border-sm" role="status"></span>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<i class="fas fa-save"></i>
|
||||||
|
}
|
||||||
|
Salva Profilo
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</EditForm>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,123 @@
|
|||||||
|
using Microsoft.AspNetCore.Components;
|
||||||
|
using CredentialManager.Models;
|
||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
|
||||||
|
namespace Components;
|
||||||
|
|
||||||
|
public partial class ProfileSaver
|
||||||
|
{
|
||||||
|
[Parameter] public bool CanSave { get; set; }
|
||||||
|
[Parameter] public string SourceType { get; set; } = "";
|
||||||
|
[Parameter] public int? SourceCredentialId { get; set; }
|
||||||
|
[Parameter] public string? SourceSchema { get; set; }
|
||||||
|
[Parameter] public string? SourceTable { get; set; }
|
||||||
|
[Parameter] public string? SourceFilePath { get; set; }
|
||||||
|
[Parameter] public string DestinationType { get; set; } = "";
|
||||||
|
[Parameter] public int? DestinationCredentialId { get; set; }
|
||||||
|
[Parameter] public string? DestinationSchema { get; set; }
|
||||||
|
[Parameter] public string? DestinationTable { get; set; }
|
||||||
|
[Parameter] public string? DestinationEndpoint { get; set; }
|
||||||
|
[Parameter] public List<FieldMappingDto>? FieldMappings { get; set; }
|
||||||
|
[Parameter] public EventCallback<DataCouplerProfileDto> OnProfileSaved { get; set; }
|
||||||
|
|
||||||
|
private bool ShowSaveForm { get; set; } = false;
|
||||||
|
private bool IsSaving { get; set; } = false;
|
||||||
|
private string SaveMessage { get; set; } = "";
|
||||||
|
private string SaveMessageType { get; set; } = "info";
|
||||||
|
private ProfileFormModel ProfileData { get; set; } = new();
|
||||||
|
|
||||||
|
private void ShowSaveDialog()
|
||||||
|
{
|
||||||
|
ProfileData = new ProfileFormModel();
|
||||||
|
ShowSaveForm = true;
|
||||||
|
SaveMessage = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CancelSave()
|
||||||
|
{
|
||||||
|
ShowSaveForm = false;
|
||||||
|
SaveMessage = "";
|
||||||
|
ProfileData = new();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task SaveProfile()
|
||||||
|
{
|
||||||
|
IsSaving = true;
|
||||||
|
SaveMessage = "";
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var profileDto = new DataCouplerProfileDto
|
||||||
|
{
|
||||||
|
Name = ProfileData.Name,
|
||||||
|
Description = ProfileData.Description,
|
||||||
|
SourceType = SourceType,
|
||||||
|
SourceCredentialId = SourceCredentialId,
|
||||||
|
SourceSchema = SourceSchema,
|
||||||
|
SourceTable = SourceTable,
|
||||||
|
SourceFilePath = SourceFilePath,
|
||||||
|
DestinationType = DestinationType,
|
||||||
|
DestinationCredentialId = DestinationCredentialId,
|
||||||
|
DestinationSchema = DestinationSchema,
|
||||||
|
DestinationTable = DestinationTable,
|
||||||
|
DestinationEndpoint = DestinationEndpoint,
|
||||||
|
FieldMappings = FieldMappings
|
||||||
|
};
|
||||||
|
|
||||||
|
await OnProfileSaved.InvokeAsync(profileDto);
|
||||||
|
|
||||||
|
SaveMessage = $"Profilo '{ProfileData.Name}' salvato con successo!";
|
||||||
|
SaveMessageType = "success";
|
||||||
|
|
||||||
|
// Reset form after successful save
|
||||||
|
await Task.Delay(1500); // Show success message briefly
|
||||||
|
ShowSaveForm = false;
|
||||||
|
ProfileData = new();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
SaveMessage = $"Errore nel salvataggio: {ex.Message}";
|
||||||
|
SaveMessageType = "danger";
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
IsSaving = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GetSourceSummary()
|
||||||
|
{
|
||||||
|
return SourceType switch
|
||||||
|
{
|
||||||
|
"database" => "Database",
|
||||||
|
"file" => "File Excel/CSV",
|
||||||
|
_ => "Non configurato"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GetDestinationSummary()
|
||||||
|
{
|
||||||
|
return DestinationType switch
|
||||||
|
{
|
||||||
|
"database" => "Database",
|
||||||
|
"rest" => "REST API",
|
||||||
|
_ => "Non configurato"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetMessage(string message, string type = "info")
|
||||||
|
{
|
||||||
|
SaveMessage = message;
|
||||||
|
SaveMessageType = type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ProfileFormModel
|
||||||
|
{
|
||||||
|
[Required(ErrorMessage = "Il nome del profilo è obbligatorio")]
|
||||||
|
[StringLength(100, ErrorMessage = "Il nome non può superare i 100 caratteri")]
|
||||||
|
public string Name { get; set; } = "";
|
||||||
|
|
||||||
|
[StringLength(500, ErrorMessage = "La descrizione non può superare i 500 caratteri")]
|
||||||
|
public string? Description { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
@* Componente per la selezione e caricamento dei profili *@
|
||||||
|
<div class="card mb-3">
|
||||||
|
<div class="card-header bg-info text-white">
|
||||||
|
<h6 class="mb-0">
|
||||||
|
<i class="fas fa-user-cog"></i> Gestione Profili
|
||||||
|
</h6>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="row">
|
||||||
|
<!-- Selezione Profilo Esistente -->
|
||||||
|
<div class="col-md-8">
|
||||||
|
<label class="form-label">Carica Profilo Salvato:</label>
|
||||||
|
<div class="input-group">
|
||||||
|
<select class="form-select" @onchange="OnProfileSelected">
|
||||||
|
<option value="">-- Seleziona un profilo --</option>
|
||||||
|
@if (Profiles != null)
|
||||||
|
{
|
||||||
|
@foreach (var profile in Profiles)
|
||||||
|
{
|
||||||
|
<option value="@profile.Id" selected="@(SelectedProfileId == profile.Id)">
|
||||||
|
@profile.Name @if (!string.IsNullOrEmpty(profile.Description)) { <span class="text-muted">- @profile.Description</span> }
|
||||||
|
</option>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</select>
|
||||||
|
<button type="button" class="btn btn-outline-primary" @onclick="LoadSelectedProfile"
|
||||||
|
disabled="@(SelectedProfileId == 0 || IsLoading)">
|
||||||
|
@if (IsLoading)
|
||||||
|
{
|
||||||
|
<span class="spinner-border spinner-border-sm" role="status"></span>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<i class="fas fa-download"></i>
|
||||||
|
}
|
||||||
|
Carica
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
@if (!string.IsNullOrEmpty(LoadMessage))
|
||||||
|
{
|
||||||
|
<div class="alert alert-@(LoadMessageType) alert-sm mt-2 mb-0">
|
||||||
|
<i class="fas fa-@(LoadMessageType == "success" ? "check-circle" : "exclamation-triangle")"></i>
|
||||||
|
@LoadMessage
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Pulsante Gestione Profili -->
|
||||||
|
<div class="col-md-4 d-flex align-items-end">
|
||||||
|
<button type="button" class="btn btn-outline-secondary w-100" @onclick="OpenProfileManagement">
|
||||||
|
<i class="fas fa-cogs"></i> Gestisci Profili
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
using Microsoft.AspNetCore.Components;
|
||||||
|
using CredentialManager.Models;
|
||||||
|
|
||||||
|
namespace Components;
|
||||||
|
|
||||||
|
public partial class ProfileSelector
|
||||||
|
{
|
||||||
|
[Parameter] public List<DataCouplerProfile>? Profiles { get; set; }
|
||||||
|
[Parameter] public EventCallback<DataCouplerProfile> OnProfileLoaded { get; set; }
|
||||||
|
[Parameter] public EventCallback OnManageProfiles { get; set; }
|
||||||
|
[Parameter] public bool IsLoading { get; set; }
|
||||||
|
|
||||||
|
private int SelectedProfileId { get; set; }
|
||||||
|
private string LoadMessage { get; set; } = "";
|
||||||
|
private string LoadMessageType { get; set; } = "info";
|
||||||
|
|
||||||
|
private void OnProfileSelected(ChangeEventArgs e)
|
||||||
|
{
|
||||||
|
if (int.TryParse(e.Value?.ToString(), out int profileId))
|
||||||
|
{
|
||||||
|
SelectedProfileId = profileId;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
SelectedProfileId = 0;
|
||||||
|
}
|
||||||
|
LoadMessage = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task LoadSelectedProfile()
|
||||||
|
{
|
||||||
|
if (SelectedProfileId == 0 || Profiles == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var selectedProfile = Profiles.FirstOrDefault(p => p.Id == SelectedProfileId);
|
||||||
|
if (selectedProfile != null)
|
||||||
|
{
|
||||||
|
LoadMessage = $"Profilo '{selectedProfile.Name}' caricato con successo!";
|
||||||
|
LoadMessageType = "success";
|
||||||
|
await OnProfileLoaded.InvokeAsync(selectedProfile);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
LoadMessage = "Errore nel caricamento del profilo selezionato.";
|
||||||
|
LoadMessageType = "danger";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task OpenProfileManagement()
|
||||||
|
{
|
||||||
|
await OnManageProfiles.InvokeAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ClearMessage()
|
||||||
|
{
|
||||||
|
LoadMessage = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetMessage(string message, string type = "info")
|
||||||
|
{
|
||||||
|
LoadMessage = message;
|
||||||
|
LoadMessageType = type;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
@using Microsoft.AspNetCore.Components.Web
|
||||||
|
@using Microsoft.AspNetCore.Components.Forms
|
||||||
|
@using CredentialManager.Models
|
||||||
|
@using CredentialManager.Services
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<configuration>
|
||||||
|
<packageSources>
|
||||||
|
<!--To inherit the global NuGet package sources remove the <clear/> line below -->
|
||||||
|
<clear />
|
||||||
|
<add key="nuget" value="https://api.nuget.org/v3/index.json" />
|
||||||
|
</packageSources>
|
||||||
|
</configuration>
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 378 B |
@@ -40,6 +40,7 @@ public static class CredentialManagerConfiguration
|
|||||||
services.AddScoped<IEncryptionService, EncryptionService>();
|
services.AddScoped<IEncryptionService, EncryptionService>();
|
||||||
services.AddScoped<ICredentialService, CredentialService>();
|
services.AddScoped<ICredentialService, CredentialService>();
|
||||||
services.AddScoped<IDatabaseInitializer, DatabaseInitializer>();
|
services.AddScoped<IDatabaseInitializer, DatabaseInitializer>();
|
||||||
|
services.AddScoped<IDataCouplerProfileService, DataCouplerProfileService>();
|
||||||
|
|
||||||
return services;
|
return services;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ public class CredentialDbContext : DbContext
|
|||||||
{
|
{
|
||||||
public DbSet<CredentialEntity> Credentials { get; set; }
|
public DbSet<CredentialEntity> Credentials { get; set; }
|
||||||
public DbSet<KeyAssociation> KeyAssociations { get; set; }
|
public DbSet<KeyAssociation> KeyAssociations { get; set; }
|
||||||
|
public DbSet<DataCouplerProfile> DataCouplerProfiles { get; set; }
|
||||||
|
|
||||||
public CredentialDbContext(DbContextOptions<CredentialDbContext> options) : base(options)
|
public CredentialDbContext(DbContextOptions<CredentialDbContext> options) : base(options)
|
||||||
{
|
{
|
||||||
@@ -141,5 +142,80 @@ public class CredentialDbContext : DbContext
|
|||||||
entity.HasIndex(e => e.CreatedAt);
|
entity.HasIndex(e => e.CreatedAt);
|
||||||
entity.HasIndex(e => e.LastVerifiedAt);
|
entity.HasIndex(e => e.LastVerifiedAt);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Configurazione della tabella DataCouplerProfiles
|
||||||
|
modelBuilder.Entity<DataCouplerProfile>(entity =>
|
||||||
|
{
|
||||||
|
entity.ToTable("DataCouplerProfiles");
|
||||||
|
|
||||||
|
entity.HasKey(e => e.Id);
|
||||||
|
|
||||||
|
entity.Property(e => e.Name)
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(100);
|
||||||
|
|
||||||
|
entity.Property(e => e.Description)
|
||||||
|
.HasMaxLength(500);
|
||||||
|
|
||||||
|
entity.Property(e => e.SourceType)
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(20);
|
||||||
|
|
||||||
|
entity.Property(e => e.SourceSchema)
|
||||||
|
.HasMaxLength(200);
|
||||||
|
|
||||||
|
entity.Property(e => e.SourceTable)
|
||||||
|
.HasMaxLength(200);
|
||||||
|
|
||||||
|
entity.Property(e => e.SourceFilePath)
|
||||||
|
.HasMaxLength(500);
|
||||||
|
|
||||||
|
entity.Property(e => e.DestinationType)
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(20);
|
||||||
|
|
||||||
|
entity.Property(e => e.DestinationSchema)
|
||||||
|
.HasMaxLength(200);
|
||||||
|
|
||||||
|
entity.Property(e => e.DestinationTable)
|
||||||
|
.HasMaxLength(200);
|
||||||
|
|
||||||
|
entity.Property(e => e.DestinationEndpoint)
|
||||||
|
.HasMaxLength(500);
|
||||||
|
|
||||||
|
entity.Property(e => e.FieldMappingJson)
|
||||||
|
.HasMaxLength(4000);
|
||||||
|
|
||||||
|
entity.Property(e => e.CreatedBy)
|
||||||
|
.HasMaxLength(100);
|
||||||
|
|
||||||
|
// Valori di default
|
||||||
|
entity.Property(e => e.CreatedAt)
|
||||||
|
.HasDefaultValueSql("CURRENT_TIMESTAMP");
|
||||||
|
|
||||||
|
entity.Property(e => e.IsActive)
|
||||||
|
.HasDefaultValue(true);
|
||||||
|
|
||||||
|
// Indici
|
||||||
|
entity.HasIndex(e => e.Name)
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
|
entity.HasIndex(e => e.SourceType);
|
||||||
|
entity.HasIndex(e => e.DestinationType);
|
||||||
|
entity.HasIndex(e => e.IsActive);
|
||||||
|
entity.HasIndex(e => e.CreatedAt);
|
||||||
|
entity.HasIndex(e => e.LastUsedAt);
|
||||||
|
|
||||||
|
// Relazioni con le credenziali
|
||||||
|
entity.HasOne(e => e.SourceCredential)
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey(e => e.SourceCredentialId)
|
||||||
|
.OnDelete(DeleteBehavior.SetNull);
|
||||||
|
|
||||||
|
entity.HasOne(e => e.DestinationCredential)
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey(e => e.DestinationCredentialId)
|
||||||
|
.OnDelete(DeleteBehavior.SetNull);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Design;
|
||||||
|
|
||||||
|
namespace CredentialManager.Data;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Factory per creare il DbContext durante la fase di design (migrations)
|
||||||
|
/// </summary>
|
||||||
|
public class CredentialDbContextFactory : IDesignTimeDbContextFactory<CredentialDbContext>
|
||||||
|
{
|
||||||
|
public CredentialDbContext CreateDbContext(string[] args)
|
||||||
|
{
|
||||||
|
var optionsBuilder = new DbContextOptionsBuilder<CredentialDbContext>();
|
||||||
|
|
||||||
|
// Usa un database SQLite temporaneo per le migrations
|
||||||
|
var connectionString = "Data Source=design_time_temp.db";
|
||||||
|
optionsBuilder.UseSqlite(connectionString);
|
||||||
|
|
||||||
|
return new CredentialDbContext(optionsBuilder.Options);
|
||||||
|
}
|
||||||
|
}
|
||||||
+326
@@ -0,0 +1,326 @@
|
|||||||
|
// <auto-generated />
|
||||||
|
using System;
|
||||||
|
using CredentialManager.Data;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace CredentialManager.Migrations
|
||||||
|
{
|
||||||
|
[DbContext(typeof(CredentialDbContext))]
|
||||||
|
[Migration("20250701203438_AddDataCouplerProfiles")]
|
||||||
|
partial class AddDataCouplerProfiles
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||||
|
{
|
||||||
|
#pragma warning disable 612, 618
|
||||||
|
modelBuilder.HasAnnotation("ProductVersion", "9.0.0");
|
||||||
|
|
||||||
|
modelBuilder.Entity("CredentialManager.Models.CredentialEntity", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("AdditionalParameters")
|
||||||
|
.HasMaxLength(2000)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<int>("CommandTimeout")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasDefaultValue(30);
|
||||||
|
|
||||||
|
b.Property<string>("ConnectionString")
|
||||||
|
.HasMaxLength(500)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("CreatedBy")
|
||||||
|
.HasMaxLength(100)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("DatabaseName")
|
||||||
|
.HasMaxLength(100)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("DatabaseType")
|
||||||
|
.HasMaxLength(50)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("EncryptedApiKey")
|
||||||
|
.HasMaxLength(500)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("EncryptedAuthToken")
|
||||||
|
.HasMaxLength(500)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("EncryptedPassword")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("Headers")
|
||||||
|
.HasMaxLength(2000)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("Host")
|
||||||
|
.HasMaxLength(200)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<bool>("IgnoreSslErrors")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasDefaultValue(false);
|
||||||
|
|
||||||
|
b.Property<bool>("IsActive")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasDefaultValue(true);
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(100)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<int?>("Port")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("RestServiceType")
|
||||||
|
.HasMaxLength(50)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<int>("TimeoutSeconds")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasDefaultValue(100);
|
||||||
|
|
||||||
|
b.Property<string>("Type")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(50)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("UpdatedAt")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("Username")
|
||||||
|
.HasMaxLength(100)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("DatabaseType");
|
||||||
|
|
||||||
|
b.HasIndex("IsActive");
|
||||||
|
|
||||||
|
b.HasIndex("Name")
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
|
b.HasIndex("Type");
|
||||||
|
|
||||||
|
b.ToTable("Credentials", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("CredentialManager.Models.DataCouplerProfile", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasDefaultValueSql("CURRENT_TIMESTAMP");
|
||||||
|
|
||||||
|
b.Property<string>("CreatedBy")
|
||||||
|
.HasMaxLength(100)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("Description")
|
||||||
|
.HasMaxLength(500)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<int?>("DestinationCredentialId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("DestinationEndpoint")
|
||||||
|
.HasMaxLength(500)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("DestinationSchema")
|
||||||
|
.HasMaxLength(200)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("DestinationTable")
|
||||||
|
.HasMaxLength(200)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("DestinationType")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(20)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("FieldMappingJson")
|
||||||
|
.HasMaxLength(4000)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<bool>("IsActive")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasDefaultValue(true);
|
||||||
|
|
||||||
|
b.Property<DateTime?>("LastUsedAt")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(100)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<int?>("SourceCredentialId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("SourceFilePath")
|
||||||
|
.HasMaxLength(500)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("SourceSchema")
|
||||||
|
.HasMaxLength(200)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("SourceTable")
|
||||||
|
.HasMaxLength(200)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("SourceType")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(20)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("CreatedAt");
|
||||||
|
|
||||||
|
b.HasIndex("DestinationCredentialId");
|
||||||
|
|
||||||
|
b.HasIndex("DestinationType");
|
||||||
|
|
||||||
|
b.HasIndex("IsActive");
|
||||||
|
|
||||||
|
b.HasIndex("LastUsedAt");
|
||||||
|
|
||||||
|
b.HasIndex("Name")
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
|
b.HasIndex("SourceCredentialId");
|
||||||
|
|
||||||
|
b.HasIndex("SourceType");
|
||||||
|
|
||||||
|
b.ToTable("DataCouplerProfiles", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("CredentialManager.Models.KeyAssociation", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("AdditionalInfo")
|
||||||
|
.HasMaxLength(2000)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("DestinationEntity")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(200)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("DestinationId")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(200)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("DestinationKeyField")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(200)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<bool>("IsActive")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasDefaultValue(true);
|
||||||
|
|
||||||
|
b.Property<string>("KeyValue")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(500)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("LastVerifiedAt")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("RestCredentialName")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(100)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("SourceKeyField")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(200)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("SourcesInfo")
|
||||||
|
.HasMaxLength(2000)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("UpdatedAt")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("CreatedAt");
|
||||||
|
|
||||||
|
b.HasIndex("DestinationEntity");
|
||||||
|
|
||||||
|
b.HasIndex("IsActive");
|
||||||
|
|
||||||
|
b.HasIndex("KeyValue")
|
||||||
|
.HasDatabaseName("IX_KeyAssociations_KeyValue");
|
||||||
|
|
||||||
|
b.HasIndex("LastVerifiedAt");
|
||||||
|
|
||||||
|
b.HasIndex("RestCredentialName");
|
||||||
|
|
||||||
|
b.HasIndex("KeyValue", "DestinationEntity", "RestCredentialName")
|
||||||
|
.IsUnique()
|
||||||
|
.HasDatabaseName("IX_KeyAssociations_Unique");
|
||||||
|
|
||||||
|
b.ToTable("KeyAssociations", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("CredentialManager.Models.DataCouplerProfile", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("CredentialManager.Models.CredentialEntity", "DestinationCredential")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("DestinationCredentialId")
|
||||||
|
.OnDelete(DeleteBehavior.SetNull);
|
||||||
|
|
||||||
|
b.HasOne("CredentialManager.Models.CredentialEntity", "SourceCredential")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("SourceCredentialId")
|
||||||
|
.OnDelete(DeleteBehavior.SetNull);
|
||||||
|
|
||||||
|
b.Navigation("DestinationCredential");
|
||||||
|
|
||||||
|
b.Navigation("SourceCredential");
|
||||||
|
});
|
||||||
|
#pragma warning restore 612, 618
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,104 @@
|
|||||||
|
using System;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace CredentialManager.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class AddDataCouplerProfiles : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "DataCouplerProfiles",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<int>(type: "INTEGER", nullable: false)
|
||||||
|
.Annotation("Sqlite:Autoincrement", true),
|
||||||
|
Name = table.Column<string>(type: "TEXT", maxLength: 100, nullable: false),
|
||||||
|
Description = table.Column<string>(type: "TEXT", maxLength: 500, nullable: true),
|
||||||
|
SourceType = table.Column<string>(type: "TEXT", maxLength: 20, nullable: false),
|
||||||
|
SourceCredentialId = table.Column<int>(type: "INTEGER", nullable: true),
|
||||||
|
SourceSchema = table.Column<string>(type: "TEXT", maxLength: 200, nullable: true),
|
||||||
|
SourceTable = table.Column<string>(type: "TEXT", maxLength: 200, nullable: true),
|
||||||
|
SourceFilePath = table.Column<string>(type: "TEXT", maxLength: 500, nullable: true),
|
||||||
|
DestinationType = table.Column<string>(type: "TEXT", maxLength: 20, nullable: false),
|
||||||
|
DestinationCredentialId = table.Column<int>(type: "INTEGER", nullable: true),
|
||||||
|
DestinationSchema = table.Column<string>(type: "TEXT", maxLength: 200, nullable: true),
|
||||||
|
DestinationTable = table.Column<string>(type: "TEXT", maxLength: 200, nullable: true),
|
||||||
|
DestinationEndpoint = table.Column<string>(type: "TEXT", maxLength: 500, nullable: true),
|
||||||
|
FieldMappingJson = table.Column<string>(type: "TEXT", maxLength: 4000, nullable: true),
|
||||||
|
CreatedBy = table.Column<string>(type: "TEXT", maxLength: 100, nullable: true),
|
||||||
|
CreatedAt = table.Column<DateTime>(type: "TEXT", nullable: false, defaultValueSql: "CURRENT_TIMESTAMP"),
|
||||||
|
LastUsedAt = table.Column<DateTime>(type: "TEXT", nullable: true),
|
||||||
|
IsActive = table.Column<bool>(type: "INTEGER", nullable: false, defaultValue: true)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_DataCouplerProfiles", x => x.Id);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_DataCouplerProfiles_Credentials_DestinationCredentialId",
|
||||||
|
column: x => x.DestinationCredentialId,
|
||||||
|
principalTable: "Credentials",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.SetNull);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_DataCouplerProfiles_Credentials_SourceCredentialId",
|
||||||
|
column: x => x.SourceCredentialId,
|
||||||
|
principalTable: "Credentials",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.SetNull);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_DataCouplerProfiles_CreatedAt",
|
||||||
|
table: "DataCouplerProfiles",
|
||||||
|
column: "CreatedAt");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_DataCouplerProfiles_DestinationCredentialId",
|
||||||
|
table: "DataCouplerProfiles",
|
||||||
|
column: "DestinationCredentialId");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_DataCouplerProfiles_DestinationType",
|
||||||
|
table: "DataCouplerProfiles",
|
||||||
|
column: "DestinationType");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_DataCouplerProfiles_IsActive",
|
||||||
|
table: "DataCouplerProfiles",
|
||||||
|
column: "IsActive");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_DataCouplerProfiles_LastUsedAt",
|
||||||
|
table: "DataCouplerProfiles",
|
||||||
|
column: "LastUsedAt");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_DataCouplerProfiles_Name",
|
||||||
|
table: "DataCouplerProfiles",
|
||||||
|
column: "Name",
|
||||||
|
unique: true);
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_DataCouplerProfiles_SourceCredentialId",
|
||||||
|
table: "DataCouplerProfiles",
|
||||||
|
column: "SourceCredentialId");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_DataCouplerProfiles_SourceType",
|
||||||
|
table: "DataCouplerProfiles",
|
||||||
|
column: "SourceType");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "DataCouplerProfiles");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -15,7 +15,7 @@ namespace CredentialManager.Migrations
|
|||||||
protected override void BuildModel(ModelBuilder modelBuilder)
|
protected override void BuildModel(ModelBuilder modelBuilder)
|
||||||
{
|
{
|
||||||
#pragma warning disable 612, 618
|
#pragma warning disable 612, 618
|
||||||
modelBuilder.HasAnnotation("ProductVersion", "9.0.6");
|
modelBuilder.HasAnnotation("ProductVersion", "9.0.0");
|
||||||
|
|
||||||
modelBuilder.Entity("CredentialManager.Models.CredentialEntity", b =>
|
modelBuilder.Entity("CredentialManager.Models.CredentialEntity", b =>
|
||||||
{
|
{
|
||||||
@@ -123,6 +123,104 @@ namespace CredentialManager.Migrations
|
|||||||
b.ToTable("Credentials", (string)null);
|
b.ToTable("Credentials", (string)null);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("CredentialManager.Models.DataCouplerProfile", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasDefaultValueSql("CURRENT_TIMESTAMP");
|
||||||
|
|
||||||
|
b.Property<string>("CreatedBy")
|
||||||
|
.HasMaxLength(100)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("Description")
|
||||||
|
.HasMaxLength(500)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<int?>("DestinationCredentialId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("DestinationEndpoint")
|
||||||
|
.HasMaxLength(500)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("DestinationSchema")
|
||||||
|
.HasMaxLength(200)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("DestinationTable")
|
||||||
|
.HasMaxLength(200)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("DestinationType")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(20)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("FieldMappingJson")
|
||||||
|
.HasMaxLength(4000)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<bool>("IsActive")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasDefaultValue(true);
|
||||||
|
|
||||||
|
b.Property<DateTime?>("LastUsedAt")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(100)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<int?>("SourceCredentialId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("SourceFilePath")
|
||||||
|
.HasMaxLength(500)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("SourceSchema")
|
||||||
|
.HasMaxLength(200)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("SourceTable")
|
||||||
|
.HasMaxLength(200)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("SourceType")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(20)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("CreatedAt");
|
||||||
|
|
||||||
|
b.HasIndex("DestinationCredentialId");
|
||||||
|
|
||||||
|
b.HasIndex("DestinationType");
|
||||||
|
|
||||||
|
b.HasIndex("IsActive");
|
||||||
|
|
||||||
|
b.HasIndex("LastUsedAt");
|
||||||
|
|
||||||
|
b.HasIndex("Name")
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
|
b.HasIndex("SourceCredentialId");
|
||||||
|
|
||||||
|
b.HasIndex("SourceType");
|
||||||
|
|
||||||
|
b.ToTable("DataCouplerProfiles", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("CredentialManager.Models.KeyAssociation", b =>
|
modelBuilder.Entity("CredentialManager.Models.KeyAssociation", b =>
|
||||||
{
|
{
|
||||||
b.Property<int>("Id")
|
b.Property<int>("Id")
|
||||||
@@ -202,6 +300,23 @@ namespace CredentialManager.Migrations
|
|||||||
|
|
||||||
b.ToTable("KeyAssociations", (string)null);
|
b.ToTable("KeyAssociations", (string)null);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("CredentialManager.Models.DataCouplerProfile", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("CredentialManager.Models.CredentialEntity", "DestinationCredential")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("DestinationCredentialId")
|
||||||
|
.OnDelete(DeleteBehavior.SetNull);
|
||||||
|
|
||||||
|
b.HasOne("CredentialManager.Models.CredentialEntity", "SourceCredential")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("SourceCredentialId")
|
||||||
|
.OnDelete(DeleteBehavior.SetNull);
|
||||||
|
|
||||||
|
b.Navigation("DestinationCredential");
|
||||||
|
|
||||||
|
b.Navigation("SourceCredential");
|
||||||
|
});
|
||||||
#pragma warning restore 612, 618
|
#pragma warning restore 612, 618
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,73 @@
|
|||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
using System.ComponentModel.DataAnnotations.Schema;
|
||||||
|
|
||||||
|
namespace CredentialManager.Models;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Modello per salvare le configurazioni dei profili di Data Coupler
|
||||||
|
/// </summary>
|
||||||
|
public class DataCouplerProfile
|
||||||
|
{
|
||||||
|
[Key]
|
||||||
|
public int Id { get; set; }
|
||||||
|
|
||||||
|
[Required]
|
||||||
|
[MaxLength(100)]
|
||||||
|
public string Name { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
[MaxLength(500)]
|
||||||
|
public string? Description { get; set; }
|
||||||
|
|
||||||
|
// Configurazione Fonte Dati
|
||||||
|
[Required]
|
||||||
|
[MaxLength(20)]
|
||||||
|
public string SourceType { get; set; } = string.Empty; // "database" o "file"
|
||||||
|
|
||||||
|
public int? SourceCredentialId { get; set; }
|
||||||
|
|
||||||
|
[MaxLength(200)]
|
||||||
|
public string? SourceSchema { get; set; }
|
||||||
|
|
||||||
|
[MaxLength(200)]
|
||||||
|
public string? SourceTable { get; set; }
|
||||||
|
|
||||||
|
[MaxLength(500)]
|
||||||
|
public string? SourceFilePath { get; set; }
|
||||||
|
|
||||||
|
// Configurazione Destinazione
|
||||||
|
[Required]
|
||||||
|
[MaxLength(20)]
|
||||||
|
public string DestinationType { get; set; } = string.Empty; // "database" o "rest"
|
||||||
|
|
||||||
|
public int? DestinationCredentialId { get; set; }
|
||||||
|
|
||||||
|
[MaxLength(200)]
|
||||||
|
public string? DestinationSchema { get; set; }
|
||||||
|
|
||||||
|
[MaxLength(200)]
|
||||||
|
public string? DestinationTable { get; set; }
|
||||||
|
|
||||||
|
[MaxLength(500)]
|
||||||
|
public string? DestinationEndpoint { get; set; }
|
||||||
|
|
||||||
|
// Mapping dei campi salvato come JSON
|
||||||
|
[MaxLength(4000)]
|
||||||
|
public string? FieldMappingJson { get; set; }
|
||||||
|
|
||||||
|
// Metadati
|
||||||
|
[MaxLength(100)]
|
||||||
|
public string? CreatedBy { get; set; }
|
||||||
|
|
||||||
|
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
|
||||||
|
|
||||||
|
public DateTime? LastUsedAt { get; set; }
|
||||||
|
|
||||||
|
public bool IsActive { get; set; } = true;
|
||||||
|
|
||||||
|
// Relazioni opzionali con le credenziali
|
||||||
|
[ForeignKey(nameof(SourceCredentialId))]
|
||||||
|
public virtual CredentialEntity? SourceCredential { get; set; }
|
||||||
|
|
||||||
|
[ForeignKey(nameof(DestinationCredentialId))]
|
||||||
|
public virtual CredentialEntity? DestinationCredential { get; set; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
namespace CredentialManager.Models;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// DTO per la creazione/aggiornamento di un profilo DataCoupler
|
||||||
|
/// </summary>
|
||||||
|
public class DataCouplerProfileDto
|
||||||
|
{
|
||||||
|
public int? Id { get; set; }
|
||||||
|
public string Name { get; set; } = string.Empty;
|
||||||
|
public string? Description { get; set; }
|
||||||
|
|
||||||
|
// Informazioni sorgente
|
||||||
|
public string SourceType { get; set; } = string.Empty;
|
||||||
|
public int? SourceCredentialId { get; set; }
|
||||||
|
public string? SourceSchema { get; set; }
|
||||||
|
public string? SourceTable { get; set; }
|
||||||
|
public string? SourceFilePath { get; set; }
|
||||||
|
|
||||||
|
// Informazioni destinazione
|
||||||
|
public string DestinationType { get; set; } = string.Empty;
|
||||||
|
public int? DestinationCredentialId { get; set; }
|
||||||
|
public string? DestinationSchema { get; set; }
|
||||||
|
public string? DestinationTable { get; set; }
|
||||||
|
public string? DestinationEndpoint { get; set; }
|
||||||
|
|
||||||
|
// Mapping dei campi
|
||||||
|
public List<FieldMappingDto>? FieldMappings { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// DTO per il mapping dei campi
|
||||||
|
/// </summary>
|
||||||
|
public class FieldMappingDto
|
||||||
|
{
|
||||||
|
public string SourceField { get; set; } = string.Empty;
|
||||||
|
public string DestinationField { get; set; } = string.Empty;
|
||||||
|
public string? DataType { get; set; }
|
||||||
|
public bool IsKey { get; set; }
|
||||||
|
public bool IsRequired { get; set; }
|
||||||
|
public string? DefaultValue { get; set; }
|
||||||
|
public string? Transformation { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// DTO per la visualizzazione di un profilo nella lista
|
||||||
|
/// </summary>
|
||||||
|
public class DataCouplerProfileSummaryDto
|
||||||
|
{
|
||||||
|
public int Id { get; set; }
|
||||||
|
public string Name { get; set; } = string.Empty;
|
||||||
|
public string? Description { get; set; }
|
||||||
|
public string SourceType { get; set; } = string.Empty;
|
||||||
|
public string? SourceName { get; set; }
|
||||||
|
public string DestinationType { get; set; } = string.Empty;
|
||||||
|
public string? DestinationName { get; set; }
|
||||||
|
public DateTime CreatedAt { get; set; }
|
||||||
|
public DateTime? LastUsedAt { get; set; }
|
||||||
|
public string? CreatedBy { get; set; }
|
||||||
|
public bool IsActive { get; set; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,234 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using CredentialManager.Data;
|
||||||
|
using CredentialManager.Models;
|
||||||
|
using System.Text.Json;
|
||||||
|
|
||||||
|
namespace CredentialManager.Services;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Implementazione del servizio per la gestione dei profili Data Coupler
|
||||||
|
/// </summary>
|
||||||
|
public class DataCouplerProfileService : IDataCouplerProfileService
|
||||||
|
{
|
||||||
|
private readonly CredentialDbContext _context;
|
||||||
|
|
||||||
|
public DataCouplerProfileService(CredentialDbContext context)
|
||||||
|
{
|
||||||
|
_context = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Ottiene tutti i profili attivi
|
||||||
|
/// </summary>
|
||||||
|
public async Task<IEnumerable<DataCouplerProfile>> GetAllProfilesAsync()
|
||||||
|
{
|
||||||
|
return await _context.DataCouplerProfiles
|
||||||
|
.Include(p => p.SourceCredential)
|
||||||
|
.Include(p => p.DestinationCredential)
|
||||||
|
.Where(p => p.IsActive)
|
||||||
|
.OrderByDescending(p => p.LastUsedAt)
|
||||||
|
.ThenByDescending(p => p.CreatedAt)
|
||||||
|
.ToListAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Ottiene un profilo per ID
|
||||||
|
/// </summary>
|
||||||
|
public async Task<DataCouplerProfile?> GetProfileByIdAsync(int id)
|
||||||
|
{
|
||||||
|
return await _context.DataCouplerProfiles
|
||||||
|
.Include(p => p.SourceCredential)
|
||||||
|
.Include(p => p.DestinationCredential)
|
||||||
|
.FirstOrDefaultAsync(p => p.Id == id && p.IsActive);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Ottiene un profilo per nome
|
||||||
|
/// </summary>
|
||||||
|
public async Task<DataCouplerProfile?> GetProfileByNameAsync(string name)
|
||||||
|
{
|
||||||
|
return await _context.DataCouplerProfiles
|
||||||
|
.Include(p => p.SourceCredential)
|
||||||
|
.Include(p => p.DestinationCredential)
|
||||||
|
.FirstOrDefaultAsync(p => p.Name == name && p.IsActive);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Salva un nuovo profilo
|
||||||
|
/// </summary>
|
||||||
|
public async Task<DataCouplerProfile> SaveProfileAsync(DataCouplerProfile profile)
|
||||||
|
{
|
||||||
|
profile.CreatedAt = DateTime.UtcNow;
|
||||||
|
profile.IsActive = true;
|
||||||
|
|
||||||
|
_context.DataCouplerProfiles.Add(profile);
|
||||||
|
await _context.SaveChangesAsync();
|
||||||
|
|
||||||
|
return profile;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Aggiorna un profilo esistente
|
||||||
|
/// </summary>
|
||||||
|
public async Task<DataCouplerProfile> UpdateProfileAsync(DataCouplerProfile profile)
|
||||||
|
{
|
||||||
|
var existingProfile = await _context.DataCouplerProfiles
|
||||||
|
.FirstOrDefaultAsync(p => p.Id == profile.Id);
|
||||||
|
|
||||||
|
if (existingProfile == null)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException($"Profilo con ID {profile.Id} non trovato");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Aggiorna le proprietà
|
||||||
|
existingProfile.Name = profile.Name;
|
||||||
|
existingProfile.Description = profile.Description;
|
||||||
|
existingProfile.SourceType = profile.SourceType;
|
||||||
|
existingProfile.SourceCredentialId = profile.SourceCredentialId;
|
||||||
|
existingProfile.SourceSchema = profile.SourceSchema;
|
||||||
|
existingProfile.SourceTable = profile.SourceTable;
|
||||||
|
existingProfile.SourceFilePath = profile.SourceFilePath;
|
||||||
|
existingProfile.DestinationType = profile.DestinationType;
|
||||||
|
existingProfile.DestinationCredentialId = profile.DestinationCredentialId;
|
||||||
|
existingProfile.DestinationSchema = profile.DestinationSchema;
|
||||||
|
existingProfile.DestinationTable = profile.DestinationTable;
|
||||||
|
existingProfile.DestinationEndpoint = profile.DestinationEndpoint;
|
||||||
|
existingProfile.FieldMappingJson = profile.FieldMappingJson;
|
||||||
|
|
||||||
|
await _context.SaveChangesAsync();
|
||||||
|
return existingProfile;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Elimina un profilo (soft delete)
|
||||||
|
/// </summary>
|
||||||
|
public async Task<bool> DeleteProfileAsync(int id)
|
||||||
|
{
|
||||||
|
var profile = await _context.DataCouplerProfiles
|
||||||
|
.FirstOrDefaultAsync(p => p.Id == id);
|
||||||
|
|
||||||
|
if (profile == null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
profile.IsActive = false;
|
||||||
|
await _context.SaveChangesAsync();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Aggiorna la data di ultimo utilizzo di un profilo
|
||||||
|
/// </summary>
|
||||||
|
public async Task UpdateLastUsedAsync(int id)
|
||||||
|
{
|
||||||
|
var profile = await _context.DataCouplerProfiles
|
||||||
|
.FirstOrDefaultAsync(p => p.Id == id);
|
||||||
|
|
||||||
|
if (profile != null)
|
||||||
|
{
|
||||||
|
profile.LastUsedAt = DateTime.UtcNow;
|
||||||
|
await _context.SaveChangesAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Verifica se esiste un profilo con il nome specificato
|
||||||
|
/// </summary>
|
||||||
|
public async Task<bool> ProfileExistsAsync(string name, int? excludeId = null)
|
||||||
|
{
|
||||||
|
var query = _context.DataCouplerProfiles
|
||||||
|
.Where(p => p.Name == name && p.IsActive);
|
||||||
|
|
||||||
|
if (excludeId.HasValue)
|
||||||
|
{
|
||||||
|
query = query.Where(p => p.Id != excludeId.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return await query.AnyAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Serializza la lista di mapping dei campi in JSON
|
||||||
|
/// </summary>
|
||||||
|
public string SerializeFieldMappings(List<FieldMappingDto>? mappings)
|
||||||
|
{
|
||||||
|
if (mappings == null || !mappings.Any())
|
||||||
|
return string.Empty;
|
||||||
|
|
||||||
|
return JsonSerializer.Serialize(mappings, new JsonSerializerOptions
|
||||||
|
{
|
||||||
|
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Deserializza il JSON dei mapping dei campi
|
||||||
|
/// </summary>
|
||||||
|
public List<FieldMappingDto> DeserializeFieldMappings(string? json)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(json))
|
||||||
|
return new List<FieldMappingDto>();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return JsonSerializer.Deserialize<List<FieldMappingDto>>(json, new JsonSerializerOptions
|
||||||
|
{
|
||||||
|
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
|
||||||
|
}) ?? new List<FieldMappingDto>();
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return new List<FieldMappingDto>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Converte un DataCouplerProfile in DTO
|
||||||
|
/// </summary>
|
||||||
|
public DataCouplerProfileDto ToDto(DataCouplerProfile profile)
|
||||||
|
{
|
||||||
|
return new DataCouplerProfileDto
|
||||||
|
{
|
||||||
|
Id = profile.Id,
|
||||||
|
Name = profile.Name,
|
||||||
|
Description = profile.Description,
|
||||||
|
SourceType = profile.SourceType,
|
||||||
|
SourceCredentialId = profile.SourceCredentialId,
|
||||||
|
SourceSchema = profile.SourceSchema,
|
||||||
|
SourceTable = profile.SourceTable,
|
||||||
|
SourceFilePath = profile.SourceFilePath,
|
||||||
|
DestinationType = profile.DestinationType,
|
||||||
|
DestinationCredentialId = profile.DestinationCredentialId,
|
||||||
|
DestinationSchema = profile.DestinationSchema,
|
||||||
|
DestinationTable = profile.DestinationTable,
|
||||||
|
DestinationEndpoint = profile.DestinationEndpoint,
|
||||||
|
FieldMappings = DeserializeFieldMappings(profile.FieldMappingJson)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Converte un DTO in DataCouplerProfile
|
||||||
|
/// </summary>
|
||||||
|
public DataCouplerProfile FromDto(DataCouplerProfileDto dto, string? createdBy = null)
|
||||||
|
{
|
||||||
|
return new DataCouplerProfile
|
||||||
|
{
|
||||||
|
Id = dto.Id ?? 0,
|
||||||
|
Name = dto.Name,
|
||||||
|
Description = dto.Description,
|
||||||
|
SourceType = dto.SourceType,
|
||||||
|
SourceCredentialId = dto.SourceCredentialId,
|
||||||
|
SourceSchema = dto.SourceSchema,
|
||||||
|
SourceTable = dto.SourceTable,
|
||||||
|
SourceFilePath = dto.SourceFilePath,
|
||||||
|
DestinationType = dto.DestinationType,
|
||||||
|
DestinationCredentialId = dto.DestinationCredentialId,
|
||||||
|
DestinationSchema = dto.DestinationSchema,
|
||||||
|
DestinationTable = dto.DestinationTable,
|
||||||
|
DestinationEndpoint = dto.DestinationEndpoint,
|
||||||
|
FieldMappingJson = SerializeFieldMappings(dto.FieldMappings),
|
||||||
|
CreatedBy = createdBy
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
using CredentialManager.Models;
|
||||||
|
|
||||||
|
namespace CredentialManager.Services;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Interfaccia per il servizio di gestione dei profili Data Coupler
|
||||||
|
/// </summary>
|
||||||
|
public interface IDataCouplerProfileService
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Ottiene tutti i profili attivi
|
||||||
|
/// </summary>
|
||||||
|
Task<IEnumerable<DataCouplerProfile>> GetAllProfilesAsync();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Ottiene un profilo per ID
|
||||||
|
/// </summary>
|
||||||
|
Task<DataCouplerProfile?> GetProfileByIdAsync(int id);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Ottiene un profilo per nome
|
||||||
|
/// </summary>
|
||||||
|
Task<DataCouplerProfile?> GetProfileByNameAsync(string name);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Salva un nuovo profilo
|
||||||
|
/// </summary>
|
||||||
|
Task<DataCouplerProfile> SaveProfileAsync(DataCouplerProfile profile);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Aggiorna un profilo esistente
|
||||||
|
/// </summary>
|
||||||
|
Task<DataCouplerProfile> UpdateProfileAsync(DataCouplerProfile profile);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Elimina un profilo
|
||||||
|
/// </summary>
|
||||||
|
Task<bool> DeleteProfileAsync(int id);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Aggiorna la data di ultimo utilizzo di un profilo
|
||||||
|
/// </summary>
|
||||||
|
Task UpdateLastUsedAsync(int id);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Verifica se esiste un profilo con il nome specificato
|
||||||
|
/// </summary>
|
||||||
|
Task<bool> ProfileExistsAsync(string name, int? excludeId = null);
|
||||||
|
}
|
||||||
Binary file not shown.
@@ -9,6 +9,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DataConnection", "DataConne
|
|||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CredentialManager", "CredentialManager\CredentialManager.csproj", "{30B369DE-A0BA-4AD7-8895-7BEBD244E782}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CredentialManager", "CredentialManager\CredentialManager.csproj", "{30B369DE-A0BA-4AD7-8895-7BEBD244E782}"
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Components", "Components\Components.csproj", "{B5114CAC-3E03-4150-B93C-652882F66CB7}"
|
||||||
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
Debug|Any CPU = Debug|Any CPU
|
||||||
@@ -55,6 +57,18 @@ Global
|
|||||||
{30B369DE-A0BA-4AD7-8895-7BEBD244E782}.Release|x64.Build.0 = Release|Any CPU
|
{30B369DE-A0BA-4AD7-8895-7BEBD244E782}.Release|x64.Build.0 = Release|Any CPU
|
||||||
{30B369DE-A0BA-4AD7-8895-7BEBD244E782}.Release|x86.ActiveCfg = Release|Any CPU
|
{30B369DE-A0BA-4AD7-8895-7BEBD244E782}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
{30B369DE-A0BA-4AD7-8895-7BEBD244E782}.Release|x86.Build.0 = Release|Any CPU
|
{30B369DE-A0BA-4AD7-8895-7BEBD244E782}.Release|x86.Build.0 = Release|Any CPU
|
||||||
|
{B5114CAC-3E03-4150-B93C-652882F66CB7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{B5114CAC-3E03-4150-B93C-652882F66CB7}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{B5114CAC-3E03-4150-B93C-652882F66CB7}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||||
|
{B5114CAC-3E03-4150-B93C-652882F66CB7}.Debug|x64.Build.0 = Debug|Any CPU
|
||||||
|
{B5114CAC-3E03-4150-B93C-652882F66CB7}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||||
|
{B5114CAC-3E03-4150-B93C-652882F66CB7}.Debug|x86.Build.0 = Debug|Any CPU
|
||||||
|
{B5114CAC-3E03-4150-B93C-652882F66CB7}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{B5114CAC-3E03-4150-B93C-652882F66CB7}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{B5114CAC-3E03-4150-B93C-652882F66CB7}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
|
{B5114CAC-3E03-4150-B93C-652882F66CB7}.Release|x64.Build.0 = Release|Any CPU
|
||||||
|
{B5114CAC-3E03-4150-B93C-652882F66CB7}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{B5114CAC-3E03-4150-B93C-652882F66CB7}.Release|x86.Build.0 = Release|Any CPU
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\DataConnection\DataConnection.csproj" />
|
<ProjectReference Include="..\DataConnection\DataConnection.csproj" />
|
||||||
<ProjectReference Include="..\CredentialManager\CredentialManager.csproj" />
|
<ProjectReference Include="..\CredentialManager\CredentialManager.csproj" />
|
||||||
|
<ProjectReference Include="..\Components\Components.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
@@ -15,6 +15,7 @@
|
|||||||
@inject IDataConnectionFactory ConnectionFactory
|
@inject IDataConnectionFactory ConnectionFactory
|
||||||
@inject IJSRuntime JSRuntime
|
@inject IJSRuntime JSRuntime
|
||||||
@inject ILogger<DataCoupler> Logger
|
@inject ILogger<DataCoupler> Logger
|
||||||
|
@inject CredentialManager.Services.IDataCouplerProfileService ProfileService
|
||||||
|
|
||||||
<PageTitle>Data Coupler</PageTitle>
|
<PageTitle>Data Coupler</PageTitle>
|
||||||
|
|
||||||
@@ -24,7 +25,19 @@
|
|||||||
<h3><i class="fas fa-exchange-alt"></i> Data Coupler - Coupling Database e REST API</h3>
|
<h3><i class="fas fa-exchange-alt"></i> Data Coupler - Coupling Database e REST API</h3>
|
||||||
<p class="text-muted">Connetti database e servizi REST per il trasferimento dati</p>
|
<p class="text-muted">Connetti database e servizi REST per il trasferimento dati</p>
|
||||||
</div>
|
</div>
|
||||||
</div> <div class="row">
|
</div>
|
||||||
|
|
||||||
|
<!-- Sezione Gestione Profili -->
|
||||||
|
<div class="row mb-3">
|
||||||
|
<div class="col-12">
|
||||||
|
<ProfileSelector Profiles="availableProfiles"
|
||||||
|
OnProfileLoaded="OnProfileLoaded"
|
||||||
|
OnManageProfiles="OnManageProfiles"
|
||||||
|
IsLoading="isLoadingProfiles" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
<!-- Lato Sinistro - Fonte Dati -->
|
<!-- Lato Sinistro - Fonte Dati -->
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<div class="card h-100">
|
<div class="card h-100">
|
||||||
@@ -1103,6 +1116,31 @@
|
|||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Sezione Salvataggio Profilo -->
|
||||||
|
@if (isDatabaseConnected && isRestConnected && fieldMappings.Any())
|
||||||
|
{
|
||||||
|
<div class="row mt-4">
|
||||||
|
<div class="col-12">
|
||||||
|
<ProfileSaver CanSave="CanSaveProfile()"
|
||||||
|
SourceType="selectedSourceType"
|
||||||
|
SourceSchema="@(databaseTables.Keys.FirstOrDefault()?.Split('.').FirstOrDefault())"
|
||||||
|
SourceTable="selectedTable"
|
||||||
|
DestinationType="rest"
|
||||||
|
DestinationEndpoint="@(selectedRestEntity?.Name)"
|
||||||
|
FieldMappings="GetCurrentFieldMappings()"
|
||||||
|
OnProfileSaved="OnProfileSaved" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
<!-- Componente Gestione Profili -->
|
||||||
|
<ProfileManagement ShowModal="showProfileManagement"
|
||||||
|
Profiles="availableProfiles"
|
||||||
|
OnCloseModal="OnCloseProfileManagement"
|
||||||
|
OnProfileLoaded="OnProfileLoaded"
|
||||||
|
OnProfileDeleted="OnProfileDeleted"
|
||||||
|
IsLoading="isLoadingProfiles" />
|
||||||
|
|
||||||
<!-- Modal per la selezione del database -->
|
<!-- Modal per la selezione del database -->
|
||||||
@if (showDatabaseSelectionModal)
|
@if (showDatabaseSelectionModal)
|
||||||
{
|
{
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,4 @@
|
|||||||
@using System.Net.Http
|
@using System.Net.Http
|
||||||
@using Microsoft.AspNetCore.Authorization
|
|
||||||
@using Microsoft.AspNetCore.Components.Authorization
|
@using Microsoft.AspNetCore.Components.Authorization
|
||||||
@using Microsoft.AspNetCore.Components.Forms
|
@using Microsoft.AspNetCore.Components.Forms
|
||||||
@using Microsoft.AspNetCore.Components.Routing
|
@using Microsoft.AspNetCore.Components.Routing
|
||||||
@@ -8,3 +7,4 @@
|
|||||||
@using Microsoft.JSInterop
|
@using Microsoft.JSInterop
|
||||||
@using Data_Coupler
|
@using Data_Coupler
|
||||||
@using Data_Coupler.Shared
|
@using Data_Coupler.Shared
|
||||||
|
@using Components
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,56 @@
|
|||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using CredentialManager;
|
||||||
|
using CredentialManager.Services;
|
||||||
|
using CredentialManager.Models;
|
||||||
|
|
||||||
|
Console.WriteLine("🧪 Testing DataCouplerProfile Service...");
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Crea un service provider con CredentialManager
|
||||||
|
var serviceProvider = await CredentialManagerFactory.CreateServiceProviderAsync();
|
||||||
|
|
||||||
|
// Ottieni il servizio per i profili
|
||||||
|
var profileService = serviceProvider.GetRequiredService<IDataCouplerProfileService>();
|
||||||
|
|
||||||
|
Console.WriteLine("✅ Service created successfully!");
|
||||||
|
|
||||||
|
// Test: Ottieni tutti i profili (dovrebbe essere vuoto)
|
||||||
|
var profiles = await profileService.GetAllProfilesAsync();
|
||||||
|
Console.WriteLine($"📋 Found {profiles.Count()} existing profiles");
|
||||||
|
|
||||||
|
// Test: Crea un profilo di test
|
||||||
|
var testProfile = new DataCouplerProfile
|
||||||
|
{
|
||||||
|
Name = "Test Profile",
|
||||||
|
Description = "Profile creato durante il test",
|
||||||
|
SourceType = "database",
|
||||||
|
DestinationType = "rest",
|
||||||
|
SourceSchema = "dbo",
|
||||||
|
SourceTable = "customers",
|
||||||
|
DestinationEndpoint = "/api/customers",
|
||||||
|
CreatedBy = "System Test"
|
||||||
|
};
|
||||||
|
|
||||||
|
// Salva il profilo
|
||||||
|
var savedProfile = await profileService.SaveProfileAsync(testProfile);
|
||||||
|
Console.WriteLine($"💾 Test profile saved with ID: {savedProfile.Id}");
|
||||||
|
|
||||||
|
// Ricarica i profili
|
||||||
|
profiles = await profileService.GetAllProfilesAsync();
|
||||||
|
Console.WriteLine($"📋 Now found {profiles.Count()} profiles");
|
||||||
|
|
||||||
|
// Elimina il profilo di test
|
||||||
|
var deleted = await profileService.DeleteProfileAsync(savedProfile.Id);
|
||||||
|
Console.WriteLine($"🗑️ Test profile deleted: {deleted}");
|
||||||
|
|
||||||
|
Console.WriteLine("✅ All tests passed! DataCouplerProfile service is working correctly.");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"❌ Error during testing: {ex.Message}");
|
||||||
|
Console.WriteLine($"Stack trace: {ex.StackTrace}");
|
||||||
|
}
|
||||||
|
|
||||||
|
Console.WriteLine("Press any key to exit...");
|
||||||
|
Console.ReadKey();
|
||||||
+1
-6
@@ -3,6 +3,7 @@
|
|||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<OutputType>Exe</OutputType>
|
<OutputType>Exe</OutputType>
|
||||||
<TargetFramework>net9.0</TargetFramework>
|
<TargetFramework>net9.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
@@ -10,10 +11,4 @@
|
|||||||
<ProjectReference Include="..\CredentialManager\CredentialManager.csproj" />
|
<ProjectReference Include="..\CredentialManager\CredentialManager.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<PackageReference Include="Microsoft.Extensions.Hosting" Version="9.0.1" />
|
|
||||||
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="9.0.1" />
|
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="9.0.3" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
@@ -1,50 +0,0 @@
|
|||||||
using CredentialManager.Data;
|
|
||||||
using CredentialManager.Services;
|
|
||||||
using Microsoft.EntityFrameworkCore;
|
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
|
||||||
using Microsoft.Extensions.Logging;
|
|
||||||
|
|
||||||
namespace TestDatabaseFix;
|
|
||||||
|
|
||||||
class Program
|
|
||||||
{
|
|
||||||
static async Task Main(string[] args)
|
|
||||||
{
|
|
||||||
Console.WriteLine("Test Database Initialization Fix");
|
|
||||||
|
|
||||||
var services = new ServiceCollection();
|
|
||||||
services.AddLogging(builder => builder.AddConsole());
|
|
||||||
|
|
||||||
// Configura il DbContext per usare SQLite
|
|
||||||
services.AddDbContext<CredentialDbContext>(options =>
|
|
||||||
options.UseSqlite("Data Source=test_credentials.db"));
|
|
||||||
|
|
||||||
services.AddScoped<DatabaseInitializer>();
|
|
||||||
|
|
||||||
var serviceProvider = services.BuildServiceProvider();
|
|
||||||
|
|
||||||
using var scope = serviceProvider.CreateScope();
|
|
||||||
var dbContext = scope.ServiceProvider.GetRequiredService<CredentialDbContext>();
|
|
||||||
var initializer = scope.ServiceProvider.GetRequiredService<DatabaseInitializer>();
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
Console.WriteLine("Inizializzando il database...");
|
|
||||||
await initializer.InitializeAsync();
|
|
||||||
|
|
||||||
Console.WriteLine("Verifica tabelle...");
|
|
||||||
var credentialsCount = await dbContext.Credentials.CountAsync();
|
|
||||||
var associationsCount = await dbContext.RecordAssociations.CountAsync();
|
|
||||||
|
|
||||||
Console.WriteLine($"Tabella Credentials: {credentialsCount} record");
|
|
||||||
Console.WriteLine($"Tabella RecordAssociations: {associationsCount} record");
|
|
||||||
|
|
||||||
Console.WriteLine("Test completato con successo!");
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Console.WriteLine($"Errore: {ex.Message}");
|
|
||||||
Console.WriteLine($"Stack trace: {ex.StackTrace}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user