Files
gott/Endpoints/ServerSettingsEndpoint.cs
Meleki e61f3ef082 feat: DB-Explorer, Scanner-KeyTypes-Setting, Stripe-Checker, Anthropic-Auth-Fix, Progress-Tracking
Backend:
- Neuer Database Explorer Endpoint (GET /api/admin/db/tables, GET /api/admin/db/tables/{name})
- DatabaseService: public GetConnection() Methode
- StripeKeyChecker: 2-Schritt-Check (/v1/balance + /v1/account), erkennt Live/Test, Balance, Account-Details
- AnthropicKeyChecker: Auth-Header fix (x-api-key für API-Keys, Bearer für OAuth), 400-Handler für credit balance
- KeyPatterns: Stripe einkommentiert, Krea AI Pattern, AllTypes-Array, MinLength 4
- ServerSettingsService: string_array Typ (Get/SetStringArray), ScannerKeyTypes Setting mit Checkbox-Optionen
- ServerSettingsEndpoint: string_array Speichern (Array/CSV)
- KeyScannerService: GetActiveSearchPatterns() filtert nach ScannerKeyTypes, Progress-Tracking
- GitHubEventScannerService: CheckNewKeys nur für aktuelle Poll-Keys (statt alle unchecked), klarere Log-Meldung

Frontend:
- Database Explorer Page: Tabellenliste mit Klick -> Tabelleninhalt, Pagination, sticky Header
- Server Settings: Checkbox-Gruppe für string_array (Alle/Keine Buttons)
- API Keys: adoptedApiKeyId Fix (leerer String == null)
- Dashboard: DB-Explorer Link für Admins
- Scan-Progress: Live-Fortschritt mit Phase/Detail/Counter
2026-03-14 16:19:13 +01:00

142 lines
4.9 KiB
C#
Raw Permalink 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.IO;
using System.Text.Json;
using System.Text.Json.Nodes;
using System.Threading.Tasks;
using Gott.Extensions;
using Gott.Services;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Configuration;
namespace Gott.Endpoints;
public static class ServerSettingsEndpoint
{
public static void MapServerSettingsEndpoints(this WebApplication app)
{
app.MapGet("/api/admin/server-settings", HandleGet);
app.MapPut("/api/admin/server-settings", HandleUpdate);
}
private static async Task HandleGet(HttpContext ctx, ServerSettingsService settings, IConfiguration config)
{
if (!ctx.IsAdmin())
{
ctx.Response.StatusCode = 403;
await ctx.Response.WriteAsJsonAsync(new { error = "Admin access required" });
return;
}
// Port aus appsettings (nicht DB) – als erstes Feld
var port = config.GetValue<int>("Port", 19000);
var allSettings = new List<object>
{
new
{
key = "Port",
type = "int",
value = (object)port,
label = "Server-Port (erfordert Neustart)",
inputType = "number",
}
};
allSettings.AddRange(settings.GetAll());
await ctx.Response.WriteAsJsonAsync(new { settings = allSettings });
}
private static async Task HandleUpdate(HttpContext ctx, ServerSettingsService settings, IConfiguration config)
{
if (!ctx.IsAdmin())
{
ctx.Response.StatusCode = 403;
await ctx.Response.WriteAsJsonAsync(new { error = "Admin access required" });
return;
}
var body = await JsonSerializer.DeserializeAsync<JsonElement>(ctx.Request.Body);
var hint = "";
// Port separat behandeln – in appsettings.Production.json speichern
if (body.TryGetProperty("Port", out var portProp) && portProp.TryGetInt32(out var newPort))
{
var currentPort = config.GetValue<int>("Port", 19000);
if (newPort != currentPort && newPort > 0 && newPort <= 65535)
{
SavePortToAppSettings(ctx, newPort);
hint = "Port geändert – Neustart erforderlich!";
}
}
// Alle anderen Settings in DB speichern
foreach (var def in ServerSettingsService.Definitions)
{
if (!body.TryGetProperty(def.Key, out var prop)) continue;
switch (def.Type)
{
case "string":
settings.Set(def.Key, prop.GetString() ?? "");
break;
case "int":
settings.SetInt(def.Key, prop.TryGetInt32(out var i) ? i : 0);
break;
case "bool":
settings.SetBool(def.Key, prop.ValueKind == JsonValueKind.True);
break;
case "string_array":
if (prop.ValueKind == JsonValueKind.Array)
{
var arr = new List<string>();
foreach (var el in prop.EnumerateArray())
{
var s = el.GetString();
if (!string.IsNullOrWhiteSpace(s)) arr.Add(s);
}
settings.SetStringArray(def.Key, arr.ToArray());
}
else if (prop.ValueKind == JsonValueKind.String)
{
// Fallback: CSV-String
settings.Set(def.Key, prop.GetString() ?? "");
}
break;
}
}
await ctx.Response.WriteAsJsonAsync(new { ok = true, hint = string.IsNullOrEmpty(hint) ? "Einstellungen gespeichert" : hint });
}
private static void SavePortToAppSettings(HttpContext ctx, int port)
{
// appsettings.Production.json im ContentRootPath (= publish-Verzeichnis)
var contentRoot = ctx.RequestServices.GetService(typeof(Microsoft.AspNetCore.Hosting.IWebHostEnvironment)) as Microsoft.AspNetCore.Hosting.IWebHostEnvironment;
var path = Path.Combine(contentRoot?.ContentRootPath ?? AppContext.BaseDirectory, "appsettings.Production.json");
JsonObject obj;
if (File.Exists(path))
{
try
{
var existing = File.ReadAllText(path);
obj = JsonNode.Parse(existing) as JsonObject ?? new JsonObject();
}
catch
{
obj = new JsonObject();
}
}
else
{
obj = new JsonObject();
}
obj["Port"] = port;
var options = new JsonSerializerOptions { WriteIndented = true };
File.WriteAllText(path, obj.ToJsonString(options));
}
}