Eigenständiges token-geschütztes Verwaltungs-WebGUI (Admin WebGUI) hinzugefügt
- Unabhängige Kestrel-Instanz auf Port 5001 (vollständig getrennt vom Analyse-Webserver) - Admin API Endpunkte ohne Swagger-Exposition - Token-Gatekeeper mit sofortigem Login-Overlay bei unautorisiertem Zugriff - Live-Terminal via SSE-Log-Streaming und Log-Download-Übersicht - Steuerung von Workern, Analyse-Webserver, Wartungsaktionen und Einstellungen - Nahtlose Integration in Avalonia Desktop-Shell und Linux Headless-Modus - Unit-Tests für Admin-Filter, Optionen und Log-Puffer
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
using System.ComponentModel;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
|
||||
namespace Predictalytics.Hosting;
|
||||
|
||||
@@ -63,6 +64,60 @@ public sealed class PredictalyticsOptions
|
||||
}
|
||||
}
|
||||
|
||||
// ─── Verwaltungs-Webserver (Admin WebGUI) ───
|
||||
|
||||
[Category("Verwaltungs-Webserver")]
|
||||
[DisplayName("Admin Webserver aktiv")]
|
||||
[Description("Aktiviert das separate Verwaltungs-WebGUI zur Steuerung und Wartung.")]
|
||||
[DefaultValue(true)]
|
||||
public bool AdminServerEnabled { get; set; } = true;
|
||||
|
||||
[Category("Verwaltungs-Webserver")]
|
||||
[DisplayName("Port")]
|
||||
[Description("Der Port, über den das Verwaltungs-WebGUI erreichbar ist.")]
|
||||
[DefaultValue(5001)]
|
||||
public int AdminServerPort { get; set; } = 5001;
|
||||
|
||||
[Category("Verwaltungs-Webserver")]
|
||||
[DisplayName("Bind-Adresse")]
|
||||
[Description("Netzwerkschnittstelle, auf der das Verwaltungs-WebGUI lauscht. 'localhost' = nur lokal (Standard), '0.0.0.0' = netzwerkweit erreichbar.")]
|
||||
[DefaultValue("localhost")]
|
||||
public string AdminServerHost { get; set; } = "localhost";
|
||||
|
||||
[Category("Verwaltungs-Webserver")]
|
||||
[DisplayName("Admin-Token")]
|
||||
[Description("Schützt das gesamte Verwaltungs-WebGUI. Ohne diesen Token ist kein Zugriff möglich. Kann neu generiert werden.")]
|
||||
[PasswordPropertyText(true)]
|
||||
public string AdminToken { get; set; } = "";
|
||||
|
||||
/// <summary>
|
||||
/// Prüft, ob die Admin-Bind-Adresse öffentlich bzw. über das Netzwerk erreichbar ist.
|
||||
/// </summary>
|
||||
[Browsable(false)]
|
||||
[JsonIgnore]
|
||||
public bool IsAdminPubliclyBound
|
||||
{
|
||||
get
|
||||
{
|
||||
var host = (AdminServerHost ?? "").Trim();
|
||||
return host.Length > 0
|
||||
&& !host.Equals("localhost", StringComparison.OrdinalIgnoreCase)
|
||||
&& host != "127.0.0.1"
|
||||
&& host != "::1";
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Kestrel-Bind-URL für das Admin-WebGUI.</summary>
|
||||
[Browsable(false)]
|
||||
[JsonIgnore]
|
||||
public string AdminServerUrl =>
|
||||
$"http://{(string.IsNullOrWhiteSpace(AdminServerHost) ? "localhost" : AdminServerHost.Trim())}:{AdminServerPort}";
|
||||
|
||||
/// <summary>Generiert einen sicheren, kryptografischen Zufallstoken für das Admin-WebGUI.</summary>
|
||||
public static string GenerateSecureToken(int byteLength = 24) =>
|
||||
Convert.ToHexString(System.Security.Cryptography.RandomNumberGenerator.GetBytes(byteLength)).ToLowerInvariant();
|
||||
|
||||
|
||||
// ─── Egress ───
|
||||
|
||||
private string _egressChannelsText = "";
|
||||
@@ -224,12 +279,40 @@ public sealed class PredictalyticsOptions
|
||||
{
|
||||
var options = JsonSerializer.Deserialize<PredictalyticsOptions>(File.ReadAllText(path))
|
||||
?? new PredictalyticsOptions();
|
||||
if (path == LegacySettingsFilePath) options.Save(); // Migration festschreiben
|
||||
var modified = path == LegacySettingsFilePath;
|
||||
if (string.IsNullOrWhiteSpace(options.AdminToken))
|
||||
{
|
||||
options.AdminToken = GenerateSecureToken();
|
||||
modified = true;
|
||||
}
|
||||
if (modified) options.Save(); // Migration oder Initial-Token festschreiben
|
||||
return options;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return new PredictalyticsOptions();
|
||||
var fallback = new PredictalyticsOptions();
|
||||
if (string.IsNullOrWhiteSpace(fallback.AdminToken))
|
||||
{
|
||||
fallback.AdminToken = GenerateSecureToken();
|
||||
try { fallback.Save(); } catch { }
|
||||
}
|
||||
return fallback;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ermöglicht das Übersteuern von Einstellungen über appsettings.json oder Umgebungsvariablen.
|
||||
/// </summary>
|
||||
public void ApplyConfigurationOverrides(Microsoft.Extensions.Configuration.IConfiguration? configuration)
|
||||
{
|
||||
if (configuration == null) return;
|
||||
var section = configuration.GetSection("Admin");
|
||||
if (section.Exists())
|
||||
{
|
||||
if (bool.TryParse(section["Enabled"], out var enabled)) AdminServerEnabled = enabled;
|
||||
if (int.TryParse(section["Port"], out var port) && port > 0) AdminServerPort = port;
|
||||
if (!string.IsNullOrWhiteSpace(section["Host"])) AdminServerHost = section["Host"]!.Trim();
|
||||
if (!string.IsNullOrWhiteSpace(section["Token"])) AdminToken = section["Token"]!.Trim();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user