using System; using System.Collections.Generic; using System.Linq; using System.Text.Json; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Routing; using Microsoft.Extensions.DependencyInjection; using PolyTrader.Core.Configuration; using PolyTrader.Web.Configuration; using PolyTraderSharp.Models; using PolyTraderSharp.Services; namespace PolyTrader.Web.Endpoints { /// /// Server-Einstellungen ohne Geheimnisse (#561). Was angezeigt und geändert werden darf, legt /// fest. /// public static class ServerSettingsEndpoints { public sealed record UpdateServerSettingsRequest(Dictionary? Values); public static IEndpointRouteBuilder MapServerSettingsEndpoints(this IEndpointRouteBuilder routes) { var group = routes.MapGroup("/api/settings/server"); group.MapGet("/", (IServiceProvider sp) => { var file = sp.GetService(); if (file == null) return Results.Problem("Die Einstellungsdatei ist nicht verfügbar.", statusCode: StatusCodes.Status503ServiceUnavailable); try { return Results.Ok(Describe(file.Read())); } catch (Exception ex) { return Results.Problem(ex.Message, statusCode: StatusCodes.Status500InternalServerError); } }); // Alles oder nichts: Erst werden alle Werte geprüft, dann wird in einem Zug geschrieben. Ein // halb übernommener Satz Einstellungen wäre schwerer zu verstehen als ein abgelehnter. group.MapPost("/", (UpdateServerSettingsRequest? req, IServiceProvider sp, TerminalLogger logger) => { if (req?.Values == null || req.Values.Count == 0) return Results.BadRequest(new { error = "Keine Werte übergeben: {\"values\": {\"Feld\": Wert}}." }); var parsed = new List<(SettingDescriptor Field, object Value)>(); var errors = new List(); foreach (var (key, raw) in req.Values) { if (ServerSettingsCatalog.DesktopOnly.TryGetValue(key, out var reason)) { logger.Warning($"🌐 WebUI: Änderung an {key} abgelehnt – nur im Desktop ({reason})."); return Results.Json(new { error = $"{key} lässt sich nur im Desktop ändern ({reason}). Es wurde nichts geändert." }, statusCode: StatusCodes.Status403Forbidden); } var field = ServerSettingsCatalog.Find(key); if (field == null || !field.Editable) { errors.Add($"{key}: im WebUI nicht vorhanden"); continue; } var (value, error) = field.Parse!(raw); if (error != null) errors.Add($"{field.Label}: {error}"); else parsed.Add((field, value!)); } if (errors.Count > 0) return Results.BadRequest(new { error = "Nichts gespeichert. " + string.Join(" · ", errors), errors }); var file = sp.GetService(); ServerSettings? written = null; var failed = PersistenceGuard.Write(file != null, () => written = file!.Update(s => { foreach (var (f, v) in parsed) f.Assign!(s, v); }), logger, "Server-Einstellungen"); if (failed != null) return failed; // Den laufenden Stand nachziehen. Das Singleton lesen WSS-Client, Alchemy-Listener und der // VPN-Check des Watchdogs; VPN- und Watchdog-Dienst halten eigene, aus der Datei geladene Kopien. var live = sp.GetService(); if (live != null) foreach (var (f, v) in parsed) f.Assign!(live, v); sp.GetService()?.ReloadSettings(); sp.GetService()?.ReloadSettings(); logger.Info("🌐 WebUI: Server-Einstellungen geändert – " + string.Join(", ", parsed.Select(p => $"{p.Field.Key}={p.Value}"))); return Results.Ok(new { success = true, changed = parsed.Select(p => new { key = p.Field.Key, label = p.Field.Label, requiresRestart = p.Field.RequiresRestart }).ToList(), restartRequired = parsed.Any(p => p.Field.RequiresRestart), settings = Describe(written!) }); }); return routes; } internal static object Describe(ServerSettings s) => new { managedIn = "desktop", sections = ServerSettingsCatalog.Fields .GroupBy(f => f.Category) .Select(g => new { title = g.Key, fields = g.Select(f => new { key = f.Key, label = f.Label, description = f.Description, kind = f.Kind.ToString().ToLowerInvariant(), value = f.Get(s), options = f.Options, min = f.Min, max = f.Max, editable = f.Editable, requiresRestart = f.RequiresRestart }).ToList() }).ToList(), secrets = ServerSettingsCatalog.SecretStatus(s).Select(x => new { label = x.Label, status = x.Status }).ToList() }; } }