Files
PolyTraderSharp/src/PolyTrader.Core/Modularity/KnownModules.cs
T
RichardandClaude Opus 5 68d8d6f8d2 Module im WebUI aktivieren und deaktivieren
Umsetzung von #560.

Die Ansicht "Moduleinstellungen" zeigte bisher nur an, ob ein Modul laeuft. Jetzt
laesst sich dort wie im Modul-Tab des Desktops festlegen, welche Module beim
naechsten Start geladen werden. POST /api/modules/{name}/enabled nimmt den
gewuenschten Zustand ausdruecklich entgegen ({"enabled": true|false}) und
schaltet nicht einfach um. Zwei Klicks aus zwei Fenstern heben sich damit
nicht auf, und ein wiederholter Aufruf aendert nichts mehr.

Fehlerfaelle:
- Unbekanntes Modul: 404.
- Modul mit Aktivierungs-Blocker: 409.
- Fehlender Zustand: 400. Die Pruefung war zuerst lueckenhaft, der Test hat es
  gefunden: Mit "bool Enabled" machte ein leerer Rumpf {} daraus false, und ein
  unvollstaendiger Aufruf haette das Modul deaktiviert.

Gemeinsame Quelle statt Kopien:
- KnownModules: Die Modulliste stand viermal im Code (Program, CoreViews,
  ModuleViews, hartkodiert im WebUI). Der Start registriert jetzt die eine
  Liste.
- ModuleActivationView: Die Statusregeln des Desktops ("Aktiv - stoppt nach
  Neustart" usw.) liegen im Core. Desktop und WebUI zeigen dasselbe.

ServerSettingsFile - gefunden, bevor das WebUI in die Datei schreiben durfte:
ServerSettings.Load verschluckt jeden Lesefehler und liefert Vorgaben. "Frisch
lesen, ein Feld aendern, speichern" haette eine gesperrte oder beschaedigte
server_settings.xml durch Vorgaben ersetzt, und mit ihr Lizenzschluessel,
Watchdog-Token, Mullvad-Account und Alchemy-Key. Genau so schrieb auch der
Modul-Schalter des Desktops. ServerSettingsFile:
- liest streng und schreibt bei unlesbarer Datei nichts,
- schreibt atomar ueber eine Nachbardatei,
- erhaelt unter Linux die Dateirechte,
- serialisiert Aenderungen prozessweit.
Der Desktop-Schalter benutzt ihn jetzt ebenfalls.

Tests: Lizenzschluessel, Token und Alchemy-Key bleiben beim Umschalten in der
Datei; eine kaputte Datei bleibt Byte fuer Byte unveraendert und die Anzeige
bietet dann kein Umschalten an; Blocker, 404, 400, Idempotenz; die
Statustabelle; strenges Lesen, atomares Schreiben und Rechte am Dateiobjekt.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-13 14:03:31 +02:00

64 lines
2.5 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System;
using System.Collections.Generic;
using System.Linq;
namespace PolyTrader.Core.Modularity
{
/// <summary>
/// Alle Module, die diese Installation kennt – geladen oder nicht (#560).
///
/// <para>Die Liste stand bis dahin viermal im Code: beim Start (Program), im Modul-Tab des Desktops
/// (CoreViews), bei den nicht verfügbaren Ansichten (ModuleViews) und hartkodiert im WebUI. Ein neues
/// Modul musste an allen vier Stellen nachgetragen werden. Fehlte es an einer, ließ es sich dort weder
/// sehen noch wieder aktivieren. Der Start registriert jetzt diese eine Liste; wer sie braucht, fragt
/// sie ab.</para>
/// </summary>
public sealed class KnownModules
{
public KnownModules(IEnumerable<IPolyTraderModule> modules)
{
All = (modules ?? throw new ArgumentNullException(nameof(modules))).ToList();
}
public IReadOnlyList<IPolyTraderModule> All { get; }
public IPolyTraderModule? Find(string name) =>
All.FirstOrDefault(m => string.Equals(m.Name, name, StringComparison.OrdinalIgnoreCase));
}
/// <summary>
/// Was ein Modul-Schalter zeigt und darf. Die Regeln stammen aus dem Modul-Tab des Desktops und liegen
/// jetzt hier, damit Desktop und WebUI dasselbe anzeigen (#560).
/// </summary>
public sealed record ModuleActivationView(
string Name,
bool IsRunning,
bool DesiredEnabled,
string? Blocker,
string Status,
string Hint,
string Action,
bool CanToggle)
{
public const string RestartHint = "Änderung greift nach Neustart";
public static ModuleActivationView Describe(ModuleActivationInfo info, bool desiredEnabled)
{
if (!string.IsNullOrEmpty(info.Blocker))
return new(info.Name, info.IsRunning, desiredEnabled, info.Blocker,
"⚠ Nicht aktivierbar", info.Blocker!, "—", false);
if (info.IsRunning && desiredEnabled)
return new(info.Name, true, true, null, "Aktiv", "", "Deaktivieren", true);
if (info.IsRunning)
return new(info.Name, true, false, null, "Aktiv – stoppt nach Neustart", RestartHint, "Aktivieren", true);
if (desiredEnabled)
return new(info.Name, false, true, null, "Startet nach Neustart", RestartHint, "Deaktivieren", true);
return new(info.Name, false, false, null, "Deaktiviert", "", "Aktivieren", true);
}
}
}