Die schreibenden API-Endpunkte waren ungeschuetzt. /api/capabilities meldete zwar CanControl und AuthRequired aus der Konfiguration, erzwungen wurde davon nichts: MapPredictalyticsControlEndpoints mappte Jobs, Trader-Steuerung und Dev-Endpunkte bedingungslos. Neu entscheidet die Bind-Adresse: - localhost ohne Token: aktiv wie bisher, das Betriebssystem schuetzt - localhost mit Token: aktiv, Token wird verlangt - extern ohne Token: Endpunkte werden gar nicht gemappt, dazu ein Log.Error - extern mit Token: Endpunkte verlangen X-Predictalytics-Key Bewusst fail-safe herum, damit eine unbedachte Umstellung der Bind-Adresse nicht stillschweigend die Steuerschnittstelle oeffnet. ApiTokenFilter vergleicht laufzeitkonstant ueber CryptographicOperations.FixedTimeEquals. Swagger ist bei externer Bindung abgeschaltet. /api/capabilities meldet jetzt den tatsaechlichen Zustand statt einer Konfigurationsabsicht. Weiter: - RuntimeSetup.UseInvariantCulture als Erstes in beiden Startpfaden, damit die Systemlocale nicht auf Zahlen und Zeitstempel durchschlaegt - Logverzeichnis neben den Einstellungen statt neben der Programmdatei, ueber PREDICTALYTICS_LOG_DIR uebersteuerbar - CORS erlaubt immer die eigene Bind-URL, zusaetzliche Herkuenfte weiter ueber appsettings.json - fehlendes Schwester-Repo Deploymentcenter scheitert mit verstaendlicher Meldung samt Klon-URL statt mit einem Fehler ueber eine fehlende csproj - EgressPoolService: Linux-Verhalten von Socket.Bind kommentiert 24 neue Tests: IsPubliclyBound fuer Loopback-Schreibweisen, Leerwerte und externe Adressen; dass leere Eingaben bestehende DB-Zugangsdaten nicht ueberschreiben; und der Token-Filter gegen fehlende, falsche, zu kurze, zu lange und abweichend geschriebene Token. Build: 0 Fehler, 8 Warnungen (alle vorbestehend). Tests: 124 bestanden, 0 Fehler, 1 uebersprungen. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
259 lines
14 KiB
XML
259 lines
14 KiB
XML
<Window xmlns="https://github.com/avaloniaui"
|
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
|
xmlns:vm="using:Predictalytics.Shell.ViewModels"
|
|
xmlns:hosting="using:Predictalytics.Hosting"
|
|
xmlns:sql="using:MySqlConnector"
|
|
xmlns:sys="using:System"
|
|
x:Class="Predictalytics.Shell.Views.MainWindow"
|
|
x:DataType="vm:MainWindowViewModel"
|
|
Title="Predictalytics Analytics — Backend Server"
|
|
Width="1400" Height="900"
|
|
MinWidth="900" MinHeight="600"
|
|
WindowStartupLocation="CenterScreen">
|
|
|
|
<Window.Resources>
|
|
<!-- Monospace mit Fallback-Kette: Cascadia gibt es unter Linux meist nicht. -->
|
|
<FontFamily x:Key="TerminalFont">Cascadia Code,DejaVu Sans Mono,Liberation Mono,Consolas,monospace</FontFamily>
|
|
</Window.Resources>
|
|
|
|
<Window.Styles>
|
|
<Style Selector="TextBlock.label">
|
|
<Setter Property="VerticalAlignment" Value="Center" />
|
|
<Setter Property="Margin" Value="0,0,12,0" />
|
|
</Style>
|
|
<Style Selector="TextBlock.hint">
|
|
<Setter Property="Opacity" Value="0.6" />
|
|
<Setter Property="TextWrapping" Value="Wrap" />
|
|
<Setter Property="FontSize" Value="11" />
|
|
<Setter Property="Margin" Value="0,2,0,0" />
|
|
</Style>
|
|
<Style Selector="HeaderedContentControl.group">
|
|
<Setter Property="Margin" Value="0,0,0,20" />
|
|
</Style>
|
|
</Window.Styles>
|
|
|
|
<DockPanel>
|
|
|
|
<!-- ─── Menü ─── -->
|
|
<Menu DockPanel.Dock="Top">
|
|
<MenuItem Header="_Datei">
|
|
<MenuItem Header="_Beenden" Click="OnExitClick" />
|
|
</MenuItem>
|
|
<MenuItem Header="_Ansicht">
|
|
<MenuItem Header="Log-Ordner öffnen" Command="{Binding OpenLogFolderCommand}" />
|
|
<MenuItem Header="WebUI öffnen" Command="{Binding OpenWebUiCommand}" />
|
|
</MenuItem>
|
|
<MenuItem Header="_Entwicklung">
|
|
<MenuItem Header="Märkte synchronisieren" Command="{Binding SyncMarketsCommand}" />
|
|
<MenuItem Header="Datenbank aktualisieren" Command="{Binding UpdateDatabaseCommand}" />
|
|
<Separator />
|
|
<MenuItem Header="Alle Trader neu berechnen…" Command="{Binding RecalculateAllCommand}" />
|
|
</MenuItem>
|
|
<MenuItem Header="Deployment _Center">
|
|
<MenuItem Header="Nach Updates suchen" Command="{Binding CheckForUpdatesInteractiveCommand}" />
|
|
<MenuItem Header="Lizenzstatus anzeigen" Command="{Binding ShowLicenseStatusCommand}" />
|
|
</MenuItem>
|
|
</Menu>
|
|
|
|
<!-- ─── Werkzeugleiste ─── -->
|
|
<Border DockPanel.Dock="Top" Padding="8" BorderThickness="0,0,0,1"
|
|
BorderBrush="{DynamicResource SystemControlForegroundBaseLowBrush}">
|
|
<StackPanel Orientation="Horizontal" Spacing="8">
|
|
<Button Content="{Binding ServerButtonText}" Command="{Binding ToggleServerCommand}" MinWidth="150" />
|
|
<Button Content="{Binding WebserverButtonText}" Command="{Binding ToggleWebserverCommand}" MinWidth="170" />
|
|
<ProgressBar IsIndeterminate="True" Width="120" VerticalAlignment="Center"
|
|
IsVisible="{Binding IsBusy}" />
|
|
</StackPanel>
|
|
</Border>
|
|
|
|
<!-- ─── Statusleiste ─── -->
|
|
<Border DockPanel.Dock="Bottom" Padding="8,4" BorderThickness="0,1,0,0"
|
|
BorderBrush="{DynamicResource SystemControlForegroundBaseLowBrush}">
|
|
<Grid ColumnDefinitions="*,Auto,Auto">
|
|
<TextBlock Grid.Column="0" Text="{Binding StatusText}" VerticalAlignment="Center" />
|
|
<TextBlock Grid.Column="1" Text="{Binding DbSizeText}" Margin="16,0" VerticalAlignment="Center" />
|
|
<TextBlock Grid.Column="2" Text="{Binding BuildVersionText}" VerticalAlignment="Center" Opacity="0.7" />
|
|
</Grid>
|
|
</Border>
|
|
|
|
<!-- ─── Inhalt ─── -->
|
|
<TabControl>
|
|
|
|
<TabItem Header="Terminal">
|
|
<ListBox Name="LogList"
|
|
ItemsSource="{Binding LogLines}"
|
|
Background="#0F0F14"
|
|
FontFamily="{StaticResource TerminalFont}"
|
|
FontSize="12"
|
|
SelectionMode="Multiple">
|
|
<ListBox.ItemTemplate>
|
|
<DataTemplate x:DataType="vm:LogLine">
|
|
<TextBlock Text="{Binding Text}"
|
|
Foreground="{Binding Foreground}"
|
|
TextWrapping="NoWrap" />
|
|
</DataTemplate>
|
|
</ListBox.ItemTemplate>
|
|
</ListBox>
|
|
</TabItem>
|
|
|
|
<TabItem Header="Einstellungen">
|
|
<Grid RowDefinitions="*,Auto">
|
|
<ScrollViewer Grid.Row="0" Padding="20">
|
|
<StackPanel MaxWidth="760" HorizontalAlignment="Left"
|
|
DataContext="{Binding Options}" x:DataType="hosting:PredictalyticsOptions">
|
|
|
|
<!-- Webserver -->
|
|
<HeaderedContentControl Classes="group" Header="Webserver">
|
|
<StackPanel Spacing="12" Margin="0,8,0,0">
|
|
<Grid ColumnDefinitions="200,*">
|
|
<TextBlock Grid.Column="0" Classes="label" Text="Port" />
|
|
<NumericUpDown Grid.Column="1" Value="{Binding WebserverPort}"
|
|
Minimum="1" Maximum="65535" FormatString="0" HorizontalAlignment="Left" Width="160" />
|
|
</Grid>
|
|
<Grid ColumnDefinitions="200,*">
|
|
<TextBlock Grid.Column="0" Classes="label" Text="Bind-Adresse" />
|
|
<StackPanel Grid.Column="1">
|
|
<TextBox Text="{Binding WebserverHost}" Width="260" HorizontalAlignment="Left" />
|
|
<TextBlock Classes="hint"
|
|
Text="'localhost' = nur lokal erreichbar. '0.0.0.0' macht die API von außen erreichbar — dann unbedingt absichern." />
|
|
</StackPanel>
|
|
</Grid>
|
|
<Grid ColumnDefinitions="200,*">
|
|
<TextBlock Grid.Column="0" Classes="label" Text="API-Token" />
|
|
<StackPanel Grid.Column="1">
|
|
<TextBox Text="{Binding ApiControlToken}" PasswordChar="•" />
|
|
<TextBlock Classes="hint"
|
|
Text="Schützt die schreibenden Endpunkte (Jobs, Trader-Steuerung, Dev) über den Header X-Predictalytics-Key. Bei einer Bind-Adresse außerhalb von localhost Pflicht — ohne Token werden diese Endpunkte gar nicht bereitgestellt." />
|
|
</StackPanel>
|
|
</Grid>
|
|
<Grid ColumnDefinitions="200,*">
|
|
<TextBlock Grid.Column="0" Classes="label" Text="Datenbank-Debug" />
|
|
<CheckBox Grid.Column="1" IsChecked="{Binding DbConnectionDebug}"
|
|
Content="Ausführliche Verbindungsinfos im Terminal" />
|
|
</Grid>
|
|
</StackPanel>
|
|
</HeaderedContentControl>
|
|
|
|
<!-- Datenbank -->
|
|
<HeaderedContentControl Classes="group" Header="Datenbank">
|
|
<StackPanel Spacing="12" Margin="0,8,0,0">
|
|
<Grid ColumnDefinitions="200,*">
|
|
<TextBlock Grid.Column="0" Classes="label" Text="Server" />
|
|
<TextBox Grid.Column="1" Text="{Binding DbServer}" />
|
|
</Grid>
|
|
<Grid ColumnDefinitions="200,*">
|
|
<TextBlock Grid.Column="0" Classes="label" Text="Datenbank" />
|
|
<TextBox Grid.Column="1" Text="{Binding DbName}" />
|
|
</Grid>
|
|
<Grid ColumnDefinitions="200,*">
|
|
<TextBlock Grid.Column="0" Classes="label" Text="Benutzer" />
|
|
<TextBox Grid.Column="1" Text="{Binding DbUser}" />
|
|
</Grid>
|
|
<Grid ColumnDefinitions="200,*">
|
|
<TextBlock Grid.Column="0" Classes="label" Text="Passwort" />
|
|
<TextBox Grid.Column="1" Text="{Binding DbPassword}" PasswordChar="•" />
|
|
</Grid>
|
|
<Grid ColumnDefinitions="200,*">
|
|
<TextBlock Grid.Column="0" Classes="label" Text="SSL-Modus" />
|
|
<StackPanel Grid.Column="1">
|
|
<ComboBox SelectedItem="{Binding DbSslMode}" Width="200" HorizontalAlignment="Left"
|
|
ItemsSource="{x:Static vm:OptionSources.SslModes}" />
|
|
<TextBlock Classes="hint"
|
|
Text="Bei entfernten Servern 'Preferred' empfohlen. 'None' überträgt unverschlüsselt." />
|
|
</StackPanel>
|
|
</Grid>
|
|
</StackPanel>
|
|
</HeaderedContentControl>
|
|
|
|
<!-- Deployment Center -->
|
|
<HeaderedContentControl Classes="group" Header="Deployment Center">
|
|
<StackPanel Spacing="12" Margin="0,8,0,0">
|
|
<Grid ColumnDefinitions="200,*">
|
|
<TextBlock Grid.Column="0" Classes="label" Text="Server-URL" />
|
|
<StackPanel Grid.Column="1">
|
|
<SelectableTextBlock Text="{Binding DcServerUrl}" VerticalAlignment="Center" Opacity="0.8" />
|
|
<TextBlock Classes="hint"
|
|
Text="Fest einkompiliert — ein einstellbarer Endpoint würde erlauben, die App auf einen gefälschten Lizenz- oder Update-Server zu zeigen." />
|
|
</StackPanel>
|
|
</Grid>
|
|
<Grid ColumnDefinitions="200,*">
|
|
<TextBlock Grid.Column="0" Classes="label" Text="API Token" />
|
|
<StackPanel Grid.Column="1">
|
|
<TextBox Text="{Binding DcToken}" PasswordChar="•" />
|
|
<TextBlock Classes="hint"
|
|
Text="Benötigte Rechte: 'watchdog:ping' für Heartbeats, 'bugtracker:report' für das Fehler-Reporting." />
|
|
</StackPanel>
|
|
</Grid>
|
|
<Grid ColumnDefinitions="200,*">
|
|
<TextBlock Grid.Column="0" Classes="label" Text="Heartbeat aktiv" />
|
|
<CheckBox Grid.Column="1" IsChecked="{Binding DcHeartbeatEnabled}"
|
|
Content="Heartbeats senden (Dead-Man's-Switch)" />
|
|
</Grid>
|
|
<Grid ColumnDefinitions="200,*">
|
|
<TextBlock Grid.Column="0" Classes="label" Text="Monitor Source" />
|
|
<TextBox Grid.Column="1" Text="{Binding DcSource}" />
|
|
</Grid>
|
|
<Grid ColumnDefinitions="200,*">
|
|
<TextBlock Grid.Column="0" Classes="label" Text="Monitor Instance" />
|
|
<TextBox Grid.Column="1" Text="{Binding DcInstance}" />
|
|
</Grid>
|
|
<Grid ColumnDefinitions="200,*">
|
|
<TextBlock Grid.Column="0" Classes="label" Text="Intervall (Sek.)" />
|
|
<StackPanel Grid.Column="1">
|
|
<NumericUpDown Value="{Binding DcHeartbeatIntervalSeconds}"
|
|
Minimum="15" Maximum="3600" FormatString="0" HorizontalAlignment="Left" Width="160" />
|
|
<TextBlock Classes="hint"
|
|
Text="Der Evaluator stuft nach dem Doppelten auf 'warning' und nach dem Vierfachen auf 'down'." />
|
|
</StackPanel>
|
|
</Grid>
|
|
<Grid ColumnDefinitions="200,*">
|
|
<TextBlock Grid.Column="0" Classes="label" Text="Fehler melden" />
|
|
<CheckBox Grid.Column="1" IsChecked="{Binding DcErrorReportingEnabled}"
|
|
Content="Laufzeitfehler (Error/Fatal) an den Fehler-Stream melden" />
|
|
</Grid>
|
|
<Grid ColumnDefinitions="200,*">
|
|
<TextBlock Grid.Column="0" Classes="label" Text="Update-Prüfung" />
|
|
<CheckBox Grid.Column="1" IsChecked="{Binding DcUpdateCheckEnabled}"
|
|
Content="Beim Start auf neuere Releases prüfen (installiert nichts automatisch)" />
|
|
</Grid>
|
|
<Grid ColumnDefinitions="200,*">
|
|
<TextBlock Grid.Column="0" Classes="label" Text="Update-Kanal" />
|
|
<ComboBox Grid.Column="1" SelectedItem="{Binding DcUpdateChannel}" Width="200"
|
|
HorizontalAlignment="Left"
|
|
ItemsSource="{x:Static vm:OptionSources.UpdateChannels}" />
|
|
</Grid>
|
|
</StackPanel>
|
|
</HeaderedContentControl>
|
|
|
|
<!-- Egress -->
|
|
<HeaderedContentControl Classes="group" Header="Egress (Proxy / Quell-IP)">
|
|
<StackPanel Spacing="8" Margin="0,8,0,0">
|
|
<TextBlock Classes="hint"
|
|
Text="Ein Kanal pro Zeile im Format: id|type|value Beispiel: prox-1|Proxy|http://user:pass@proxy:8080 ip-1|SourceIp|192.168.1.100" />
|
|
<TextBox Text="{Binding EgressChannelsText}"
|
|
AcceptsReturn="True"
|
|
TextWrapping="NoWrap"
|
|
MinHeight="120"
|
|
FontFamily="{StaticResource TerminalFont}"
|
|
FontSize="12" />
|
|
</StackPanel>
|
|
</HeaderedContentControl>
|
|
|
|
</StackPanel>
|
|
</ScrollViewer>
|
|
|
|
<Border Grid.Row="1" Padding="20,12" BorderThickness="0,1,0,0"
|
|
BorderBrush="{DynamicResource SystemControlForegroundBaseLowBrush}">
|
|
<StackPanel Orientation="Horizontal" Spacing="12">
|
|
<Button Content="Speichern & übernehmen" Command="{Binding SaveSettingsCommand}" />
|
|
<TextBlock VerticalAlignment="Center" Opacity="0.6"
|
|
Text="Port und Datenbank wirken beim nächsten Start des Webservers." />
|
|
</StackPanel>
|
|
</Border>
|
|
</Grid>
|
|
</TabItem>
|
|
|
|
</TabControl>
|
|
</DockPanel>
|
|
</Window>
|