Files
Data-Coupler/Data_Coupler/Pages/CredentialManagement.razor
Alessio 483eb7b407 Fix: Risolto double-mapping negli External ID Relationships per Salesforce
- 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
2026-02-15 18:44:15 +01:00

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
}