e70abcdcb1
Aggiunge un connettore completamente managed per database Visual FoxPro e dBase, utilizzabile come sorgente dati in tutti i flussi dell'applicazione (discovery manuale, esecuzione schedulata, hash/change-detection, backup/restore). ## Motivazione Il provider OLE DB ufficiale (VFPOLEDB.1) è solo a 32-bit, deprecato e richiede installazione separata. Il connettore managed usa DbfDataReader 1.0.0 (MIT), legge direttamente i file .dbf/.fpt/.dbc a 64-bit senza dipendenze esterne e con streaming efficiente (tabelle da centinaia di MB con uso di memoria contenuto). ## Nuovi file - DataConnection/DB/FoxPro/FoxProConnectionInfo.cs Classe sealed con le informazioni di connessione risolte (cartella, percorso .dbc, encoding, flag IncludeDeleted). Proprietà di convenienza IsContainer e DisplayName. - DataConnection/DB/FoxPro/FoxProReader.cs Helper statico che registra CodePagesEncodingProvider, risolve la connection string (path diretto, stile OLE DB con Provider=vfpoledb, chiave=valore), verifica l'accesso al filesystem, legge il catalogo .dbc (il file .dbc è esso stesso un DBF; filtro su OBJECTTYPE='Table' + cross-check esistenza fisica .dbf), enumera le tabelle free, mappa i tipi VFP (Character, Memo, Numeric, Float, Double, Integer, Currency, Date, DateTime, Logical, General) in descrizioni leggibili, esegue streaming dei record via DbfDataReader, auto-rileva l'encoding dall'header DBF (language driver byte) con fallback a code page 1252, e analizza query SELECT [TOP n] * FROM tabella. - DataConnection/DB/FoxProDatabaseManager.cs Implementa IDatabaseManager. Lettura: TestConnectionAsync, GetTableNamesAsync, GetTableSchemaAsync, GetDatabaseSchemaAsync, GetAllRecordsAsync, ExecuteRawQueryAsync, GetPrimaryKeyFieldAsync (ritorna null, selezione manuale chiave), GetAvailableDatabasesAsync, ChangeDatabaseAsync (no-op per sorgenti file-based). Scrittura: tutti i metodi di modifica (Insert/Update/Delete/Upsert/BulkInsert/ ExecuteCommand/ExecuteNonQuery) lanciano NotSupportedException con messaggio esplicito. - DataConnection/DB/EF/SchemaProviders/FoxProSchemaProvider.cs Implementa IDatabaseSchemaProvider delegando a FoxProReader. Gestione errori per tabella in GetDatabaseSchemaAsync (una tabella non apribile non blocca la discovery). - FOXPRO_CONNECTION.md Guida utente: passo-passo per creare la credenziale, formato connection string, tabella tipi VFP supportati, note su sola lettura e limitazioni query, tabella risoluzione problemi (caratteri accentati, tabelle mancanti, ecc.). ## File modificati - DataConnection/DataConnection.csproj Aggiunto DbfDataReader 1.0.0 (porta transitivamente System.Text.Encoding.CodePages >= 10.0.3; rimossa dipendenza esplicita alla versione 9.0.3 che causava NU1605). - DataConnection/DB/Enums/DatabaseType.cs Aggiunto valore Foxpro in fondo all'enum (preserva valori già persistiti). - CredentialManager/Models/CredentialModels.cs Aggiunto DatabaseType.Foxpro all'enum e metodo BuildFoxproConnectionString che genera "Data Source=percorso[;CodePage=n][;IncludeDeleted=true]". - DataConnection/CredentialManagement/Models/CredentialExtensions.cs Mappatura bidirezionale Foxpro tra enum CredentialManager e DataConnection. - DataConnection/CredentialManagement/Services/DataConnectionCredentialService.cs Aggiunto TestFoxproConnection: verifica percorso accessibile, legge nomi tabelle, restituisce messaggio con conteggio tabelle, encoding rilevato e nota sola lettura. - DataConnection/DB/EF/DatabaseSchemaProviderFactory.cs Caso Foxpro => new FoxProSchemaProvider(). - Data_Coupler/Services/DataConnectionFactory.cs Branch Foxpro => new FoxProDatabaseManager(connectionString). - Data_Coupler/Extensions/DataCoupler/DatabaseMethod.cs Aggiunto IsFoxproConnection() e caso Foxpro in CreateLimitedQuery (SELECT TOP n * FROM (query) AS subquery). - CredentialManager/Integration/DataConnectionHelper.cs ValidateDatabaseCredential: introdotto flag isFileBased (Sqlite || Foxpro) per esonerare host/utente/password/porta; messaggio di errore specifico per il campo percorso FoxPro. - Data_Coupler/Pages/CredentialManagement.razor Aggiunta opzione "Visual FoxPro (.dbc / .dbf)" nel selettore tipo database. Sezione di configurazione dedicata: banner sola lettura, campo percorso con esempi (.dbc e cartella), dropdown code page (Auto/1252/1250/1251/850/437/65001), checkbox record cancellati, pannello tipi supportati, anteprima connection string. Fix validazione form test-connessione: branch Foxpro che verifica solo DatabaseName (non Host/Username/Password), eliminando il falso errore "Il campo Host è obbligatorio". Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2062 lines
108 KiB
Plaintext
2062 lines
108 KiB
Plaintext
@page "/credentials"
|
|
@using System.Linq
|
|
@using CredentialManager.Models
|
|
@using CredentialManager.Services
|
|
@using DataConnection.CredentialManagement.Interfaces
|
|
@using DataConnection.CredentialManagement.Models
|
|
@using Microsoft.AspNetCore.Components.Forms
|
|
@using Microsoft.JSInterop
|
|
@inject IDataConnectionCredentialService CredentialService
|
|
@inject IOdbcDsnDiscoveryService OdbcDsnDiscoveryService
|
|
@inject IOleDbProviderDiscoveryService OleDbProviderDiscoveryService
|
|
@inject IJSRuntime JSRuntime
|
|
@inject NavigationManager Navigation
|
|
|
|
<PageTitle>Gestione Credenziali</PageTitle>
|
|
|
|
<h3>Gestione Credenziali</h3>
|
|
|
|
@* Controllo per credenziali problematiche *@
|
|
@if (hasProblematicCredentials && !loading)
|
|
{
|
|
<div class="alert alert-warning mb-4" role="alert">
|
|
<h4 class="alert-heading"><i class="oi oi-warning"></i> Attenzione!</h4>
|
|
<p>
|
|
Sono state rilevate credenziali che non possono essere decrittografate.
|
|
Questo può accadere quando l'applicazione viene eseguita su una macchina o con un utente diverso
|
|
da quello utilizzato per creare le credenziali.
|
|
</p>
|
|
<hr>
|
|
<p class="mb-0">
|
|
<button class="btn btn-warning" @onclick="@(() => Navigation.NavigateTo("/credential-migration"))">
|
|
<i class="oi oi-wrench"></i> Risolvi Problema Credenziali
|
|
</button>
|
|
<button class="btn btn-outline-warning ms-2" @onclick="CheckForProblematicCredentials">
|
|
<i class="oi oi-reload"></i> Verifica Nuovamente
|
|
</button>
|
|
</p>
|
|
</div>
|
|
}
|
|
|
|
<div class="row mb-3">
|
|
<div class="col">
|
|
<div class="btn-group" role="group">
|
|
<button class="btn btn-primary" @onclick="async () => await ShowAddDatabaseModal()">
|
|
<i class="oi oi-plus"></i> Database
|
|
</button>
|
|
<button class="btn btn-secondary" @onclick="ShowAddRestApiModal">
|
|
<i class="oi oi-plus"></i> REST API / Servizi
|
|
</button>
|
|
</div>
|
|
<button class="btn btn-info ms-3" @onclick="RefreshCredentials">
|
|
<i class="oi oi-reload"></i> Aggiorna
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
@if (loading)
|
|
{
|
|
<div class="text-center">
|
|
<div class="spinner-border" role="status">
|
|
<span class="visually-hidden">Caricamento...</span>
|
|
</div>
|
|
</div>
|
|
}
|
|
else if (!string.IsNullOrEmpty(errorMessage))
|
|
{
|
|
<div class="alert alert-danger" role="alert">
|
|
<h4 class="alert-heading">Errore di Sistema</h4>
|
|
<p>@errorMessage</p>
|
|
<hr>
|
|
<p class="mb-0">
|
|
<button class="btn btn-outline-danger" @onclick="RefreshCredentials">
|
|
<i class="oi oi-reload"></i> Riprova
|
|
</button>
|
|
</p>
|
|
</div>
|
|
}
|
|
else
|
|
{
|
|
<!-- Credenziali Database -->
|
|
<div class="row">
|
|
<div class="col-12">
|
|
<h4>Credenziali Database (@databaseCredentials.Count)</h4>
|
|
@if (databaseCredentials.Any())
|
|
{
|
|
<div class="table-responsive">
|
|
<table class="table table-striped">
|
|
<thead>
|
|
<tr>
|
|
<th>Nome</th>
|
|
<th>Tipo Database</th>
|
|
<th>Host:Porta</th>
|
|
<th>Database</th>
|
|
<th>Username</th>
|
|
<th>Azioni</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
@foreach (var credential in databaseCredentials)
|
|
{ <tr>
|
|
<td>@credential.Name</td>
|
|
<td>@credential.DatabaseType</td>
|
|
<td>@credential.Host:@credential.Port</td>
|
|
<td>
|
|
@if (string.IsNullOrEmpty(credential.DatabaseName))
|
|
{
|
|
<em class="text-muted">Connessione server</em>
|
|
}
|
|
else
|
|
{
|
|
@credential.DatabaseName
|
|
}
|
|
</td>
|
|
<td>@credential.Username</td>
|
|
<td>
|
|
<button class="btn btn-sm btn-outline-primary" @onclick="async () => await EditDatabaseCredential(credential)">
|
|
<i class="oi oi-pencil"></i>
|
|
</button>
|
|
<button class="btn btn-sm btn-outline-success ms-1" @onclick="() => TestDatabaseConnection(credential)">
|
|
<i class="oi oi-check"></i>
|
|
</button>
|
|
<button class="btn btn-sm btn-outline-danger ms-1" @onclick="() => DeleteCredential(credential.Name, true)">
|
|
<i class="oi oi-trash"></i>
|
|
</button>
|
|
</td>
|
|
</tr>
|
|
}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
}
|
|
else
|
|
{
|
|
<div class="alert alert-info">
|
|
Nessuna credenziale database configurata.
|
|
</div>
|
|
}
|
|
</div>
|
|
</div>
|
|
|
|
<hr /> <!-- Credenziali REST API -->
|
|
<div class="row">
|
|
<div class="col-12">
|
|
<h4>Credenziali REST API / Servizi (@restApiCredentials.Count)</h4>
|
|
@if (restApiCredentials.Any())
|
|
{
|
|
<div class="table-responsive">
|
|
<table class="table table-striped">
|
|
<thead>
|
|
<tr>
|
|
<th>Nome</th>
|
|
<th>Tipo Servizio</th>
|
|
<th>Base URL</th>
|
|
<th>Autenticazione</th>
|
|
<th>Timeout (s)</th>
|
|
<th>Azioni</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
@foreach (var credential in restApiCredentials)
|
|
{
|
|
<tr>
|
|
<td>@credential.Name</td>
|
|
<td>
|
|
@if (credential.ServiceType == RestServiceType.SapB1ServiceLayer)
|
|
{
|
|
<span class="badge bg-warning">SAP B1</span>
|
|
}
|
|
else if (credential.ServiceType == RestServiceType.Salesforce)
|
|
{
|
|
<span class="badge bg-success">Salesforce</span>
|
|
@if (credential.IsSandbox)
|
|
{
|
|
<span class="badge bg-secondary ms-1">Sandbox</span>
|
|
}
|
|
}
|
|
else
|
|
{
|
|
<span class="badge bg-info">Generic REST</span>
|
|
}
|
|
</td>
|
|
<td>@credential.BaseUrl</td>
|
|
<td>@GetAuthenticationType(credential)</td>
|
|
<td>@credential.TimeoutSeconds</td>
|
|
<td>
|
|
<button class="btn btn-sm btn-outline-primary" @onclick="() => EditRestApiCredential(credential)">
|
|
<i class="oi oi-pencil"></i>
|
|
</button>
|
|
<button class="btn btn-sm btn-outline-success ms-1" @onclick="() => TestRestApiConnection(credential)">
|
|
<i class="oi oi-check"></i>
|
|
</button>
|
|
<button class="btn btn-sm btn-outline-danger ms-1" @onclick="() => DeleteCredential(credential.Name, false)">
|
|
<i class="oi oi-trash"></i>
|
|
</button>
|
|
</td>
|
|
</tr>
|
|
}
|
|
</tbody>
|
|
</table>
|
|
</div> }
|
|
else
|
|
{
|
|
<div class="alert alert-info">
|
|
Nessuna credenziale REST API configurata.
|
|
</div>
|
|
}
|
|
</div>
|
|
</div>
|
|
}
|
|
|
|
<!-- Modal per Aggiungere/Modificare Credenziale Database -->
|
|
@if (showDatabaseModal)
|
|
{
|
|
<div class="modal fade show d-block" tabindex="-1" style="background-color: rgba(0,0,0,0.5);">
|
|
<div class="modal-dialog modal-lg">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h5 class="modal-title">@(editingDatabaseCredential == null ? "Aggiungi" : "Modifica") Credenziale Database</h5>
|
|
<button type="button" class="btn-close" @onclick="CloseDatabaseModal"></button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<EditForm Model="currentDatabaseCredential" OnValidSubmit="SaveDatabaseCredential">
|
|
<DataAnnotationsValidator />
|
|
<ValidationSummary />
|
|
|
|
<div class="row">
|
|
<div class="col-md-6">
|
|
<div class="mb-3">
|
|
<label class="form-label">Nome *</label>
|
|
<InputText class="form-control" @bind-Value="currentDatabaseCredential.Name" />
|
|
</div>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<div class="mb-3">
|
|
<label class="form-label">Tipo Database *</label>
|
|
<InputSelect class="form-select" @bind-Value="currentDatabaseCredential.DatabaseType"
|
|
@bind-Value:after="OnDatabaseTypeChangedAsync">
|
|
<option value="@CredentialManager.Models.DatabaseType.SqlServer">SQL Server</option>
|
|
@* <option value="@CredentialManager.Models.DatabaseType.MySql">MySQL</option>
|
|
<option value="@CredentialManager.Models.DatabaseType.PostgreSql">PostgreSQL</option>
|
|
<option value="@CredentialManager.Models.DatabaseType.Oracle">Oracle</option>
|
|
<option value="@CredentialManager.Models.DatabaseType.Sqlite">SQLite</option>
|
|
<option value="@CredentialManager.Models.DatabaseType.DB2">DB2</option>
|
|
<option value="@CredentialManager.Models.DatabaseType.SapHana">SAP HANA</option>*@
|
|
<option value="@CredentialManager.Models.DatabaseType.Odbc">ODBC</option>
|
|
<option value="@CredentialManager.Models.DatabaseType.OleDb">OLE DB</option>
|
|
<option value="@CredentialManager.Models.DatabaseType.Foxpro">Visual FoxPro (.dbc / .dbf)</option>
|
|
</InputSelect>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
@if (currentDatabaseCredential.DatabaseType == CredentialManager.Models.DatabaseType.Odbc)
|
|
{
|
|
<!-- Configurazione ODBC -->
|
|
<div class="card mb-3">
|
|
<div class="card-header bg-info text-white">
|
|
<h6 class="mb-0"><i class="oi oi-link-intact"></i> Configurazione ODBC</h6>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="mb-3">
|
|
<label class="form-label">Modalità Connessione *</label>
|
|
<select class="form-select" @bind="currentDatabaseCredential.OdbcMode">
|
|
<option value="@CredentialManager.Models.OdbcConnectionMode.Dsn">Utilizza DSN (Data Source Name)</option>
|
|
<option value="@CredentialManager.Models.OdbcConnectionMode.Custom">Connection String Personalizzata</option>
|
|
</select>
|
|
<small class="form-text text-muted">
|
|
@if (currentDatabaseCredential.OdbcMode == CredentialManager.Models.OdbcConnectionMode.Dsn)
|
|
{
|
|
<span>Seleziona un DSN ODBC configurato sul sistema</span>
|
|
}
|
|
else
|
|
{
|
|
<span>Crea una connection string personalizzata con guida passo-passo</span>
|
|
}
|
|
</small>
|
|
</div>
|
|
|
|
@if (currentDatabaseCredential.OdbcMode == CredentialManager.Models.OdbcConnectionMode.Dsn)
|
|
{
|
|
<!-- Modalità DSN -->
|
|
<div class="row">
|
|
<div class="col-md-12">
|
|
<div class="mb-3">
|
|
<label class="form-label">
|
|
Seleziona DSN *
|
|
<button type="button" class="btn btn-sm btn-outline-secondary ms-2" @onclick="RefreshOdbcDsnList">
|
|
<i class="oi oi-reload"></i> Aggiorna Lista
|
|
</button>
|
|
</label>
|
|
<select class="form-select" @bind="currentDatabaseCredential.OdbcDsnName">
|
|
<option value="">-- Seleziona un DSN --</option>
|
|
@if (availableOdbcDsn.Any())
|
|
{
|
|
<optgroup label="DSN Utente">
|
|
@foreach (var dsn in availableOdbcDsn.Where(d => d.IsUserDsn))
|
|
{
|
|
<option value="@dsn.Name">@dsn.Name (@dsn.Driver)</option>
|
|
}
|
|
</optgroup>
|
|
<optgroup label="DSN di Sistema">
|
|
@foreach (var dsn in availableOdbcDsn.Where(d => !d.IsUserDsn))
|
|
{
|
|
<option value="@dsn.Name">@dsn.Name (@dsn.Driver)</option>
|
|
}
|
|
</optgroup>
|
|
}
|
|
else
|
|
{
|
|
<option disabled>Nessun DSN ODBC configurato</option>
|
|
}
|
|
</select>
|
|
@if (!string.IsNullOrEmpty(currentDatabaseCredential.OdbcDsnName))
|
|
{
|
|
var selectedDsn = availableOdbcDsn.FirstOrDefault(d => d.Name == currentDatabaseCredential.OdbcDsnName);
|
|
if (selectedDsn != null)
|
|
{
|
|
<div class="alert alert-info mt-2">
|
|
<strong>Driver:</strong> @selectedDsn.Driver<br />
|
|
@if (!string.IsNullOrEmpty(selectedDsn.Description))
|
|
{
|
|
<strong>Descrizione:</strong> @selectedDsn.Description<br />
|
|
}
|
|
<strong>Tipo:</strong> @(selectedDsn.IsUserDsn ? "DSN Utente" : "DSN di Sistema")
|
|
</div>
|
|
}
|
|
}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="row">
|
|
<div class="col-md-6">
|
|
<div class="mb-3">
|
|
<label class="form-label">Username</label>
|
|
<InputText class="form-control" @bind-Value="currentDatabaseCredential.Username"
|
|
placeholder="Lascia vuoto se incluso nel DSN" />
|
|
</div>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<div class="mb-3">
|
|
<label class="form-label">Password</label>
|
|
<InputText type="password" class="form-control" @bind-Value="currentDatabaseCredential.Password"
|
|
placeholder="Lascia vuoto se inclusa nel DSN" />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
}
|
|
else
|
|
{
|
|
<!-- Modalità Custom Connection String Builder -->
|
|
<div class="alert alert-warning">
|
|
<i class="oi oi-info"></i> <strong>Costruzione Guidata Connection String</strong><br />
|
|
Compila i campi per costruire automaticamente la connection string ODBC.
|
|
</div>
|
|
|
|
<div class="mb-3">
|
|
<label class="form-label">
|
|
Driver ODBC *
|
|
<button type="button" class="btn btn-sm btn-outline-secondary ms-2" @onclick="RefreshOdbcDriverList">
|
|
<i class="oi oi-reload"></i> Aggiorna Lista
|
|
</button>
|
|
</label>
|
|
<select class="form-select" @bind="selectedOdbcDriver">
|
|
<option value="">-- Seleziona Driver --</option>
|
|
@foreach (var driver in availableOdbcDrivers)
|
|
{
|
|
<option value="@driver">@driver</option>
|
|
}
|
|
</select>
|
|
@if (!string.IsNullOrEmpty(selectedOdbcDriver))
|
|
{
|
|
<small class="form-text text-success">
|
|
<i class="oi oi-check"></i> Driver selezionato: @selectedOdbcDriver
|
|
</small>
|
|
}
|
|
</div>
|
|
|
|
<div class="row">
|
|
<div class="col-md-8">
|
|
<div class="mb-3">
|
|
<label class="form-label">Server/Host</label>
|
|
<InputText class="form-control" @bind-Value="currentDatabaseCredential.Host"
|
|
placeholder="es. localhost o 192.168.1.100" />
|
|
</div>
|
|
</div>
|
|
<div class="col-md-4">
|
|
<div class="mb-3">
|
|
<label class="form-label">Porta <small class="text-muted">(opzionale)</small></label>
|
|
<InputNumber class="form-control" @bind-Value="currentDatabaseCredential.Port"
|
|
placeholder="0 = default" />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="mb-3">
|
|
<label class="form-label">Nome Database</label>
|
|
<InputText class="form-control" @bind-Value="currentDatabaseCredential.DatabaseName"
|
|
placeholder="es. mydatabase" />
|
|
</div>
|
|
|
|
<div class="row">
|
|
<div class="col-md-6">
|
|
<div class="mb-3">
|
|
<label class="form-label">Username</label>
|
|
<InputText class="form-control" @bind-Value="currentDatabaseCredential.Username"
|
|
placeholder="Opzionale se incluso nel driver" />
|
|
</div>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<div class="mb-3">
|
|
<label class="form-label">Password</label>
|
|
<InputText type="password" class="form-control" @bind-Value="currentDatabaseCredential.Password"
|
|
placeholder="Opzionale se inclusa nel driver" />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Parametri Personalizzati -->
|
|
<div class="mb-3">
|
|
<label class="form-label">
|
|
Parametri Personalizzati <small class="text-muted">(opzionale)</small>
|
|
<button type="button" class="btn btn-sm btn-success ms-2" @onclick="AddOdbcCustomParameter">
|
|
<i class="oi oi-plus"></i> Aggiungi
|
|
</button>
|
|
</label>
|
|
<small class="form-text text-muted d-block mb-2">
|
|
Aggiungi parametri aggiuntivi alla connection string (es. TrustServerCertificate=yes, Encrypt=no, etc.)
|
|
</small>
|
|
|
|
@if (currentDatabaseCredential.AdditionalParameters != null && currentDatabaseCredential.AdditionalParameters.Any())
|
|
{
|
|
@foreach (var param in currentDatabaseCredential.AdditionalParameters.Where(p => p.Key != "Driver").ToList())
|
|
{
|
|
<div class="input-group mb-2">
|
|
<input type="text" class="form-control" placeholder="Nome parametro"
|
|
value="@param.Key" @onchange="@(e => UpdateOdbcParameterKey(param.Key, e.Value?.ToString() ?? string.Empty))" />
|
|
<span class="input-group-text">=</span>
|
|
<input type="text" class="form-control" placeholder="Valore"
|
|
value="@param.Value" @onchange="@(e => UpdateOdbcParameterValue(param.Key, e.Value?.ToString() ?? string.Empty))" />
|
|
<button type="button" class="btn btn-outline-danger" @onclick="@(() => RemoveOdbcParameter(param.Key))">
|
|
<i class="oi oi-trash"></i>
|
|
</button>
|
|
</div>
|
|
}
|
|
}
|
|
else
|
|
{
|
|
<div class="alert alert-light small mb-0">
|
|
<i class="oi oi-info"></i> Nessun parametro personalizzato aggiunto
|
|
</div>
|
|
}
|
|
</div>
|
|
|
|
<!-- Anteprima Connection String -->
|
|
@if (!string.IsNullOrEmpty(selectedOdbcDriver) ||
|
|
!string.IsNullOrEmpty(currentDatabaseCredential.Host))
|
|
{
|
|
<div class="mb-3">
|
|
<label class="form-label">Anteprima Connection String</label>
|
|
<textarea class="form-control font-monospace" rows="3" readonly>@GetOdbcConnectionStringPreview()</textarea>
|
|
<small class="form-text text-muted">
|
|
Questa è un'anteprima della connection string che verrà generata
|
|
</small>
|
|
</div>
|
|
}
|
|
}
|
|
</div>
|
|
</div>
|
|
}
|
|
else if (currentDatabaseCredential.DatabaseType == CredentialManager.Models.DatabaseType.OleDb)
|
|
{
|
|
<!-- Configurazione OLE DB -->
|
|
<div class="card mb-3">
|
|
<div class="card-header bg-warning text-dark">
|
|
<h6 class="mb-0"><i class="oi oi-link-intact"></i> Configurazione OLE DB</h6>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="alert alert-danger py-2">
|
|
<i class="oi oi-warning"></i> <strong>Attenzione — Compatibilità 32-bit:</strong>
|
|
Driver come <strong>VFPOLEDB.1</strong> (Visual FoxPro) sono <strong>esclusivamente 32-bit</strong>.
|
|
Pubblica l'applicazione con <code>dotnet publish --runtime win-x86</code>.
|
|
Vedi <strong>PUBLISH_32BIT_64BIT.md</strong> per tutti i dettagli.
|
|
</div>
|
|
|
|
<div class="mb-3">
|
|
<label class="form-label">
|
|
Provider OLE DB *
|
|
<button type="button" class="btn btn-sm btn-outline-secondary ms-2" @onclick="RefreshOleDbProviderList">
|
|
<i class="oi oi-reload"></i> Aggiorna Lista
|
|
</button>
|
|
</label>
|
|
@if (availableOleDbProviders.Any())
|
|
{
|
|
<select class="form-select" @bind="selectedOleDbProvider">
|
|
<option value="">-- Seleziona Provider --</option>
|
|
@foreach (var prov in availableOleDbProviders)
|
|
{
|
|
<option value="@prov.ProgId">@prov.ProgId — @prov.Description</option>
|
|
}
|
|
</select>
|
|
@if (!string.IsNullOrEmpty(selectedOleDbProvider))
|
|
{
|
|
var info = availableOleDbProviders.FirstOrDefault(p => p.ProgId == selectedOleDbProvider);
|
|
if (info?.Note != null)
|
|
{
|
|
<div class="alert alert-warning mt-2 py-2 small">
|
|
<i class="oi oi-warning"></i> @info.Note
|
|
</div>
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
<div class="alert alert-info py-2 small">
|
|
<i class="oi oi-info"></i> Nessun provider OLE DB rilevato automaticamente.
|
|
Potrebbe essere necessario eseguire in modalità 32-bit. Inserisci manualmente il ProgID:
|
|
</div>
|
|
}
|
|
<InputText class="form-control mt-1" @bind-Value="selectedOleDbProvider"
|
|
placeholder="es. VFPOLEDB.1, Microsoft.ACE.OLEDB.12.0, Microsoft.Jet.OLEDB.4.0" />
|
|
<small class="form-text text-muted">
|
|
Provider comuni: <code>VFPOLEDB.1</code> (VFP), <code>Microsoft.ACE.OLEDB.12.0</code> (Access/Excel), <code>Microsoft.Jet.OLEDB.4.0</code> (Access 97-2003)
|
|
</small>
|
|
</div>
|
|
|
|
@if (IsVfpProvider())
|
|
{
|
|
<!-- Sezione specifica Visual FoxPro -->
|
|
<div class="mb-3">
|
|
<label class="form-label">Percorso Database VFP * <small class="text-muted">(.dbc o cartella .dbf)</small></label>
|
|
<InputText class="form-control font-monospace"
|
|
@bind-Value="currentDatabaseCredential.DatabaseName"
|
|
placeholder="es. C:\VFP\Database\miodb.dbc oppure C:\VFP\Tabelle\" />
|
|
<small class="form-text text-muted">
|
|
Per database container (<code>.dbc</code>): percorso completo al file.<br />
|
|
Per tabelle free (<code>.dbf</code>): percorso della cartella contenente i file.
|
|
</small>
|
|
</div>
|
|
<div class="row">
|
|
<div class="col-md-6">
|
|
<div class="mb-3">
|
|
<label class="form-label">Collating Sequence</label>
|
|
<select class="form-select" @bind="oleDbCollatingSequence">
|
|
<option value="">-- Default (machine) --</option>
|
|
<option value="machine">machine</option>
|
|
<option value="general">general</option>
|
|
<option value="spanish">spanish</option>
|
|
<option value="dutch">dutch</option>
|
|
<option value="french">french</option>
|
|
<option value="german">german</option>
|
|
<option value="italian">italian</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<div class="mb-3">
|
|
<label class="form-label">Record Cancellati (DELETED)</label>
|
|
<select class="form-select" @bind="oleDbDeleted">
|
|
<option value="">-- Default (ON) --</option>
|
|
<option value="ON">ON — escludi record cancellati</option>
|
|
<option value="OFF">OFF — includi record cancellati</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="row">
|
|
<div class="col-md-6">
|
|
<div class="mb-3">
|
|
<label class="form-label">Username <small class="text-muted">(raramente richiesto per VFP)</small></label>
|
|
<InputText class="form-control" @bind-Value="currentDatabaseCredential.Username" placeholder="Opzionale" />
|
|
</div>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<div class="mb-3">
|
|
<label class="form-label">Password <small class="text-muted">(raramente richiesta per VFP)</small></label>
|
|
<InputText type="password" class="form-control" @bind-Value="currentDatabaseCredential.Password" placeholder="Opzionale" />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
}
|
|
else
|
|
{
|
|
<div class="mb-3">
|
|
<label class="form-label">Data Source <small class="text-muted">(percorso file o server)</small></label>
|
|
<InputText class="form-control" @bind-Value="currentDatabaseCredential.DatabaseName"
|
|
placeholder="es. C:\Access\miodb.accdb oppure server\database" />
|
|
</div>
|
|
<div class="row">
|
|
<div class="col-md-6">
|
|
<div class="mb-3">
|
|
<label class="form-label">Username</label>
|
|
<InputText class="form-control" @bind-Value="currentDatabaseCredential.Username" placeholder="Opzionale" />
|
|
</div>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<div class="mb-3">
|
|
<label class="form-label">Password</label>
|
|
<InputText type="password" class="form-control" @bind-Value="currentDatabaseCredential.Password" placeholder="Opzionale" />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
}
|
|
|
|
<div class="mb-3">
|
|
<label class="form-label">Anteprima Connection String</label>
|
|
<textarea class="form-control font-monospace" rows="3" readonly>@GetOleDbConnectionStringPreview()</textarea>
|
|
<small class="form-text text-muted">Anteprima della connection string che verrà generata</small>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
}
|
|
else if (currentDatabaseCredential.DatabaseType == CredentialManager.Models.DatabaseType.Foxpro)
|
|
{
|
|
<!-- Configurazione Visual FoxPro -->
|
|
<div class="card mb-3">
|
|
<div class="card-header bg-success text-white">
|
|
<h6 class="mb-0"><i class="oi oi-hard-drive"></i> Configurazione Visual FoxPro</h6>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="alert alert-info py-2">
|
|
<i class="oi oi-info"></i> Lettura <strong>managed</strong> dei file <code>.dbf</code>/<code>.fpt</code>/<code>.dbc</code>:
|
|
funziona a <strong>64-bit senza installare alcun provider</strong>. FoxPro è utilizzabile come
|
|
<strong>sorgente</strong> (sola lettura).
|
|
</div>
|
|
|
|
<div class="mb-3">
|
|
<label class="form-label">Percorso Database FoxPro *</label>
|
|
<InputText class="form-control font-monospace"
|
|
@bind-Value="currentDatabaseCredential.DatabaseName"
|
|
placeholder="es. C:\dati\Data.dbc oppure C:\dati" />
|
|
<small class="form-text text-muted">
|
|
<strong>Database container</strong>: percorso completo al file <code>.dbc</code>
|
|
(es. <code>C:\Users\aless\Desktop\data\Data.dbc</code>).<br />
|
|
<strong>Tabelle libere</strong>: percorso della <strong>cartella</strong> che contiene i file <code>.dbf</code>
|
|
(es. <code>C:\Users\aless\Desktop\data</code>).
|
|
</small>
|
|
</div>
|
|
|
|
<div class="row">
|
|
<div class="col-md-6">
|
|
<div class="mb-3">
|
|
<label class="form-label">Code Page (encoding)</label>
|
|
<select class="form-select" @bind="foxproCodePage">
|
|
<option value="">-- Auto / Default (1252) --</option>
|
|
<option value="1252">1252 — Europa occidentale (ANSI)</option>
|
|
<option value="1250">1250 — Europa centrale</option>
|
|
<option value="1251">1251 — Cirillico</option>
|
|
<option value="850">850 — DOS Latin-1</option>
|
|
<option value="437">437 — DOS US</option>
|
|
<option value="65001">65001 — UTF-8</option>
|
|
</select>
|
|
<small class="form-text text-muted">Per dati italiani lasciare 1252.</small>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<div class="mb-3">
|
|
<label class="form-label">Record cancellati</label>
|
|
<div class="form-check mt-2">
|
|
<input class="form-check-input" type="checkbox" id="foxproIncludeDeleted" @bind="foxproIncludeDeleted" />
|
|
<label class="form-check-label" for="foxproIncludeDeleted">
|
|
Includi i record marcati come cancellati
|
|
</label>
|
|
</div>
|
|
<small class="form-text text-muted">Di default i record cancellati vengono esclusi.</small>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="alert alert-light border py-2 small mb-3">
|
|
<i class="oi oi-list"></i> Tipi VFP supportati: Character, Memo, Numeric, Float, Double,
|
|
Integer, Currency, Date, DateTime, Logical. I campi memo (<code>.fpt</code>) e gli indici
|
|
(<code>.cdx</code>) vengono rilevati automaticamente accanto al <code>.dbf</code>.
|
|
</div>
|
|
|
|
<div class="mb-3">
|
|
<label class="form-label">Anteprima percorso/connessione</label>
|
|
<textarea class="form-control font-monospace" rows="2" readonly>@GetFoxproConnectionStringPreview()</textarea>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
}
|
|
else
|
|
{
|
|
<!-- Configurazione Standard Database -->
|
|
<div class="row">
|
|
<div class="col-md-8">
|
|
<div class="mb-3">
|
|
<label class="form-label">Host/Server *</label>
|
|
<InputText class="form-control" @bind-Value="currentDatabaseCredential.Host"
|
|
placeholder="es. localhost o server.dominio.com" />
|
|
@if (currentDatabaseCredential.DatabaseType == DatabaseType.SqlServer)
|
|
{
|
|
<div class="form-text">
|
|
<strong>SQL Server locale:</strong><br/>
|
|
• Named Instance: <code>localhost\SQLEXPRESS</code> o <code>.\SQLEXPRESS</code><br/>
|
|
• LocalDB: <code>(localdb)\MSSQLLocalDB</code><br/>
|
|
• Default: <code>localhost</code> o <code>.</code> (usa porta 1433)
|
|
</div>
|
|
}
|
|
</div>
|
|
</div>
|
|
<div class="col-md-4">
|
|
<div class="mb-3">
|
|
<label class="form-label">Porta *</label>
|
|
<InputNumber class="form-control" @bind-Value="currentDatabaseCredential.Port" />
|
|
@if (currentDatabaseCredential.DatabaseType == DatabaseType.SqlServer)
|
|
{
|
|
<div class="form-text">
|
|
<small>Ignorata per named instances e LocalDB</small>
|
|
</div>
|
|
}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="mb-3">
|
|
<label class="form-label">Nome Database <small class="text-muted">(opzionale)</small></label>
|
|
<InputText class="form-control" @bind-Value="currentDatabaseCredential.DatabaseName"
|
|
placeholder="Lascia vuoto per connessione al server senza database specifico" />
|
|
<div class="form-text">Se non specificato, la connessione sarà al server senza selezionare un database specifico</div>
|
|
</div>
|
|
|
|
<div class="row">
|
|
<div class="col-md-6">
|
|
<div class="mb-3">
|
|
<label class="form-label">Username *</label>
|
|
<InputText class="form-control" @bind-Value="currentDatabaseCredential.Username"
|
|
placeholder="o scrivi 'Integrated' per Windows Auth" />
|
|
@if (currentDatabaseCredential.DatabaseType == DatabaseType.SqlServer)
|
|
{
|
|
<div class="form-text">
|
|
<small>Per Windows Authentication, scrivi <strong>Integrated</strong> o lascia vuoto</small>
|
|
</div>
|
|
}
|
|
</div>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<div class="mb-3">
|
|
<label class="form-label">Password *</label>
|
|
<InputText type="password" class="form-control" @bind-Value="currentDatabaseCredential.Password" />
|
|
@if (currentDatabaseCredential.DatabaseType == DatabaseType.SqlServer)
|
|
{
|
|
<div class="form-text">
|
|
<small>Non richiesta per Windows Authentication</small>
|
|
</div>
|
|
}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
}
|
|
|
|
<div class="row">
|
|
<div class="col-md-6">
|
|
<div class="mb-3">
|
|
<label class="form-label">Timeout Comando (s)</label>
|
|
<InputNumber class="form-control" @bind-Value="currentDatabaseCredential.CommandTimeout" />
|
|
</div>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<div class="mb-3 form-check">
|
|
<InputCheckbox class="form-check-input" @bind-Value="currentDatabaseCredential.IgnoreSslErrors" />
|
|
<label class="form-check-label">
|
|
Ignora errori SSL
|
|
</label>
|
|
</div>
|
|
</div>
|
|
</div> <div class="modal-footer">
|
|
<button type="button" class="btn btn-secondary" @onclick="CloseDatabaseModal">Annulla</button>
|
|
<button type="button" class="btn btn-info" @onclick="TestCurrentDatabaseConnection" disabled="@testingConnection">
|
|
@if (testingConnection)
|
|
{
|
|
<span class="spinner-border spinner-border-sm me-2" role="status" aria-hidden="true"></span>
|
|
}
|
|
<i class="oi oi-check"></i> Testa Connessione
|
|
</button>
|
|
<button type="submit" class="btn btn-primary">Salva</button>
|
|
</div>
|
|
</EditForm>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
}
|
|
|
|
<!-- Modal per Aggiungere/Modificare Credenziale REST API -->
|
|
@if (showRestApiModal)
|
|
{
|
|
<div class="modal fade show d-block" tabindex="-1" style="background-color: rgba(0,0,0,0.5);">
|
|
<div class="modal-dialog modal-lg">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h5 class="modal-title">
|
|
@(editingRestApiCredential == null ? "Aggiungi" : "Modifica") Credenziale
|
|
@GetServiceTypeDisplayName(currentRestApiCredential.ServiceType)
|
|
</h5>
|
|
<button type="button" class="btn-close" @onclick="CloseRestApiModal"></button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<EditForm Model="currentRestApiCredential" OnValidSubmit="SaveRestApiCredential">
|
|
<DataAnnotationsValidator />
|
|
<ValidationSummary />
|
|
|
|
<div class="row">
|
|
<div class="col-md-6">
|
|
<div class="mb-3">
|
|
<label class="form-label">Nome *</label>
|
|
<InputText class="form-control" @bind-Value="currentRestApiCredential.Name" />
|
|
</div>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<div class="mb-3">
|
|
<label class="form-label">Tipo Servizio *</label>
|
|
<InputSelect class="form-select" @bind-Value="currentRestApiCredential.ServiceType"
|
|
@onchange="OnServiceTypeChanged">
|
|
<option value="@RestServiceType.Generic">REST API Generico</option>
|
|
<option value="@RestServiceType.SapB1ServiceLayer">SAP B1 Service Layer</option>
|
|
<option value="@RestServiceType.Salesforce">Salesforce</option>
|
|
</InputSelect>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Campi comuni -->
|
|
<div class="mb-3">
|
|
<label class="form-label">
|
|
@GetUrlFieldLabel(currentRestApiCredential.ServiceType) *
|
|
</label>
|
|
<InputText class="form-control" @bind-Value="currentRestApiCredential.BaseUrl"
|
|
placeholder="@GetUrlPlaceholder(currentRestApiCredential.ServiceType)" />
|
|
<div class="form-text">@GetUrlHelpText(currentRestApiCredential.ServiceType)</div>
|
|
</div>
|
|
|
|
<!-- Campi specifici per SAP B1 Service Layer -->
|
|
@if (currentRestApiCredential.ServiceType == RestServiceType.SapB1ServiceLayer)
|
|
{
|
|
<div class="row">
|
|
<div class="col-md-6"> <div class="mb-3">
|
|
<label class="form-label">@GetFieldLabel("CompanyDatabase", currentRestApiCredential.ServiceType)</label>
|
|
<InputText class="form-control" @bind-Value="currentRestApiCredential.CompanyDatabase"
|
|
placeholder="SBODemoUS" />
|
|
<div class="form-text">Nome del database azienda SAP B1</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<div class="mb-3">
|
|
<label class="form-label">Versione Service Layer</label>
|
|
<InputSelect class="form-select" @bind-Value="currentRestApiCredential.Version">
|
|
<option value="v1">v1</option>
|
|
<option value="v2">v2</option>
|
|
</InputSelect>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="row">
|
|
<div class="col-md-6">
|
|
<div class="mb-3">
|
|
<label class="form-label">Lingua</label>
|
|
<InputSelect class="form-select" @bind-Value="currentRestApiCredential.Language">
|
|
<option value="en-US">English (US)</option>
|
|
<option value="it-IT">Italiano</option>
|
|
<option value="de-DE">Deutsch</option>
|
|
<option value="fr-FR">Français</option>
|
|
<option value="es-ES">Español</option>
|
|
</InputSelect>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<div class="form-check mt-4">
|
|
<InputCheckbox class="form-check-input" @bind-Value="currentRestApiCredential.UseTrustedConnection" />
|
|
<label class="form-check-label">
|
|
Usa autenticazione Windows
|
|
</label>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
} <!-- Campi specifici per Salesforce -->
|
|
@if (currentRestApiCredential.ServiceType == RestServiceType.Salesforce)
|
|
{
|
|
<div class="row mb-3">
|
|
<div class="col-md-12">
|
|
<label class="form-label fw-semibold">Tipo di Autenticazione OAuth2</label>
|
|
<InputSelect class="form-select" @bind-Value="currentRestApiCredential.GrantType">
|
|
<option value="Password">Password Flow — Username + Password + Security Token (grant_type=password)</option>
|
|
<option value="ClientCredentials">Client Credentials — Server-to-Server, nessun utente (grant_type=client_credentials)</option>
|
|
</InputSelect>
|
|
@if (currentRestApiCredential.GrantType == CredentialManager.Models.SalesforceGrantType.ClientCredentials)
|
|
{
|
|
<div class="alert alert-warning mt-2 py-2">
|
|
<i class="fa fa-exclamation-triangle me-1"></i>
|
|
<strong>client_credentials</strong>: il <strong>Base URL</strong> deve essere il <strong>My Domain URL</strong> della tua org
|
|
(es. <code>https://myorg.my.salesforce.com</code>), <strong>non</strong> login.salesforce.com.<br/>
|
|
Richiede: Connected App con "Enable Client Credentials Flow" attivato e un Integration User assegnato.
|
|
</div>
|
|
}
|
|
else
|
|
{
|
|
<div class="alert alert-info mt-2 py-2">
|
|
<i class="fa fa-info-circle me-1"></i>
|
|
<strong>password flow</strong>: Username, Password + Security Token. Client ID/Secret facoltativi (Connected App).
|
|
</div>
|
|
}
|
|
</div>
|
|
</div>
|
|
<div class="row">
|
|
<div class="col-md-6">
|
|
<div class="mb-3">
|
|
<label class="form-label">Tipo Ambiente</label>
|
|
<InputSelect class="form-select" @bind-Value="currentRestApiCredential.IsSandbox"
|
|
@onchange="OnSandboxChanged">
|
|
<option value="false">Production</option>
|
|
<option value="true">Sandbox</option>
|
|
</InputSelect>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<div class="mb-3">
|
|
<label class="form-label">API Version</label>
|
|
<InputText class="form-control" @bind-Value="currentRestApiCredential.ApiVersion" />
|
|
<div class="form-text">Esempio: 59.0</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
@if (currentRestApiCredential.GrantType != CredentialManager.Models.SalesforceGrantType.ClientCredentials)
|
|
{
|
|
<div class="mb-3">
|
|
<label class="form-label">@GetFieldLabel("SecurityToken", currentRestApiCredential.ServiceType)</label>
|
|
<InputText type="password" class="form-control" @bind-Value="currentRestApiCredential.SecurityToken" />
|
|
<div class="form-text">Token di sicurezza Salesforce (richiesto solo se non si usa OAuth o Connected App)</div>
|
|
</div>
|
|
}
|
|
|
|
<div class="row">
|
|
<div class="col-md-6">
|
|
<div class="mb-3">
|
|
<label class="form-label">Client ID (Connected App)</label>
|
|
<InputText class="form-control" @bind-Value="currentRestApiCredential.ClientId" />
|
|
<div class="form-text">Consumer Key per OAuth</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<div class="mb-3">
|
|
<label class="form-label">Client Secret (Connected App)</label>
|
|
<InputText type="password" class="form-control" @bind-Value="currentRestApiCredential.ClientSecret" />
|
|
<div class="form-text">Consumer Secret per OAuth</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="form-check mb-3">
|
|
<InputCheckbox class="form-check-input" @bind-Value="currentRestApiCredential.UseSoapApi" />
|
|
<label class="form-check-label">
|
|
Usa SOAP API (invece di REST)
|
|
</label>
|
|
</div>
|
|
}
|
|
|
|
<!-- Campi per autenticazione generica (solo per REST Generico) -->
|
|
@if (currentRestApiCredential.ServiceType == RestServiceType.Generic)
|
|
{
|
|
<div class="row">
|
|
<div class="col-md-6">
|
|
<div class="mb-3">
|
|
<label class="form-label">API Key</label>
|
|
<InputText class="form-control" @bind-Value="currentRestApiCredential.ApiKey" />
|
|
</div>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<div class="mb-3">
|
|
<label class="form-label">Auth Token</label>
|
|
<InputText class="form-control" @bind-Value="currentRestApiCredential.AuthToken" />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
}
|
|
|
|
<!-- Campi comuni per autenticazione username/password -->
|
|
@if ((currentRestApiCredential.ServiceType != RestServiceType.Generic ||
|
|
(string.IsNullOrEmpty(currentRestApiCredential.ApiKey) && string.IsNullOrEmpty(currentRestApiCredential.AuthToken))) &&
|
|
!(currentRestApiCredential.ServiceType == RestServiceType.Salesforce && currentRestApiCredential.GrantType == CredentialManager.Models.SalesforceGrantType.ClientCredentials))
|
|
{ <div class="row">
|
|
<div class="col-md-6">
|
|
<div class="mb-3">
|
|
<label class="form-label">@GetFieldLabel("Username", currentRestApiCredential.ServiceType)</label>
|
|
<InputText class="form-control" @bind-Value="currentRestApiCredential.Username" />
|
|
</div>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<div class="mb-3">
|
|
<label class="form-label">@GetFieldLabel("Password", currentRestApiCredential.ServiceType)</label>
|
|
<InputText type="password" class="form-control" @bind-Value="currentRestApiCredential.Password" />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
}
|
|
|
|
<!-- Campi comuni finali -->
|
|
<div class="row">
|
|
<div class="col-md-6">
|
|
<div class="mb-3">
|
|
<label class="form-label">Timeout (secondi)</label>
|
|
<InputNumber class="form-control" @bind-Value="currentRestApiCredential.TimeoutSeconds" />
|
|
</div>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<div class="form-check mt-4">
|
|
<InputCheckbox class="form-check-input" @bind-Value="currentRestApiCredential.IgnoreSslErrors" />
|
|
<label class="form-check-label">
|
|
Ignora errori SSL
|
|
</label>
|
|
</div>
|
|
</div>
|
|
</div> <div class="modal-footer">
|
|
<button type="button" class="btn btn-secondary" @onclick="CloseRestApiModal">Annulla</button>
|
|
@if (!string.IsNullOrEmpty(currentRestApiCredential.Name))
|
|
{
|
|
<button type="button" class="btn btn-info" @onclick="() => TestRestApiConnectionFromModal()"
|
|
disabled="@testingConnection">
|
|
@if (testingConnection)
|
|
{
|
|
<span class="spinner-border spinner-border-sm me-2" role="status"></span>
|
|
}
|
|
<i class="oi oi-check"></i> Test Connessione
|
|
</button>
|
|
}
|
|
<button type="submit" class="btn btn-primary">Salva</button>
|
|
</div>
|
|
</EditForm>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
}
|
|
|
|
@* Modale di Conferma Eliminazione *@
|
|
@if (showDeleteConfirmModal)
|
|
{
|
|
<div class="modal fade show d-block" tabindex="-1" style="background-color: rgba(0,0,0,0.5);">
|
|
<div class="modal-dialog">
|
|
<div class="modal-content">
|
|
<div class="modal-header bg-danger text-white">
|
|
<h5 class="modal-title">
|
|
<i class="oi oi-warning"></i> Conferma Eliminazione
|
|
</h5>
|
|
<button type="button" class="btn-close btn-close-white" @onclick="CloseDeleteConfirmModal"></button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<div class="alert alert-danger" role="alert">
|
|
<h5 class="alert-heading">
|
|
<i class="oi oi-warning"></i> ATTENZIONE!
|
|
</h5>
|
|
<p class="mb-0">
|
|
All'eliminazione delle credenziali <strong>@credentialToDeleteName</strong> verranno eliminati anche:
|
|
</p>
|
|
<ul class="mt-2 mb-2">
|
|
<li>Tutti i <strong>profili</strong> associati a queste credenziali</li>
|
|
<li>Tutte le <strong>schedulazioni</strong> associate a questi profili</li>
|
|
</ul>
|
|
<p class="mb-0">
|
|
<strong>L'eliminazione è irreversibile!</strong> Procedere?
|
|
</p>
|
|
</div>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button type="button" class="btn btn-secondary" @onclick="CloseDeleteConfirmModal">
|
|
<i class="oi oi-x"></i> Annulla
|
|
</button>
|
|
<button type="button" class="btn btn-danger" @onclick="ConfirmDeleteCredential">
|
|
<i class="oi oi-trash"></i> Elimina Definitivamente
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
}
|
|
|
|
|
|
@code { private List<DatabaseCredential> databaseCredentials = new();
|
|
private List<RestApiCredential> restApiCredentials = new();
|
|
private bool loading = true;
|
|
private string? errorMessage = null;
|
|
private bool testingConnection = false;
|
|
private bool hasProblematicCredentials = false;
|
|
|
|
// Modal state
|
|
private bool showDatabaseModal = false;
|
|
private bool showRestApiModal = false;
|
|
private bool showDeleteConfirmModal = false;
|
|
private string? credentialToDeleteName = null;
|
|
private bool credentialToDeleteIsDatabase = false;
|
|
private DatabaseCredential? editingDatabaseCredential = null;
|
|
private RestApiCredential? editingRestApiCredential = null;
|
|
private DatabaseCredential currentDatabaseCredential = new();
|
|
private RestApiCredential currentRestApiCredential = new();
|
|
|
|
// ODBC specific state
|
|
private List<OdbcDsnInfo> availableOdbcDsn = new();
|
|
private List<string> availableOdbcDrivers = new();
|
|
private string selectedOdbcDriver = string.Empty;
|
|
private bool loadingOdbcData = false;
|
|
|
|
// OLE DB specific state
|
|
private List<OleDbProviderInfo> availableOleDbProviders = new();
|
|
private string selectedOleDbProvider = string.Empty;
|
|
private string oleDbCollatingSequence = string.Empty;
|
|
private string oleDbDeleted = string.Empty;
|
|
|
|
// Visual FoxPro specific state
|
|
private string foxproCodePage = string.Empty;
|
|
private bool foxproIncludeDeleted = false;
|
|
|
|
protected override async Task OnInitializedAsync()
|
|
{ await RefreshCredentials();
|
|
CheckForProblematicCredentials();
|
|
} private async Task RefreshCredentials()
|
|
{
|
|
loading = true;
|
|
errorMessage = null;
|
|
try
|
|
{ databaseCredentials = await CredentialService.GetAllDatabaseCredentialsAsync();
|
|
restApiCredentials = await CredentialService.GetAllRestApiCredentialsAsync();
|
|
CheckForProblematicCredentials();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
errorMessage = $"Errore nel caricamento delle credenziali: {ex.Message}";
|
|
// Se l'errore è relativo alla tabella mancante, mostriamo un messaggio più specifico
|
|
if (ex.Message.Contains("no such table: Credentials"))
|
|
{
|
|
errorMessage = "Database non inizializzato correttamente. Riavviare l'applicazione.";
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
loading = false;
|
|
}
|
|
}
|
|
|
|
#region Database Credential Methods
|
|
|
|
private async Task ShowAddDatabaseModal()
|
|
{
|
|
editingDatabaseCredential = null;
|
|
currentDatabaseCredential = new DatabaseCredential
|
|
{
|
|
DatabaseType = CredentialManager.Models.DatabaseType.SqlServer,
|
|
Port = 1433,
|
|
CommandTimeout = 30,
|
|
AdditionalParameters = new Dictionary<string, string>()
|
|
};
|
|
showDatabaseModal = true;
|
|
|
|
// Se è ODBC, carica i dati automaticamente
|
|
if (currentDatabaseCredential.DatabaseType == DatabaseType.Odbc)
|
|
{
|
|
await LoadOdbcData();
|
|
}
|
|
// Se è OLE DB, carica i provider
|
|
if (currentDatabaseCredential.DatabaseType == DatabaseType.OleDb)
|
|
{
|
|
LoadOleDbData();
|
|
}
|
|
}
|
|
|
|
private async Task EditDatabaseCredential(DatabaseCredential credential)
|
|
{
|
|
editingDatabaseCredential = credential;
|
|
currentDatabaseCredential = new DatabaseCredential
|
|
{
|
|
Name = credential.Name,
|
|
DatabaseType = credential.DatabaseType,
|
|
Host = credential.Host,
|
|
Port = credential.Port,
|
|
DatabaseName = credential.DatabaseName,
|
|
Username = credential.Username,
|
|
Password = credential.Password,
|
|
CommandTimeout = credential.CommandTimeout,
|
|
IgnoreSslErrors = credential.IgnoreSslErrors,
|
|
OdbcDsnName = credential.OdbcDsnName,
|
|
OdbcMode = credential.OdbcMode,
|
|
AdditionalParameters = credential.AdditionalParameters != null
|
|
? new Dictionary<string, string>(credential.AdditionalParameters)
|
|
: new Dictionary<string, string>()
|
|
};
|
|
|
|
// Se è ODBC, carica i dati e ripristina il driver selezionato
|
|
if (currentDatabaseCredential.DatabaseType == DatabaseType.Odbc)
|
|
{
|
|
await LoadOdbcData();
|
|
if (currentDatabaseCredential.AdditionalParameters?.ContainsKey("Driver") == true)
|
|
{
|
|
selectedOdbcDriver = currentDatabaseCredential.AdditionalParameters["Driver"];
|
|
}
|
|
}
|
|
// Se è OLE DB, carica i provider e ripristina il provider selezionato
|
|
if (currentDatabaseCredential.DatabaseType == DatabaseType.OleDb)
|
|
{
|
|
LoadOleDbData();
|
|
if (currentDatabaseCredential.AdditionalParameters?.ContainsKey("Provider") == true)
|
|
{
|
|
selectedOleDbProvider = currentDatabaseCredential.AdditionalParameters["Provider"];
|
|
}
|
|
if (currentDatabaseCredential.AdditionalParameters?.ContainsKey("Collating Sequence") == true)
|
|
oleDbCollatingSequence = currentDatabaseCredential.AdditionalParameters["Collating Sequence"];
|
|
if (currentDatabaseCredential.AdditionalParameters?.ContainsKey("DELETED") == true)
|
|
oleDbDeleted = currentDatabaseCredential.AdditionalParameters["DELETED"];
|
|
}
|
|
|
|
// Se è Visual FoxPro, ripristina encoding e gestione record cancellati
|
|
if (currentDatabaseCredential.DatabaseType == DatabaseType.Foxpro)
|
|
{
|
|
foxproCodePage = currentDatabaseCredential.AdditionalParameters?.GetValueOrDefault("Encoding") ?? string.Empty;
|
|
var incDel = currentDatabaseCredential.AdditionalParameters?.GetValueOrDefault("IncludeDeleted", "") ?? "";
|
|
foxproIncludeDeleted = string.Equals(incDel, "true", StringComparison.OrdinalIgnoreCase);
|
|
}
|
|
|
|
showDatabaseModal = true;
|
|
}
|
|
|
|
private async Task SaveDatabaseCredential()
|
|
{
|
|
try
|
|
{
|
|
// Sincronizza i parametri OLE DB negli AdditionalParameters prima del salvataggio
|
|
if (currentDatabaseCredential.DatabaseType == DatabaseType.OleDb)
|
|
{
|
|
currentDatabaseCredential.AdditionalParameters ??= new Dictionary<string, string>();
|
|
if (!string.IsNullOrEmpty(selectedOleDbProvider))
|
|
currentDatabaseCredential.AdditionalParameters["Provider"] = selectedOleDbProvider;
|
|
if (!string.IsNullOrEmpty(oleDbCollatingSequence))
|
|
currentDatabaseCredential.AdditionalParameters["Collating Sequence"] = oleDbCollatingSequence;
|
|
else
|
|
currentDatabaseCredential.AdditionalParameters.Remove("Collating Sequence");
|
|
if (!string.IsNullOrEmpty(oleDbDeleted))
|
|
currentDatabaseCredential.AdditionalParameters["DELETED"] = oleDbDeleted;
|
|
else
|
|
currentDatabaseCredential.AdditionalParameters.Remove("DELETED");
|
|
}
|
|
|
|
// Sincronizza i parametri Visual FoxPro negli AdditionalParameters
|
|
if (currentDatabaseCredential.DatabaseType == DatabaseType.Foxpro)
|
|
{
|
|
currentDatabaseCredential.AdditionalParameters ??= new Dictionary<string, string>();
|
|
|
|
if (!string.IsNullOrEmpty(foxproCodePage))
|
|
currentDatabaseCredential.AdditionalParameters["Encoding"] = foxproCodePage;
|
|
else
|
|
currentDatabaseCredential.AdditionalParameters.Remove("Encoding");
|
|
|
|
if (foxproIncludeDeleted)
|
|
currentDatabaseCredential.AdditionalParameters["IncludeDeleted"] = "true";
|
|
else
|
|
currentDatabaseCredential.AdditionalParameters.Remove("IncludeDeleted");
|
|
|
|
// Sorgente file-based: nessun host/porta/credenziali
|
|
currentDatabaseCredential.Host = string.Empty;
|
|
currentDatabaseCredential.Port = 0;
|
|
}
|
|
|
|
await CredentialService.SaveDatabaseCredentialAsync(currentDatabaseCredential);
|
|
await JSRuntime.InvokeVoidAsync("alert", "Credenziale database salvata con successo!");
|
|
CloseDatabaseModal();
|
|
await RefreshCredentials();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
await JSRuntime.InvokeVoidAsync("alert", $"Errore nel salvare la credenziale: {ex.Message}");
|
|
}
|
|
}
|
|
|
|
private void CloseDatabaseModal()
|
|
{
|
|
showDatabaseModal = false;
|
|
editingDatabaseCredential = null;
|
|
} private async Task TestDatabaseConnection(DatabaseCredential credential)
|
|
{
|
|
try
|
|
{
|
|
var (success, message) = await CredentialService.TestDatabaseConnectionAsync(credential.Name);
|
|
|
|
var title = success ? "Test Connessione - Successo" : "Test Connessione - Errore";
|
|
await JSRuntime.InvokeVoidAsync("alert", $"{title}\n\n{message}");
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
await JSRuntime.InvokeVoidAsync("alert", $"Errore nel test della connessione: {ex.Message}");
|
|
}
|
|
}
|
|
|
|
private async Task TestCurrentDatabaseConnection()
|
|
{
|
|
if (testingConnection) return;
|
|
|
|
testingConnection = true;
|
|
try
|
|
{
|
|
// Validazione base: Nome sempre obbligatorio
|
|
if (string.IsNullOrEmpty(currentDatabaseCredential.Name))
|
|
{
|
|
await JSRuntime.InvokeVoidAsync("alert", "Il nome della credenziale è obbligatorio.");
|
|
return;
|
|
}
|
|
|
|
// Validazione specifica per tipo database
|
|
if (currentDatabaseCredential.DatabaseType == DatabaseType.Odbc)
|
|
{
|
|
// ODBC: Validazione in base alla modalità
|
|
if (currentDatabaseCredential.OdbcMode == OdbcConnectionMode.Dsn)
|
|
{
|
|
// Modalità DSN: richiede DSN selezionato
|
|
if (string.IsNullOrEmpty(currentDatabaseCredential.OdbcDsnName))
|
|
{
|
|
await JSRuntime.InvokeVoidAsync("alert", "Seleziona un DSN ODBC.");
|
|
return;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Modalità Custom: richiede driver e host
|
|
if (!currentDatabaseCredential.AdditionalParameters?.ContainsKey("Driver") ?? true)
|
|
{
|
|
await JSRuntime.InvokeVoidAsync("alert", "Seleziona un driver ODBC.");
|
|
return;
|
|
}
|
|
if (string.IsNullOrEmpty(currentDatabaseCredential.Host))
|
|
{
|
|
await JSRuntime.InvokeVoidAsync("alert", "Inserisci il server/host.");
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
else if (currentDatabaseCredential.DatabaseType == DatabaseType.Foxpro)
|
|
{
|
|
// Visual FoxPro: sorgente file-based, richiede solo il percorso (DatabaseName)
|
|
if (string.IsNullOrWhiteSpace(currentDatabaseCredential.DatabaseName))
|
|
{
|
|
await JSRuntime.InvokeVoidAsync("alert", "Inserisci il percorso del database FoxPro (file .dbc o cartella con i .dbf).");
|
|
return;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Altri database: validazione standard (Host, Username, Password)
|
|
// Per SQL Server, permetti Windows Authentication (username vuoto o "Integrated")
|
|
bool isSqlServerWithWindowsAuth = currentDatabaseCredential.DatabaseType == DatabaseType.SqlServer &&
|
|
(string.IsNullOrWhiteSpace(currentDatabaseCredential.Username) ||
|
|
currentDatabaseCredential.Username.Equals("Integrated", StringComparison.OrdinalIgnoreCase) ||
|
|
currentDatabaseCredential.Username.Equals("Windows", StringComparison.OrdinalIgnoreCase));
|
|
|
|
if (string.IsNullOrEmpty(currentDatabaseCredential.Host))
|
|
{
|
|
await JSRuntime.InvokeVoidAsync("alert", "Il campo Host è obbligatorio.");
|
|
return;
|
|
}
|
|
|
|
if (!isSqlServerWithWindowsAuth)
|
|
{
|
|
// Per database che non usano Windows Authentication, richiedi username e password
|
|
if (string.IsNullOrEmpty(currentDatabaseCredential.Username) ||
|
|
string.IsNullOrEmpty(currentDatabaseCredential.Password))
|
|
{
|
|
await JSRuntime.InvokeVoidAsync("alert", "Username e Password sono obbligatori. Per SQL Server con Windows Authentication, inserisci 'Integrated' come username.");
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
var (success, message) = await CredentialService.TestDatabaseConnectionAsync(currentDatabaseCredential);
|
|
|
|
var title = success ? "Test Connessione - Successo" : "Test Connessione - Errore";
|
|
await JSRuntime.InvokeVoidAsync("alert", $"{title}\n\n{message}");
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
await JSRuntime.InvokeVoidAsync("alert", $"Errore nel test della connessione: {ex.Message}");
|
|
}
|
|
finally
|
|
{
|
|
testingConnection = false;
|
|
}
|
|
}
|
|
|
|
#region ODBC Methods
|
|
|
|
/// <summary>
|
|
/// Gestisce il cambio di tipo database per caricare le liste ODBC quando necessario
|
|
/// </summary>
|
|
private async Task OnDatabaseTypeChangedAsync()
|
|
{
|
|
// Se è ODBC, carica le liste DSN e driver
|
|
if (currentDatabaseCredential.DatabaseType == DatabaseType.Odbc)
|
|
{
|
|
await LoadOdbcData();
|
|
}
|
|
|
|
// Se è Visual FoxPro, reimposta i valori di default (sola lettura, file-based)
|
|
if (currentDatabaseCredential.DatabaseType == DatabaseType.Foxpro)
|
|
{
|
|
currentDatabaseCredential.AdditionalParameters ??= new Dictionary<string, string>();
|
|
foxproCodePage = string.Empty;
|
|
foxproIncludeDeleted = false;
|
|
}
|
|
|
|
StateHasChanged();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Carica i dati ODBC (DSN e driver disponibili)
|
|
/// </summary>
|
|
private async Task LoadOdbcData()
|
|
{
|
|
if (loadingOdbcData) return;
|
|
|
|
loadingOdbcData = true;
|
|
try
|
|
{
|
|
await Task.Run(() =>
|
|
{
|
|
try
|
|
{
|
|
availableOdbcDsn = OdbcDsnDiscoveryService.GetAllDsn();
|
|
availableOdbcDrivers = OdbcDsnDiscoveryService.GetInstalledDrivers();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Console.WriteLine($"Errore nel caricamento dati ODBC: {ex.Message}");
|
|
availableOdbcDsn = new List<OdbcDsnInfo>();
|
|
availableOdbcDrivers = new List<string>();
|
|
}
|
|
});
|
|
}
|
|
finally
|
|
{
|
|
loadingOdbcData = false;
|
|
StateHasChanged();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Ricarica manualmente la lista dei DSN ODBC
|
|
/// </summary>
|
|
private async Task RefreshOdbcDsnList()
|
|
{
|
|
await LoadOdbcData();
|
|
await JSRuntime.InvokeVoidAsync("alert", $"Lista DSN aggiornata: {availableOdbcDsn.Count} DSN trovati");
|
|
}
|
|
|
|
/// <summary>
|
|
/// Ricarica manualmente la lista dei driver ODBC
|
|
/// </summary>
|
|
private async Task RefreshOdbcDriverList()
|
|
{
|
|
await LoadOdbcData();
|
|
await JSRuntime.InvokeVoidAsync("alert", $"Lista driver aggiornata: {availableOdbcDrivers.Count} driver trovati");
|
|
}
|
|
|
|
/// <summary>
|
|
/// Genera l'anteprima della stringa di connessione ODBC
|
|
/// </summary>
|
|
private string GetOdbcConnectionStringPreview()
|
|
{
|
|
if (currentDatabaseCredential.DatabaseType != DatabaseType.Odbc)
|
|
return string.Empty;
|
|
|
|
try
|
|
{
|
|
// Salva il driver selezionato nei parametri aggiuntivi temporaneamente
|
|
if (!string.IsNullOrEmpty(selectedOdbcDriver))
|
|
{
|
|
currentDatabaseCredential.AdditionalParameters ??= new Dictionary<string, string>();
|
|
currentDatabaseCredential.AdditionalParameters["Driver"] = selectedOdbcDriver;
|
|
}
|
|
|
|
// Usa il metodo di ConnectionStringBuilder per generare la stringa
|
|
return ConnectionStringBuilder.BuildConnectionString(currentDatabaseCredential);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
return $"Errore nella generazione: {ex.Message}";
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gestisce la selezione di un DSN dalla lista
|
|
/// </summary>
|
|
private void OnOdbcDsnSelected(ChangeEventArgs e)
|
|
{
|
|
var dsnName = e.Value?.ToString();
|
|
if (!string.IsNullOrEmpty(dsnName))
|
|
{
|
|
currentDatabaseCredential.OdbcDsnName = dsnName;
|
|
StateHasChanged();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gestisce il cambio di modalità ODBC (DSN vs Custom)
|
|
/// </summary>
|
|
private void OnOdbcModeChanged(ChangeEventArgs e)
|
|
{
|
|
if (Enum.TryParse<OdbcConnectionMode>(e.Value?.ToString(), out var mode))
|
|
{
|
|
currentDatabaseCredential.OdbcMode = mode;
|
|
StateHasChanged();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Ottiene i dettagli di un DSN selezionato
|
|
/// </summary>
|
|
private OdbcDsnInfo? GetSelectedDsnDetails()
|
|
{
|
|
if (string.IsNullOrEmpty(currentDatabaseCredential.OdbcDsnName))
|
|
return null;
|
|
|
|
return availableOdbcDsn.FirstOrDefault(dsn =>
|
|
dsn.Name.Equals(currentDatabaseCredential.OdbcDsnName, StringComparison.OrdinalIgnoreCase));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Aggiunge un nuovo parametro personalizzato ODBC
|
|
/// </summary>
|
|
private void AddOdbcCustomParameter()
|
|
{
|
|
currentDatabaseCredential.AdditionalParameters ??= new Dictionary<string, string>();
|
|
|
|
// Genera un nome univoco per il nuovo parametro
|
|
var index = 1;
|
|
var paramName = $"Param{index}";
|
|
while (currentDatabaseCredential.AdditionalParameters.ContainsKey(paramName))
|
|
{
|
|
index++;
|
|
paramName = $"Param{index}";
|
|
}
|
|
|
|
currentDatabaseCredential.AdditionalParameters[paramName] = string.Empty;
|
|
StateHasChanged();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Aggiorna la chiave di un parametro personalizzato
|
|
/// </summary>
|
|
private void UpdateOdbcParameterKey(string oldKey, string newKey)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(newKey) || oldKey == newKey)
|
|
return;
|
|
|
|
if (currentDatabaseCredential.AdditionalParameters == null)
|
|
return;
|
|
|
|
// Se la nuova chiave esiste già, non fare nulla
|
|
if (currentDatabaseCredential.AdditionalParameters.ContainsKey(newKey))
|
|
{
|
|
StateHasChanged();
|
|
return;
|
|
}
|
|
|
|
var value = currentDatabaseCredential.AdditionalParameters[oldKey];
|
|
currentDatabaseCredential.AdditionalParameters.Remove(oldKey);
|
|
currentDatabaseCredential.AdditionalParameters[newKey] = value;
|
|
StateHasChanged();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Aggiorna il valore di un parametro personalizzato
|
|
/// </summary>
|
|
private void UpdateOdbcParameterValue(string key, string value)
|
|
{
|
|
if (currentDatabaseCredential.AdditionalParameters == null)
|
|
return;
|
|
|
|
if (currentDatabaseCredential.AdditionalParameters.ContainsKey(key))
|
|
{
|
|
currentDatabaseCredential.AdditionalParameters[key] = value;
|
|
StateHasChanged();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Rimuove un parametro personalizzato
|
|
/// </summary>
|
|
private void RemoveOdbcParameter(string key)
|
|
{
|
|
if (currentDatabaseCredential.AdditionalParameters == null)
|
|
return;
|
|
|
|
// Non permettere la rimozione del parametro Driver
|
|
if (key == "Driver")
|
|
return;
|
|
|
|
currentDatabaseCredential.AdditionalParameters.Remove(key);
|
|
StateHasChanged();
|
|
}
|
|
|
|
// OLE DB Methods
|
|
|
|
private void LoadOleDbData()
|
|
{
|
|
try
|
|
{
|
|
availableOleDbProviders = OleDbProviderDiscoveryService.GetInstalledProviders();
|
|
}
|
|
catch
|
|
{
|
|
availableOleDbProviders = new List<OleDbProviderInfo>();
|
|
}
|
|
}
|
|
|
|
private void RefreshOleDbProviderList()
|
|
{
|
|
LoadOleDbData();
|
|
StateHasChanged();
|
|
}
|
|
|
|
private bool IsVfpProvider()
|
|
{
|
|
if (string.IsNullOrEmpty(selectedOleDbProvider))
|
|
return false;
|
|
return selectedOleDbProvider.StartsWith("VFPOLEDB", StringComparison.OrdinalIgnoreCase);
|
|
}
|
|
|
|
private string GetOleDbConnectionStringPreview()
|
|
{
|
|
if (currentDatabaseCredential.DatabaseType != DatabaseType.OleDb)
|
|
return string.Empty;
|
|
|
|
try
|
|
{
|
|
// Copia i parametri OLE DB nei AdditionalParameters temporaneamente
|
|
currentDatabaseCredential.AdditionalParameters ??= new Dictionary<string, string>();
|
|
|
|
if (!string.IsNullOrEmpty(selectedOleDbProvider))
|
|
currentDatabaseCredential.AdditionalParameters["Provider"] = selectedOleDbProvider;
|
|
|
|
if (!string.IsNullOrEmpty(oleDbCollatingSequence))
|
|
currentDatabaseCredential.AdditionalParameters["Collating Sequence"] = oleDbCollatingSequence;
|
|
else
|
|
currentDatabaseCredential.AdditionalParameters.Remove("Collating Sequence");
|
|
|
|
if (!string.IsNullOrEmpty(oleDbDeleted))
|
|
currentDatabaseCredential.AdditionalParameters["DELETED"] = oleDbDeleted;
|
|
else
|
|
currentDatabaseCredential.AdditionalParameters.Remove("DELETED");
|
|
|
|
return ConnectionStringBuilder.BuildConnectionString(currentDatabaseCredential);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
return $"Errore nella generazione: {ex.Message}";
|
|
}
|
|
}
|
|
|
|
private string GetFoxproConnectionStringPreview()
|
|
{
|
|
if (currentDatabaseCredential.DatabaseType != DatabaseType.Foxpro)
|
|
return string.Empty;
|
|
|
|
try
|
|
{
|
|
currentDatabaseCredential.AdditionalParameters ??= new Dictionary<string, string>();
|
|
|
|
if (!string.IsNullOrEmpty(foxproCodePage))
|
|
currentDatabaseCredential.AdditionalParameters["Encoding"] = foxproCodePage;
|
|
else
|
|
currentDatabaseCredential.AdditionalParameters.Remove("Encoding");
|
|
|
|
if (foxproIncludeDeleted)
|
|
currentDatabaseCredential.AdditionalParameters["IncludeDeleted"] = "true";
|
|
else
|
|
currentDatabaseCredential.AdditionalParameters.Remove("IncludeDeleted");
|
|
|
|
if (string.IsNullOrWhiteSpace(currentDatabaseCredential.DatabaseName))
|
|
return "(inserire il percorso al file .dbc o alla cartella di .dbf)";
|
|
|
|
return ConnectionStringBuilder.BuildConnectionString(currentDatabaseCredential);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
return $"Errore nella generazione: {ex.Message}";
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#endregion
|
|
|
|
#region REST API Credential Methods
|
|
|
|
private void ShowAddRestApiModal()
|
|
{
|
|
editingRestApiCredential = null;
|
|
currentRestApiCredential = new RestApiCredential
|
|
{
|
|
ServiceType = RestServiceType.Generic,
|
|
TimeoutSeconds = 100
|
|
};
|
|
SetDefaultsForServiceType(currentRestApiCredential.ServiceType);
|
|
showRestApiModal = true;
|
|
}
|
|
|
|
private void EditRestApiCredential(RestApiCredential credential)
|
|
{
|
|
editingRestApiCredential = credential;
|
|
currentRestApiCredential = new RestApiCredential
|
|
{
|
|
Name = credential.Name,
|
|
ServiceType = credential.ServiceType,
|
|
BaseUrl = credential.BaseUrl,
|
|
ApiKey = credential.ApiKey,
|
|
Username = credential.Username,
|
|
Password = credential.Password,
|
|
AuthToken = credential.AuthToken,
|
|
BearerToken = credential.BearerToken,
|
|
TimeoutSeconds = credential.TimeoutSeconds,
|
|
IgnoreSslErrors = credential.IgnoreSslErrors,
|
|
Headers = credential.Headers,
|
|
AdditionalParameters = credential.AdditionalParameters,
|
|
// Campi SAP B1
|
|
CompanyDatabase = credential.CompanyDatabase,
|
|
Language = credential.Language,
|
|
Version = credential.Version,
|
|
UseTrustedConnection = credential.UseTrustedConnection,
|
|
// Campi Salesforce
|
|
SecurityToken = credential.SecurityToken,
|
|
ClientId = credential.ClientId,
|
|
ClientSecret = credential.ClientSecret,
|
|
ApiVersion = credential.ApiVersion,
|
|
IsSandbox = credential.IsSandbox,
|
|
UseSoapApi = credential.UseSoapApi,
|
|
GrantType = credential.GrantType,
|
|
RefreshToken = credential.RefreshToken,
|
|
AccessToken = credential.AccessToken,
|
|
TokenExpiry = credential.TokenExpiry
|
|
};
|
|
showRestApiModal = true;
|
|
}
|
|
|
|
private async Task SaveRestApiCredential()
|
|
{
|
|
try
|
|
{
|
|
await CredentialService.SaveRestApiCredentialAsync(currentRestApiCredential);
|
|
await JSRuntime.InvokeVoidAsync("alert", $"Credenziale {GetServiceTypeDisplayName(currentRestApiCredential.ServiceType)} salvata con successo!");
|
|
CloseRestApiModal();
|
|
await RefreshCredentials();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
await JSRuntime.InvokeVoidAsync("alert", $"Errore nel salvare la credenziale: {ex.Message}");
|
|
}
|
|
}
|
|
|
|
private void CloseRestApiModal()
|
|
{
|
|
showRestApiModal = false;
|
|
editingRestApiCredential = null;
|
|
}
|
|
|
|
private async Task TestRestApiConnection(RestApiCredential credential)
|
|
{
|
|
try
|
|
{
|
|
var (success, message) = credential.ServiceType switch
|
|
{
|
|
RestServiceType.SapB1ServiceLayer => await CredentialService.TestSapB1ConnectionAsync(credential.Name),
|
|
RestServiceType.Salesforce => await CredentialService.TestSalesforceConnectionAsync(credential.Name),
|
|
_ => await CredentialService.TestRestApiConnectionAsync(credential.Name)
|
|
};
|
|
|
|
var title = success ? $"Test {GetServiceTypeDisplayName(credential.ServiceType)} - Successo" : $"Test {GetServiceTypeDisplayName(credential.ServiceType)} - Errore";
|
|
await JSRuntime.InvokeVoidAsync("alert", $"{title}\n\n{message}");
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
await JSRuntime.InvokeVoidAsync("alert", $"Errore nel test della connessione: {ex.Message}");
|
|
}
|
|
}
|
|
|
|
private void OnServiceTypeChanged(ChangeEventArgs e)
|
|
{
|
|
if (Enum.TryParse<RestServiceType>(e.Value?.ToString(), out var serviceType))
|
|
{
|
|
currentRestApiCredential.ServiceType = serviceType;
|
|
SetDefaultsForServiceType(serviceType);
|
|
}
|
|
}
|
|
|
|
private void SetDefaultsForServiceType(RestServiceType serviceType)
|
|
{
|
|
switch (serviceType)
|
|
{
|
|
case RestServiceType.SapB1ServiceLayer:
|
|
currentRestApiCredential.Language = "en-US";
|
|
currentRestApiCredential.Version = "v1";
|
|
currentRestApiCredential.TimeoutSeconds = 300;
|
|
if (string.IsNullOrEmpty(currentRestApiCredential.BaseUrl))
|
|
currentRestApiCredential.BaseUrl = "https://server:50000/b1s/v1/";
|
|
break;
|
|
case RestServiceType.Salesforce:
|
|
currentRestApiCredential.ApiVersion = "59.0";
|
|
currentRestApiCredential.TimeoutSeconds = 120;
|
|
currentRestApiCredential.IsSandbox = false;
|
|
currentRestApiCredential.UseSoapApi = false;
|
|
if (string.IsNullOrEmpty(currentRestApiCredential.BaseUrl))
|
|
currentRestApiCredential.BaseUrl = "https://login.salesforce.com";
|
|
break;
|
|
case RestServiceType.Generic:
|
|
default:
|
|
currentRestApiCredential.TimeoutSeconds = 100;
|
|
break;
|
|
}
|
|
}
|
|
|
|
private string GetServiceTypeDisplayName(RestServiceType serviceType)
|
|
{
|
|
return serviceType switch
|
|
{
|
|
RestServiceType.SapB1ServiceLayer => "SAP B1 Service Layer",
|
|
RestServiceType.Salesforce => "Salesforce",
|
|
RestServiceType.Generic => "REST API",
|
|
_ => "REST API"
|
|
};
|
|
}
|
|
|
|
private string GetUrlFieldLabel(RestServiceType serviceType)
|
|
{
|
|
return serviceType switch
|
|
{
|
|
RestServiceType.SapB1ServiceLayer => "Server URL",
|
|
RestServiceType.Salesforce => "Login URL",
|
|
_ => "Base URL"
|
|
};
|
|
}
|
|
|
|
private string GetUrlPlaceholder(RestServiceType serviceType)
|
|
{
|
|
return serviceType switch
|
|
{
|
|
RestServiceType.SapB1ServiceLayer => "https://server:50000/b1s/v1/",
|
|
RestServiceType.Salesforce => "https://login.salesforce.com",
|
|
_ => "https://api.example.com"
|
|
};
|
|
}
|
|
|
|
private string GetUrlHelpText(RestServiceType serviceType)
|
|
{
|
|
return serviceType switch
|
|
{
|
|
RestServiceType.SapB1ServiceLayer => "URL del SAP B1 Service Layer (esempio: https://server:50000/b1s/v1/)",
|
|
RestServiceType.Salesforce => "Production: https://login.salesforce.com | Sandbox: https://test.salesforce.com",
|
|
_ => "URL base del servizio REST API"
|
|
};
|
|
} private void OnSandboxChanged(ChangeEventArgs e)
|
|
{
|
|
if (bool.TryParse(e.Value?.ToString(), out bool isSandbox))
|
|
{
|
|
currentRestApiCredential.IsSandbox = isSandbox;
|
|
currentRestApiCredential.BaseUrl = isSandbox
|
|
? "https://test.salesforce.com"
|
|
: "https://login.salesforce.com";
|
|
} }
|
|
|
|
#endregion
|
|
|
|
#region Common Methods
|
|
|
|
private void DeleteCredential(string name, bool isDatabase)
|
|
{
|
|
credentialToDeleteName = name;
|
|
credentialToDeleteIsDatabase = isDatabase;
|
|
showDeleteConfirmModal = true;
|
|
}
|
|
|
|
private void CloseDeleteConfirmModal()
|
|
{
|
|
showDeleteConfirmModal = false;
|
|
credentialToDeleteName = null;
|
|
credentialToDeleteIsDatabase = false;
|
|
}
|
|
|
|
private async Task ConfirmDeleteCredential()
|
|
{
|
|
if (string.IsNullOrEmpty(credentialToDeleteName))
|
|
{
|
|
CloseDeleteConfirmModal();
|
|
return;
|
|
}
|
|
|
|
try
|
|
{
|
|
bool success = await CredentialService.DeleteCredentialCascadeAsync(credentialToDeleteName);
|
|
|
|
if (success)
|
|
{
|
|
await JSRuntime.InvokeVoidAsync("alert", "Credenziale e tutti i dati associati eliminati con successo!");
|
|
CloseDeleteConfirmModal();
|
|
await RefreshCredentials();
|
|
}
|
|
else
|
|
{
|
|
await JSRuntime.InvokeVoidAsync("alert", "Errore nell'eliminazione della credenziale.");
|
|
CloseDeleteConfirmModal();
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
await JSRuntime.InvokeVoidAsync("alert", $"Errore nell'eliminazione: {ex.Message}");
|
|
CloseDeleteConfirmModal();
|
|
}
|
|
}
|
|
|
|
private string GetAuthenticationType(RestApiCredential credential)
|
|
{
|
|
if (!string.IsNullOrEmpty(credential.ApiKey))
|
|
return "API Key";
|
|
if (!string.IsNullOrEmpty(credential.AuthToken))
|
|
return "Auth Token";
|
|
if (!string.IsNullOrEmpty(credential.Username))
|
|
return "Basic Auth";
|
|
return "Nessuna";
|
|
}
|
|
|
|
private async Task TestRestApiConnectionFromModal()
|
|
{
|
|
try
|
|
{
|
|
testingConnection = true;
|
|
|
|
// Creiamo una credenziale temporanea per il test
|
|
var tempCredential = new RestApiCredential
|
|
{
|
|
Name = $"temp_test_{Guid.NewGuid():N}",
|
|
ServiceType = currentRestApiCredential.ServiceType,
|
|
BaseUrl = currentRestApiCredential.BaseUrl,
|
|
Username = currentRestApiCredential.Username,
|
|
Password = currentRestApiCredential.Password,
|
|
ApiKey = currentRestApiCredential.ApiKey,
|
|
AuthToken = currentRestApiCredential.AuthToken,
|
|
TimeoutSeconds = currentRestApiCredential.TimeoutSeconds,
|
|
IgnoreSslErrors = currentRestApiCredential.IgnoreSslErrors,
|
|
// Campi SAP B1
|
|
CompanyDatabase = currentRestApiCredential.CompanyDatabase,
|
|
Version = currentRestApiCredential.Version,
|
|
Language = currentRestApiCredential.Language,
|
|
UseTrustedConnection = currentRestApiCredential.UseTrustedConnection,
|
|
// Campi Salesforce
|
|
SecurityToken = currentRestApiCredential.SecurityToken,
|
|
ClientId = currentRestApiCredential.ClientId,
|
|
ClientSecret = currentRestApiCredential.ClientSecret,
|
|
ApiVersion = currentRestApiCredential.ApiVersion,
|
|
IsSandbox = currentRestApiCredential.IsSandbox,
|
|
UseSoapApi = currentRestApiCredential.UseSoapApi,
|
|
GrantType = currentRestApiCredential.GrantType
|
|
}; // Salviamo temporaneamente la credenziale per il test
|
|
await CredentialService.SaveRestApiCredentialAsync(tempCredential);
|
|
|
|
// Testiamo la connessione
|
|
var (success, message) = await CredentialService.TestRestApiConnectionAsync(tempCredential.Name);
|
|
|
|
// Rimuoviamo la credenziale temporanea
|
|
await CredentialService.DeleteRestApiCredentialAsync(tempCredential.Name);
|
|
|
|
var title = success ? "Test Connessione - Successo" : "Test Connessione - Errore";
|
|
await JSRuntime.InvokeVoidAsync("alert", $"{title}\n\n{message}");
|
|
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
await JSRuntime.InvokeVoidAsync("alert", $"Errore nel test della connessione: {ex.Message}");
|
|
}
|
|
finally
|
|
{
|
|
testingConnection = false;
|
|
}
|
|
}
|
|
|
|
private bool IsFieldRequired(string fieldName, RestServiceType serviceType)
|
|
{
|
|
return (fieldName, serviceType) switch
|
|
{
|
|
("Username", RestServiceType.Generic) => string.IsNullOrEmpty(currentRestApiCredential.ApiKey) && string.IsNullOrEmpty(currentRestApiCredential.AuthToken),
|
|
("Password", RestServiceType.Generic) => string.IsNullOrEmpty(currentRestApiCredential.ApiKey) && string.IsNullOrEmpty(currentRestApiCredential.AuthToken),
|
|
("Username", RestServiceType.SapB1ServiceLayer) => !currentRestApiCredential.UseTrustedConnection,
|
|
("Password", RestServiceType.SapB1ServiceLayer) => !currentRestApiCredential.UseTrustedConnection,
|
|
("CompanyDatabase", RestServiceType.SapB1ServiceLayer) => true,
|
|
("Username", RestServiceType.Salesforce) => true,
|
|
("Password", RestServiceType.Salesforce) => true,
|
|
("SecurityToken", RestServiceType.Salesforce) => string.IsNullOrEmpty(currentRestApiCredential.ClientId) && string.IsNullOrEmpty(currentRestApiCredential.ClientSecret),
|
|
_ => false
|
|
};
|
|
}
|
|
|
|
private string GetFieldLabel(string fieldName, RestServiceType serviceType)
|
|
{
|
|
var label = (fieldName, serviceType) switch
|
|
{
|
|
("Username", RestServiceType.SapB1ServiceLayer) => "Username",
|
|
("Password", RestServiceType.SapB1ServiceLayer) => "Password",
|
|
("CompanyDatabase", RestServiceType.SapB1ServiceLayer) => "Company Database",
|
|
("Username", RestServiceType.Salesforce) => "Username",
|
|
("Password", RestServiceType.Salesforce) => "Password",
|
|
("SecurityToken", RestServiceType.Salesforce) => "Security Token",
|
|
_ => fieldName
|
|
};
|
|
|
|
return IsFieldRequired(fieldName, serviceType) ? $"{label} *" : label;
|
|
} /// <summary>
|
|
/// Verifica se ci sono credenziali che non possono essere decrittografate
|
|
/// </summary>
|
|
private void CheckForProblematicCredentials()
|
|
{
|
|
try
|
|
{
|
|
hasProblematicCredentials = false;
|
|
|
|
// Verifica credenziali database
|
|
foreach (var dbCred in databaseCredentials)
|
|
{
|
|
if (HasProblematicPassword(dbCred.Password))
|
|
{
|
|
hasProblematicCredentials = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Verifica credenziali REST API se non trovate problematiche
|
|
if (!hasProblematicCredentials)
|
|
{
|
|
foreach (var restCred in restApiCredentials)
|
|
{
|
|
if (HasProblematicPassword(restCred.Password) ||
|
|
HasProblematicPassword(restCred.ApiKey) ||
|
|
HasProblematicPassword(restCred.AuthToken) ||
|
|
HasProblematicPassword(restCred.ClientSecret))
|
|
{
|
|
hasProblematicCredentials = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
StateHasChanged();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
// Log dell'errore, ma non bloccare l'interfaccia
|
|
Console.WriteLine($"Errore nella verifica delle credenziali problematiche: {ex.Message}");
|
|
}
|
|
} /// <summary>
|
|
/// Verifica se una password indica un problema di decrittografia
|
|
/// </summary>
|
|
private bool HasProblematicPassword(string? password)
|
|
{
|
|
return !string.IsNullOrEmpty(password) &&
|
|
(password.Contains("*** CREDENZIALI NON DISPONIBILI") ||
|
|
password.Contains("*** ERRORE DECRITTOGRAFIA ***"));
|
|
}
|
|
|
|
#endregion
|
|
}
|