Panel: German/English, redesigned sidebar, in-game admins page, Steam Web API key link, RAR/7z save upload (SharpCompress), responsive cards; upload errors return 400; tests

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-09-27 19:40:46 +02:00
co-authored by Claude Opus 5.5
parent c74c48dfc8
commit 8df6cbcafc
12 changed files with 700 additions and 58 deletions
+42 -3
View File
@@ -251,7 +251,7 @@ public sealed class AdminPanel
void MapFiles()
{
byte[] html = Resource("panel.index.html"), js = Resource("panel.app.js"), css = Resource("panel.app.css"), logo = Resource("panel.logo.png");
byte[] html = Resource("panel.index.html"), js = Resource("panel.app.js"), i18n = Resource("panel.i18n.js"), css = Resource("panel.app.css"), logo = Resource("panel.logo.png");
app.MapGet("/", (HttpContext ctx) =>
{
// /panel ohne Schrägstrich -> /panel/ (sonst zeigen die relativen Adressen der Seite ins Leere)
@@ -260,6 +260,7 @@ public sealed class AdminPanel
return Results.Bytes(html, "text/html; charset=utf-8");
});
app.MapGet("/app.js", () => Results.Bytes(js, "text/javascript; charset=utf-8"));
app.MapGet("/i18n.js", () => Results.Bytes(i18n, "text/javascript; charset=utf-8"));
app.MapGet("/app.css", () => Results.Bytes(css, "text/css; charset=utf-8"));
if (logo != null) app.MapGet("/logo.png", () => Results.Bytes(logo, "image/png"));
}
@@ -410,6 +411,35 @@ public sealed class AdminPanel
return await RunText(s => s.PanelUnban(id, by), () => OfflineBan(id, false, by));
});
// --- Ingame-Admins (dürfen im Spiel /admin benutzen)
app.MapGet("/api/admins", async (HttpContext ctx) =>
{
var user = Require(ctx, PanelRole.Moderator, out var denied); if (user == null) return denied;
return await Run(s => s.PanelAdmins(), () => new
{
steamVerification = !string.IsNullOrWhiteSpace(config.SteamWebApiKey),
admins = config.Admins.Select(id => new { steamId = id, name = (string)null, online = false }).ToList()
});
});
app.MapPost("/api/admins/{action}", async (HttpContext ctx, string action) =>
{
var user = Require(ctx, PanelRole.Admin, out var denied); if (user == null) return denied;
var body = await Body(ctx);
string id = Str(body, "id").Trim(), by = By(user);
if (id.Length != 17 || !id.All(char.IsDigit)) return Error(400, "Enter a SteamID64 (17 digits)");
if (action != "add" && action != "remove") return Error(404, "Unknown action");
bool add = action == "add";
return await RunText(s => s.RunCommandCaptured((add ? "op " : "deop ") + id, by), () =>
{
config.Admins.Remove(id);
if (add) config.Admins.Add(id);
try { config.Save(host.ConfigPath); } catch (Exception e) { return "Could not save server.json: " + e.Message; }
GameServer.Log($"SteamID {id} is {(add ? "now an admin" : "no longer an admin")} ({by})");
return $"SteamID {id} is {(add ? "now an admin" : "no longer an admin")}";
});
});
// --- Konsole
app.MapGet("/api/console", (HttpContext ctx) =>
{
@@ -512,9 +542,18 @@ public sealed class AdminPanel
zip = ms.ToArray();
}
catch (Exception e) { return Error(400, "Upload failed: " + e.Message); }
if (zip.Length == 0) return Error(400, "Choose a ZIP file");
if (zip.Length == 0) return Error(400, "Choose a ZIP, RAR or 7z file");
string by = By(user);
return await RunText(s => s.ImportSaveZip(zip, by), () => SaveStore.ImportZipOffline(dataDir, zip, by));
var server = host.Server;
string result;
if (server == null || host.State != HostState.Running) result = SaveStore.ImportZipOffline(dataDir, zip, by);
else
{
var task = server.Invoke(() => server.ImportSaveZip(zip, by));
if (await Task.WhenAny(task, Task.Delay(ActionTimeout)) != task) return Error(503, "The server is busy");
result = await task;
}
return result.StartsWith("Save game uploaded") ? Ok(result) : Error(400, result);
});
// --- Benutzer (nur Owner)