Sicherungscommit vor dem Aufraeumen des Repos, damit nachvollziehbar bleibt, welcher Stand vor der Bereinigung galt. Build gruen, 476 Tests gruen. Zwei Straenge, die sich ueber .csproj, Program.cs und appsettings.json ueberschneiden und darum gemeinsam abgelegt werden: Deploymentcenter-Integration (P3c, Plan D-0 bis D-5 code-seitig fertig): - Deploymentcenter.Client 2.5.0 als lokales Paket, Source-Mapping erweitert - DeploymentcenterOptions, DeploymentcenterErrorReporter, LicenseGate/LicenseCli - WatchdogHeartbeatService auf die Deploymentcenter-API umgestellt (version, os, checks, metrics, status stopped) - Security: MasterKeyResolver, SecretRedactor, FilePermissions - Directory.Build.props mit zentraler Version 0.1.0 (Packager-Versionsdisziplin) - deploy/: Packager-Vorlage und systemd-Unit; echte Zugangsdaten bleiben ueber .gitignore aussen vor - setup.json fuer die Erstinstallation - UMSETZUNGSPLAN-Deploymentcenter-Integration.md; ANALYSE-Linux-Portierung.md verweist auf den neuen Plan Einfenster-Shell (UI-Redesign): - ShellWindow + ShellNavModel als Seitenleisten-Shell - WindowMenuBar und WindowMenuModel entfallen - Modul- und Kernfenster auf die Shell-Einbettung angepasst Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
180 lines
8.2 KiB
C#
180 lines
8.2 KiB
C#
using System.Collections.Generic;
|
||
using System.ComponentModel;
|
||
using System.IO;
|
||
using System.Xml.Serialization;
|
||
|
||
namespace PolyTraderSharp.Models
|
||
{
|
||
/// <summary>Farbschema der Oberfläche.</summary>
|
||
public enum AppThemeVariant
|
||
{
|
||
/// <summary>Folgt der Einstellung des Betriebssystems.</summary>
|
||
System,
|
||
Hell,
|
||
Dunkel
|
||
}
|
||
|
||
public class ServerSettings
|
||
{
|
||
[Category("Module")]
|
||
[DisplayName("Deaktivierte Module")]
|
||
[Description("Namen der Module (z.B. \"CopyTrading\"), die beim App-Start NICHT geladen werden. " +
|
||
"Leer = alle Module aktiv. Änderungen greifen nach dem nächsten Neustart. " +
|
||
"Wird i.d.R. über das Dashboard gepflegt.")]
|
||
[XmlArray("DisabledModules")]
|
||
[XmlArrayItem("Module")]
|
||
public List<string> DisabledModules { get; set; } = new();
|
||
|
||
[Category("Anzeige")]
|
||
[DisplayName("Farbschema")]
|
||
[Description("Hell, Dunkel oder der Einstellung des Betriebssystems folgen. " +
|
||
"Lässt sich jederzeit über den Umschalter in der Fensterleiste wechseln; " +
|
||
"der zuletzt gewählte Stand wird hier gespeichert.")]
|
||
public AppThemeVariant Theme { get; set; } = AppThemeVariant.System;
|
||
|
||
[Category("Anzeige")]
|
||
[DisplayName("Zeitzone")]
|
||
[Description("Zeitzone für Anzeige und Logdatei-Tagesgrenzen, IANA-Schreibweise (z.B. \"Europe/Berlin\", " +
|
||
"\"America/New_York\"). Leer = Zeitzone des Systems. Wird üblicherweise EINMALIG bei der " +
|
||
"Installation gesetzt: eine spätere Änderung verschiebt Tagesgrenzen in Logs und Auswertungen. " +
|
||
"Greift erst nach einem Neustart.")]
|
||
public string ApplicationTimeZoneId { get; set; } = PolyTraderSharp.Services.AppTimeZone.DefaultId;
|
||
|
||
[Category("Mullvad VPN")]
|
||
[DisplayName("VPN Enabled")]
|
||
[Description("Enable or disable automatic VPN rotation.")]
|
||
public bool VpnEnabled { get; set; } = false;
|
||
|
||
[Category("Mullvad VPN")]
|
||
[DisplayName("Mullvad Account")]
|
||
[Description("Account ID for Mullvad VPN. In server_settings.xml (gitignored) hinterlegen – NICHT im Code committen.")]
|
||
public string MullvadAccount { get; set; } = ""; // F3: Secret nicht mehr im Quellcode
|
||
|
||
[Category("Mullvad VPN")]
|
||
[DisplayName("VPN Location")]
|
||
[Description("Target VPN location (e.g. cz).")]
|
||
public string VpnLocation { get; set; } = "cz";
|
||
|
||
[Category("Mullvad VPN")]
|
||
[DisplayName("Mullvad CLI Path")]
|
||
[Description("Path to mullvad.exe")]
|
||
public string MullvadCliPath { get; set; } = @"C:\Program Files\Mullvad VPN\resources\mullvad.exe";
|
||
|
||
|
||
[Category("Blockchain Listener")]
|
||
[DisplayName("Enable Blockchain Listener")]
|
||
[Description("If true, connects to Alchemy WSS for faster on-chain signal detection.")]
|
||
public bool EnableBlockchainListener { get; set; } = true;
|
||
|
||
[Category("Blockchain Listener")]
|
||
[DisplayName("Polygon RPC URL")]
|
||
[Description("RPC-/WSS-URL inkl. Alchemy-API-Key. In server_settings.xml (gitignored) hinterlegen – der Key gehört NICHT in den Quellcode/Git. Format: wss://polygon-mainnet.g.alchemy.com/v2/<KEY>")]
|
||
public string PolygonRpcUrl { get; set; } = ""; // F3: Alchemy-Key nicht mehr im Quellcode
|
||
|
||
[Category("Polymarket WebSockets")]
|
||
[DisplayName("Use Polymarket WebSockets")]
|
||
[Description("If true, connects to Polymarket WSS for live market prices and user events.")]
|
||
public bool UsePolymarketWebsockets { get; set; } = false;
|
||
|
||
|
||
[Category("Watchdog Monitoring")]
|
||
[DisplayName("Watchdog Enabled")]
|
||
[Description("Wenn true, sendet PolyTrader im festen Takt einen Heartbeat an den Watchdog " +
|
||
"des Deploymentcenters. Bleiben die Heartbeats aus, alarmiert der Watchdog " +
|
||
"(Dead-Man's-Switch). Ein nicht erreichbarer Watchdog beeinträchtigt PolyTrader NIE.")]
|
||
public bool WatchdogEnabled { get; set; } = false;
|
||
|
||
[Category("Watchdog Monitoring")]
|
||
[DisplayName("Deploymentcenter-URL")]
|
||
[Description("Basis-URL des Deploymentcenters, z.B. https://dc.mhdf.de (ersetzt den früheren " +
|
||
"eigenständigen Watchdog-Server). HTTPS ist Pflicht – http:// wird nur für localhost " +
|
||
"akzeptiert, damit der Agent-Token nicht im Klartext über das Netz geht.")]
|
||
public string WatchdogUrl { get; set; } = "";
|
||
|
||
/// <summary>
|
||
/// Agent-Token für den Header <c>X-Watchdog-Key</c>. Bewusst NICHT im PropertyGrid sichtbar:
|
||
/// Setzen erfolgt über den Button „Watchdog-Token setzen …" mit maskierter Eingabe, damit das
|
||
/// Secret nicht offen im Fenster steht und bei gesetztem Master-Key verschlüsselt
|
||
/// (<c>enc:v1:…</c>) in der gitignorierten server_settings.xml landet.
|
||
/// </summary>
|
||
[Browsable(false)]
|
||
public string WatchdogToken { get; set; } = "";
|
||
|
||
[Category("Watchdog Monitoring")]
|
||
[DisplayName("Agent-Token")]
|
||
[Description("Status des Agent-Tokens. Setzen/Ändern über den Button „Watchdog-Token setzen …" +
|
||
"\" in der Werkzeugleiste (maskierte Eingabe).")]
|
||
[ReadOnly(true)]
|
||
[XmlIgnore]
|
||
public string WatchdogTokenStatus =>
|
||
string.IsNullOrWhiteSpace(WatchdogToken)
|
||
? "nicht gesetzt"
|
||
: (WatchdogToken.StartsWith("enc:v1:", System.StringComparison.Ordinal)
|
||
? "gesetzt (verschlüsselt)"
|
||
: "gesetzt (Klartext)");
|
||
|
||
[Category("Watchdog Monitoring")]
|
||
[DisplayName("Source")]
|
||
[Description("Dienst-Kennung im Watchdog (muss dem dort angelegten Monitor entsprechen), z.B. \"polytrader\".")]
|
||
public string WatchdogSource { get; set; } = "polytrader";
|
||
|
||
[Category("Watchdog Monitoring")]
|
||
[DisplayName("Instance")]
|
||
[Description("Instanz-Kennung, falls mehrere PolyTrader-Instanzen laufen, z.B. \"prod-1\".")]
|
||
public string WatchdogInstance { get; set; } = "prod-1";
|
||
|
||
[Category("Watchdog Monitoring")]
|
||
[DisplayName("Heartbeat-Intervall (Sekunden)")]
|
||
[Description("Sende-Takt des Heartbeats. Der Watchdog leitet daraus seine Staleness-Frist ab " +
|
||
"(Intervall × Faktor + Grace). Minimum 10 s, Empfehlung 60 s.")]
|
||
public int WatchdogIntervalSeconds { get; set; } = 60;
|
||
|
||
|
||
/// <summary>
|
||
/// Lizenzschlüssel (LicenseLabrador). Wie der Watchdog-Token bewusst NICHT im PropertyGrid
|
||
/// sichtbar; gepflegt über den Button „Lizenzschlüssel setzen …" bzw. den Startdialog. Bei
|
||
/// gesetztem Master-Key verschlüsselt (<c>enc:v1:…</c>) in der gitignorierten server_settings.xml.
|
||
/// </summary>
|
||
[Browsable(false)]
|
||
public string LicenseKey { get; set; } = "";
|
||
|
||
[Category("Lizenz")]
|
||
[DisplayName("Lizenzschlüssel")]
|
||
[Description("Status des Lizenzschlüssels. Setzen/Ändern über den Button „Lizenzschlüssel setzen …\" " +
|
||
"in der Werkzeugleiste bzw. beim Programmstart, falls keine gültige Lizenz vorliegt.")]
|
||
[ReadOnly(true)]
|
||
[XmlIgnore]
|
||
public string LicenseKeyStatus =>
|
||
string.IsNullOrWhiteSpace(LicenseKey)
|
||
? "nicht gesetzt"
|
||
: (LicenseKey.StartsWith("enc:v1:", System.StringComparison.Ordinal)
|
||
? "gesetzt (verschlüsselt)"
|
||
: "gesetzt (Klartext)");
|
||
|
||
|
||
public static ServerSettings Load(string path)
|
||
{
|
||
if (!File.Exists(path))
|
||
return new ServerSettings();
|
||
|
||
try
|
||
{
|
||
var serializer = new XmlSerializer(typeof(ServerSettings));
|
||
using var fs = new FileStream(path, FileMode.Open);
|
||
return (ServerSettings?)serializer.Deserialize(fs) ?? new ServerSettings();
|
||
}
|
||
catch
|
||
{
|
||
return new ServerSettings();
|
||
}
|
||
}
|
||
|
||
public void Save(string path)
|
||
{
|
||
var serializer = new XmlSerializer(typeof(ServerSettings));
|
||
using var fs = new FileStream(path, FileMode.Create);
|
||
serializer.Serialize(fs, this);
|
||
}
|
||
}
|
||
}
|