Implementato sistema robusto di salvataggio/caricamento profili Data Coupler
- Aggiunto metodo GetCredentialIdByNameAsync in CredentialService per recuperare ID credenziali per nome - Implementata gestione robusta dei profili duplicati con riattivazione, sovrascrittura e auto-rinomina - Migliorata logica di caricamento profili con simulazione workflow utente e logging dettagliato - Fixata gestione errori UNIQUE constraint nel salvataggio profili - Aggiunto supporto per salvataggio ID credenziali reali invece di placeholder - Implementato metodo GetProfileByNameIncludingInactiveAsync per gestire profili inattivi - Aggiunto logging esteso per debug e troubleshooting - Integrato componente ProfileSaver nella UI principale - Risolti errori di compilazione e validazione build completa - Migliorata gestione errori con feedback utente per credenziali/entità mancanti
This commit is contained in:
@@ -1,4 +1,6 @@
|
|||||||
@* Componente per salvare la configurazione corrente come profilo *@
|
@* Componente per salvare la configurazione corrente come profilo *@
|
||||||
|
@using System.IO
|
||||||
|
|
||||||
<div class="card mb-3">
|
<div class="card mb-3">
|
||||||
<div class="card-header bg-success text-white">
|
<div class="card-header bg-success text-white">
|
||||||
<h6 class="mb-0">
|
<h6 class="mb-0">
|
||||||
@@ -57,6 +59,10 @@
|
|||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<strong>Fonte:</strong> @GetSourceSummary()<br />
|
<strong>Fonte:</strong> @GetSourceSummary()<br />
|
||||||
|
@if (!string.IsNullOrEmpty(SourceCredentialName))
|
||||||
|
{
|
||||||
|
<span class="text-muted">Credenziali: @SourceCredentialName</span><br />
|
||||||
|
}
|
||||||
@if (!string.IsNullOrEmpty(SourceSchema))
|
@if (!string.IsNullOrEmpty(SourceSchema))
|
||||||
{
|
{
|
||||||
<span class="text-muted">Schema: @SourceSchema</span><br />
|
<span class="text-muted">Schema: @SourceSchema</span><br />
|
||||||
@@ -65,9 +71,17 @@
|
|||||||
{
|
{
|
||||||
<span class="text-muted">Tabella: @SourceTable</span>
|
<span class="text-muted">Tabella: @SourceTable</span>
|
||||||
}
|
}
|
||||||
|
@if (!string.IsNullOrEmpty(SourceFilePath))
|
||||||
|
{
|
||||||
|
<span class="text-muted">File: @Path.GetFileName(SourceFilePath)</span>
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<strong>Destinazione:</strong> @GetDestinationSummary()<br />
|
<strong>Destinazione:</strong> @GetDestinationSummary()<br />
|
||||||
|
@if (!string.IsNullOrEmpty(DestinationCredentialName))
|
||||||
|
{
|
||||||
|
<span class="text-muted">Credenziali: @DestinationCredentialName</span><br />
|
||||||
|
}
|
||||||
@if (!string.IsNullOrEmpty(DestinationSchema))
|
@if (!string.IsNullOrEmpty(DestinationSchema))
|
||||||
{
|
{
|
||||||
<span class="text-muted">Schema: @DestinationSchema</span><br />
|
<span class="text-muted">Schema: @DestinationSchema</span><br />
|
||||||
@@ -85,10 +99,32 @@
|
|||||||
@if (FieldMappings != null && FieldMappings.Any())
|
@if (FieldMappings != null && FieldMappings.Any())
|
||||||
{
|
{
|
||||||
<hr class="my-2" />
|
<hr class="my-2" />
|
||||||
<small class="text-muted">
|
<div class="row">
|
||||||
<i class="fas fa-exchange-alt"></i>
|
<div class="col-md-6">
|
||||||
@FieldMappings.Count mapping dei campi configurati
|
<small class="text-muted">
|
||||||
</small>
|
<i class="fas fa-exchange-alt"></i>
|
||||||
|
@FieldMappings.Count mapping dei campi configurati
|
||||||
|
</small>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
@if (UseRecordAssociations)
|
||||||
|
{
|
||||||
|
<small class="text-info">
|
||||||
|
<i class="fas fa-sync-alt"></i> Smart Update attivo
|
||||||
|
@if (!string.IsNullOrEmpty(SourceKeyField))
|
||||||
|
{
|
||||||
|
<span> (Chiave: @SourceKeyField)</span>
|
||||||
|
}
|
||||||
|
</small>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<small class="text-warning">
|
||||||
|
<i class="fas fa-plus"></i> Solo inserimenti
|
||||||
|
</small>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -9,15 +9,19 @@ public partial class ProfileSaver
|
|||||||
[Parameter] public bool CanSave { get; set; }
|
[Parameter] public bool CanSave { get; set; }
|
||||||
[Parameter] public string SourceType { get; set; } = "";
|
[Parameter] public string SourceType { get; set; } = "";
|
||||||
[Parameter] public int? SourceCredentialId { get; set; }
|
[Parameter] public int? SourceCredentialId { get; set; }
|
||||||
|
[Parameter] public string? SourceCredentialName { get; set; }
|
||||||
[Parameter] public string? SourceSchema { get; set; }
|
[Parameter] public string? SourceSchema { get; set; }
|
||||||
[Parameter] public string? SourceTable { get; set; }
|
[Parameter] public string? SourceTable { get; set; }
|
||||||
[Parameter] public string? SourceFilePath { get; set; }
|
[Parameter] public string? SourceFilePath { get; set; }
|
||||||
[Parameter] public string DestinationType { get; set; } = "";
|
[Parameter] public string DestinationType { get; set; } = "";
|
||||||
[Parameter] public int? DestinationCredentialId { get; set; }
|
[Parameter] public int? DestinationCredentialId { get; set; }
|
||||||
|
[Parameter] public string? DestinationCredentialName { get; set; }
|
||||||
[Parameter] public string? DestinationSchema { get; set; }
|
[Parameter] public string? DestinationSchema { get; set; }
|
||||||
[Parameter] public string? DestinationTable { get; set; }
|
[Parameter] public string? DestinationTable { get; set; }
|
||||||
[Parameter] public string? DestinationEndpoint { get; set; }
|
[Parameter] public string? DestinationEndpoint { get; set; }
|
||||||
[Parameter] public List<FieldMappingDto>? FieldMappings { get; set; }
|
[Parameter] public List<FieldMappingDto>? FieldMappings { get; set; }
|
||||||
|
[Parameter] public string? SourceKeyField { get; set; }
|
||||||
|
[Parameter] public bool UseRecordAssociations { get; set; }
|
||||||
[Parameter] public EventCallback<DataCouplerProfileDto> OnProfileSaved { get; set; }
|
[Parameter] public EventCallback<DataCouplerProfileDto> OnProfileSaved { get; set; }
|
||||||
|
|
||||||
private bool ShowSaveForm { get; set; } = false;
|
private bool ShowSaveForm { get; set; } = false;
|
||||||
@@ -53,15 +57,19 @@ public partial class ProfileSaver
|
|||||||
Description = ProfileData.Description,
|
Description = ProfileData.Description,
|
||||||
SourceType = SourceType,
|
SourceType = SourceType,
|
||||||
SourceCredentialId = SourceCredentialId,
|
SourceCredentialId = SourceCredentialId,
|
||||||
|
SourceCredentialName = SourceCredentialName,
|
||||||
SourceSchema = SourceSchema,
|
SourceSchema = SourceSchema,
|
||||||
SourceTable = SourceTable,
|
SourceTable = SourceTable,
|
||||||
SourceFilePath = SourceFilePath,
|
SourceFilePath = SourceFilePath,
|
||||||
DestinationType = DestinationType,
|
DestinationType = DestinationType,
|
||||||
DestinationCredentialId = DestinationCredentialId,
|
DestinationCredentialId = DestinationCredentialId,
|
||||||
|
DestinationCredentialName = DestinationCredentialName,
|
||||||
DestinationSchema = DestinationSchema,
|
DestinationSchema = DestinationSchema,
|
||||||
DestinationTable = DestinationTable,
|
DestinationTable = DestinationTable,
|
||||||
DestinationEndpoint = DestinationEndpoint,
|
DestinationEndpoint = DestinationEndpoint,
|
||||||
FieldMappings = FieldMappings
|
FieldMappings = FieldMappings,
|
||||||
|
SourceKeyField = SourceKeyField,
|
||||||
|
UseRecordAssociations = UseRecordAssociations
|
||||||
};
|
};
|
||||||
|
|
||||||
await OnProfileSaved.InvokeAsync(profileDto);
|
await OnProfileSaved.InvokeAsync(profileDto);
|
||||||
|
|||||||
Generated
+333
@@ -0,0 +1,333 @@
|
|||||||
|
// <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("20250703085823_AddProfileKeyFieldsAndAssociations")]
|
||||||
|
partial class AddProfileKeyFieldsAndAssociations
|
||||||
|
{
|
||||||
|
/// <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>("SourceKeyField")
|
||||||
|
.HasMaxLength(200)
|
||||||
|
.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.Property<bool>("UseRecordAssociations")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
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,40 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace CredentialManager.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class AddProfileKeyFieldsAndAssociations : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.AddColumn<string>(
|
||||||
|
name: "SourceKeyField",
|
||||||
|
table: "DataCouplerProfiles",
|
||||||
|
type: "TEXT",
|
||||||
|
maxLength: 200,
|
||||||
|
nullable: true);
|
||||||
|
|
||||||
|
migrationBuilder.AddColumn<bool>(
|
||||||
|
name: "UseRecordAssociations",
|
||||||
|
table: "DataCouplerProfiles",
|
||||||
|
type: "INTEGER",
|
||||||
|
nullable: false,
|
||||||
|
defaultValue: false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "SourceKeyField",
|
||||||
|
table: "DataCouplerProfiles");
|
||||||
|
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "UseRecordAssociations",
|
||||||
|
table: "DataCouplerProfiles");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -186,6 +186,10 @@ namespace CredentialManager.Migrations
|
|||||||
.HasMaxLength(500)
|
.HasMaxLength(500)
|
||||||
.HasColumnType("TEXT");
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("SourceKeyField")
|
||||||
|
.HasMaxLength(200)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
b.Property<string>("SourceSchema")
|
b.Property<string>("SourceSchema")
|
||||||
.HasMaxLength(200)
|
.HasMaxLength(200)
|
||||||
.HasColumnType("TEXT");
|
.HasColumnType("TEXT");
|
||||||
@@ -199,6 +203,9 @@ namespace CredentialManager.Migrations
|
|||||||
.HasMaxLength(20)
|
.HasMaxLength(20)
|
||||||
.HasColumnType("TEXT");
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<bool>("UseRecordAssociations")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
b.HasKey("Id");
|
b.HasKey("Id");
|
||||||
|
|
||||||
b.HasIndex("CreatedAt");
|
b.HasIndex("CreatedAt");
|
||||||
|
|||||||
@@ -54,6 +54,12 @@ public class DataCouplerProfile
|
|||||||
[MaxLength(4000)]
|
[MaxLength(4000)]
|
||||||
public string? FieldMappingJson { get; set; }
|
public string? FieldMappingJson { get; set; }
|
||||||
|
|
||||||
|
// Configurazione chiave sorgente e associazioni
|
||||||
|
[MaxLength(200)]
|
||||||
|
public string? SourceKeyField { get; set; }
|
||||||
|
|
||||||
|
public bool UseRecordAssociations { get; set; } = false;
|
||||||
|
|
||||||
// Metadati
|
// Metadati
|
||||||
[MaxLength(100)]
|
[MaxLength(100)]
|
||||||
public string? CreatedBy { get; set; }
|
public string? CreatedBy { get; set; }
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ public class DataCouplerProfileDto
|
|||||||
// Informazioni sorgente
|
// Informazioni sorgente
|
||||||
public string SourceType { get; set; } = string.Empty;
|
public string SourceType { get; set; } = string.Empty;
|
||||||
public int? SourceCredentialId { get; set; }
|
public int? SourceCredentialId { get; set; }
|
||||||
|
public string? SourceCredentialName { get; set; }
|
||||||
public string? SourceSchema { get; set; }
|
public string? SourceSchema { get; set; }
|
||||||
public string? SourceTable { get; set; }
|
public string? SourceTable { get; set; }
|
||||||
public string? SourceFilePath { get; set; }
|
public string? SourceFilePath { get; set; }
|
||||||
@@ -19,12 +20,17 @@ public class DataCouplerProfileDto
|
|||||||
// Informazioni destinazione
|
// Informazioni destinazione
|
||||||
public string DestinationType { get; set; } = string.Empty;
|
public string DestinationType { get; set; } = string.Empty;
|
||||||
public int? DestinationCredentialId { get; set; }
|
public int? DestinationCredentialId { get; set; }
|
||||||
|
public string? DestinationCredentialName { get; set; }
|
||||||
public string? DestinationSchema { get; set; }
|
public string? DestinationSchema { get; set; }
|
||||||
public string? DestinationTable { get; set; }
|
public string? DestinationTable { get; set; }
|
||||||
public string? DestinationEndpoint { get; set; }
|
public string? DestinationEndpoint { get; set; }
|
||||||
|
|
||||||
// Mapping dei campi
|
// Mapping dei campi
|
||||||
public List<FieldMappingDto>? FieldMappings { get; set; }
|
public List<FieldMappingDto>? FieldMappings { get; set; }
|
||||||
|
|
||||||
|
// Configurazione chiave sorgente e associazioni
|
||||||
|
public string? SourceKeyField { get; set; }
|
||||||
|
public bool UseRecordAssociations { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -39,6 +39,9 @@ public interface ICredentialService
|
|||||||
Task<bool> DeleteCredentialAsync(int id);
|
Task<bool> DeleteCredentialAsync(int id);
|
||||||
Task<bool> DeleteCredentialAsync(string name);
|
Task<bool> DeleteCredentialAsync(string name);
|
||||||
Task<List<string>> GetCredentialNamesAsync(CredentialType? type = null);
|
Task<List<string>> GetCredentialNamesAsync(CredentialType? type = null);
|
||||||
|
|
||||||
|
// Helper methods to get credential ID by name
|
||||||
|
Task<int?> GetCredentialIdByNameAsync(string name, CredentialType type);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -960,5 +963,27 @@ public class CredentialService : ICredentialService
|
|||||||
credentialValue.Contains("*** ERRORE DECRITTOGRAFIA ***");
|
credentialValue.Contains("*** ERRORE DECRITTOGRAFIA ***");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Ottiene l'ID di una credenziale per nome e tipo
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="name">Nome della credenziale</param>
|
||||||
|
/// <param name="type">Tipo della credenziale</param>
|
||||||
|
/// <returns>ID della credenziale se trovata, null altrimenti</returns>
|
||||||
|
public async Task<int?> GetCredentialIdByNameAsync(string name, CredentialType type)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var entity = await _context.Credentials
|
||||||
|
.FirstOrDefaultAsync(c => c.Name == name && c.Type == type.ToString() && c.IsActive);
|
||||||
|
|
||||||
|
return entity?.Id;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Errore nel recuperare l'ID della credenziale: {Name}, Tipo: {Type}", name, type);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,6 +31,17 @@ public class DataCouplerProfileService : IDataCouplerProfileService
|
|||||||
.ToListAsync();
|
.ToListAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Ottiene tutti i profili per nome (inclusi quelli inattivi)
|
||||||
|
/// </summary>
|
||||||
|
public async Task<DataCouplerProfile?> GetProfileByNameIncludingInactiveAsync(string name)
|
||||||
|
{
|
||||||
|
return await _context.DataCouplerProfiles
|
||||||
|
.Include(p => p.SourceCredential)
|
||||||
|
.Include(p => p.DestinationCredential)
|
||||||
|
.FirstOrDefaultAsync(p => p.Name.ToLower() == name.ToLower());
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Ottiene un profilo per ID
|
/// Ottiene un profilo per ID
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -80,8 +91,12 @@ public class DataCouplerProfileService : IDataCouplerProfileService
|
|||||||
throw new InvalidOperationException($"Profilo con ID {profile.Id} non trovato");
|
throw new InvalidOperationException($"Profilo con ID {profile.Id} non trovato");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Aggiorna le proprietà
|
// Aggiorna le proprietà (evita di aggiornare il nome se è uguale per evitare unique constraint)
|
||||||
existingProfile.Name = profile.Name;
|
if (!string.Equals(existingProfile.Name, profile.Name, StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
existingProfile.Name = profile.Name;
|
||||||
|
}
|
||||||
|
|
||||||
existingProfile.Description = profile.Description;
|
existingProfile.Description = profile.Description;
|
||||||
existingProfile.SourceType = profile.SourceType;
|
existingProfile.SourceType = profile.SourceType;
|
||||||
existingProfile.SourceCredentialId = profile.SourceCredentialId;
|
existingProfile.SourceCredentialId = profile.SourceCredentialId;
|
||||||
@@ -94,6 +109,9 @@ public class DataCouplerProfileService : IDataCouplerProfileService
|
|||||||
existingProfile.DestinationTable = profile.DestinationTable;
|
existingProfile.DestinationTable = profile.DestinationTable;
|
||||||
existingProfile.DestinationEndpoint = profile.DestinationEndpoint;
|
existingProfile.DestinationEndpoint = profile.DestinationEndpoint;
|
||||||
existingProfile.FieldMappingJson = profile.FieldMappingJson;
|
existingProfile.FieldMappingJson = profile.FieldMappingJson;
|
||||||
|
existingProfile.SourceKeyField = profile.SourceKeyField;
|
||||||
|
existingProfile.UseRecordAssociations = profile.UseRecordAssociations;
|
||||||
|
existingProfile.IsActive = profile.IsActive;
|
||||||
|
|
||||||
await _context.SaveChangesAsync();
|
await _context.SaveChangesAsync();
|
||||||
return existingProfile;
|
return existingProfile;
|
||||||
@@ -195,15 +213,19 @@ public class DataCouplerProfileService : IDataCouplerProfileService
|
|||||||
Description = profile.Description,
|
Description = profile.Description,
|
||||||
SourceType = profile.SourceType,
|
SourceType = profile.SourceType,
|
||||||
SourceCredentialId = profile.SourceCredentialId,
|
SourceCredentialId = profile.SourceCredentialId,
|
||||||
|
SourceCredentialName = profile.SourceCredential?.Name,
|
||||||
SourceSchema = profile.SourceSchema,
|
SourceSchema = profile.SourceSchema,
|
||||||
SourceTable = profile.SourceTable,
|
SourceTable = profile.SourceTable,
|
||||||
SourceFilePath = profile.SourceFilePath,
|
SourceFilePath = profile.SourceFilePath,
|
||||||
DestinationType = profile.DestinationType,
|
DestinationType = profile.DestinationType,
|
||||||
DestinationCredentialId = profile.DestinationCredentialId,
|
DestinationCredentialId = profile.DestinationCredentialId,
|
||||||
|
DestinationCredentialName = profile.DestinationCredential?.Name,
|
||||||
DestinationSchema = profile.DestinationSchema,
|
DestinationSchema = profile.DestinationSchema,
|
||||||
DestinationTable = profile.DestinationTable,
|
DestinationTable = profile.DestinationTable,
|
||||||
DestinationEndpoint = profile.DestinationEndpoint,
|
DestinationEndpoint = profile.DestinationEndpoint,
|
||||||
FieldMappings = DeserializeFieldMappings(profile.FieldMappingJson)
|
FieldMappings = DeserializeFieldMappings(profile.FieldMappingJson),
|
||||||
|
SourceKeyField = profile.SourceKeyField,
|
||||||
|
UseRecordAssociations = profile.UseRecordAssociations
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -228,6 +250,8 @@ public class DataCouplerProfileService : IDataCouplerProfileService
|
|||||||
DestinationTable = dto.DestinationTable,
|
DestinationTable = dto.DestinationTable,
|
||||||
DestinationEndpoint = dto.DestinationEndpoint,
|
DestinationEndpoint = dto.DestinationEndpoint,
|
||||||
FieldMappingJson = SerializeFieldMappings(dto.FieldMappings),
|
FieldMappingJson = SerializeFieldMappings(dto.FieldMappings),
|
||||||
|
SourceKeyField = dto.SourceKeyField,
|
||||||
|
UseRecordAssociations = dto.UseRecordAssociations,
|
||||||
CreatedBy = createdBy
|
CreatedBy = createdBy
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,11 @@ public interface IDataCouplerProfileService
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
Task<IEnumerable<DataCouplerProfile>> GetAllProfilesAsync();
|
Task<IEnumerable<DataCouplerProfile>> GetAllProfilesAsync();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Ottiene tutti i profili per nome (inclusi quelli inattivi)
|
||||||
|
/// </summary>
|
||||||
|
Task<DataCouplerProfile?> GetProfileByNameIncludingInactiveAsync(string name);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Ottiene un profilo per ID
|
/// Ottiene un profilo per ID
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
Binary file not shown.
@@ -48,6 +48,9 @@ public interface IDataConnectionCredentialService
|
|||||||
Task<DataConnection.REST.Configuration.RestServiceOptions> GetRestServiceOptionsAsync(string credentialName);
|
Task<DataConnection.REST.Configuration.RestServiceOptions> GetRestServiceOptionsAsync(string credentialName);
|
||||||
Task<DataConnection.REST.Configuration.RestServiceOptions> GetRestServiceOptionsAsync(int credentialId);
|
Task<DataConnection.REST.Configuration.RestServiceOptions> GetRestServiceOptionsAsync(int credentialId);
|
||||||
|
|
||||||
|
// Helper methods
|
||||||
|
Task<int?> GetCredentialIdByNameAsync(string name, CredentialManager.Models.CredentialType type);
|
||||||
|
|
||||||
// Connection testing
|
// Connection testing
|
||||||
Task<(bool Success, string Message)> TestDatabaseConnectionAsync(string credentialName);
|
Task<(bool Success, string Message)> TestDatabaseConnectionAsync(string credentialName);
|
||||||
Task<(bool Success, string Message)> TestDatabaseConnectionAsync(DatabaseCredential credential);
|
Task<(bool Success, string Message)> TestDatabaseConnectionAsync(DatabaseCredential credential);
|
||||||
|
|||||||
@@ -936,5 +936,14 @@ public class DataConnectionCredentialService : IDataConnectionCredentialService
|
|||||||
return await _keyAssociationService.GetStatisticsAsync();
|
return await _keyAssociationService.GetStatisticsAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#region Helper Methods
|
||||||
|
|
||||||
|
public async Task<int?> GetCredentialIdByNameAsync(string name, CredentialManager.Models.CredentialType type)
|
||||||
|
{
|
||||||
|
return await _credentialService.GetCredentialIdByNameAsync(name, type);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -997,6 +997,25 @@
|
|||||||
<i class="fas fa-list"></i> Riepilogo Mapping
|
<i class="fas fa-list"></i> Riepilogo Mapping
|
||||||
</button>
|
</button>
|
||||||
}
|
}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Sezione Salvataggio Profilo -->
|
||||||
|
<div>
|
||||||
|
<ProfileSaver CanSave="@CanSaveProfile()"
|
||||||
|
SourceType="@selectedSourceType"
|
||||||
|
SourceCredentialId="@(GetCurrentSourceCredentialIdAsync().Result)"
|
||||||
|
SourceCredentialName="@selectedDatabaseCredential"
|
||||||
|
SourceSchema="@GetCurrentDatabaseSchema()"
|
||||||
|
SourceTable="@(useCustomQuery ? "custom_query" : selectedTable)"
|
||||||
|
SourceFilePath="@selectedFileName"
|
||||||
|
DestinationType="rest"
|
||||||
|
DestinationCredentialId="@(GetCurrentDestinationCredentialIdAsync().Result)"
|
||||||
|
DestinationCredentialName="@selectedRestCredential"
|
||||||
|
DestinationEndpoint="@selectedRestEntity?.Name"
|
||||||
|
FieldMappings="@GetCurrentFieldMappings()"
|
||||||
|
SourceKeyField="@sourceKeyField"
|
||||||
|
UseRecordAssociations="@useRecordAssociations"
|
||||||
|
OnProfileSaved="@OnProfileSaved" />
|
||||||
</div> <div class="text-muted">
|
</div> <div class="text-muted">
|
||||||
@if (fieldMappings.Any())
|
@if (fieldMappings.Any())
|
||||||
{
|
{
|
||||||
@@ -1124,23 +1143,6 @@
|
|||||||
}
|
}
|
||||||
</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="@(availableTableNames.FirstOrDefault()?.Split('.').FirstOrDefault())"
|
|
||||||
SourceTable="selectedTable"
|
|
||||||
DestinationType="rest"
|
|
||||||
DestinationEndpoint="@(selectedRestEntity?.Name)"
|
|
||||||
FieldMappings="GetCurrentFieldMappings()"
|
|
||||||
OnProfileSaved="OnProfileSaved" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
<!-- Componente Gestione Profili -->
|
<!-- Componente Gestione Profili -->
|
||||||
<ProfileManagement ShowModal="showProfileManagement"
|
<ProfileManagement ShowModal="showProfileManagement"
|
||||||
Profiles="availableProfiles"
|
Profiles="availableProfiles"
|
||||||
|
|||||||
@@ -191,120 +191,427 @@ public partial class DataCoupler : ComponentBase
|
|||||||
|
|
||||||
private async Task ApplyProfileConfiguration(DataCouplerProfile profile)
|
private async Task ApplyProfileConfiguration(DataCouplerProfile profile)
|
||||||
{
|
{
|
||||||
// Reset dello stato corrente
|
Logger.LogInformation("=== INIZIO APPLICAZIONE PROFILO ===");
|
||||||
ResetAllState();
|
Logger.LogInformation("Applicando configurazione profilo: {ProfileName}", profile.Name);
|
||||||
|
Logger.LogInformation("Profilo - SourceType: {SourceType}, SourceCredentialId: {SourceCredentialId}, DestinationCredentialId: {DestinationCredentialId}",
|
||||||
// Applica configurazione sorgente
|
profile.SourceType, profile.SourceCredentialId, profile.DestinationCredentialId);
|
||||||
selectedSourceType = profile.SourceType;
|
|
||||||
|
|
||||||
if (profile.SourceCredentialId.HasValue)
|
try
|
||||||
{
|
{
|
||||||
// Per ora, uso il nome della credenziale come identificatore
|
// Step 0: Log dello stato iniziale
|
||||||
// TODO: Implementare risoluzione corretta tramite ID quando disponibile
|
Logger.LogInformation("Stato iniziale - SelectedSourceType: {SourceType}, DatabaseConnected: {DatabaseConnected}, RestConnected: {RestConnected}",
|
||||||
// In alternativa, potremmo aggiungere il nome della credenziale al profilo
|
selectedSourceType, isDatabaseConnected, isRestConnected);
|
||||||
|
|
||||||
// Se c'è uno schema salvato nel profilo, utilizziamolo per la connessione
|
// Reset dello stato corrente
|
||||||
if (!string.IsNullOrEmpty(profile.SourceSchema))
|
Logger.LogInformation("Resettando stato corrente...");
|
||||||
{
|
ResetAllState();
|
||||||
Logger.LogInformation("Applicando schema dal profilo: {Schema}", profile.SourceSchema);
|
Logger.LogInformation("Stato dopo reset - SelectedSourceType: {SourceType}, DatabaseConnected: {DatabaseConnected}, RestConnected: {RestConnected}",
|
||||||
// Prima verifichiamo che ci sia una credenziale selezionata
|
selectedSourceType, isDatabaseConnected, isRestConnected);
|
||||||
if (!string.IsNullOrEmpty(selectedDatabaseCredential))
|
|
||||||
{
|
|
||||||
await ConnectToDatabaseWithSchema(profile.SourceSchema);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Logger.LogWarning("Nessuna credenziale database selezionata per applicare lo schema");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (!string.IsNullOrEmpty(selectedDatabaseCredential))
|
|
||||||
{
|
|
||||||
// Connetti al database senza schema specifico
|
|
||||||
await ConnectToDatabase();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Seleziona la tabella se specificata
|
// Step 1: Applica configurazione sorgente
|
||||||
if (!string.IsNullOrEmpty(profile.SourceTable))
|
selectedSourceType = profile.SourceType;
|
||||||
{
|
Logger.LogInformation("Step 1 - Tipo sorgente impostato: {SourceType}", selectedSourceType);
|
||||||
selectedTable = profile.SourceTable;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Applica configurazione destinazione
|
|
||||||
if (profile.DestinationCredentialId.HasValue)
|
|
||||||
{
|
|
||||||
// Similmente, per ora gestiamo senza risoluzione diretta dell'ID
|
|
||||||
// TODO: Implementare risoluzione corretta tramite ID quando disponibile
|
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(selectedRestCredential))
|
// Force UI update for source type change
|
||||||
|
StateHasChanged();
|
||||||
|
await Task.Delay(100); // Give UI time to react to source type change
|
||||||
|
|
||||||
|
// Step 2: Configura e connetti la sorgente
|
||||||
|
if (profile.SourceCredentialId.HasValue)
|
||||||
{
|
{
|
||||||
// Connetti al servizio REST
|
Logger.LogInformation("Step 2 - Configurazione sorgente con ID credenziale: {CredentialId}", profile.SourceCredentialId);
|
||||||
await ConnectToRestApi();
|
|
||||||
|
|
||||||
// Trova e seleziona l'entità REST
|
if (profile.SourceType == "database")
|
||||||
if (!string.IsNullOrEmpty(profile.DestinationEndpoint))
|
|
||||||
{
|
{
|
||||||
var entity = restEntities.FirstOrDefault(e => e.Name == profile.DestinationEndpoint);
|
var sourceCredential = await CredentialService.GetDatabaseCredentialAsync(profile.SourceCredentialId.Value);
|
||||||
if (entity != null)
|
if (sourceCredential != null)
|
||||||
{
|
{
|
||||||
await SelectRestEntity(entity);
|
selectedDatabaseCredential = sourceCredential.Name;
|
||||||
|
Logger.LogInformation("Credenziale database selezionata: {Credential}", selectedDatabaseCredential);
|
||||||
|
|
||||||
|
// Force UI update for credential selection
|
||||||
|
StateHasChanged();
|
||||||
|
await Task.Delay(200);
|
||||||
|
|
||||||
|
// Connetti al database
|
||||||
|
Logger.LogInformation("Iniziando connessione database...");
|
||||||
|
if (!string.IsNullOrEmpty(profile.SourceSchema))
|
||||||
|
{
|
||||||
|
Logger.LogInformation("Connessione con schema specifico: {Schema}", profile.SourceSchema);
|
||||||
|
await ConnectToDatabaseWithSchema(profile.SourceSchema);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Logger.LogInformation("Connessione senza schema specifico");
|
||||||
|
await ConnectToDatabase();
|
||||||
|
}
|
||||||
|
|
||||||
|
Logger.LogInformation("Stato dopo connessione database - Connected: {Connected}, Tables: {TableCount}",
|
||||||
|
isDatabaseConnected, availableTableNames.Count);
|
||||||
|
|
||||||
|
// Seleziona la tabella se specificata e se la connessione è riuscita
|
||||||
|
if (!string.IsNullOrEmpty(profile.SourceTable) && isDatabaseConnected)
|
||||||
|
{
|
||||||
|
Logger.LogInformation("Selezione tabella: {Table}", profile.SourceTable);
|
||||||
|
await SelectTable(profile.SourceTable);
|
||||||
|
Logger.LogInformation("Tabella selezionata: {SelectedTable}, Schema caricato: {SchemaLoaded}",
|
||||||
|
selectedTable, databaseTables.ContainsKey(profile.SourceTable));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Logger.LogWarning("Impossibile selezionare tabella - Table: {Table}, Connected: {Connected}",
|
||||||
|
profile.SourceTable, isDatabaseConnected);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Logger.LogWarning("Entità REST con endpoint {Endpoint} non trovata", profile.DestinationEndpoint);
|
Logger.LogWarning("Credenziale database con ID {CredentialId} non trovata", profile.SourceCredentialId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
else if (profile.SourceType == "file")
|
||||||
}
|
|
||||||
|
|
||||||
// Applica mapping dei campi se disponibile
|
|
||||||
if (!string.IsNullOrEmpty(profile.FieldMappingJson))
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var service = new DataCouplerProfileService(null!); // Temporaneo per deserializzazione
|
|
||||||
var mappings = service.DeserializeFieldMappings(profile.FieldMappingJson);
|
|
||||||
|
|
||||||
// Applica i mapping
|
|
||||||
fieldMappings.Clear();
|
|
||||||
keyFields.Clear();
|
|
||||||
|
|
||||||
foreach (var mapping in mappings)
|
|
||||||
{
|
{
|
||||||
fieldMappings[mapping.SourceField] = mapping.DestinationField;
|
// Per i file, non possiamo ricreare il file caricato, ma possiamo impostare le informazioni
|
||||||
if (mapping.IsKey)
|
if (!string.IsNullOrEmpty(profile.SourceFilePath))
|
||||||
{
|
{
|
||||||
keyFields.Add(mapping.DestinationField);
|
selectedFileName = Path.GetFileName(profile.SourceFilePath);
|
||||||
|
Logger.LogInformation("Informazioni file impostate: {FileName}", selectedFileName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Logger.LogInformation("Applicati {MappingCount} mapping dei campi dal profilo", mappings.Count);
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
else
|
||||||
{
|
{
|
||||||
Logger.LogWarning(ex, "Errore nel caricamento dei mapping dei campi dal profilo");
|
Logger.LogInformation("Nessuna credenziale sorgente da configurare");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Small delay to let source configuration settle
|
||||||
|
await Task.Delay(300);
|
||||||
|
|
||||||
|
// Step 3: Configura e connetti la destinazione
|
||||||
|
if (profile.DestinationCredentialId.HasValue)
|
||||||
|
{
|
||||||
|
Logger.LogInformation("Step 3 - Configurazione destinazione con ID credenziale: {CredentialId}", profile.DestinationCredentialId);
|
||||||
|
|
||||||
|
var destinationCredential = await CredentialService.GetRestApiCredentialAsync(profile.DestinationCredentialId.Value);
|
||||||
|
if (destinationCredential != null)
|
||||||
|
{
|
||||||
|
selectedRestCredential = destinationCredential.Name;
|
||||||
|
Logger.LogInformation("Credenziale REST selezionata: {Credential}", selectedRestCredential);
|
||||||
|
|
||||||
|
// Force UI update for REST credential selection
|
||||||
|
StateHasChanged();
|
||||||
|
await Task.Delay(200);
|
||||||
|
|
||||||
|
// Connetti al servizio REST
|
||||||
|
Logger.LogInformation("Iniziando connessione REST...");
|
||||||
|
await ConnectToRestApi();
|
||||||
|
|
||||||
|
Logger.LogInformation("Stato dopo connessione REST - Connected: {Connected}, Entities: {EntityCount}",
|
||||||
|
isRestConnected, restEntities.Count);
|
||||||
|
|
||||||
|
// Seleziona l'entità REST se la connessione è riuscita
|
||||||
|
if (!string.IsNullOrEmpty(profile.DestinationEndpoint) && isRestConnected)
|
||||||
|
{
|
||||||
|
var entity = restEntities.FirstOrDefault(e => e.Name == profile.DestinationEndpoint);
|
||||||
|
if (entity != null)
|
||||||
|
{
|
||||||
|
Logger.LogInformation("Selezione entità REST: {Entity}", entity.Name);
|
||||||
|
await SelectRestEntity(entity);
|
||||||
|
Logger.LogInformation("Entità REST selezionata: {SelectedEntity}, Dettagli caricati: {DetailsLoaded}",
|
||||||
|
selectedRestEntity?.Name, restEntityDetails != null);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Logger.LogWarning("Entità REST non trovata: {Endpoint} - Entities disponibili: {Entities}",
|
||||||
|
profile.DestinationEndpoint, string.Join(", ", restEntities.Select(e => e.Name)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Logger.LogWarning("Impossibile selezionare entità REST - Endpoint: {Endpoint}, Connected: {Connected}",
|
||||||
|
profile.DestinationEndpoint, isRestConnected);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Logger.LogWarning("Credenziale REST con ID {CredentialId} non trovata", profile.DestinationCredentialId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Logger.LogInformation("Nessuna credenziale destinazione da configurare");
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
StateHasChanged();
|
// Small delay to let destination configuration settle
|
||||||
|
await Task.Delay(300);
|
||||||
|
|
||||||
|
// Step 4: Applica mapping dei campi se disponibile
|
||||||
|
if (!string.IsNullOrEmpty(profile.FieldMappingJson))
|
||||||
|
{
|
||||||
|
Logger.LogInformation("Step 4 - Applicazione mapping campi...");
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var service = new DataCouplerProfileService(null!);
|
||||||
|
var mappings = service.DeserializeFieldMappings(profile.FieldMappingJson);
|
||||||
|
|
||||||
|
Logger.LogInformation("Mappings deserializzati: {Count}", mappings.Count);
|
||||||
|
|
||||||
|
// Applica i mapping
|
||||||
|
fieldMappings.Clear();
|
||||||
|
keyFields.Clear();
|
||||||
|
|
||||||
|
foreach (var mapping in mappings)
|
||||||
|
{
|
||||||
|
fieldMappings[mapping.SourceField] = mapping.DestinationField;
|
||||||
|
if (mapping.IsKey)
|
||||||
|
{
|
||||||
|
keyFields.Add(mapping.DestinationField);
|
||||||
|
}
|
||||||
|
Logger.LogInformation("Mapping applicato: {Source} -> {Destination} (IsKey: {IsKey})",
|
||||||
|
mapping.SourceField, mapping.DestinationField, mapping.IsKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
Logger.LogInformation("Mappings applicati - Totale: {MappingCount}, Chiavi: {KeyCount}",
|
||||||
|
fieldMappings.Count, keyFields.Count);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logger.LogWarning(ex, "Errore nel caricamento dei mapping dei campi dal profilo");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Logger.LogInformation("Nessun mapping campi da applicare");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 5: Applica configurazione chiave sorgente
|
||||||
|
if (!string.IsNullOrEmpty(profile.SourceKeyField))
|
||||||
|
{
|
||||||
|
sourceKeyField = profile.SourceKeyField;
|
||||||
|
Logger.LogInformation("Step 5 - Chiave sorgente applicata: {SourceKey}", sourceKeyField);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Logger.LogInformation("Nessuna chiave sorgente da applicare");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 6: Applica configurazione associazioni record
|
||||||
|
useRecordAssociations = profile.UseRecordAssociations;
|
||||||
|
Logger.LogInformation("Step 6 - Associazioni record configurate: {UseAssociations}", useRecordAssociations);
|
||||||
|
|
||||||
|
Logger.LogInformation("=== FINE APPLICAZIONE PROFILO ===");
|
||||||
|
Logger.LogInformation("Stato finale - Source: {SourceType}, DatabaseConnected: {DatabaseConnected}, RestConnected: {RestConnected}, Mappings: {MappingCount}",
|
||||||
|
selectedSourceType, isDatabaseConnected, isRestConnected, fieldMappings.Count);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logger.LogError(ex, "Errore nell'applicazione della configurazione del profilo {ProfileName}", profile.Name);
|
||||||
|
await JSRuntime.InvokeVoidAsync("alert", $"Errore nel caricamento del profilo: {ex.Message}");
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
// Force final UI update
|
||||||
|
StateHasChanged();
|
||||||
|
Logger.LogInformation("Aggiornamento finale UI completato");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task OnProfileSaved(DataCouplerProfileDto profileDto)
|
private async Task OnProfileSaved(DataCouplerProfileDto profileDto)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
Logger.LogInformation("Tentativo di salvataggio profilo: {ProfileName}", profileDto.Name);
|
||||||
|
|
||||||
var profileService = new DataCouplerProfileService(null!); // Usa il service di conversione
|
var profileService = new DataCouplerProfileService(null!); // Usa il service di conversione
|
||||||
var profile = profileService.FromDto(profileDto, "System"); // TODO: Usa utente corrente
|
var profile = profileService.FromDto(profileDto, "System"); // TODO: Usa utente corrente
|
||||||
|
|
||||||
await ProfileService.SaveProfileAsync(profile);
|
// Controlla se esiste già un profilo con lo stesso nome (inclusi quelli inattivi)
|
||||||
await LoadProfiles(); // Ricarica la lista
|
Logger.LogInformation("Controllo esistenza profilo con nome: {ProfileName}", profileDto.Name);
|
||||||
|
var existingProfile = await ProfileService.GetProfileByNameIncludingInactiveAsync(profileDto.Name);
|
||||||
|
|
||||||
await JSRuntime.InvokeVoidAsync("alert", $"Profilo '{profileDto.Name}' salvato con successo!");
|
if (existingProfile != null)
|
||||||
|
{
|
||||||
|
Logger.LogInformation("Trovato profilo esistente con ID: {ProfileId}, IsActive: {IsActive}",
|
||||||
|
existingProfile.Id, existingProfile.IsActive);
|
||||||
|
|
||||||
|
if (!existingProfile.IsActive)
|
||||||
|
{
|
||||||
|
// Il profilo esiste ma è inattivo - riattivalo e aggiornalo
|
||||||
|
Logger.LogInformation("Riattivazione del profilo inattivo: {ProfileName}", profileDto.Name);
|
||||||
|
profile.Id = existingProfile.Id;
|
||||||
|
profile.IsActive = true;
|
||||||
|
|
||||||
|
// Aggiorna direttamente il profilo esistente invece di creare un nuovo oggetto
|
||||||
|
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;
|
||||||
|
existingProfile.SourceKeyField = profile.SourceKeyField;
|
||||||
|
existingProfile.UseRecordAssociations = profile.UseRecordAssociations;
|
||||||
|
existingProfile.IsActive = true;
|
||||||
|
|
||||||
|
await ProfileService.UpdateProfileAsync(existingProfile);
|
||||||
|
await LoadProfiles();
|
||||||
|
|
||||||
|
await JSRuntime.InvokeVoidAsync("alert", $"Profilo '{profileDto.Name}' riattivato e aggiornato con successo!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Il profilo esiste ed è attivo - chiedi conferma per sovrascrittura
|
||||||
|
var confirmOverwrite = await JSRuntime.InvokeAsync<bool>("confirm",
|
||||||
|
$"Esiste già un profilo attivo con il nome '{profileDto.Name}'. Vuoi sovrascriverlo?");
|
||||||
|
|
||||||
|
if (confirmOverwrite)
|
||||||
|
{
|
||||||
|
Logger.LogInformation("Utente ha confermato la sovrascrittura del profilo: {ProfileName}", profileDto.Name);
|
||||||
|
|
||||||
|
// Aggiorna il profilo esistente direttamente
|
||||||
|
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;
|
||||||
|
existingProfile.SourceKeyField = profile.SourceKeyField;
|
||||||
|
existingProfile.UseRecordAssociations = profile.UseRecordAssociations;
|
||||||
|
|
||||||
|
await ProfileService.UpdateProfileAsync(existingProfile);
|
||||||
|
await LoadProfiles(); // Ricarica la lista
|
||||||
|
|
||||||
|
await JSRuntime.InvokeVoidAsync("alert", $"Profilo '{profileDto.Name}' aggiornato con successo!");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Logger.LogInformation("Utente ha annullato la sovrascrittura del profilo: {ProfileName}", profileDto.Name);
|
||||||
|
|
||||||
|
// Proponi di creare con un nome unico
|
||||||
|
var useUniqueName = await JSRuntime.InvokeAsync<bool>("confirm",
|
||||||
|
"Vuoi salvare il profilo con un nome unico automatico (es. 'Nome Profilo (1)')?");
|
||||||
|
|
||||||
|
if (useUniqueName)
|
||||||
|
{
|
||||||
|
var uniqueName = await GenerateUniqueProfileName(profileDto.Name);
|
||||||
|
profile.Name = uniqueName;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await ProfileService.SaveProfileAsync(profile);
|
||||||
|
await LoadProfiles();
|
||||||
|
|
||||||
|
await JSRuntime.InvokeVoidAsync("alert", $"Profilo salvato con nome '{uniqueName}'!");
|
||||||
|
}
|
||||||
|
catch (Exception uniqueEx)
|
||||||
|
{
|
||||||
|
Logger.LogError(uniqueEx, "Errore durante il salvataggio del profilo con nome unico: {UniqueName}", uniqueName);
|
||||||
|
|
||||||
|
// Gestisci l'errore di unique constraint anche per il nome unico
|
||||||
|
if (uniqueEx.Message.Contains("UNIQUE constraint failed"))
|
||||||
|
{
|
||||||
|
await JSRuntime.InvokeVoidAsync("alert",
|
||||||
|
$"Errore: Non è stato possibile generare un nome unico per il profilo. " +
|
||||||
|
"Prova a ricaricare la pagina e riprova.");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
await JSRuntime.InvokeVoidAsync("alert", $"Errore nel salvataggio del profilo: {uniqueEx.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Altrimenti, non salvare nulla
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Logger.LogInformation("Nessun profilo esistente trovato, creazione nuovo profilo: {ProfileName}", profileDto.Name);
|
||||||
|
|
||||||
|
// Crea un nuovo profilo
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await ProfileService.SaveProfileAsync(profile);
|
||||||
|
await LoadProfiles(); // Ricarica la lista
|
||||||
|
|
||||||
|
await JSRuntime.InvokeVoidAsync("alert", $"Profilo '{profileDto.Name}' salvato con successo!");
|
||||||
|
}
|
||||||
|
catch (Exception saveEx)
|
||||||
|
{
|
||||||
|
Logger.LogError(saveEx, "Errore durante il salvataggio del nuovo profilo: {ProfileName}", profileDto.Name);
|
||||||
|
|
||||||
|
// Possibile race condition - riprova con controllo duplicato
|
||||||
|
if (saveEx.Message.Contains("UNIQUE constraint failed"))
|
||||||
|
{
|
||||||
|
Logger.LogWarning("Race condition rilevata durante il salvataggio, gestione del duplicato...");
|
||||||
|
|
||||||
|
// Chiedi se vuole sovrascrivere o creare nome unico
|
||||||
|
var handleDuplicate = await JSRuntime.InvokeAsync<bool>("confirm",
|
||||||
|
$"Un profilo con il nome '{profileDto.Name}' è stato creato nel frattempo. " +
|
||||||
|
"Vuoi sovrascriverlo? (Clicca 'Annulla' per salvare con un nome unico)");
|
||||||
|
|
||||||
|
if (handleDuplicate)
|
||||||
|
{
|
||||||
|
// Trova il profilo e aggiornalo
|
||||||
|
var duplicateProfile = await ProfileService.GetProfileByNameIncludingInactiveAsync(profileDto.Name);
|
||||||
|
if (duplicateProfile != null)
|
||||||
|
{
|
||||||
|
profile.Id = duplicateProfile.Id;
|
||||||
|
await ProfileService.UpdateProfileAsync(profile);
|
||||||
|
await LoadProfiles();
|
||||||
|
|
||||||
|
await JSRuntime.InvokeVoidAsync("alert", $"Profilo '{profileDto.Name}' aggiornato con successo!");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
await JSRuntime.InvokeVoidAsync("alert", "Errore: Il profilo duplicato non è stato trovato.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Crea con nome unico
|
||||||
|
var uniqueName = await GenerateUniqueProfileName(profileDto.Name);
|
||||||
|
profile.Name = uniqueName;
|
||||||
|
|
||||||
|
await ProfileService.SaveProfileAsync(profile);
|
||||||
|
await LoadProfiles();
|
||||||
|
|
||||||
|
await JSRuntime.InvokeVoidAsync("alert", $"Profilo salvato con nome '{uniqueName}'!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw; // Rilancia eccezioni non gestite
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Logger.LogError(ex, "Errore nel salvataggio del profilo");
|
Logger.LogError(ex, "Errore generale nel salvataggio del profilo: {ProfileName}", profileDto.Name);
|
||||||
await JSRuntime.InvokeVoidAsync("alert", $"Errore nel salvataggio del profilo: {ex.Message}");
|
|
||||||
|
// Gestione generica degli errori
|
||||||
|
if (ex.Message.Contains("UNIQUE constraint failed"))
|
||||||
|
{
|
||||||
|
await JSRuntime.InvokeVoidAsync("alert",
|
||||||
|
$"Errore: Esiste già un profilo con il nome '{profileDto.Name}'. " +
|
||||||
|
"Questo può accadere se ci sono stati problemi di sincronizzazione. " +
|
||||||
|
"Prova a ricaricare la pagina e riprova.");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
await JSRuntime.InvokeVoidAsync("alert", $"Errore nel salvataggio del profilo: {ex.Message}");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -930,7 +1237,7 @@ public partial class DataCoupler : ComponentBase
|
|||||||
{
|
{
|
||||||
isConnectingRest = false;
|
isConnectingRest = false;
|
||||||
}
|
}
|
||||||
} private async void SelectTable(string tableName)
|
} private async Task SelectTable(string tableName)
|
||||||
{
|
{
|
||||||
selectedTable = tableName;
|
selectedTable = tableName;
|
||||||
|
|
||||||
@@ -1005,7 +1312,9 @@ public partial class DataCoupler : ComponentBase
|
|||||||
}
|
}
|
||||||
|
|
||||||
StateHasChanged();
|
StateHasChanged();
|
||||||
} private async Task SelectRestEntity(RestEntitySummary entity)
|
}
|
||||||
|
|
||||||
|
private async Task SelectRestEntity(RestEntitySummary entity)
|
||||||
{
|
{
|
||||||
selectedRestEntity = entity;
|
selectedRestEntity = entity;
|
||||||
|
|
||||||
@@ -2015,6 +2324,7 @@ public partial class DataCoupler : ComponentBase
|
|||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Verifica se una query è una SELECT query sicura
|
/// Verifica se una query è una SELECT query sicura
|
||||||
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private bool IsSelectQuery(string query)
|
private bool IsSelectQuery(string query)
|
||||||
{
|
{
|
||||||
@@ -2262,20 +2572,44 @@ public partial class DataCoupler : ComponentBase
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Ottiene l'ID della credenziale sorgente corrente
|
/// Ottiene l'ID della credenziale sorgente corrente
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private int? GetCurrentSourceCredentialId()
|
private async Task<int?> GetCurrentSourceCredentialIdAsync()
|
||||||
{
|
{
|
||||||
// TODO: Implementare logica per ottenere l'ID dalla credenziale selezionata
|
if (selectedSourceType == "database" && !string.IsNullOrEmpty(selectedDatabaseCredential))
|
||||||
// Per ora ritorniamo null dato che i DTO non hanno ID
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Usa il nuovo metodo per ottenere direttamente l'ID della credenziale
|
||||||
|
return await CredentialService.GetCredentialIdByNameAsync(selectedDatabaseCredential, CredentialManager.Models.CredentialType.Database);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logger.LogError(ex, "Errore nell'ottenere l'ID della credenziale database: {CredentialName}", selectedDatabaseCredential);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Ottiene l'ID della credenziale destinazione corrente
|
/// Ottiene l'ID della credenziale destinazione corrente
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private int? GetCurrentDestinationCredentialId()
|
private async Task<int?> GetCurrentDestinationCredentialIdAsync()
|
||||||
{
|
{
|
||||||
// TODO: Implementare logica per ottenere l'ID dalla credenziale selezionata
|
if (!string.IsNullOrEmpty(selectedRestCredential))
|
||||||
// Per ora ritorniamo null dato che i DTO non hanno ID
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Usa il nuovo metodo per ottenere direttamente l'ID della credenziale
|
||||||
|
return await CredentialService.GetCredentialIdByNameAsync(selectedRestCredential, CredentialManager.Models.CredentialType.RestApi);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logger.LogError(ex, "Errore nell'ottenere l'ID della credenziale REST: {CredentialName}", selectedRestCredential);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2400,7 +2734,7 @@ public partial class DataCoupler : ComponentBase
|
|||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Logger.LogError(ex, "Errore nella connessione con lo schema selezionato");
|
Logger.LogError(ex, "Errore nella connessione con lo schema selezionato");
|
||||||
databaseErrorMessage = $"Errore nella connessione con schema {selectedSchema}: {ex.Message}";
|
databaseErrorMessage = $"Errore nella connessione al database {selectedSchema}: {ex.Message}";
|
||||||
}
|
}
|
||||||
|
|
||||||
StateHasChanged();
|
StateHasChanged();
|
||||||
@@ -2778,8 +3112,9 @@ public partial class DataCoupler : ComponentBase
|
|||||||
"id", "ID", "Id",
|
"id", "ID", "Id",
|
||||||
"_id", "_ID", "_Id",
|
"_id", "_ID", "_Id",
|
||||||
"key", "KEY", "Key",
|
"key", "KEY", "Key",
|
||||||
"code", "CODE", "Code",
|
"code", "CODE", "Code", "codice", "CODICE", "Codice",
|
||||||
"number", "NUMBER", "Number"
|
"number", "NUMBER", "Number", "numero", "NUMERO", "Numero",
|
||||||
|
"index", "INDEX", "Index", "indice", "INDICE", "Indice"
|
||||||
};
|
};
|
||||||
|
|
||||||
// Cerca colonne che potrebbero essere chiavi primarie
|
// Cerca colonne che potrebbero essere chiavi primarie
|
||||||
@@ -2912,5 +3247,18 @@ public partial class DataCoupler : ComponentBase
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task<string> GenerateUniqueProfileName(string baseName)
|
||||||
|
{
|
||||||
|
var uniqueName = baseName;
|
||||||
|
var counter = 1;
|
||||||
|
|
||||||
|
while (await ProfileService.GetProfileByNameIncludingInactiveAsync(uniqueName) != null)
|
||||||
|
{
|
||||||
|
uniqueName = $"{baseName} ({counter})";
|
||||||
|
counter++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return uniqueName;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user