Paralleles WebGUI mit Kestrel, Token-Sicherung und SSE-Logs implementiert
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
<Window xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
x:Class="PolyTrader.App.Avalonia.Views.DialogWindow"
|
||||
Icon="avares://PolyTrader.App.Avalonia/Assets/icon.png"
|
||||
Width="520" SizeToContent="Height"
|
||||
WindowStartupLocation="CenterOwner"
|
||||
CanResize="False"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:controls="using:PolyTrader.App.Avalonia.Controls"
|
||||
xmlns:models="using:PolyTraderSharp.Models"
|
||||
@@ -70,6 +70,64 @@
|
||||
</DockPanel>
|
||||
</TabItem>
|
||||
|
||||
<TabItem Header="WebUI">
|
||||
<ScrollViewer HorizontalScrollBarVisibility="Disabled" VerticalScrollBarVisibility="Auto">
|
||||
<StackPanel Margin="24,20" MaxWidth="680" HorizontalAlignment="Left" Spacing="16">
|
||||
<TextBlock Text="WebUI Konfiguration & Fernzugriff" FontSize="18" FontWeight="SemiBold" />
|
||||
<TextBlock Text="Das WebUI ermöglicht die Steuerung und Überwachung von PolyTrader über einen Browser – parallel zum Desktop GUI oder im kopflosen Server-Betrieb."
|
||||
Foreground="{DynamicResource AppMutedTextBrush}" TextWrapping="Wrap" />
|
||||
|
||||
<Border Background="{DynamicResource AppSurfaceBrush}" BorderBrush="{DynamicResource AppBorderBrush}" BorderThickness="1" CornerRadius="8" Padding="16">
|
||||
<StackPanel Spacing="12">
|
||||
<TextBlock Text="Server-Status" FontWeight="SemiBold" FontSize="14" />
|
||||
<TextBlock Name="lblWebUiStatus" Text="Status wird ermittelt …" Foreground="{DynamicResource AppAccentBrush}" />
|
||||
<TextBlock Name="lblWebUiUrl" Text="http://127.0.0.1:5001" Foreground="{DynamicResource AppMutedTextBrush}" FontSize="12" />
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<Border Background="{DynamicResource AppSurfaceBrush}" BorderBrush="{DynamicResource AppBorderBrush}" BorderThickness="1" CornerRadius="8" Padding="16">
|
||||
<StackPanel Spacing="16">
|
||||
<TextBlock Text="Netzwerk & Port" FontWeight="SemiBold" FontSize="14" />
|
||||
|
||||
<CheckBox Name="chkWebUiEnabled" Content="WebUI Server aktivieren" />
|
||||
|
||||
<Grid ColumnDefinitions="140,*" Margin="0,4">
|
||||
<TextBlock Grid.Column="0" Text="Port:" VerticalAlignment="Center" />
|
||||
<NumericUpDown Name="numWebUiPort" Grid.Column="1" Minimum="1024" Maximum="65535" Value="5001" Width="150" HorizontalAlignment="Left" />
|
||||
</Grid>
|
||||
|
||||
<CheckBox Name="chkWebUiLocalOnly" Content="Nur lokaler Zugriff (127.0.0.1)" />
|
||||
<TextBlock Text="Wenn deaktiviert, lauscht der Server auf 0.0.0.0 und ist über LAN/VPN erreichbar."
|
||||
Foreground="{DynamicResource AppMutedTextBrush}" FontSize="12" Margin="24,-8,0,0" TextWrapping="Wrap" />
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<Border Background="{DynamicResource AppSurfaceBrush}" BorderBrush="{DynamicResource AppBorderBrush}" BorderThickness="1" CornerRadius="8" Padding="16">
|
||||
<StackPanel Spacing="12">
|
||||
<TextBlock Text="Zugangssicherung (Token)" FontWeight="SemiBold" FontSize="14" />
|
||||
<TextBlock Text="Ohne gültiges Token verweigert das WebUI jeden Zugriff. Das Token wird in appsettings.Local.json gespeichert."
|
||||
Foreground="{DynamicResource AppMutedTextBrush}" FontSize="12" TextWrapping="Wrap" />
|
||||
|
||||
<Grid ColumnDefinitions="*,Auto,Auto" Margin="0,6">
|
||||
<TextBox Name="txtWebUiToken" Grid.Column="0" PasswordChar="●" FontFamily="Consolas,monospace" FontSize="12" VerticalAlignment="Center" Margin="0,0,8,0" />
|
||||
<Button Name="btnWebUiToggleShowToken" Grid.Column="1" Content="Anzeigen" Margin="0,0,8,0" />
|
||||
<Button Name="btnWebUiCopyToken" Grid.Column="2" Content="Kopieren" />
|
||||
</Grid>
|
||||
|
||||
<StackPanel Orientation="Horizontal" Spacing="10" Margin="0,4,0,0">
|
||||
<Button Name="btnWebUiGenToken" Content="Neuen Token generieren" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<StackPanel Orientation="Horizontal" Spacing="12" Margin="0,8,0,0">
|
||||
<Button Name="btnSaveWebUi" Content="WebUI-Einstellungen speichern" />
|
||||
<TextBlock Name="lblWebUiSaveStatus" Text="" VerticalAlignment="Center" Foreground="{DynamicResource AppPositiveBrush}" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
</TabItem>
|
||||
|
||||
</TabControl>
|
||||
</DockPanel>
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ using PolyTrader.Core.Security;
|
||||
using PolyTraderSharp;
|
||||
using PolyTraderSharp.Models;
|
||||
using PolyTraderSharp.Services;
|
||||
using PolyTrader.Web.Configuration;
|
||||
|
||||
namespace PolyTrader.App.Avalonia.Views
|
||||
{
|
||||
@@ -36,12 +37,14 @@ namespace PolyTrader.App.Avalonia.Views
|
||||
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) : this()
|
||||
MullvadVpnService? vpn = null, DeploymentcenterOptions? dcOptions = null,
|
||||
WebUiOptions? webOptions = null) : this()
|
||||
{
|
||||
_accountRepo = accountRepo;
|
||||
_state = state;
|
||||
@@ -49,6 +52,7 @@ namespace PolyTrader.App.Avalonia.Views
|
||||
_watchdog = watchdog;
|
||||
_vpn = vpn;
|
||||
_dcOptions = dcOptions;
|
||||
_webOptions = webOptions ?? Program.LoadWebUiOptions();
|
||||
|
||||
var grid = this.FindControl<DataGrid>("gridAccounts")!;
|
||||
grid.ItemsSource = _accounts;
|
||||
@@ -68,8 +72,14 @@ namespace PolyTrader.App.Avalonia.Views
|
||||
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();
|
||||
}
|
||||
|
||||
@@ -422,5 +432,98 @@ namespace PolyTrader.App.Avalonia.Views
|
||||
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}";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
x:Class="PolyTrader.App.Avalonia.Views.ShellWindow"
|
||||
x:DataType="shell:ShellNavModel"
|
||||
Title="PolyTrader"
|
||||
Icon="avares://PolyTrader.App.Avalonia/Assets/icon.png"
|
||||
Width="1600" Height="900"
|
||||
WindowState="Maximized">
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
x:Class="PolyTrader.App.Avalonia.Views.ShutdownConfirmWindow"
|
||||
Title="PolyTrader beenden"
|
||||
Icon="avares://PolyTrader.App.Avalonia/Assets/icon.png"
|
||||
Width="480" SizeToContent="Height"
|
||||
WindowStartupLocation="CenterOwner"
|
||||
CanResize="False"
|
||||
|
||||
Reference in New Issue
Block a user