77efe986a0
- Modifica IsDatabaseSpecifiedInConnectionString per verificare prima il campo DatabaseName della credenziale - Aggiunge logging dettagliato per debugging del processo di connessione database - Corregge il flusso di connessione per evitare il modale quando il database è già specificato - Migliora la gestione degli errori nel caricamento tabelle dal database specificato - Rimuove codice non raggiungibile nella logica di connessione database Il bug precedente mostrava sempre il modale di selezione database anche quando il database era specificato nel campo DatabaseName della credenziale, ora la verifica segue la logica corretta: 1. Controlla se DatabaseName è valorizzato nella credenziale 2. Solo se vuoto, verifica i parametri Database=/Initial Catalog= nella connection string
1271 lines
84 KiB
Plaintext
1271 lines
84 KiB
Plaintext
@page "/data-coupler"
|
|
@using CredentialManager.Models
|
|
@using DataConnection.Interfaces
|
|
@using DataConnection.CredentialManagement.Interfaces
|
|
@using DataConnection.REST.Interfaces
|
|
@using DataConnection.REST.Models
|
|
@using Data_Coupler.Services
|
|
@using Microsoft.AspNetCore.Components.Forms
|
|
@using Microsoft.JSInterop
|
|
@using System.IO
|
|
@using System.Text
|
|
@using System.Data
|
|
@using ExcelDataReader
|
|
@using Microsoft.AspNetCore.Components
|
|
@using Microsoft.AspNetCore.Components.Web
|
|
@using Components
|
|
|
|
<PageTitle>Data Coupler</PageTitle>
|
|
|
|
<div class="container-fluid">
|
|
<div class="row">
|
|
<div class="col-12">
|
|
<h3><i class="fas fa-exchange-alt"></i> Data Coupler - Coupling Database e REST API</h3>
|
|
<p class="text-muted">Connetti database e servizi REST per il trasferimento dati</p>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Sezione Gestione Profili -->
|
|
<div class="row mb-3">
|
|
<div class="col-12">
|
|
<ProfileSelector Profiles="availableProfiles"
|
|
OnProfileLoaded="OnProfileLoaded"
|
|
OnManageProfiles="OnManageProfiles"
|
|
IsLoading="isLoadingProfiles" />
|
|
</div>
|
|
</div>
|
|
|
|
<div class="row">
|
|
<!-- Lato Sinistro - Fonte Dati -->
|
|
<div class="col-md-6">
|
|
<div class="card h-100">
|
|
<div class="card-header bg-primary text-white">
|
|
<h5><i class="fas fa-database"></i> Fonte Dati</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
<!-- Selezione Tipo Fonte -->
|
|
<div class="mb-3">
|
|
<label class="form-label">Tipo di Fonte:</label>
|
|
<select class="form-select" @onchange="OnSourceTypeChanged" value="@selectedSourceType">
|
|
<option value="">-- Seleziona Tipo --</option>
|
|
<option value="database">Database</option>
|
|
<option value="file">File (Excel/CSV)</option>
|
|
</select>
|
|
</div>
|
|
|
|
<!-- Sezione Database -->
|
|
@if (selectedSourceType == "database")
|
|
{
|
|
<!-- Selezione Credenziali Database -->
|
|
<div class="mb-3">
|
|
<label class="form-label">Credenziali Database:</label>
|
|
<select class="form-select" @onchange="OnDatabaseCredentialChanged" value="@selectedDatabaseCredential">
|
|
<option value="">-- Seleziona Database --</option>
|
|
@foreach (var cred in databaseCredentials)
|
|
{
|
|
<option value="@cred.Name">@cred.Name (@cred.DatabaseType - @cred.Host)</option>
|
|
}
|
|
</select>
|
|
</div>
|
|
|
|
@if (!string.IsNullOrEmpty(selectedDatabaseCredential))
|
|
{
|
|
<div class="mb-3">
|
|
<button class="btn btn-success btn-sm" @onclick="ConnectToDatabase" disabled="@isConnectingDatabase">
|
|
@if (isConnectingDatabase)
|
|
{
|
|
<span class="spinner-border spinner-border-sm me-2"></span>
|
|
}
|
|
<i class="fas fa-plug"></i> Connetti e Scopri Schema
|
|
</button>
|
|
@if (isDatabaseConnected)
|
|
{
|
|
<span class="badge bg-success ms-2">Connesso</span>
|
|
}
|
|
</div>
|
|
} @if (!string.IsNullOrEmpty(databaseErrorMessage))
|
|
{
|
|
<div class="alert alert-danger" role="alert">
|
|
@databaseErrorMessage
|
|
</div>
|
|
}
|
|
|
|
<!-- Lista Tabelle -->
|
|
@if (isDatabaseConnected)
|
|
{
|
|
<!-- Selezione modalità: Tabelle o Query Custom -->
|
|
<div class="mb-3">
|
|
<div class="form-check form-switch">
|
|
<input class="form-check-input" type="checkbox" role="switch" id="useCustomQuerySwitch"
|
|
@onchange="OnQueryModeChanged" checked="@useCustomQuery">
|
|
<label class="form-check-label" for="useCustomQuerySwitch">
|
|
<i class="fas fa-code"></i> Usa Query SQL Custom
|
|
</label>
|
|
</div>
|
|
<small class="text-muted">
|
|
Scegli se selezionare una tabella o scrivere una query SQL personalizzata
|
|
</small>
|
|
</div>
|
|
|
|
@if (useCustomQuery)
|
|
{
|
|
<!-- Sezione Query Custom -->
|
|
<div class="mb-3">
|
|
<h6>Query SQL Custom:</h6>
|
|
|
|
<div class="mb-2">
|
|
<label class="form-label">Scrivi la tua query SELECT:</label>
|
|
<textarea class="form-control" rows="6" placeholder="SELECT * FROM your_table WHERE condition..."
|
|
@bind="customQuery" @bind:event="oninput"></textarea>
|
|
<div class="mt-2">
|
|
<div class="alert alert-warning d-flex align-items-start" role="alert">
|
|
<i class="fas fa-shield-alt me-2 mt-1"></i>
|
|
<div>
|
|
<strong>Controlli di Sicurezza Attivi:</strong><br>
|
|
<small>
|
|
• Solo query <strong>SELECT</strong> sono permesse<br>
|
|
• Operazioni come INSERT, UPDATE, DELETE, DROP sono bloccate<br>
|
|
• Query multiple separate da ; non sono consentite<br>
|
|
• La query verrà automaticamente ottimizzata per il trasferimento dati
|
|
</small>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="mb-2">
|
|
<button class="btn btn-primary btn-sm me-2" @onclick="ValidateCustomQuery"
|
|
disabled="@(isValidatingQuery || string.IsNullOrWhiteSpace(customQuery))">
|
|
@if (isValidatingQuery)
|
|
{
|
|
<span class="spinner-border spinner-border-sm me-2"></span>
|
|
}
|
|
<i class="fas fa-check-circle"></i> Valida Query
|
|
</button>
|
|
|
|
@if (isQueryValid)
|
|
{
|
|
<button class="btn btn-info btn-sm me-2" @onclick="LoadQueryPreview"
|
|
disabled="@isLoadingPreview">
|
|
@if (isLoadingPreview)
|
|
{
|
|
<span class="spinner-border spinner-border-sm me-2"></span>
|
|
}
|
|
<i class="fas fa-eye"></i> Anteprima Risultati
|
|
</button>
|
|
|
|
@if (showQueryPreview)
|
|
{
|
|
<button class="btn btn-outline-secondary btn-sm" @onclick="HideQueryPreview">
|
|
<i class="fas fa-eye-slash"></i> Nascondi Anteprima
|
|
</button>
|
|
}
|
|
}
|
|
</div>
|
|
|
|
@if (!string.IsNullOrEmpty(queryValidationMessage))
|
|
{
|
|
@if (isQueryValid)
|
|
{
|
|
<div class="alert alert-success" role="alert">
|
|
<i class="fas fa-check-circle"></i>
|
|
@queryValidationMessage
|
|
</div>
|
|
}
|
|
else
|
|
{
|
|
<div class="alert alert-danger" role="alert">
|
|
<i class="fas fa-exclamation-triangle"></i>
|
|
@queryValidationMessage
|
|
</div>
|
|
}
|
|
}
|
|
|
|
<!-- Anteprima risultati query -->
|
|
@if (showQueryPreview && queryPreviewData.Any())
|
|
{
|
|
<div class="card mt-3">
|
|
<div class="card-header">
|
|
<h6 class="mb-0">
|
|
<i class="fas fa-table"></i> Anteprima Risultati Query
|
|
<span class="badge bg-info ms-2">@queryPreviewData.Count righe</span>
|
|
</h6>
|
|
</div>
|
|
<div class="card-body p-0">
|
|
<div class="table-responsive" style="max-height: 400px;">
|
|
<table class="table table-striped table-hover mb-0">
|
|
<thead class="table-dark sticky-top">
|
|
<tr>
|
|
@if (queryColumns.Any())
|
|
{
|
|
@foreach (var column in queryColumns)
|
|
{
|
|
<th>@column</th>
|
|
}
|
|
}
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
@foreach (var row in queryPreviewData.Take(20))
|
|
{
|
|
<tr>
|
|
@foreach (var column in queryColumns)
|
|
{
|
|
<td>
|
|
@(row.ContainsKey(column) ? row[column]?.ToString() ?? "" : "")
|
|
</td>
|
|
}
|
|
</tr>
|
|
}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
@if (queryPreviewData.Count > 20)
|
|
{
|
|
<div class="card-footer text-muted">
|
|
<small>
|
|
<i class="fas fa-info-circle"></i>
|
|
Mostrate prime 20 righe di @queryPreviewData.Count risultati totali
|
|
</small>
|
|
</div>
|
|
}
|
|
</div>
|
|
</div>
|
|
}
|
|
</div>
|
|
}
|
|
else if (availableTableNames.Any())
|
|
{
|
|
<!-- Sezione Tabelle (modalità standard) -->
|
|
<div class="mb-3">
|
|
<h6>Tabelle Database (@availableTableNames.Count disponibili):</h6>
|
|
|
|
<!-- Campo di ricerca -->
|
|
<div class="mb-2">
|
|
<div class="input-group">
|
|
<span class="input-group-text">
|
|
<i class="fas fa-search"></i>
|
|
</span>
|
|
<input type="text" class="form-control" placeholder="Cerca tabelle..."
|
|
@bind="databaseSearchTerm" @oninput="FilterDatabaseTables" />
|
|
@if (!string.IsNullOrEmpty(databaseSearchTerm))
|
|
{
|
|
<button class="btn btn-outline-secondary" @onclick="ClearDatabaseSearch">
|
|
<i class="fas fa-times"></i>
|
|
</button>
|
|
}
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Lista tabelle filtrate -->
|
|
<div class="list-group" style="max-height: 300px; overflow-y: auto;">
|
|
@foreach (var table in GetFilteredDatabaseTables())
|
|
{
|
|
<a class="list-group-item list-group-item-action @(selectedTable == table ? "active" : "")"
|
|
@onclick="@(() => SelectTable(table))">
|
|
<div class="d-flex justify-content-between align-items-center">
|
|
<div>
|
|
<i class="fas fa-table"></i> @table
|
|
@if (databaseTables.ContainsKey(table))
|
|
{
|
|
<small class="text-muted d-block">@databaseTables[table].Count() campi</small>
|
|
}
|
|
</div>
|
|
@if (selectedTable == table)
|
|
{
|
|
<span class="badge bg-primary">Selezionata</span>
|
|
}
|
|
</div>
|
|
</a>
|
|
}
|
|
</div>
|
|
|
|
@if (!GetFilteredDatabaseTables().Any())
|
|
{
|
|
<div class="alert alert-info mt-2">
|
|
<i class="fas fa-info-circle"></i> Nessuna tabella trovata con il termine di ricerca "@databaseSearchTerm"
|
|
</div>
|
|
}
|
|
</div>
|
|
}
|
|
}
|
|
}
|
|
|
|
<!-- Sezione File -->
|
|
@if (selectedSourceType == "file")
|
|
{
|
|
<div class="mb-3">
|
|
<label class="form-label">Seleziona File (Excel/CSV):</label>
|
|
<InputFile class="form-control" OnChange="OnFileSelected" accept=".xlsx,.xls,.csv" />
|
|
@if (!string.IsNullOrEmpty(selectedFileName))
|
|
{
|
|
<small class="text-muted">File selezionato: @selectedFileName</small>
|
|
}
|
|
</div>
|
|
|
|
@if (isProcessingFile)
|
|
{
|
|
<div class="mb-3">
|
|
<div class="d-flex align-items-center">
|
|
<span class="spinner-border spinner-border-sm me-2"></span>
|
|
Elaborazione file in corso...
|
|
</div>
|
|
</div>
|
|
}
|
|
|
|
@if (!string.IsNullOrEmpty(fileErrorMessage))
|
|
{
|
|
<div class="alert alert-danger" role="alert">
|
|
@fileErrorMessage
|
|
</div>
|
|
} <!-- Lista Fogli/Sheet -->
|
|
@if (fileSheets.Any())
|
|
{
|
|
<div class="mb-3">
|
|
@{
|
|
var fileExtension = Path.GetExtension(selectedFileName).ToLowerInvariant();
|
|
var isExcel = fileExtension == ".xlsx" || fileExtension == ".xls";
|
|
var fileTypeIcon = isExcel ? "fa-file-excel text-success" : "fa-file-csv text-info";
|
|
var fileTypeName = isExcel ? "Excel" : "CSV";
|
|
}
|
|
<h6>
|
|
<i class="fas @fileTypeIcon"></i>
|
|
Fogli @fileTypeName (@fileSheets.Count):
|
|
</h6>
|
|
|
|
<div class="list-group" style="max-height: 300px; overflow-y: auto;">
|
|
@foreach (var sheet in fileSheets)
|
|
{
|
|
<a class="list-group-item list-group-item-action @(selectedSheet == sheet.Key ? "active" : "")"
|
|
@onclick="@(() => SelectSheet(sheet.Key))">
|
|
<div class="d-flex justify-content-between align-items-center">
|
|
<div>
|
|
<i class="fas @fileTypeIcon"></i> @sheet.Key
|
|
<small class="text-muted d-block">@sheet.Value.Count() colonne</small>
|
|
</div>
|
|
@if (selectedSheet == sheet.Key)
|
|
{
|
|
<span class="badge bg-primary">Selezionato</span>
|
|
}
|
|
</div>
|
|
</a>
|
|
} </div>
|
|
</div>
|
|
} else if (selectedSourceType == "file" && !string.IsNullOrEmpty(selectedFileName) && !isProcessingFile && string.IsNullOrEmpty(fileErrorMessage))
|
|
{
|
|
<div class="alert alert-info" role="alert">
|
|
<i class="fas fa-info-circle"></i>
|
|
<strong>File selezionato:</strong> @selectedFileName
|
|
<br><small>Il file è stato elaborato ma non contiene dati validi o fogli leggibili.</small>
|
|
</div>
|
|
}
|
|
else if (selectedSourceType == "file" && string.IsNullOrEmpty(selectedFileName))
|
|
{
|
|
<div class="alert alert-light border" role="alert">
|
|
<div class="text-center">
|
|
<i class="fas fa-upload fa-2x text-muted mb-2"></i>
|
|
<h6>Carica un file per iniziare</h6>
|
|
<small class="text-muted">
|
|
<strong>Formati supportati:</strong><br>
|
|
• <i class="fas fa-file-excel text-success"></i> Excel (.xlsx, .xls) - Supporto completo per fogli multipli<br>
|
|
• <i class="fas fa-file-csv text-info"></i> CSV (.csv) - Prima riga come intestazioni<br><br>
|
|
Una volta caricato, potrai visualizzare immediatamente il contenuto e navigare tra tutti i record
|
|
</small>
|
|
</div>
|
|
</div>
|
|
}
|
|
|
|
<!-- Anteprima Dati File -->
|
|
@if (!string.IsNullOrEmpty(selectedSheet) && fileData.ContainsKey(selectedSheet) && fileData[selectedSheet].Any())
|
|
{
|
|
<div class="mb-3">
|
|
<div class="alert alert-success" role="alert">
|
|
<div class="d-flex justify-content-between align-items-center">
|
|
<div>
|
|
@{
|
|
var fileExtension = Path.GetExtension(selectedFileName).ToLowerInvariant();
|
|
var isExcel = fileExtension == ".xlsx" || fileExtension == ".xls";
|
|
var fileTypeIcon = isExcel ? "fa-file-excel text-white" : "fa-file-csv text-white";
|
|
var fileTypeName = isExcel ? "Excel" : "CSV";
|
|
}
|
|
<i class="fas fa-check-circle"></i> <strong>File @fileTypeName Caricato!</strong>
|
|
<div class="mt-1">
|
|
<small>
|
|
<i class="fas @fileTypeIcon"></i>
|
|
Foglio: <strong>@selectedSheet</strong> |
|
|
Record: <strong>@fileData[selectedSheet].Count</strong> |
|
|
Colonne: <strong>@fileSheets[selectedSheet].Count()</strong>
|
|
</small>
|
|
</div>
|
|
</div>
|
|
<div class="d-flex align-items-center gap-2">
|
|
<div class="d-flex align-items-center">
|
|
<small class="me-2">Righe per pagina:</small>
|
|
<select class="form-select form-select-sm" style="width: auto;"
|
|
@onchange="OnPageSizeChanged" value="@pageSize">
|
|
<option value="10">10</option>
|
|
<option value="20">20</option>
|
|
<option value="50">50</option>
|
|
<option value="100">100</option>
|
|
</select>
|
|
</div>
|
|
<button class="btn btn-outline-primary btn-sm" type="button"
|
|
data-bs-toggle="collapse" data-bs-target="#fileDataPreview"
|
|
aria-expanded="true" aria-controls="fileDataPreview">
|
|
<i class="fas fa-eye"></i> Mostra/Nascondi Preview
|
|
</button>
|
|
</div>
|
|
</div> </div>
|
|
|
|
<div class="collapse show mt-2" id="fileDataPreview">
|
|
<div class="card">
|
|
<div class="card-header bg-light">
|
|
<div class="d-flex justify-content-between align-items-center">
|
|
<h6 class="mb-0"><i class="fas fa-table"></i> Preview Dati - @selectedSheet</h6>
|
|
<div class="d-flex align-items-center gap-2">
|
|
<small class="text-muted">
|
|
Record @GetStartRecord()-@GetEndRecord() di @fileData[selectedSheet].Count
|
|
</small>
|
|
<div class="btn-group btn-group-sm" role="group">
|
|
<button type="button" class="btn btn-outline-secondary"
|
|
@onclick="FirstPage"
|
|
disabled="@(currentPage == 1)">
|
|
<i class="fas fa-angle-double-left"></i>
|
|
</button>
|
|
<button type="button" class="btn btn-outline-secondary"
|
|
@onclick="PreviousPage"
|
|
disabled="@(currentPage == 1)">
|
|
<i class="fas fa-angle-left"></i>
|
|
</button>
|
|
<span class="btn btn-outline-secondary disabled">
|
|
@currentPage di @GetTotalPages(selectedSheet)
|
|
</span>
|
|
<button type="button" class="btn btn-outline-secondary"
|
|
@onclick="NextPage"
|
|
disabled="@(currentPage >= GetTotalPages(selectedSheet))">
|
|
<i class="fas fa-angle-right"></i>
|
|
</button>
|
|
<button type="button" class="btn btn-outline-secondary"
|
|
@onclick="LastPage"
|
|
disabled="@(currentPage >= GetTotalPages(selectedSheet))">
|
|
<i class="fas fa-angle-double-right"></i>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="card-body p-0" style="max-height: 400px; overflow: auto;">
|
|
@if (fileSheets.ContainsKey(selectedSheet))
|
|
{
|
|
<div class="table-responsive">
|
|
<table class="table table-sm table-striped table-hover mb-0">
|
|
<thead class="table-dark sticky-top">
|
|
<tr>
|
|
<th style="width: 50px;">#</th>
|
|
@foreach (var column in fileSheets[selectedSheet])
|
|
{
|
|
<th>@column</th>
|
|
}
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
@{
|
|
var dataToShow = GetCurrentPageData();
|
|
var startRecord = GetStartRecord();
|
|
}
|
|
@for (int i = 0; i < dataToShow.Count; i++)
|
|
{
|
|
var row = dataToShow[i];
|
|
var absoluteRowIndex = startRecord + i;
|
|
<tr>
|
|
<td><small class="text-muted">@absoluteRowIndex</small></td>
|
|
@foreach (var column in fileSheets[selectedSheet])
|
|
{
|
|
var cellValue = row.ContainsKey(column) ? row[column]?.ToString() : "";
|
|
var displayValue = string.IsNullOrEmpty(cellValue) ? "-" :
|
|
(cellValue.Length > 50 ? cellValue.Substring(0, 50) + "..." : cellValue);
|
|
<td>
|
|
<span title="@cellValue">@displayValue</span>
|
|
</td>
|
|
}
|
|
</tr>
|
|
}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
}
|
|
</div>
|
|
|
|
<!-- Footer con controlli paginazione e informazioni -->
|
|
<div class="card-footer bg-light">
|
|
<div class="row align-items-center">
|
|
<div class="col-md-6">
|
|
<small class="text-muted">
|
|
<i class="fas fa-info-circle"></i>
|
|
Visualizzazione: @pageSize record per pagina
|
|
</small>
|
|
</div>
|
|
<div class="col-md-6 text-end">
|
|
<div class="btn-group btn-group-sm" role="group">
|
|
<button type="button" class="btn btn-outline-primary"
|
|
@onclick="FirstPage"
|
|
disabled="@(currentPage == 1)">
|
|
Prima
|
|
</button>
|
|
<button type="button" class="btn btn-outline-primary"
|
|
@onclick="PreviousPage"
|
|
disabled="@(currentPage == 1)">
|
|
Precedente
|
|
</button>
|
|
<button type="button" class="btn btn-outline-primary"
|
|
@onclick="NextPage"
|
|
disabled="@(currentPage >= GetTotalPages(selectedSheet))">
|
|
Successiva
|
|
</button>
|
|
<button type="button" class="btn btn-outline-primary"
|
|
@onclick="LastPage"
|
|
disabled="@(currentPage >= GetTotalPages(selectedSheet))">
|
|
Ultima
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div> }
|
|
}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Lato Destro - REST API -->
|
|
<div class="col-md-6">
|
|
<div class="card h-100">
|
|
<div class="card-header bg-info text-white">
|
|
<h5><i class="fas fa-cloud"></i> REST API Destination</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
<!-- Selezione Credenziali REST -->
|
|
<div class="mb-3">
|
|
<label class="form-label">Credenziali REST API:</label>
|
|
<select class="form-select" @onchange="OnRestCredentialChanged" value="@selectedRestCredential">
|
|
<option value="">-- Seleziona REST API --</option>
|
|
@foreach (var cred in restApiCredentials)
|
|
{
|
|
<option value="@cred.Name">@cred.Name (@cred.ServiceType - @cred.BaseUrl)</option>
|
|
}
|
|
</select>
|
|
</div>
|
|
|
|
@if (!string.IsNullOrEmpty(selectedRestCredential))
|
|
{
|
|
<div class="mb-3">
|
|
<button class="btn btn-success btn-sm" @onclick="ConnectToRestApi" disabled="@isConnectingRest">
|
|
@if (isConnectingRest)
|
|
{
|
|
<span class="spinner-border spinner-border-sm me-2"></span>
|
|
}
|
|
<i class="fas fa-plug"></i> Connetti e Scopri Entità
|
|
</button>
|
|
@if (isRestConnected)
|
|
{
|
|
<span class="badge bg-success ms-2">Connesso</span>
|
|
}
|
|
</div>
|
|
}
|
|
|
|
@if (!string.IsNullOrEmpty(restErrorMessage))
|
|
{
|
|
<div class="alert alert-danger" role="alert">
|
|
@restErrorMessage
|
|
</div>
|
|
} <!-- Lista Entità REST -->
|
|
@if (restEntities.Any())
|
|
{
|
|
<div class="mb-3">
|
|
<h6>Entità REST (@restEntities.Count disponibili):</h6>
|
|
|
|
<!-- Campo di ricerca -->
|
|
<div class="mb-2">
|
|
<div class="input-group">
|
|
<span class="input-group-text">
|
|
<i class="fas fa-search"></i>
|
|
</span>
|
|
<input type="text" class="form-control" placeholder="Cerca entità..."
|
|
@bind="restSearchTerm" @oninput="FilterRestEntities" />
|
|
@if (!string.IsNullOrEmpty(restSearchTerm))
|
|
{
|
|
<button class="btn btn-outline-secondary" @onclick="ClearRestSearch">
|
|
<i class="fas fa-times"></i>
|
|
</button>
|
|
}
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Lista entità filtrate -->
|
|
<div class="list-group" style="max-height: 300px; overflow-y: auto;">
|
|
@foreach (var entity in GetFilteredRestEntities())
|
|
{
|
|
<a class="list-group-item list-group-item-action @(selectedRestEntity?.Name == entity.Name ? "active" : "")"
|
|
@onclick="@(() => SelectRestEntity(entity))">
|
|
<div class="d-flex justify-content-between align-items-center">
|
|
<div>
|
|
<i class="fas fa-cube"></i> @entity.Name
|
|
@if (!string.IsNullOrEmpty(entity.Label))
|
|
{
|
|
<small class="text-muted d-block">@entity.Label</small>
|
|
}
|
|
</div>
|
|
@if (selectedRestEntity?.Name == entity.Name)
|
|
{
|
|
<span class="badge bg-primary">Selezionata</span>
|
|
}
|
|
</div>
|
|
</a>
|
|
}
|
|
</div>
|
|
|
|
@if (!GetFilteredRestEntities().Any())
|
|
{
|
|
<div class="alert alert-info mt-2">
|
|
<i class="fas fa-info-circle"></i> Nessuna entità trovata con il termine di ricerca "@restSearchTerm"
|
|
</div>
|
|
} </div>
|
|
}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div> <!-- Sezione Mapping (quando la fonte è selezionata e REST è connesso) -->
|
|
@{
|
|
var isSourceReady = (selectedSourceType == "database" && isDatabaseConnected &&
|
|
((useCustomQuery && isQueryValid) || (!useCustomQuery && !string.IsNullOrEmpty(selectedTable)))) ||
|
|
(selectedSourceType == "file" && !string.IsNullOrEmpty(selectedSheet));
|
|
}
|
|
@if (isSourceReady && isRestConnected && selectedRestEntity != null)
|
|
{
|
|
<div class="row mt-4">
|
|
<div class="col-12">
|
|
<div class="card">
|
|
<div class="card-header bg-success text-white">
|
|
<h5><i class="fas fa-exchange-alt"></i> Mapping Campi</h5>
|
|
</div> <div class="card-body">
|
|
@{
|
|
var sourceDisplayName = selectedSourceType == "database" ? selectedTable : selectedSheet;
|
|
var sourceTypeName = selectedSourceType == "database" ? "Tabella" : "Foglio";
|
|
}
|
|
<h6>Mapping tra @sourceTypeName @sourceDisplayName e @selectedRestEntity.Name</h6>
|
|
<p class="text-muted">Configura il mapping tra i campi della fonte dati e le proprietà dell'entità REST</p>
|
|
<div class="row">
|
|
<!-- Colonna Sinistra: Campi Fonte -->
|
|
<div class="col-5">
|
|
<h6>Campi @sourceTypeName (@sourceDisplayName)</h6>
|
|
@if (selectedSourceType == "database" && useCustomQuery && queryColumns.Any())
|
|
{
|
|
<!-- Le colonne della query validata sono disponibili per il mapping -->
|
|
}
|
|
<div class="list-group" style="max-height: 400px; overflow-y: auto;">
|
|
@if (selectedSourceType == "database")
|
|
{
|
|
@if (useCustomQuery && queryColumns.Any())
|
|
{
|
|
<!-- Colonne da query custom -->
|
|
@foreach (var column in queryColumns)
|
|
{
|
|
<a class="list-group-item list-group-item-action @(selectedDbColumn == column ? "active" : "")"
|
|
@onclick="@(() => SelectDbColumn(column))">
|
|
<div class="d-flex justify-content-between align-items-center">
|
|
<div>
|
|
<strong>@column</strong>
|
|
<small class="text-muted d-block">Query Column</small>
|
|
</div>
|
|
<div>
|
|
@if (fieldMappings.ContainsKey(column))
|
|
{
|
|
<span class="badge bg-success">Mapped</span>
|
|
}
|
|
</div>
|
|
</div>
|
|
</a>
|
|
}
|
|
}
|
|
else if (!useCustomQuery && databaseTables.ContainsKey(selectedTable))
|
|
{
|
|
<!-- Colonne da tabella -->
|
|
@foreach (var column in databaseTables[selectedTable])
|
|
{
|
|
<a class="list-group-item list-group-item-action @(selectedDbColumn == column.Name ? "active" : "")"
|
|
@onclick="@(() => SelectDbColumn(column.Name))">
|
|
<div class="d-flex justify-content-between align-items-center">
|
|
<div>
|
|
<strong>@column.Name</strong>
|
|
<small class="text-muted d-block">@column.DataType</small>
|
|
</div>
|
|
<div>
|
|
@if (column.IsPrimaryKey)
|
|
{
|
|
<span class="badge bg-primary">PK</span>
|
|
}
|
|
@if (fieldMappings.ContainsKey(column.Name))
|
|
{
|
|
<span class="badge bg-success">Mapped</span>
|
|
}
|
|
</div>
|
|
</div>
|
|
</a>
|
|
}
|
|
}
|
|
}
|
|
else if (selectedSourceType == "file" && fileSheets.ContainsKey(selectedSheet))
|
|
{
|
|
@foreach (var column in fileSheets[selectedSheet])
|
|
{
|
|
<a class="list-group-item list-group-item-action @(selectedDbColumn == column ? "active" : "")"
|
|
@onclick="@(() => SelectDbColumn(column))">
|
|
<div class="d-flex justify-content-between align-items-center">
|
|
<div>
|
|
<strong>@column</strong>
|
|
<small class="text-muted d-block">Colonna File</small>
|
|
</div>
|
|
<div>
|
|
@if (fieldMappings.ContainsKey(column))
|
|
{
|
|
<span class="badge bg-success">Mapped</span>
|
|
}
|
|
</div>
|
|
</div>
|
|
</a>
|
|
}
|
|
}
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Colonna Centrale: Controlli Mapping -->
|
|
<div class="col-2 text-center">
|
|
<div class="d-flex flex-column justify-content-center h-100">
|
|
<button class="btn btn-success mb-2" @onclick="CreateMapping"
|
|
disabled="@(string.IsNullOrEmpty(selectedDbColumn) || string.IsNullOrEmpty(selectedRestProperty))">
|
|
<i class="fas fa-arrow-right"></i>
|
|
<small class="d-block">Map</small>
|
|
</button>
|
|
<button class="btn btn-danger mb-2" @onclick="RemoveMapping"
|
|
disabled="@(string.IsNullOrEmpty(selectedDbColumn) || !fieldMappings.ContainsKey(selectedDbColumn))">
|
|
<i class="fas fa-times"></i>
|
|
<small class="d-block">Remove</small>
|
|
</button>
|
|
<button class="btn btn-warning mb-2" @onclick="AutoMapFields">
|
|
<i class="fas fa-magic"></i>
|
|
<small class="d-block">Auto</small>
|
|
</button>
|
|
<button class="btn btn-secondary" @onclick="ClearAllMappings">
|
|
<i class="fas fa-trash"></i>
|
|
<small class="d-block">Clear</small>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Colonna Destra: Proprietà REST -->
|
|
<div class="col-5">
|
|
<h6>Proprietà REST (@selectedRestEntity.Name)</h6>
|
|
<div class="list-group" style="max-height: 400px; overflow-y: auto;">
|
|
@if (restEntityDetails != null)
|
|
{
|
|
@foreach (var property in restEntityDetails.Properties)
|
|
{
|
|
<a class="list-group-item list-group-item-action @(selectedRestProperty == property.Name ? "active" : "")"
|
|
@onclick="@(() => SelectRestProperty(property.Name))">
|
|
<div class="d-flex justify-content-between align-items-center">
|
|
<div>
|
|
<strong>@property.Name</strong>
|
|
<small class="text-muted d-block">@property.Type</small>
|
|
</div>
|
|
<div>
|
|
@if (property.IsRequired)
|
|
{
|
|
<span class="badge bg-danger">Required</span>
|
|
}
|
|
@if (fieldMappings.ContainsValue(property.Name))
|
|
{
|
|
<span class="badge bg-success">Mapped</span>
|
|
}
|
|
</div>
|
|
</div>
|
|
</a>
|
|
}
|
|
}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Sezione Mappature Correnti --> @if (fieldMappings.Any())
|
|
{
|
|
<div class="mt-4">
|
|
<div class="d-flex justify-content-between align-items-center">
|
|
<h6>Mappature Correnti (@fieldMappings.Count)</h6>
|
|
@if (keyFields.Any())
|
|
{
|
|
<small class="text-info">
|
|
<i class="fas fa-key"></i> @keyFields.Count campo/i chiave: @string.Join(", ", keyFields)
|
|
</small>
|
|
}
|
|
</div>
|
|
<div class="table-responsive">
|
|
<table class="table table-sm table-striped">
|
|
<thead>
|
|
<tr>
|
|
<th>Campo Database</th>
|
|
<th>Tipo DB</th>
|
|
<th>→</th>
|
|
<th>Proprietà REST</th>
|
|
<th>Tipo REST</th>
|
|
<th>Azioni</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
@foreach (var mapping in fieldMappings)
|
|
{
|
|
DbColumnInfo? dbColumn = null;
|
|
if (selectedSourceType == "database" && !string.IsNullOrEmpty(selectedTable))
|
|
{
|
|
dbColumn = databaseTables.ContainsKey(selectedTable) ?
|
|
databaseTables[selectedTable].FirstOrDefault(c => c.Name == mapping.Key) : null;
|
|
}
|
|
var restProperty = restEntityDetails?.Properties.FirstOrDefault(p => p.Name == mapping.Value);
|
|
<tr>
|
|
<td><strong>@mapping.Key</strong></td>
|
|
<td><small class="text-muted">@(dbColumn?.DataType ?? (selectedSourceType == "file" ? "Text" : "Unknown"))</small></td>
|
|
<td><i class="fas fa-arrow-right text-success"></i></td>
|
|
<td><strong>@mapping.Value</strong></td>
|
|
<td><small class="text-muted">@(restProperty?.Type ?? "Unknown")</small></td>
|
|
<td>
|
|
<button class="btn btn-sm btn-danger" @onclick="@(() => RemoveSpecificMapping(mapping.Key))">
|
|
<i class="fas fa-trash"></i>
|
|
</button>
|
|
</td>
|
|
</tr>
|
|
}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
}
|
|
|
|
<!-- Configurazione Chiave Sorgente -->
|
|
@if (fieldMappings.Any())
|
|
{
|
|
<div class="mt-4">
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<h6 class="mb-0">
|
|
<i class="fas fa-key"></i> Configurazione Chiave Sorgente
|
|
</h6>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="form-check mb-3">
|
|
<input class="form-check-input" type="checkbox" id="useAssociations"
|
|
@bind="useRecordAssociations" />
|
|
<label class="form-check-label" for="useAssociations">
|
|
<strong>Utilizza sistema di associazioni basato sui valori delle chiavi</strong>
|
|
<br><small class="text-muted">Raccomandato: il sistema manterrà traccia delle associazioni tra valori chiave e record di destinazione, permettendo aggiornamenti indipendentemente dalla sorgente</small>
|
|
</label>
|
|
</div>
|
|
|
|
@if (useRecordAssociations)
|
|
{
|
|
<div class="alert alert-info">
|
|
<i class="fas fa-lightbulb"></i>
|
|
<strong>Come funziona il nuovo sistema:</strong>
|
|
<ul class="mb-0 mt-2">
|
|
<li>Ogni valore di chiave univoco viene associato a un record di destinazione</li>
|
|
<li>Più sorgenti diverse possono gestire lo stesso oggetto business usando lo stesso valore chiave</li>
|
|
<li>Gli aggiornamenti avvengono automaticamente quando si trova un'associazione esistente</li>
|
|
<li>Il sistema individua automaticamente le chiavi dove possibile, ma puoi sempre scegliere manualmente</li>
|
|
</ul>
|
|
</div>
|
|
|
|
<div class="row">
|
|
<div class="col-md-6">
|
|
<label class="form-label">Campo Chiave Sorgente: <span class="text-danger">*</span></label>
|
|
<select class="form-select" @bind="sourceKeyField">
|
|
<option value="">-- Seleziona Campo Chiave --</option>
|
|
@if (!string.IsNullOrEmpty(suggestedPrimaryKey))
|
|
{
|
|
<option value="@suggestedPrimaryKey">@suggestedPrimaryKey (Primary Key - Consigliato)</option>
|
|
}
|
|
@if (selectedSourceType == "database")
|
|
{
|
|
@if (useCustomQuery && queryColumns.Any())
|
|
{
|
|
@foreach (var column in queryColumns)
|
|
{
|
|
<option value="@column">@column (Query Column)</option>
|
|
}
|
|
}
|
|
else if (!useCustomQuery && databaseTables.ContainsKey(selectedTable))
|
|
{
|
|
@foreach (var column in databaseTables[selectedTable].Where(c => c.Name != suggestedPrimaryKey))
|
|
{
|
|
<option value="@column.Name">@column.Name (@column.DataType)</option>
|
|
}
|
|
}
|
|
}
|
|
else if (selectedSourceType == "file" && fileSheets.ContainsKey(selectedSheet))
|
|
{
|
|
@foreach (var column in fileSheets[selectedSheet])
|
|
{
|
|
<option value="@column">@column</option>
|
|
}
|
|
}
|
|
</select>
|
|
@if (requiresManualKeySelection || selectedSourceType != "database")
|
|
{
|
|
<small class="form-text text-danger">
|
|
<i class="fas fa-exclamation-triangle"></i>
|
|
Selezione del campo chiave obbligatoria. Scegli un campo che identifichi univocamente ogni record.
|
|
</small>
|
|
}
|
|
else if (!string.IsNullOrEmpty(suggestedPrimaryKey))
|
|
{
|
|
<small class="form-text text-success">
|
|
<i class="fas fa-key"></i>
|
|
Primary Key rilevata: <strong>@suggestedPrimaryKey</strong> (consigliato per l'identificazione univoca)
|
|
</small>
|
|
}
|
|
</div>
|
|
<div class="col-md-6">
|
|
@if (!string.IsNullOrEmpty(sourceKeyField))
|
|
{
|
|
<div class="mt-4">
|
|
<div class="alert alert-success">
|
|
<i class="fas fa-check-circle"></i>
|
|
<strong>Campo chiave selezionato:</strong> @sourceKeyField
|
|
<br><small>Questo campo verrà utilizzato per identificare univocamente i record sorgente</small>
|
|
@if (sourceKeyField == suggestedPrimaryKey)
|
|
{
|
|
<br><small class="text-success"><i class="fas fa-thumbs-up"></i> Ottima scelta! Stai usando la Primary Key della tabella.</small>
|
|
}
|
|
</div>
|
|
</div>
|
|
}
|
|
else
|
|
{
|
|
<div class="mt-4">
|
|
<div class="alert alert-warning">
|
|
<i class="fas fa-exclamation-triangle"></i>
|
|
<strong>Campo chiave richiesto</strong>
|
|
<br><small>Seleziona un campo che identifichi univocamente ogni record per abilitare il sistema di associazioni.</small>
|
|
@if (!string.IsNullOrEmpty(suggestedPrimaryKey))
|
|
{
|
|
<br><small class="text-info"><i class="fas fa-lightbulb"></i> Consiglio: seleziona <strong>@suggestedPrimaryKey</strong> (Primary Key rilevata)</small>
|
|
}
|
|
</div>
|
|
</div>
|
|
}
|
|
</div>
|
|
</div>
|
|
}
|
|
else
|
|
{
|
|
<div class="alert alert-warning">
|
|
<i class="fas fa-exclamation-triangle"></i>
|
|
<strong>Sistema associazioni disabilitato</strong><br>
|
|
Tutti i record verranno sempre inseriti come nuovi. Non sarà possibile tracciare aggiornamenti automatici.
|
|
</div>
|
|
}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
}
|
|
|
|
<div class="mt-3">
|
|
<div class="d-flex justify-content-between align-items-center">
|
|
<div>
|
|
<button class="btn btn-success" @onclick="StartDataTransfer" disabled="@(!IsTransferButtonEnabled() || isTransferringData)"> @if (isTransferringData)
|
|
{
|
|
<span class="spinner-border spinner-border-sm me-2"></span>
|
|
<i class="fas fa-sync-alt"></i> @("Trasferimento in corso")
|
|
}
|
|
else
|
|
{
|
|
<i class="fas fa-play"></i> @("Avvia Trasferimento Dati")
|
|
}
|
|
</button>
|
|
|
|
@if (fieldMappings.Any())
|
|
{
|
|
<button class="btn btn-info ms-2" @onclick="ShowMappingSummary">
|
|
<i class="fas fa-list"></i> Riepilogo Mapping
|
|
</button>
|
|
}
|
|
</div> <div class="text-muted">
|
|
@if (fieldMappings.Any())
|
|
{
|
|
<small>
|
|
<i class="fas fa-arrow-right"></i> @fieldMappings.Count mapping(s) configurati<br/>
|
|
@if (useRecordAssociations)
|
|
{
|
|
<span><i class="fas fa-sync-alt text-info"></i> <strong>Modalità Smart Update</strong></span>
|
|
@if (!string.IsNullOrEmpty(sourceKeyField))
|
|
{
|
|
<span> (Chiave: @sourceKeyField)</span>
|
|
}
|
|
else
|
|
{
|
|
<span> (Rilevamento automatico)</span>
|
|
}
|
|
}
|
|
else
|
|
{
|
|
<span><i class="fas fa-plus text-success"></i> <strong>Modalità Insert Only</strong></span>
|
|
}
|
|
</small>
|
|
}
|
|
else
|
|
{
|
|
<small>
|
|
<i class="fas fa-exclamation-triangle"></i> Configura almeno una mappatura per iniziare
|
|
</small>
|
|
}
|
|
</div>
|
|
</div>
|
|
@if (!string.IsNullOrEmpty(transferMessage))
|
|
{
|
|
<div class="alert @(transferMessageType == "success" ? "alert-success" : transferMessageType == "warning" ? "alert-warning" : "alert-danger") mt-3" role="alert">
|
|
<i class="fas @(transferMessageType == "success" ? "fa-check-circle" : transferMessageType == "warning" ? "fa-exclamation-triangle" : "fa-exclamation-circle")"></i>
|
|
@transferMessage
|
|
</div>
|
|
}
|
|
|
|
@if (transferResults.Any())
|
|
{
|
|
<div class="mt-3">
|
|
<div class="d-flex justify-content-between align-items-center">
|
|
<h6><i class="fas fa-list-alt"></i> Risultati Dettagliati Trasferimento (@transferResults.Count record)</h6>
|
|
<button type="button" class="btn btn-sm btn-outline-secondary"
|
|
data-bs-toggle="collapse" data-bs-target="#transferResults"
|
|
aria-expanded="@showDetailedResults.ToString().ToLower()" aria-controls="transferResults"
|
|
@onclick="() => showDetailedResults = !showDetailedResults">
|
|
<i class="fas @(showDetailedResults ? "fa-chevron-up" : "fa-chevron-down")"></i>
|
|
@(showDetailedResults ? "Nascondi" : "Mostra") Dettagli
|
|
</button>
|
|
</div>
|
|
|
|
<div class="collapse @(showDetailedResults ? "show" : "")" id="transferResults">
|
|
<div class="card mt-2">
|
|
<div class="card-header">
|
|
<div class="row text-center">
|
|
<div class="col-3">
|
|
<small class="text-success"><i class="fas fa-check-circle"></i>
|
|
Inseriti: @transferResults.Count(r => r.Status == "success")</small>
|
|
</div>
|
|
<div class="col-3">
|
|
<small class="text-info"><i class="fas fa-edit"></i>
|
|
Aggiornati: @transferResults.Count(r => r.Status == "updated")</small>
|
|
</div>
|
|
<div class="col-3">
|
|
<small class="text-warning"><i class="fas fa-exclamation-triangle"></i>
|
|
Duplicati: @transferResults.Count(r => r.Status == "duplicate")</small>
|
|
</div>
|
|
<div class="col-3">
|
|
<small class="text-danger"><i class="fas fa-times-circle"></i>
|
|
Errori: @transferResults.Count(r => r.Status == "error")</small>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="card-body" style="max-height: 400px; overflow-y: auto;">
|
|
<div class="table-responsive">
|
|
<table class="table table-sm table-striped">
|
|
<thead>
|
|
<tr>
|
|
<th style="width: 10%;">#</th>
|
|
<th style="width: 15%;">Stato</th>
|
|
<th style="width: 20%;">ID Entità</th>
|
|
<th style="width: 55%;">Messaggio</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
@foreach (var result in transferResults)
|
|
{
|
|
<tr class="@GetResultRowClass(result.Status)">
|
|
<td>@result.RecordNumber</td>
|
|
<td>
|
|
<span class="badge @GetResultBadgeClass(result.Status)">
|
|
<i class="fas @GetResultIcon(result.Status)"></i>
|
|
@GetResultStatusText(result.Status)
|
|
</span>
|
|
</td>
|
|
<td>
|
|
@if (!string.IsNullOrEmpty(result.EntityId))
|
|
{
|
|
<small class="text-muted">@result.EntityId</small>
|
|
}
|
|
else
|
|
{
|
|
<small class="text-muted">-</small>
|
|
}
|
|
</td>
|
|
<td>
|
|
<small>@result.Message</small>
|
|
</td>
|
|
</tr>
|
|
}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
}</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
}
|
|
</div>
|
|
|
|
<!-- Sezione Salvataggio Profilo -->
|
|
@if (isDatabaseConnected && isRestConnected && fieldMappings.Any())
|
|
{
|
|
<div class="row mt-4">
|
|
<div class="col-12">
|
|
<ProfileSaver CanSave="CanSaveProfile()"
|
|
SourceType="selectedSourceType"
|
|
SourceSchema="@(availableTableNames.FirstOrDefault()?.Split('.').FirstOrDefault())"
|
|
SourceTable="selectedTable"
|
|
DestinationType="rest"
|
|
DestinationEndpoint="@(selectedRestEntity?.Name)"
|
|
FieldMappings="GetCurrentFieldMappings()"
|
|
OnProfileSaved="OnProfileSaved" />
|
|
</div>
|
|
</div>
|
|
}
|
|
|
|
<!-- Componente Gestione Profili -->
|
|
<ProfileManagement ShowModal="showProfileManagement"
|
|
Profiles="availableProfiles"
|
|
OnCloseModal="OnCloseProfileManagement"
|
|
OnProfileLoaded="OnProfileLoaded"
|
|
OnProfileDeleted="OnProfileDeleted"
|
|
IsLoading="isLoadingProfiles" />
|
|
|
|
<!-- Modal per la selezione dello schema -->
|
|
@if (showSchemaSelectionModal)
|
|
{
|
|
<div class="modal fade show d-block" style="background-color: rgba(0,0,0,0.5);">
|
|
<div class="modal-dialog modal-dialog-centered">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h5 class="modal-title">
|
|
<i class="fas fa-sitemap"></i> Seleziona Schema
|
|
</h5>
|
|
</div>
|
|
<div class="modal-body">
|
|
<p class="text-muted">
|
|
<i class="fas fa-info-circle"></i>
|
|
Sono stati trovati più schemi nel database. Seleziona lo schema da utilizzare:
|
|
</p>
|
|
|
|
@if (isLoadingSchemas)
|
|
{
|
|
<div class="text-center">
|
|
<div class="spinner-border spinner-border-sm me-2"></div>
|
|
Caricamento schemi...
|
|
</div>
|
|
}
|
|
else if (availableSchemas != null && availableSchemas.Any())
|
|
{
|
|
<div class="mb-3">
|
|
<label for="schemaSelect" class="form-label">Schemi disponibili:</label>
|
|
<select id="schemaSelect" class="form-select" @bind="selectedSchema">
|
|
<option value="">-- Seleziona uno schema --</option>
|
|
@foreach (var schema in availableSchemas)
|
|
{
|
|
<option value="@schema">@schema</option>
|
|
}
|
|
</select>
|
|
<small class="form-text text-muted">
|
|
<i class="fas fa-lightbulb"></i>
|
|
Lo schema determina quali tabelle e viste saranno disponibili per il mapping.
|
|
</small>
|
|
</div>
|
|
}
|
|
else
|
|
{
|
|
<div class="alert alert-warning">
|
|
<i class="fas fa-exclamation-triangle"></i>
|
|
Nessuno schema trovato o errore nel caricamento.
|
|
</div>
|
|
}
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button type="button" class="btn btn-secondary" @onclick="CancelSchemaSelection">
|
|
<i class="fas fa-times"></i> Annulla
|
|
</button>
|
|
<button type="button" class="btn btn-primary" @onclick="OnSchemaSelected"
|
|
disabled="@string.IsNullOrEmpty(selectedSchema)">
|
|
<i class="fas fa-check"></i> Connetti con Schema
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
}
|
|
|
|
<!-- Modal per la selezione del database -->
|
|
@if (showDatabaseSelectionModal)
|
|
{
|
|
<div class="modal fade show d-block" style="background-color: rgba(0,0,0,0.5);">
|
|
<div class="modal-dialog modal-dialog-centered">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h5 class="modal-title">
|
|
<i class="fas fa-database"></i> Seleziona Database
|
|
</h5>
|
|
</div>
|
|
<div class="modal-body">
|
|
<p class="text-muted">
|
|
<i class="fas fa-info-circle"></i>
|
|
La connessione non specifica un database. Seleziona il database da esplorare:
|
|
</p>
|
|
@if (isLoadingDatabases)
|
|
{
|
|
<div class="text-center">
|
|
<div class="spinner-border spinner-border-sm me-2"></div>
|
|
Caricamento database...
|
|
</div>
|
|
}
|
|
else if (availableDatabases != null && availableDatabases.Any())
|
|
{
|
|
<div class="mb-3">
|
|
<label for="databaseSelect" class="form-label">Database disponibili:</label>
|
|
<select id="databaseSelect" class="form-select" @bind="selectedDatabase">
|
|
<option value="">-- Seleziona un database --</option>
|
|
@foreach (var db in availableDatabases)
|
|
{
|
|
<option value="@db">@db</option>
|
|
}
|
|
</select>
|
|
</div>
|
|
}
|
|
else
|
|
{
|
|
<div class="alert alert-warning">
|
|
<i class="fas fa-exclamation-triangle"></i>
|
|
Nessun database trovato o errore nel caricamento.
|
|
</div>
|
|
}
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button type="button" class="btn btn-secondary" @onclick="CancelDatabaseSelection">
|
|
<i class="fas fa-times"></i> Annulla
|
|
</button>
|
|
<button type="button" class="btn btn-primary" @onclick="OnDatabaseSelected" disabled="@string.IsNullOrEmpty(selectedDatabase)">
|
|
<i class="fas fa-check"></i> Connetti con Database
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
}
|