483eb7b407
- Implementata funzionalità completa External ID Relationships nell'interfaccia di mapping
- Corretto bug double-mapping: i campi sorgente usati per External ID non vengono più inclusi nei mapping normali
- Risolto errore MALFORMED_ID causato dall'invio duplicato di campi come proprietà dirette e nested objects
- Implementata logica corretta per relationship names: oggetti standard usano il nome diretto, custom objects usano suffisso __r
- Aggiunta UI a 3 colonne (Object, External ID Field, Source Field) per configurazione External ID Relationships
- Migrazione database per supporto External ID Relationships nei profili
- Aggiornato ProfileSaver.razor.cs per salvare/caricare External ID Relationships
- Aggiornato ScheduledProfileExecutionService.cs per gestire External ID nelle esecuzioni schedulate
- Formato JSON output corretto: { 'Account': { 'CardCode__c': 'V50000' } }
Documentazione: EXTERNAL_ID_RELATIONSHIPS_IMPLEMENTATION.md
1644 lines
81 KiB
Plaintext
1644 lines
81 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 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>
|
|
</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
|
|
{
|
|
<!-- 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="alert alert-info">
|
|
<strong>Opzioni di Autenticazione:</strong><br/>
|
|
• <strong>Username/Password + Security Token:</strong> Autenticazione standard<br/>
|
|
• <strong>Username/Password + Client ID/Secret:</strong> Autenticazione OAuth<br/>
|
|
• Il Security Token è richiesto solo se non si configura una Connected App (Client ID/Secret)
|
|
</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> <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)))
|
|
{ <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;
|
|
|
|
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();
|
|
}
|
|
}
|
|
|
|
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"];
|
|
}
|
|
}
|
|
|
|
showDatabaseModal = true;
|
|
}
|
|
|
|
private async Task SaveDatabaseCredential()
|
|
{
|
|
try
|
|
{
|
|
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
|
|
{
|
|
// 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();
|
|
}
|
|
|
|
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();
|
|
}
|
|
|
|
#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,
|
|
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
|
|
}; // 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
|
|
}
|