@page "/standorte" @using Microsoft.EntityFrameworkCore @using System.Text.Json @using TrafagSalesExporter.Data @using TrafagSalesExporter.Models @using TrafagSalesExporter.Services @inject IDbContextFactory DbFactory @inject IHanaQueryService HanaService @inject ISapGatewayService SapGatewayService @inject ISnackbar Snackbar @inject IDialogService DialogService Standorte Standorte HANA Server Server hinzufügen Name Host Port Username Verbindungsstatus Aktionen @context.Name @context.Host @context.Port @context.Username @if (_connectionStatus.TryGetValue(context.Id, out var status)) { @(status.Success ? "OK" : "Fehler") - @status.Stage } else { Nicht getestet } Standorte (Sites) Neuen Standort hinzufügen Land TSC Schema Quellsystem Quelle Aktiv Aktionen @context.Land @context.TSC @context.Schema @context.SourceSystem @GetConnectionTarget(context) @if (context.IsActive) { } else { } @(_editingServer.Id == 0 ? "Server hinzufügen" : "Server bearbeiten") Abbrechen Speichern @(_editingSite.Id == 0 ? "Standort hinzufügen" : "Standort bearbeiten") @foreach (var system in _sourceSystems) { @system } @if (IsSapSite()) { SAP Gateway Die Service-URL zeigt auf den OData-Service. Die verfügbaren Entity Sets werden nur per Knopfdruck aktualisiert und lokal zwischengespeichert. @if (_refreshingSapEntitySets) { @("Lade...") } else { @("Quellen refreshen") } @if (_editingSite.SapEntitySetsRefreshedAtUtc.HasValue) { Letzter Refresh: @_editingSite.SapEntitySetsRefreshedAtUtc.Value.ToLocalTime().ToString("yyyy-MM-dd HH:mm:ss") } @foreach (var entitySet in _sapEntitySetsCache) { @entitySet } } else { HANA-Verbindung Host, Port und technische HANA-Parameter kommen von dieser Verbindung. Username und Password hier dienen nur noch als Fallback für bestehende Einträge. } Abbrechen Speichern @code { private readonly string[] _sourceSystems = ["SAP", "BI1", "SAGE"]; private readonly Dictionary _connectionStatus = new(); private List _servers = new(); private List _sites = new(); private List _sapEntitySetsCache = []; private HanaServer _editingServer = new(); private Site _editingSite = new(); private HanaServer _editingSiteServer = new(); private bool _serverDialogVisible; private bool _siteDialogVisible; private bool _refreshingSapEntitySets; private bool _savingServer; private bool _savingSite; private readonly DialogOptions _dialogOptions = new() { MaxWidth = MaxWidth.Small, FullWidth = true }; protected override async Task OnInitializedAsync() { await LoadDataAsync(); } private async Task LoadDataAsync() { using var db = await DbFactory.CreateDbContextAsync(); _servers = await db.HanaServers.OrderBy(s => s.Name).ToListAsync(); _sites = await db.Sites.Include(s => s.HanaServer).OrderBy(s => s.Land).ToListAsync(); } private void AddServer() { _editingServer = new HanaServer { Port = 30015 }; _serverDialogVisible = true; } private void EditServer(HanaServer server) { _editingServer = CloneServer(server); _serverDialogVisible = true; } private async Task SaveServer() { if (_savingServer) return; _savingServer = true; try { using var db = await DbFactory.CreateDbContextAsync(); if (_editingServer.Id == 0) { db.HanaServers.Add(_editingServer); } else { var existing = await db.HanaServers.FindAsync(_editingServer.Id); if (existing is not null) { existing.Name = _editingServer.Name; existing.Host = _editingServer.Host; existing.Port = _editingServer.Port; existing.Username = _editingServer.Username; existing.Password = _editingServer.Password; existing.DatabaseName = _editingServer.DatabaseName; existing.UseSsl = _editingServer.UseSsl; existing.ValidateCertificate = _editingServer.ValidateCertificate; existing.AdditionalParams = _editingServer.AdditionalParams; } } await db.SaveChangesAsync(); _serverDialogVisible = false; await LoadDataAsync(); Snackbar.Add("Server gespeichert", Severity.Success); } finally { _savingServer = false; } } private async Task DeleteServer(HanaServer server) { var result = await DialogService.ShowMessageBox( "Server löschen", $"Server '{server.Name}' wirklich löschen?", yesText: "Löschen", cancelText: "Abbrechen"); if (result != true) return; using var db = await DbFactory.CreateDbContextAsync(); var entity = await db.HanaServers.FindAsync(server.Id); if (entity is not null) { db.HanaServers.Remove(entity); await db.SaveChangesAsync(); } await LoadDataAsync(); Snackbar.Add("Server gelöscht", Severity.Info); } private async Task TestServerConnection(HanaServer server) { var result = await Task.Run(() => HanaService.TestConnectionDetailed(server)); _connectionStatus[server.Id] = result; if (result.Success) { Snackbar.Add($"Verbindung zu '{server.Name}' erfolgreich.", Severity.Success); } else { Snackbar.Add($"{server.Name}: {result.ExceptionType} - {result.ErrorMessage}", Severity.Error); } } private static string BuildStatusTooltip(ConnectionTestResult status) { var stamp = status.TestedAtUtc.ToLocalTime().ToString("yyyy-MM-dd HH:mm:ss"); if (status.Success) return $"Letzter Test: {stamp}\nStage: {status.Stage}\n{status.ConnectionStringPreview}"; return $"Letzter Test: {stamp}\nStage: {status.Stage}\nFehler: {status.ErrorMessage}\n{status.ConnectionStringPreview}"; } private void AddSite() { _editingSite = new Site { IsActive = true, SourceSystem = "SAP", HanaServerId = 0 }; _sapEntitySetsCache = []; _editingSiteServer = CreateDefaultSiteServer(); _siteDialogVisible = true; } private void EditSite(Site site) { _editingSite = new Site { Id = site.Id, HanaServerId = site.HanaServerId, Schema = site.Schema, TSC = site.TSC, Land = site.Land, SourceSystem = string.IsNullOrWhiteSpace(site.SourceSystem) ? "SAP" : site.SourceSystem, UsernameOverride = site.UsernameOverride, PasswordOverride = site.PasswordOverride, SapServiceUrl = site.SapServiceUrl, SapEntitySet = site.SapEntitySet, SapEntitySetsCache = site.SapEntitySetsCache, SapEntitySetsRefreshedAtUtc = site.SapEntitySetsRefreshedAtUtc, IsActive = site.IsActive }; _sapEntitySetsCache = ParseSapEntitySets(site.SapEntitySetsCache); _editingSiteServer = site.HanaServer is null ? CreateDefaultSiteServer(site) : CloneServer(site.HanaServer); _siteDialogVisible = true; } private async Task SaveSite() { if (_savingSite) return; _savingSite = true; try { using var db = await DbFactory.CreateDbContextAsync(); var serverId = IsSapSite() ? (int?)null : await SaveOrCreateSiteServerAsync(db); _editingSite.HanaServerId = serverId; _editingSite.SapEntitySetsCache = SerializeSapEntitySets(_sapEntitySetsCache); if (_editingSite.Id == 0) { db.Sites.Add(_editingSite); } else { var existing = await db.Sites.FindAsync(_editingSite.Id); if (existing is not null) { existing.HanaServerId = serverId; existing.Schema = _editingSite.Schema; existing.TSC = _editingSite.TSC; existing.Land = _editingSite.Land; existing.SourceSystem = _editingSite.SourceSystem; existing.UsernameOverride = _editingSite.UsernameOverride; existing.PasswordOverride = _editingSite.PasswordOverride; existing.SapServiceUrl = _editingSite.SapServiceUrl; existing.SapEntitySet = _editingSite.SapEntitySet; existing.SapEntitySetsCache = _editingSite.SapEntitySetsCache; existing.SapEntitySetsRefreshedAtUtc = _editingSite.SapEntitySetsRefreshedAtUtc; existing.IsActive = _editingSite.IsActive; } } await db.SaveChangesAsync(); _siteDialogVisible = false; await LoadDataAsync(); Snackbar.Add("Standort gespeichert", Severity.Success); } catch (Exception ex) { Snackbar.Add($"Speichern fehlgeschlagen: {ex.Message}", Severity.Error); } finally { _savingSite = false; } } private async Task DeleteSite(Site site) { var result = await DialogService.ShowMessageBox( "Standort löschen", $"Standort '{site.Land}' wirklich löschen?", yesText: "Löschen", cancelText: "Abbrechen"); if (result != true) return; using var db = await DbFactory.CreateDbContextAsync(); var entity = await db.Sites.FindAsync(site.Id); if (entity is not null) { db.Sites.Remove(entity); await db.SaveChangesAsync(); } await LoadDataAsync(); Snackbar.Add("Standort gelöscht", Severity.Info); } private static string GetServerNode(HanaServer? server) { if (server is null || string.IsNullOrWhiteSpace(server.Host)) return "-"; return server.Host.Contains(':', StringComparison.Ordinal) ? server.Host : $"{server.Host}:{server.Port}"; } private static string GetConnectionTarget(Site site) { var sourceSystem = string.IsNullOrWhiteSpace(site.SourceSystem) ? "SAP" : site.SourceSystem; if (string.Equals(sourceSystem, "SAP", StringComparison.OrdinalIgnoreCase)) return string.IsNullOrWhiteSpace(site.SapServiceUrl) ? "-" : site.SapServiceUrl; return GetServerNode(site.HanaServer); } private HanaServer CreateDefaultSiteServer(Site? site = null) { var label = !string.IsNullOrWhiteSpace(site?.Land) ? site!.Land : site?.TSC; if (string.IsNullOrWhiteSpace(label)) label = "Neuer Standort"; return new HanaServer { Name = $"{label} HANA", Port = 30015 }; } private static HanaServer CloneServer(HanaServer server) { return new HanaServer { Id = server.Id, Name = server.Name, Host = server.Host, Port = server.Port, Username = server.Username, Password = server.Password, DatabaseName = server.DatabaseName, UseSsl = server.UseSsl, ValidateCertificate = server.ValidateCertificate, AdditionalParams = server.AdditionalParams }; } private async Task SaveOrCreateSiteServerAsync(AppDbContext db) { _editingSiteServer.Name = string.IsNullOrWhiteSpace(_editingSiteServer.Name) ? $"{_editingSite.Land} HANA".Trim() : _editingSiteServer.Name.Trim(); _editingSite.UsernameOverride = _editingSite.UsernameOverride.Trim(); _editingSite.PasswordOverride = _editingSite.PasswordOverride.Trim(); _editingSite.SapServiceUrl = _editingSite.SapServiceUrl.Trim(); _editingSite.SapEntitySet = _editingSite.SapEntitySet.Trim(); _editingSiteServer.Host = _editingSiteServer.Host.Trim(); _editingSiteServer.Username = _editingSiteServer.Username.Trim(); _editingSiteServer.DatabaseName = _editingSiteServer.DatabaseName.Trim(); _editingSiteServer.AdditionalParams = _editingSiteServer.AdditionalParams.Trim(); if (string.IsNullOrWhiteSpace(_editingSiteServer.Host)) throw new InvalidOperationException("Host oder ServerNode muss gesetzt sein."); if (_editingSite.HanaServerId == 0) { db.HanaServers.Add(_editingSiteServer); await db.SaveChangesAsync(); return _editingSiteServer.Id; } var sharedUseCount = await db.Sites.CountAsync(s => s.HanaServerId == _editingSite.HanaServerId && s.Id != _editingSite.Id); if (sharedUseCount > 0) { var dedicatedServer = CloneServer(_editingSiteServer); dedicatedServer.Id = 0; db.HanaServers.Add(dedicatedServer); await db.SaveChangesAsync(); return dedicatedServer.Id; } var existingServer = await db.HanaServers.FindAsync(_editingSite.HanaServerId); if (existingServer is null) { db.HanaServers.Add(_editingSiteServer); await db.SaveChangesAsync(); return _editingSiteServer.Id; } existingServer.Name = _editingSiteServer.Name; existingServer.Host = _editingSiteServer.Host; existingServer.Port = _editingSiteServer.Port; existingServer.Username = _editingSiteServer.Username; existingServer.Password = _editingSiteServer.Password; existingServer.DatabaseName = _editingSiteServer.DatabaseName; existingServer.UseSsl = _editingSiteServer.UseSsl; existingServer.ValidateCertificate = _editingSiteServer.ValidateCertificate; existingServer.AdditionalParams = _editingSiteServer.AdditionalParams; await db.SaveChangesAsync(); return existingServer.Id; } private bool IsSapSite() => string.Equals(_editingSite.SourceSystem, "SAP", StringComparison.OrdinalIgnoreCase); private async Task RefreshSapEntitySets() { if (_refreshingSapEntitySets) return; _refreshingSapEntitySets = true; try { if (string.IsNullOrWhiteSpace(_editingSite.SapServiceUrl)) throw new InvalidOperationException("SAP Service URL muss gesetzt sein."); using var db = await DbFactory.CreateDbContextAsync(); var settings = await db.ExportSettings.FirstOrDefaultAsync() ?? new(); var username = string.IsNullOrWhiteSpace(_editingSite.UsernameOverride) ? settings.SapUsername : _editingSite.UsernameOverride; var password = string.IsNullOrWhiteSpace(_editingSite.PasswordOverride) ? settings.SapPassword : _editingSite.PasswordOverride; if (string.IsNullOrWhiteSpace(username) || string.IsNullOrWhiteSpace(password)) throw new InvalidOperationException("Für SAP sind weder zentrale Zugangsdaten noch Standort-Overrides gesetzt."); var entitySets = await SapGatewayService.GetEntitySetsAsync(_editingSite.SapServiceUrl, username.Trim(), password.Trim()); _sapEntitySetsCache = entitySets; _editingSite.SapEntitySetsCache = SerializeSapEntitySets(entitySets); _editingSite.SapEntitySetsRefreshedAtUtc = DateTime.UtcNow; if (!string.IsNullOrWhiteSpace(_editingSite.SapEntitySet) && !_sapEntitySetsCache.Contains(_editingSite.SapEntitySet, StringComparer.OrdinalIgnoreCase)) { _editingSite.SapEntitySet = string.Empty; } Snackbar.Add($"{entitySets.Count} SAP Entity Sets geladen.", Severity.Success); } catch (Exception ex) { Snackbar.Add(ex.Message, Severity.Error); } finally { _refreshingSapEntitySets = false; } } private void CloseServerDialog() { if (_savingServer) return; _serverDialogVisible = false; } private void CloseSiteDialog() { if (_savingSite || _refreshingSapEntitySets) return; _siteDialogVisible = false; } private static List ParseSapEntitySets(string json) { if (string.IsNullOrWhiteSpace(json)) return []; try { return JsonSerializer.Deserialize>(json) ?? []; } catch { return []; } } private static string SerializeSapEntitySets(List entitySets) => JsonSerializer.Serialize(entitySets); }