License grace period: keep running when the master is down or rejects the key (LicenseGraceHours, default 72 h), start with the last confirmed license, auto-retry; panel restart button (port/panel settings without program restart, login kept); tests
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
+35
-7
@@ -306,7 +306,32 @@ function renderControl(el, s) {
|
||||
: 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 ?? ""); // replaceChildren macht aus null den Text "null"
|
||||
// Lizenz gerade nicht bestätigt (Master aus o.ä.) - der Server läuft weiter, aber nur noch eine Weile
|
||||
const license = running && s.licenseWarning
|
||||
? h("div", { class: "notice" }, h("div", {}, h("b", {}, "License not confirmed"), h("div", {}, s.licenseWarning))) : null;
|
||||
el.replaceChildren(...[buttons, problem, license].filter(Boolean)); // null würde sonst als Text "null" erscheinen
|
||||
}
|
||||
|
||||
/** Panel neu starten (Port, Unterpfad, Proxy übernehmen) und danach die (ggf. neue) Adresse laden. */
|
||||
async function restartPanel() {
|
||||
const ok = await modal({ title: "Restart the panel?",
|
||||
text: "Applies the saved port and panel settings. If the game port changed, the game server restarts too. You stay logged in.",
|
||||
confirm: "Restart panel" });
|
||||
if (!ok) return;
|
||||
const r = await act(api("POST", "server/restart-panel"));
|
||||
if (!r) return;
|
||||
const target = r.url && r.url !== location.origin + location.pathname ? r.url : location.href;
|
||||
// Warten, bis das Panel wieder antwortet, dann neu laden
|
||||
const started = Date.now();
|
||||
const tryReload = async () => {
|
||||
try {
|
||||
const res = await fetch(new URL("api/session", target).href, { credentials: "include", mode: target.startsWith(location.origin) ? "same-origin" : "no-cors" });
|
||||
if (res.ok || res.type === "opaque") { location.href = target; return; }
|
||||
} catch { /* noch nicht da */ }
|
||||
if (Date.now() - started < 30000) setTimeout(tryReload, 1000);
|
||||
else location.href = target;
|
||||
};
|
||||
setTimeout(tryReload, 2500);
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------ Einstellungen
|
||||
@@ -376,7 +401,9 @@ function renderSettings(page) {
|
||||
h("div", { class: "section-head" }, h("h2", {}, "Saving")),
|
||||
h("div", { class: "section-body form-grid wide" },
|
||||
number("saveSyncMinutes", "Take the save game from a player every … minutes", c.saveSyncMinutes, { min: 0, max: 240, hint: "0 = only when someone joins" }),
|
||||
number("autosaveSeconds", "Write the world state every … seconds", c.autosaveSeconds, { min: 10, max: 3600 }))),
|
||||
number("autosaveSeconds", "Write the world state every … seconds", c.autosaveSeconds, { min: 10, max: 3600 }),
|
||||
number("licenseGraceHours", "Keep running without license confirmation for … hours", c.licenseGraceHours, { min: 1, max: 720,
|
||||
hint: "If the master server is down or rejects the key, the server keeps running this long" }))),
|
||||
h("div", { class: "section" },
|
||||
h("div", { class: "section-head" }, h("h2", {}, "Network & panel"), h("span", { class: "muted" }, "needs a restart")),
|
||||
h("div", { class: "section-body form-grid wide" },
|
||||
@@ -396,13 +423,14 @@ function renderSettings(page) {
|
||||
h("a", { href: "https://steamcommunity.com/dev/apikey", target: "_blank", rel: "noopener noreferrer" }, "Get your free key on Steam ↗"))))),
|
||||
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 / start the server now?",
|
||||
text: "Loads the saved settings. Players reconnect automatically.", confirm: "Restart" }) }, "Restart to apply")));
|
||||
text: "Loads the saved settings. Players reconnect automatically.", confirm: "Restart" }) }, "Restart to apply"),
|
||||
h("button", { class: "btn", type: "button", onclick: restartPanel }, "⟳ Restart panel")));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Warnung, wenn Spiel- oder Panel-Port geändert werden: Ports übernimmt erst ein Neustart des ganzen Programms
|
||||
* (der Restart-Knopf startet nur den Gameserver neu), danach ist das Panel nur noch am neuen Port - in Docker nur,
|
||||
* Warnung, wenn Spiel- oder Panel-Port geändert werden: Ports übernimmt "Panel neu starten" (oder ein Programmneustart),
|
||||
* danach ist das Panel nur noch am neuen Port - in Docker nur,
|
||||
* wenn der Port in docker-compose.yml freigegeben ist. Text direkt in beiden Sprachen (enthält Zahlen).
|
||||
*/
|
||||
function portChangeWarning(c, body) {
|
||||
@@ -419,8 +447,8 @@ function portChangeWarning(c, body) {
|
||||
if (port !== c.port)
|
||||
lines.push(de ? `Spieler verbinden sich dann über Port ${port}.` : `Players then connect on port ${port}.`);
|
||||
lines.push(de
|
||||
? "Das gilt erst nach einem Neustart des ganzen Programms (docker compose restart) - der Neustart-Knopf startet nur den Gameserver neu."
|
||||
: "This only applies after the whole program restarts (docker compose restart) - the Restart button only restarts the game server.");
|
||||
? "Das gilt, sobald du unten „Panel neu starten“ drückst (oder das Programm neu startest)."
|
||||
: "It applies as soon as you press “Restart panel” at the bottom (or restart the program).");
|
||||
const ports = [...new Set([port, newPanel])].map((p) => `"${p}:${p}/tcp"`).join(", ");
|
||||
lines.push(c.inDocker
|
||||
? (de ? `Docker: trag vorher unter ports in docker-compose.yml ${ports} ein, sonst sperrst du dich aus.`
|
||||
|
||||
Reference in New Issue
Block a user