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>
64 lines
2.5 KiB
C#
64 lines
2.5 KiB
C#
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);
|
||
}
|
||
}
|
||
}
|