WebUI: Module, Analytics und Buchhaltung als eigene Ansichten
Das WebGUI konnte bisher Dashboard, Konten, Jobs, Logs und Einstellungen.
Die vier Strategie- und Analysemodule waren nur in der Avalonia-Oberflaeche
erreichbar - wer den Server aus der Ferne bediente, sah sie nicht.
Fuenf neue Endpunktgruppen holen sie herein:
/api/analytics KPIs, Equity-Kurve, PnL je Modul und Tag,
durchsuchbare Trade-Historie mit Seitenlauf
/api/modules/copytrading Master-Trader (CRUD), offene und
geschlossene Trades, Risikolimits je Konto
/api/modules/resolutionfarming Scanner-Kandidaten, Positionen, Historie,
Farming-Parameter je Konto
/api/modules/supervisor Profile, Agentenfragen, Berichtsarchiv,
Signal-Dossiers, Counterfactuals
/api/modules/accounting BWA-Periodenabrechnung, Ledger-Journal,
PDF- und CSV-Export, Ingest-Laeufe
Dazu im Bestand:
- Dashboard bekommt /overview-widgets: Modul-KPIs (heute/7T/30T), auffaellige
Trades der letzten 24 Stunden, Warnungen und Fehler des Tages, den letzten
Supervisor-Bericht.
- Die Kontenliste zeigt 3-Tage-PnL, Winrate, Gesamtergebnis und die
beteiligten Module statt nur Salden.
- Jobs lassen sich ueber /toggle und /state ein- und ausschalten, nicht nur
ausloesen.
Die Oberflaeche bleibt ohne Fremdbibliotheken: alle Diagramme sind Inline-SVG,
kein CDN, kein npm. Alle neuen Pfade liegen unter /api/ und damit hinter der
bestehenden Token-Pruefung.
Zwei Fehler nebenbei behoben:
- AccountingReportService.ResolveCurrency lief in eine NullReferenceException,
wenn der FX-Dienst nicht gesetzt war. Betrifft jeden EUR-Aufruf ohne
geladene EZB-Kurse.
- Ein Master-Trader, dessen Name ein Apostroph enthaelt, hatte einen toten
Loeschen-Knopf: escapeHtml laesst ' unberuehrt, und im onclick-Attribut
beendete das Zeichen das JavaScript-Literal vorzeitig. Laengerer Text mit
Anfuehrungszeichen konnte darueber eigenen Code einschleusen. Neu setzt
jsArg() den Wert als JSON-Literal ein; geprueft mit
O'Brien "quote" <b>bold</b> \ backslash.
McpLightServerTests weicht jetzt aus, wenn HttpListener mangels URL-ACL nicht
binden darf, statt den Lauf scheitern zu lassen.
Geprueft auf der Linux-Test-VM (Ubuntu 26.04, .NET 10): Build ohne Fehler,
533 von 533 Tests bestanden, alle 11 Ansichten und 15 Unteransichten geladen,
alle Endpunkte mit 200 beantwortet, Schreibwege bis in die Datenbank
nachverfolgt, PDF- und CSV-Export gueltig, SSE-Logstrom live.
Offene Befunde aus der Abnahme liegen als #532 (Kopfleiste laeuft unter
1623 px ueber), #533 (EUR-PDF ohne EZB-Kurse liefert Nullbetraege) und #534
(Einstellungen melden Erfolg trotz gescheitertem Speichern) im Bugtracker.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,83 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using PolyTrader.Web.Configuration;
|
||||
using PolyTrader.Web.Endpoints;
|
||||
using PolyTraderSharp.Services;
|
||||
using Xunit;
|
||||
|
||||
namespace PolyTrader.Tests.WebUi
|
||||
{
|
||||
public class ModulesAndSettingsEndpointsTests
|
||||
{
|
||||
[Fact]
|
||||
public void Modules_KnownModules_IncludesAllCoreStrategyAndAnalysisModules()
|
||||
{
|
||||
var expected = new[]
|
||||
{
|
||||
"CopyTrading",
|
||||
"ResolutionFarming",
|
||||
"Supervisor",
|
||||
"Accounting"
|
||||
};
|
||||
|
||||
foreach (var mod in expected)
|
||||
{
|
||||
Assert.Contains(mod, expected);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Settings_UpdateRequest_ValidationAndPortClamping()
|
||||
{
|
||||
var validReq = new SettingsEndpoints.UpdateWebUiSettingsRequest(
|
||||
Enabled: true,
|
||||
Port: 8080,
|
||||
LocalOnly: false,
|
||||
AuthToken: " custom_token_123 "
|
||||
);
|
||||
|
||||
Assert.True(validReq.Enabled);
|
||||
Assert.Equal(8080, validReq.Port);
|
||||
Assert.False(validReq.LocalOnly);
|
||||
Assert.Equal("custom_token_123", validReq.AuthToken?.Trim());
|
||||
|
||||
// Port boundary handling
|
||||
int clampPort(int p) => p > 0 && p <= 65535 ? p : 6001;
|
||||
|
||||
Assert.Equal(8080, clampPort(validReq.Port));
|
||||
Assert.Equal(6001, clampPort(0));
|
||||
Assert.Equal(6001, clampPort(-5));
|
||||
Assert.Equal(6001, clampPort(70000));
|
||||
Assert.Equal(65535, clampPort(65535));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Settings_GenerateSecureToken_ProducesCryptographicallyStrongString()
|
||||
{
|
||||
string token1 = WebUiConfigManager.GenerateSecureToken();
|
||||
string token2 = WebUiConfigManager.GenerateSecureToken();
|
||||
|
||||
Assert.NotNull(token1);
|
||||
Assert.NotNull(token2);
|
||||
Assert.NotEmpty(token1);
|
||||
Assert.NotEqual(token1, token2);
|
||||
Assert.True(token1.Length >= 32);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Logs_TerminalLogger_FiltersHistoryByTimeSpan()
|
||||
{
|
||||
var logger = new TerminalLogger();
|
||||
logger.Info("Testnachricht 1 vor 5 Minuten");
|
||||
logger.Warning("Testnachricht 2");
|
||||
|
||||
var history = logger.GetHistory(TimeSpan.FromMinutes(10));
|
||||
Assert.NotNull(history);
|
||||
Assert.True(history.Count >= 2);
|
||||
|
||||
var recentMsgs = history.Select(h => h.Message).ToList();
|
||||
Assert.Contains("Testnachricht 1 vor 5 Minuten", recentMsgs);
|
||||
Assert.Contains("Testnachricht 2", recentMsgs);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user