a1331212cb
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.
112 lines
4.5 KiB
JavaScript
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;
|
|
}
|
|
});
|
|
}
|