530 lines
24 KiB
C#
530 lines
24 KiB
C#
using System;
|
||
using System.Collections.ObjectModel;
|
||
using System.IO;
|
||
using System.Linq;
|
||
using System.Security.Cryptography;
|
||
using Avalonia.Controls;
|
||
using Avalonia.Markup.Xaml;
|
||
using Deploymentcenter.Client;
|
||
using PolyTrader.App.Avalonia.Controls;
|
||
using PolyTrader.Core;
|
||
using PolyTrader.Core.Configuration;
|
||
using PolyTrader.Core.Modularity;
|
||
using PolyTrader.Core.Persistence;
|
||
using PolyTrader.Core.Security;
|
||
using PolyTraderSharp;
|
||
using PolyTraderSharp.Models;
|
||
using PolyTraderSharp.Services;
|
||
using PolyTrader.Web.Configuration;
|
||
|
||
namespace PolyTrader.App.Avalonia.Views
|
||
{
|
||
/// <summary>
|
||
/// Einstellungen: allgemeine Server-Settings und die Polymarket-Accounts. Beide Bereiche nutzen
|
||
/// den <see cref="SettingsEditor"/> anstelle der früheren PropertyGrids.
|
||
/// Layout vollständig in SettingsWindow.axaml.
|
||
/// </summary>
|
||
public partial class SettingsWindow : UserControl
|
||
{
|
||
private const string SettingsPath = "server_settings.xml";
|
||
|
||
private readonly ObservableCollection<AccountState> _accounts = new();
|
||
|
||
private ServerSettings _settings = new();
|
||
private IAccountRepository? _accountRepo;
|
||
private TradingState? _state;
|
||
private WatchdogHeartbeatService? _watchdog;
|
||
private MullvadVpnService? _vpn;
|
||
private TerminalLogger? _logger;
|
||
private DeploymentcenterOptions? _dcOptions;
|
||
private WebUiOptions _webOptions = new();
|
||
|
||
public SettingsWindow() => AvaloniaXamlLoader.Load(this);
|
||
|
||
public SettingsWindow(IModuleUiHost host, IAccountRepository accountRepo, TradingState state,
|
||
TerminalLogger logger, WatchdogHeartbeatService? watchdog = null,
|
||
MullvadVpnService? vpn = null, DeploymentcenterOptions? dcOptions = null,
|
||
WebUiOptions? webOptions = null) : this()
|
||
{
|
||
_accountRepo = accountRepo;
|
||
_state = state;
|
||
_logger = logger;
|
||
_watchdog = watchdog;
|
||
_vpn = vpn;
|
||
_dcOptions = dcOptions;
|
||
_webOptions = webOptions ?? Program.LoadWebUiOptions();
|
||
|
||
var grid = this.FindControl<DataGrid>("gridAccounts")!;
|
||
grid.ItemsSource = _accounts;
|
||
grid.SelectionChanged += (_, _) =>
|
||
this.FindControl<SettingsEditor>("editorAccount")!.Show(grid.SelectedItem);
|
||
|
||
this.FindControl<Button>("btnSave")!.Click += (_, _) => SaveServerSettings();
|
||
this.FindControl<Button>("btnReload")!.Click += (_, _) => ReloadServerSettings();
|
||
this.FindControl<Button>("btnGenMasterKey")!.Click += async (_, _) => await GenerateMasterKeyAsync();
|
||
this.FindControl<Button>("btnOpenRouterKey")!.Click += async (_, _) => await SetOpenRouterKeyAsync();
|
||
this.FindControl<Button>("btnWatchdogToken")!.Click += async (_, _) => await SetWatchdogTokenAsync();
|
||
this.FindControl<Button>("btnTestWatchdog")!.Click += async (_, _) => await TestWatchdogAsync();
|
||
this.FindControl<Button>("btnLicenseKey")!.Click += async (_, _) => await SetLicenseKeyAsync();
|
||
this.FindControl<Button>("btnCheckLicense")!.Click += async (_, _) => await CheckLicenseAsync();
|
||
|
||
this.FindControl<Button>("btnAccNew")!.Click += (_, _) => AddAccount();
|
||
this.FindControl<Button>("btnAccDelete")!.Click += async (_, _) => await DeleteAccountAsync();
|
||
this.FindControl<Button>("btnAccSave")!.Click += (_, _) => SaveSelectedAccount();
|
||
|
||
this.FindControl<Button>("btnWebUiGenToken")!.Click += (_, _) => GenerateWebUiToken();
|
||
this.FindControl<Button>("btnWebUiToggleShowToken")!.Click += (_, _) => ToggleWebUiTokenVisibility();
|
||
this.FindControl<Button>("btnWebUiCopyToken")!.Click += async (_, _) => await CopyWebUiTokenAsync();
|
||
this.FindControl<Button>("btnSaveWebUi")!.Click += (_, _) => SaveWebUiSettings();
|
||
|
||
ReloadServerSettings();
|
||
LoadAccounts();
|
||
LoadWebUiSettings();
|
||
UpdateMasterKeyButtonState();
|
||
}
|
||
|
||
private void Status(string text) => this.FindControl<TextBlock>("lblStatus")!.Text = text;
|
||
|
||
// ===== Server-Settings =====
|
||
|
||
private void ReloadServerSettings()
|
||
{
|
||
_settings = ServerSettings.Load(SettingsPath);
|
||
this.FindControl<SettingsEditor>("editorServer")!.Show(_settings);
|
||
Status("Einstellungen geladen.");
|
||
}
|
||
|
||
private void SaveServerSettings()
|
||
{
|
||
try
|
||
{
|
||
_settings.Save(SettingsPath);
|
||
_vpn?.ReloadSettings();
|
||
_watchdog?.ReloadSettings();
|
||
_logger?.Info("Server-Einstellungen gespeichert und Dienste neu geladen.");
|
||
Status($"Gespeichert um {DateTime.Now:HH:mm:ss}.");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Status($"Speichern fehlgeschlagen: {ex.Message}");
|
||
}
|
||
}
|
||
|
||
// ===== Polymarket Accounts =====
|
||
|
||
private void LoadAccounts()
|
||
{
|
||
if (_state == null) return;
|
||
_accounts.Clear();
|
||
foreach (var a in _state.Accounts.Values.OrderBy(a => a.AccountId)) _accounts.Add(a);
|
||
|
||
var grid = this.FindControl<DataGrid>("gridAccounts")!;
|
||
if (_accounts.Count > 0) grid.SelectedIndex = 0;
|
||
}
|
||
|
||
private void AddAccount()
|
||
{
|
||
if (_state == null || _accountRepo == null) return;
|
||
|
||
int newId = _state.Accounts.Count > 0 ? _state.Accounts.Keys.Max() + 1 : 1;
|
||
var acc = new AccountState { AccountId = newId, Name = "Neuer Account" };
|
||
|
||
_state.Accounts[acc.AccountId] = acc;
|
||
_accountRepo.Upsert(acc);
|
||
_accounts.Add(acc);
|
||
|
||
this.FindControl<DataGrid>("gridAccounts")!.SelectedItem = acc;
|
||
Status($"Account #{newId} angelegt.");
|
||
}
|
||
|
||
private async System.Threading.Tasks.Task DeleteAccountAsync()
|
||
{
|
||
if (_state == null || _accountRepo == null) return;
|
||
if (this.FindControl<DataGrid>("gridAccounts")!.SelectedItem is not AccountState acc) return;
|
||
|
||
bool ok = await DialogWindow.Confirm(this, "Löschen bestätigen",
|
||
$"Account „{acc.Name}\" (ID {acc.AccountId}) wirklich löschen?", "Löschen");
|
||
if (!ok) return;
|
||
|
||
_state.Accounts.TryRemove(acc.AccountId, out _);
|
||
_accountRepo.Delete(acc.AccountId);
|
||
_accounts.Remove(acc);
|
||
this.FindControl<SettingsEditor>("editorAccount")!.Show(null);
|
||
Status($"Account #{acc.AccountId} gelöscht.");
|
||
}
|
||
|
||
private void SaveSelectedAccount()
|
||
{
|
||
if (this.FindControl<DataGrid>("gridAccounts")!.SelectedItem is not AccountState acc) return;
|
||
_accountRepo?.Upsert(acc);
|
||
_state?.Accounts.AddOrUpdate(acc.AccountId, acc, (_, _) => acc);
|
||
Status($"Account #{acc.AccountId} gespeichert um {DateTime.Now:HH:mm:ss}.");
|
||
}
|
||
|
||
// ===== Master-Key (at-rest-Verschlüsselung) =====
|
||
|
||
private static string MasterKeyFilePath => Path.Combine(AppContext.BaseDirectory, "master.key");
|
||
|
||
private static bool MasterKeyExists() =>
|
||
!string.IsNullOrWhiteSpace(Environment.GetEnvironmentVariable("POLYTRADER_MASTER_KEY"))
|
||
|| File.Exists(MasterKeyFilePath);
|
||
|
||
/// <summary>Schaltfläche nur aktiv, solange KEIN Master-Key existiert (Überschreiben = Lockout-Gefahr).</summary>
|
||
private void UpdateMasterKeyButtonState() =>
|
||
this.FindControl<Button>("btnGenMasterKey")!.IsEnabled = !MasterKeyExists();
|
||
|
||
private async System.Threading.Tasks.Task GenerateMasterKeyAsync()
|
||
{
|
||
// Sicherheitsnetz gegen Race/Doppelklick: einen bestehenden Key NIEMALS überschreiben.
|
||
if (MasterKeyExists())
|
||
{
|
||
await DialogWindow.Info(this, "Master-Key vorhanden",
|
||
"Es existiert bereits ein Master-Key – Erzeugung abgebrochen. Ein Überschreiben würde den " +
|
||
"Zugriff auf bereits verschlüsselte Wallet-Keys unwiederbringlich zerstören.");
|
||
UpdateMasterKeyButtonState();
|
||
return;
|
||
}
|
||
|
||
try
|
||
{
|
||
byte[] keyBytes = RandomNumberGenerator.GetBytes(32); // 256-Bit-Schlüssel
|
||
File.WriteAllText(MasterKeyFilePath, Convert.ToBase64String(keyBytes));
|
||
RestrictToOwner(MasterKeyFilePath);
|
||
|
||
_logger?.Info("🔐 Master-Key erzeugt und in master.key gespeichert. At-rest-Verschlüsselung wird beim nächsten Start aktiv.");
|
||
await DialogWindow.Info(this, "Master-Key erzeugt",
|
||
"Ein zufälliger 32-Byte-Master-Key wurde erzeugt und in der Datei „master.key\" " +
|
||
"(App-Ordner, gitignored) gespeichert.\n\n" +
|
||
"WICHTIG:\n" +
|
||
"• Sichere diese Datei SOFORT separat (Passwort-Manager / Offline-Backup).\n" +
|
||
"• Master-Key-Verlust = KEIN Zugriff mehr auf die verschlüsselten Wallet-Keys!\n" +
|
||
"• Die Verschlüsselung der Account-Credentials wird beim nächsten Programmstart aktiv.");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
await DialogWindow.Info(this, "Fehler", $"Fehler beim Erzeugen des Master-Keys: {ex.Message}");
|
||
}
|
||
|
||
UpdateMasterKeyButtonState();
|
||
}
|
||
|
||
/// <summary>
|
||
/// Beschränkt eine Schlüsseldatei auf den Besitzer. Auf Linux legt <see cref="File.WriteAllText"/>
|
||
/// mit der üblichen umask sonst <c>644</c> an – die Datei wäre für alle lesbar. Unter Windows
|
||
/// greift die NTFS-Vererbung, dort ist nichts zu tun.
|
||
/// </summary>
|
||
private void RestrictToOwner(string path) =>
|
||
FilePermissions.RestrictToOwner(path, msg => _logger?.Warning($"⚠️ {msg}"));
|
||
|
||
// ===== OpenRouter-API-Key (Supervisor) =====
|
||
|
||
private async System.Threading.Tasks.Task SetOpenRouterKeyAsync()
|
||
{
|
||
string keyFile = Path.Combine(AppContext.BaseDirectory, "openrouter.key");
|
||
bool exists = File.Exists(keyFile) ||
|
||
!string.IsNullOrWhiteSpace(Environment.GetEnvironmentVariable("POLYTRADER_OPENROUTER_KEY"));
|
||
|
||
string? key = await DialogWindow.Prompt(this, "OpenRouter-API-Key",
|
||
"OpenRouter-API-Key für den Supervisor eingeben.\n" +
|
||
"Wird in der gitignorierten Datei „openrouter.key\" gespeichert (leer = entfernen).\n" +
|
||
(exists ? "Aktuell ist bereits ein Key hinterlegt." : "Aktuell ist KEIN Key gesetzt."));
|
||
if (key == null) return; // Abbruch
|
||
|
||
try
|
||
{
|
||
if (string.IsNullOrWhiteSpace(key))
|
||
{
|
||
if (File.Exists(keyFile)) File.Delete(keyFile);
|
||
_logger?.Info("OpenRouter-Key aus openrouter.key entfernt.");
|
||
Status("OpenRouter-Key entfernt.");
|
||
return;
|
||
}
|
||
|
||
File.WriteAllText(keyFile, key.Trim());
|
||
RestrictToOwner(keyFile);
|
||
_logger?.Info("OpenRouter-Key gespeichert (openrouter.key).");
|
||
Status("OpenRouter-Key gespeichert – wirkt bei der nächsten Supervisor-Anfrage.");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
await DialogWindow.Info(this, "Fehler", $"Fehler beim Speichern des OpenRouter-Keys: {ex.Message}");
|
||
}
|
||
}
|
||
|
||
// ===== Watchdog =====
|
||
|
||
private async System.Threading.Tasks.Task SetWatchdogTokenAsync()
|
||
{
|
||
bool exists = !string.IsNullOrWhiteSpace(_settings.WatchdogToken);
|
||
string? token = await DialogWindow.Prompt(this, "Watchdog Agent-Token",
|
||
"Agent-Token aus dem Watchdog-Admin eingeben.\n" +
|
||
"Wird in der gitignorierten server_settings.xml gespeichert (leer = entfernen).\n" +
|
||
(exists ? "Aktuell ist ein Token gesetzt." : "Aktuell ist KEIN Token gesetzt."));
|
||
if (token == null) return; // Abbruch
|
||
|
||
try
|
||
{
|
||
// Protect() gibt ohne Master-Key den Klartext unverändert zurück – kein stiller
|
||
// Sicherheitsverlust, es wird unten darauf hingewiesen.
|
||
_settings.WatchdogToken = string.IsNullOrWhiteSpace(token)
|
||
? string.Empty
|
||
: SecretProtection.Protect(token.Trim());
|
||
_settings.Save(SettingsPath);
|
||
_watchdog?.ReloadSettings();
|
||
this.FindControl<SettingsEditor>("editorServer")!.Show(_settings);
|
||
|
||
if (_settings.WatchdogToken.Length == 0)
|
||
{
|
||
_logger?.Info("Watchdog-Agent-Token entfernt.");
|
||
Status("Watchdog-Token entfernt.");
|
||
return;
|
||
}
|
||
|
||
bool encrypted = SecretProtection.IsEncrypted(_settings.WatchdogToken);
|
||
_logger?.Info($"Watchdog-Agent-Token gespeichert ({(encrypted ? "verschlüsselt" : "Klartext")}).");
|
||
Status(encrypted
|
||
? "Watchdog-Token gespeichert (verschlüsselt) und sofort aktiv."
|
||
: "Watchdog-Token gespeichert – ACHTUNG: ohne Master-Key im Klartext. Mit „Master-Key erzeugen\" ändern.");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
await DialogWindow.Info(this, "Fehler", $"Fehler beim Speichern des Watchdog-Tokens: {ex.Message}");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Sendet einen Heartbeat mit den GESPEICHERTEN Einstellungen. Ungespeicherte Änderungen
|
||
/// wirken bewusst nicht – sonst würde ein erfolgreicher Test etwas bestätigen, das im
|
||
/// laufenden Betrieb gar nicht gilt.
|
||
/// </summary>
|
||
private async System.Threading.Tasks.Task TestWatchdogAsync()
|
||
{
|
||
if (_watchdog == null) return;
|
||
var btn = this.FindControl<Button>("btnTestWatchdog")!;
|
||
btn.IsEnabled = false;
|
||
try
|
||
{
|
||
Status("Sende Test-Heartbeat …");
|
||
var result = await _watchdog.SendHeartbeatAsync("ok", "Test-Heartbeat aus dem PolyTrader-Settings-Fenster");
|
||
|
||
if (result.Success)
|
||
{
|
||
_logger?.Info($"✅ Watchdog-Test-Heartbeat erfolgreich gesendet ({result.Detail}).");
|
||
Status($"Test-Heartbeat erfolgreich ({result.Detail}). Der Monitor sollte jetzt auf „up\" stehen.");
|
||
}
|
||
else
|
||
{
|
||
_logger?.Warning($"⚠️ Watchdog-Test-Heartbeat fehlgeschlagen: {result.Detail}");
|
||
await DialogWindow.Info(this, "Watchdog",
|
||
$"Test-Heartbeat fehlgeschlagen:\n\n{result.Detail}\n\n" +
|
||
"Bitte URL, Agent-Token und Source prüfen – und die Einstellungen vorher speichern.");
|
||
Status("Test-Heartbeat fehlgeschlagen.");
|
||
}
|
||
}
|
||
finally
|
||
{
|
||
btn.IsEnabled = true;
|
||
}
|
||
}
|
||
|
||
// ===== Lizenz (D-2) =====
|
||
|
||
private async System.Threading.Tasks.Task SetLicenseKeyAsync()
|
||
{
|
||
bool exists = !string.IsNullOrWhiteSpace(_settings.LicenseKey);
|
||
string? key = await DialogWindow.Prompt(this, "Deploymentcenter-Lizenzschlüssel",
|
||
"Lizenzschlüssel eingeben.\n" +
|
||
"Wird in der gitignorierten server_settings.xml gespeichert (leer = entfernen).\n" +
|
||
"Wirkt erst beim nächsten Start von PolyTrader (keine Laufzeit-Revalidierung).\n" +
|
||
(exists ? "Aktuell ist ein Schlüssel gesetzt." : "Aktuell ist KEIN Schlüssel gesetzt."));
|
||
if (key == null) return; // Abbruch
|
||
|
||
try
|
||
{
|
||
_settings.LicenseKey = string.IsNullOrWhiteSpace(key)
|
||
? string.Empty
|
||
: SecretProtection.Protect(key.Trim());
|
||
_settings.Save(SettingsPath);
|
||
this.FindControl<SettingsEditor>("editorServer")!.Show(_settings);
|
||
|
||
if (_settings.LicenseKey.Length == 0)
|
||
{
|
||
_logger?.Info("Lizenzschlüssel entfernt.");
|
||
Status("Lizenzschlüssel entfernt – PolyTrader startet beim nächsten Mal im eingeschränkten Modus.");
|
||
return;
|
||
}
|
||
|
||
bool encrypted = SecretProtection.IsEncrypted(_settings.LicenseKey);
|
||
_logger?.Info($"Lizenzschlüssel gespeichert ({(encrypted ? "verschlüsselt" : "Klartext")}).");
|
||
Status((encrypted
|
||
? "Lizenzschlüssel gespeichert (verschlüsselt). "
|
||
: "Lizenzschlüssel gespeichert – ACHTUNG: ohne Master-Key im Klartext. ") +
|
||
"Mit „Lizenz jetzt prüfen\" testen, wirkt für Module erst beim nächsten Start.");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
await DialogWindow.Info(this, "Fehler", $"Fehler beim Speichern des Lizenzschlüssels: {ex.Message}");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Prüft den GESPEICHERTEN Schlüssel gegen das Deploymentcenter und zeigt Urteil und
|
||
/// Hardware-ID (zum Kopieren, falls Richard sie für eine Aktivierung im Admin braucht).
|
||
/// Ungespeicherte Änderungen wirken bewusst nicht, aus demselben Grund wie beim Watchdog-Test.
|
||
/// </summary>
|
||
private async System.Threading.Tasks.Task CheckLicenseAsync()
|
||
{
|
||
if (_dcOptions == null) return;
|
||
var btn = this.FindControl<Button>("btnCheckLicense")!;
|
||
btn.IsEnabled = false;
|
||
try
|
||
{
|
||
if (string.IsNullOrWhiteSpace(_dcOptions.BaseUrl))
|
||
{
|
||
await DialogWindow.Info(this, "Lizenz", "Keine Deploymentcenter-URL konfiguriert (appsettings.json).");
|
||
return;
|
||
}
|
||
|
||
string key;
|
||
try { key = SecretProtection.Unprotect(_settings.LicenseKey); }
|
||
catch (Exception ex)
|
||
{
|
||
await DialogWindow.Info(this, "Lizenz", $"Gespeicherter Schlüssel nicht lesbar: {ex.Message}");
|
||
return;
|
||
}
|
||
|
||
var hw = HardwareId.GetHardwareId(_dcOptions.ProjectSlug);
|
||
|
||
if (string.IsNullOrWhiteSpace(key))
|
||
{
|
||
await DialogWindow.Info(this, "Lizenz",
|
||
$"Kein Lizenzschlüssel gesetzt.\n\nHardware-ID dieser Maschine:\n{hw.HardwareId}");
|
||
Status("Kein Lizenzschlüssel gesetzt.");
|
||
return;
|
||
}
|
||
|
||
Status("Prüfe Lizenz …");
|
||
LicenseClient.DefaultAppVersion = BuildInfo.Version;
|
||
var client = new LicenseClient();
|
||
var result = await client.ValidateAsync(_dcOptions.ProjectSlug, key, _dcOptions.BaseUrl, BuildInfo.Version);
|
||
|
||
string detail = $"Status: {result.Status}\n{result.Message}\n\nHardware-ID dieser Maschine:\n{hw.HardwareId}";
|
||
|
||
if (result.IsValid)
|
||
{
|
||
_logger?.Info($"🔑 Lizenz geprüft: gültig ({result.Status}).");
|
||
Status($"Lizenz gültig ({result.Status}).");
|
||
await DialogWindow.Info(this, "Lizenz gültig", detail);
|
||
}
|
||
else
|
||
{
|
||
_logger?.Warning($"⚠️ Lizenz geprüft: nicht gültig ({result.Status}).");
|
||
Status($"Lizenz NICHT gültig ({result.Status}).");
|
||
await DialogWindow.Info(this, "Lizenz nicht gültig", detail);
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
await DialogWindow.Info(this, "Fehler", $"Lizenzprüfung fehlgeschlagen: {ex.Message}");
|
||
}
|
||
finally
|
||
{
|
||
btn.IsEnabled = true;
|
||
}
|
||
}
|
||
|
||
// ===== WebUI-Einstellungen =====
|
||
|
||
private void LoadWebUiSettings()
|
||
{
|
||
_webOptions = Program.LoadWebUiOptions();
|
||
var chkEnabled = this.FindControl<CheckBox>("chkWebUiEnabled")!;
|
||
var numPort = this.FindControl<NumericUpDown>("numWebUiPort")!;
|
||
var chkLocal = this.FindControl<CheckBox>("chkWebUiLocalOnly")!;
|
||
var txtToken = this.FindControl<TextBox>("txtWebUiToken")!;
|
||
var lblStatus = this.FindControl<TextBlock>("lblWebUiStatus")!;
|
||
var lblUrl = this.FindControl<TextBlock>("lblWebUiUrl")!;
|
||
|
||
chkEnabled.IsChecked = _webOptions.Enabled;
|
||
numPort.Value = _webOptions.Port;
|
||
chkLocal.IsChecked = _webOptions.LocalOnly;
|
||
txtToken.Text = _webOptions.AuthToken ?? string.Empty;
|
||
|
||
if (_webOptions.Enabled)
|
||
{
|
||
string ip = _webOptions.LocalOnly ? "127.0.0.1" : "0.0.0.0";
|
||
lblStatus.Text = $"Aktiv (lauscht auf {ip}:{_webOptions.Port})";
|
||
lblUrl.Text = $"http://{(_webOptions.LocalOnly ? "localhost" : "0.0.0.0")}:{_webOptions.Port}";
|
||
}
|
||
else
|
||
{
|
||
lblStatus.Text = "Deaktiviert (WebUi:Enabled = false)";
|
||
lblUrl.Text = "—";
|
||
}
|
||
}
|
||
|
||
private void GenerateWebUiToken()
|
||
{
|
||
string newToken = WebUiConfigManager.GenerateSecureToken();
|
||
var txtToken = this.FindControl<TextBox>("txtWebUiToken")!;
|
||
txtToken.Text = newToken;
|
||
_webOptions.AuthToken = newToken;
|
||
WebUiConfigManager.SaveToLocalSettings(_webOptions);
|
||
_logger?.Info("🔐 WebUI: Neues Zugangs-Token im Einstellungsfenster generiert und gespeichert.");
|
||
SetWebUiSaveStatus("Neues Token generiert & gespeichert.");
|
||
}
|
||
|
||
private void ToggleWebUiTokenVisibility()
|
||
{
|
||
var txt = this.FindControl<TextBox>("txtWebUiToken")!;
|
||
var btn = this.FindControl<Button>("btnWebUiToggleShowToken")!;
|
||
if (txt.PasswordChar == '●')
|
||
{
|
||
txt.PasswordChar = '\0';
|
||
btn.Content = "Ausblenden";
|
||
}
|
||
else
|
||
{
|
||
txt.PasswordChar = '●';
|
||
btn.Content = "Anzeigen";
|
||
}
|
||
}
|
||
|
||
private async System.Threading.Tasks.Task CopyWebUiTokenAsync()
|
||
{
|
||
var txt = this.FindControl<TextBox>("txtWebUiToken")!;
|
||
if (string.IsNullOrEmpty(txt.Text)) return;
|
||
var topLevel = TopLevel.GetTopLevel(this);
|
||
if (topLevel?.Clipboard != null)
|
||
{
|
||
await topLevel.Clipboard.SetTextAsync(txt.Text);
|
||
SetWebUiSaveStatus("Token in Zwischenablage kopiert.");
|
||
}
|
||
}
|
||
|
||
private void SaveWebUiSettings()
|
||
{
|
||
var chkEnabled = this.FindControl<CheckBox>("chkWebUiEnabled")!;
|
||
var numPort = this.FindControl<NumericUpDown>("numWebUiPort")!;
|
||
var chkLocal = this.FindControl<CheckBox>("chkWebUiLocalOnly")!;
|
||
var txtToken = this.FindControl<TextBox>("txtWebUiToken")!;
|
||
|
||
_webOptions.Enabled = chkEnabled.IsChecked ?? true;
|
||
_webOptions.Port = (int)(numPort.Value ?? 5001);
|
||
_webOptions.LocalOnly = chkLocal.IsChecked ?? true;
|
||
_webOptions.AuthToken = txtToken.Text?.Trim() ?? string.Empty;
|
||
|
||
WebUiConfigManager.SaveToLocalSettings(_webOptions);
|
||
_logger?.Info($"WebUI-Einstellungen in appsettings.Local.json gespeichert (Port {_webOptions.Port}, LocalOnly: {_webOptions.LocalOnly}).");
|
||
SetWebUiSaveStatus("Gespeichert in appsettings.Local.json (Port-/Netzwerkänderungen greifen nach Neustart).");
|
||
LoadWebUiSettings();
|
||
}
|
||
|
||
private void SetWebUiSaveStatus(string msg)
|
||
{
|
||
var lbl = this.FindControl<TextBlock>("lblWebUiSaveStatus")!;
|
||
lbl.Text = $"✓ {msg}";
|
||
}
|
||
}
|
||
}
|