Alle vier Core-Fenster sind damit portiert. TERMINAL: - Live-Ausgabe als virtualisiertes ItemsControl ueber eine begrenzte Zeilenliste statt RichTextBox. Damit entfaellt das Auto-Clear der WinForms-Fassung, das bei Erreichen der Zeichengrenze den GESAMTEN Verlauf verwarf - jetzt werden nur die aeltesten Zeilen verdraengt (Ringpuffer, 5000 Zeilen), der juengste Verlauf bleibt immer sichtbar. Die Zeilenzahl steht in der Statuszeile. - Log-Viewer unveraendert im Funktionsumfang: JSONL-Tagesdateien, Filter nach Datum, Level, CID und Volltext, Doppelklick uebernimmt die CID (Signal-Kette verfolgen). ZEITZONE (Befund aus der Linux-Analyse, hier faellig geworden): Die Terminal-Ansicht rechnete hart gegen die WINDOWS-ID 'W. Europe Standard Time'. Auf Linux traegt die nur ueber die ICU-Zuordnung und faellt ganz aus, wenn ICU fehlt oder InvariantGlobalization gesetzt ist - das Fenster haette beim Oeffnen geworfen. Neu: PolyTraderSharp.Services.AppTimeZone + ServerSettings.ApplicationTimeZoneId (Default 'Europe/Berlin', IANA-Schreibweise). Aufloesung versucht die ID direkt, dann die jeweils andere Schreibweise (IANA<->Windows), zuletzt die Systemzeitzone - ein unbekannter Wert ist damit nie fatal, sondern erzeugt nur eine Warnung. Beide Programm-Einstiege setzen sie einmalig beim Start; laut Vorgabe wird sie bei der Installation festgelegt und nicht im laufenden Betrieb gewechselt (Aenderung verschiebt Logdatei-Tagesgrenzen). 8 neue Tests (AppTimeZoneTests) halten fest: IANA- UND Windows-ID liefern denselben UTC-Versatz (Winter +1, Sommer +2), unbekannte IDs fallen mit Warnung auf die Systemzeitzone zurueck, leere Angabe = Systemzeitzone, UTC wird korrekt umgerechnet. Verifiziert: Solution baut, 450 Tests gruen, --smoke-ui gruen (6 Fenster + Editor-Pruefung, Zeitzone loest als Europe/Berlin auf), App laeuft real, Linux-Publish laeuft. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
164 lines
7.5 KiB
C#
164 lines
7.5 KiB
C#
using System.Collections.Generic;
|
||
using System.ComponentModel;
|
||
using System.IO;
|
||
using System.Xml.Serialization;
|
||
|
||
namespace PolyTraderSharp.Models
|
||
{
|
||
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("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 externen " +
|
||
"Watchdog-Server. 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("Watchdog URL")]
|
||
[Description("Basis-URL des Watchdog-Servers, z.B. https://watchdog.example.de. " +
|
||
"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);
|
||
}
|
||
}
|
||
}
|