Panel independent of the game server: ServerHost owns port + panel and starts/stops the game server; panel works while stopped/crashed/unconfigured (settings, save ZIP upload, backups, bans offline); crash auto-restart; players told 'offline'; tests
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
+40
-19
@@ -200,11 +200,12 @@ async function refreshTopbar() {
|
||||
const s = await api("GET", "status");
|
||||
state.status = s;
|
||||
$("serverName").textContent = s.name;
|
||||
$("serverMeta").textContent = s.gameStopped
|
||||
? `game server stopped · v${s.version}`
|
||||
: `${s.players}/${s.maxPlayers} players · v${s.version} · up ${duration(s.uptimeSeconds)}`;
|
||||
$("statusDot").className = "status-dot " + (s.gameStopped ? "" : "ok");
|
||||
$("statusDot").title = s.gameStopped ? "Game server stopped" : "Online";
|
||||
const running = s.state === "running";
|
||||
$("serverMeta").textContent = running
|
||||
? `${s.players}/${s.maxPlayers} players · v${s.version} · up ${duration(s.uptimeSeconds)}`
|
||||
: `server ${s.state} · v${s.version}`;
|
||||
$("statusDot").className = "status-dot " + (running ? "ok" : s.state === "error" ? "bad" : "");
|
||||
$("statusDot").title = running ? "Online" : "Server " + s.state + (s.lastError ? ": " + s.lastError : "");
|
||||
document.title = `${s.name} · MSCO Panel`;
|
||||
} catch {
|
||||
$("statusDot").className = "status-dot bad";
|
||||
@@ -215,9 +216,9 @@ async function refreshTopbar() {
|
||||
|
||||
function renderDashboard(page) {
|
||||
const cards = h("div", { class: "cards" });
|
||||
const control = h("div", { class: "section-body row" });
|
||||
const control = h("div", { class: "section-body control" });
|
||||
if (can("Admin")) page.append(h("div", { class: "section" },
|
||||
h("div", { class: "section-head" }, h("h2", {}, "Server control"), h("span", { class: "muted", id: "gameState" }, "")),
|
||||
h("div", { class: "section-head" }, h("h2", {}, "Server"), h("span", { class: "state-label", id: "gameState" }, "")),
|
||||
control));
|
||||
const recent = h("div", { class: "console small" });
|
||||
page.append(
|
||||
@@ -260,20 +261,40 @@ async function serverAction(action, confirmText) {
|
||||
}
|
||||
await act(api("POST", "server/" + action));
|
||||
setTimeout(refreshTopbar, 800);
|
||||
setTimeout(refreshTopbar, 2500);
|
||||
}
|
||||
|
||||
const STATE_TEXT = {
|
||||
running: "Running - players can join",
|
||||
starting: "Starting …",
|
||||
stopped: "Stopped - players cannot join. The panel keeps running.",
|
||||
error: "Not running",
|
||||
};
|
||||
|
||||
function renderControl(el, s) {
|
||||
const state = $("gameState");
|
||||
if (state) state.textContent = s.gameStopped ? "game server is stopped - players cannot join" : "game server is running";
|
||||
el.replaceChildren(
|
||||
s.gameStopped
|
||||
? h("button", { class: "btn primary", type: "button", onclick: () => serverAction("start-game") }, "▶ Start")
|
||||
: h("button", { class: "btn", type: "button", onclick: () => serverAction("stop-game", { title: "Stop the game server?",
|
||||
text: "All players are kicked and nobody can join until you start it again. The panel keeps running.", danger: true, confirm: "Stop" }) }, "■ Stop"),
|
||||
h("button", { class: "btn", type: "button", onclick: () => serverAction("restart", { title: "Restart the server?",
|
||||
text: "Settings (server.json), license and save game are loaded again. Players reconnect automatically. You stay logged in.", confirm: "Restart" }) }, "⟳ Restart"),
|
||||
can("Owner") ? h("button", { class: "btn danger", type: "button", onclick: () => serverAction("shutdown", { title: "Shut down completely?",
|
||||
text: "The game server AND this panel stop. Start it again on the machine (e.g. docker compose up -d).", danger: true, confirm: "Shut down" }) }, "⏻ Shut down") : null);
|
||||
const label = $("gameState");
|
||||
const running = s.state === "running", starting = s.state === "starting";
|
||||
if (label) {
|
||||
label.textContent = STATE_TEXT[s.state] || s.state;
|
||||
label.className = "state-label " + s.state;
|
||||
}
|
||||
const buttons = h("div", { class: "row" },
|
||||
running || starting
|
||||
? h("button", { class: "btn", type: "button", disabled: starting, onclick: () => serverAction("stop", { title: "Stop the server?",
|
||||
text: "All players are disconnected and nobody can join until you start it again. The panel keeps running.", danger: true, confirm: "Stop" }) }, "■ Stop")
|
||||
: h("button", { class: "btn primary", type: "button", onclick: () => serverAction("start") }, "▶ Start"),
|
||||
h("button", { class: "btn", type: "button", disabled: starting, onclick: () => serverAction("restart", { title: "Restart the server?",
|
||||
text: "Settings (server.json), license and save game are loaded again. Players reconnect automatically.", confirm: "Restart" }) }, "⟳ Restart"));
|
||||
|
||||
// Warum läuft er nicht? Mit direktem Weg zur Lösung
|
||||
let problem = null;
|
||||
if (!running && s.lastError) {
|
||||
const fix = s.hasLicenseKey === false ? h("button", { class: "btn small", type: "button", onclick: () => go("settings") }, "Enter license key")
|
||||
: s.hasSave === false ? h("button", { class: "btn small", type: "button", onclick: () => go("world") }, "Upload save game")
|
||||
: h("button", { class: "btn small ghost", type: "button", onclick: () => go("console") }, "Open console");
|
||||
problem = h("div", { class: "notice " + (s.state === "error" ? "bad" : "") }, h("div", {}, h("b", {}, s.state === "error" ? "The server could not start" : "Note"), h("div", {}, s.lastError)), fix);
|
||||
}
|
||||
el.replaceChildren(buttons, problem);
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------ Einstellungen
|
||||
@@ -355,7 +376,7 @@ function renderSettings(page) {
|
||||
secret("licenseKey", "License key", c.licenseKeySet),
|
||||
secret("steamWebApiKey", "Steam Web API key", c.steamWebApiKeySet))),
|
||||
h("div", { class: "row" }, h("button", { class: "btn primary", type: "button", onclick: save }, "Save settings"),
|
||||
h("button", { class: "btn", type: "button", onclick: () => serverAction("restart", { title: "Restart the server now?",
|
||||
h("button", { class: "btn", type: "button", onclick: () => serverAction("restart", { title: "Restart / start the server now?",
|
||||
text: "Loads the saved settings. Players reconnect automatically.", confirm: "Restart" }) }, "Restart to apply")));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user