99da631aea
- Implementato discovery intelligente database vs schemi per ogni DBMS - Aggiornate query SQL per mostrare solo database effettivi (non ruoli/schemi di sistema) - Aggiunta UI modal per selezione database con riconnessione automatica - Aggiunta UI modal fallback per selezione schema quando necessario - Migliorate query discovery per SQL Server, PostgreSQL, MySQL e Oracle - Implementata logica di riconnessione automatica al database selezionato - Aggiornati testi e descrizioni dell'interfaccia per maggiore chiarezza - Gestione prioritaria: database disponibili fallback su schemi se necessario Fixes: Menu selezione mostrava ruoli invece dei database reali
1262 lines
83 KiB
Plaintext
1262 lines
83 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
|
|
@inject IDataConnectionCredentialService CredentialService
|
|
@inject IDataConnectionFactory ConnectionFactory
|
|
@inject IJSRuntime JSRuntime
|
|
@inject ILogger<DataCoupler> Logger
|
|
@inject CredentialManager.Services.IDataCouplerProfileService ProfileService
|
|
|
|
<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) && !isQueryValid)
|
|
{
|
|
<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 (databaseTables.Any())
|
|
{
|
|
<!-- Sezione Tabelle (modalità standard) -->
|
|
<div class="mb-3">
|
|
<h6>Tabelle Database (@databaseTables.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="@(databaseTables.Keys.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 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>
|
|
Sono stati trovati più database nel server. Seleziona il database da utilizzare:
|
|
</p>
|
|
|
|
@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>
|
|
<small class="form-text text-muted">
|
|
<i class="fas fa-lightbulb"></i>
|
|
Il database 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>
|
|
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 al Database
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
}
|
|
|
|
<!-- 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>
|
|
}
|