Files
Data-Coupler/Data_Coupler/Pages/CredentialManagement.razor
T
Alessio e70abcdcb1 [Feature] Supporto Visual FoxPro / dBase come sorgente dati (managed, sola lettura)
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>
2026-06-06 19:34:21 +02:00

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
}