Files
brass-and-sigil/server/wwwroot/modules/danger.js
T
Matt Sijbers a1331212cb Initial commit: Brass & Sigil monorepo
Self-hosted Minecraft modpack distribution + administration system.

- launcher/  Avalonia 12 desktop client; single-file win-x64 publish.
             Microsoft auth via XboxAuthNet, manifest+SHA-1 mod sync,
             portable install path, sidecar settings.
- server/    brass-sigil-server daemon (.NET 8, linux-x64). Wraps the
             MC subprocess, embedded Kestrel admin panel with cookie
             auth + rate limiting, RCON bridge, scheduled backups,
             BlueMap CLI integration with player markers + skin proxy,
             friend-side whitelist request flow, world wipe with seed
             selection (keep current / random / custom).
- pack/      pack.lock.json (Modrinth + manual CurseForge entries),
             data-only tweak source under tweaks/, build outputs in
             overrides/ (gitignored).
- scripts/   Build-Pack / Build-Tweaks / Update-Pack / Check-Updates
             plus Deploy-Brass.ps1 unified one-shot deploy with
             version-bump pre-flight and daemon-state detection.
2026-05-05 00:19:05 +01:00

112 lines
4.5 KiB
JavaScript

// Danger zone -- destructive operations.
// Currently: world wipe. Always type-to-confirm to prevent accidental clicks.
"use strict";
export function setupDanger() {
const btn = document.getElementById("wipeBtn");
const cb = document.getElementById("wipeBackup");
const msg = document.getElementById("wipeMsg");
const seedDisplay = document.getElementById("wipeCurrentSeed");
const customInput = document.getElementById("wipeCustomSeed");
if (!btn) return;
// Enable the custom-seed text field only when its radio is selected.
document.querySelectorAll('input[name="wipeSeedMode"]').forEach(radio => {
radio.addEventListener("change", () => {
const mode = document.querySelector('input[name="wipeSeedMode"]:checked')?.value;
customInput.disabled = (mode !== "custom");
if (mode === "custom") customInput.focus();
});
});
// Fetch current seed each time the wipe modal becomes visible. Watching
// the wipe section's ancestor modal works without coupling to the modal
// module's open/close API.
const refreshSeed = async () => {
seedDisplay.textContent = "loading...";
try {
const res = await fetch("/api/world/seed");
const body = await res.json();
seedDisplay.textContent = body.seed
? body.seed
: "(unknown -- server stopped or seed not set)";
} catch (e) {
seedDisplay.textContent = "(failed to read)";
}
};
// Refresh on first load + whenever the modal becomes visible. Modal markup
// uses a wrapping div with "[hidden]" attr, so we observe attribute changes.
refreshSeed();
const modal = btn.closest(".modal");
if (modal) {
new MutationObserver(muts => {
for (const m of muts) {
if (m.attributeName === "hidden" && !modal.hasAttribute("hidden")) {
refreshSeed();
}
}
}).observe(modal, { attributes: true });
}
btn.addEventListener("click", async () => {
msg.className = "acct-msg";
msg.textContent = "";
const mode = document.querySelector('input[name="wipeSeedMode"]:checked')?.value || "random";
const customSeed = (customInput.value || "").trim();
if (mode === "custom" && !customSeed) {
msg.textContent = "Custom seed selected but the field is empty.";
return;
}
// Build a confirmation prompt that reflects the chosen seed strategy
// so the user sees exactly what's about to happen.
let seedNote = "";
if (mode === "keep") seedNote = `Same seed (${seedDisplay.textContent}) will be reused.\n`;
else if (mode === "custom") seedNote = `Seed will be set to: ${customSeed}\n`;
else seedNote = "A new random seed will be generated.\n";
const typed = prompt(
"Type WIPE (uppercase, exactly) to confirm world wipe.\n" +
"Server will stop, world will be replaced, server will restart.\n\n" +
seedNote
);
if (typed !== "WIPE") {
if (typed != null) msg.textContent = "Confirmation didn't match -- nothing wiped.";
return;
}
btn.disabled = true;
msg.textContent = "Wiping...";
try {
const res = await fetch("/api/world/wipe", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
confirm: typed,
backup: cb.checked,
seedMode: mode,
customSeed: mode === "custom" ? customSeed : null,
}),
});
const body = await res.json().catch(() => ({}));
if (!res.ok || body.ok === false) {
msg.textContent = body.error || `Error ${res.status}`;
return;
}
msg.className = "acct-msg ok";
const parts = ["World wiped."];
if (body.seedUsed) parts.push(`Seed: ${body.seedUsed}.`);
if (body.backupName) parts.push(`Backup: ${body.backupName}.`);
parts.push("Server restarting...");
msg.textContent = parts.join(" ");
// Refresh the seed display so user sees the new value once MC is back.
setTimeout(refreshSeed, 5000);
} catch (e) {
msg.textContent = e.message;
} finally {
btn.disabled = false;
}
});
}