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.
77 lines
3.0 KiB
JavaScript
77 lines
3.0 KiB
JavaScript
// Live log streaming via Server-Sent Events + command-input wiring.
|
|
// EventSource gives us instant push (no 1-second polling lag) and reconnects
|
|
// automatically if the connection drops.
|
|
"use strict";
|
|
|
|
import { api, apiJson, escapeHtml } from "./api.js";
|
|
import { state } from "./state.js";
|
|
|
|
const consoleEl = () => document.getElementById("console");
|
|
|
|
export function setupConsole() {
|
|
consoleEl().textContent = "Connecting to server log…";
|
|
|
|
const es = new EventSource("/api/logs/stream");
|
|
let firstEvent = true;
|
|
es.addEventListener("log", e => {
|
|
if (firstEvent) { consoleEl().textContent = ""; firstEvent = false; }
|
|
try {
|
|
const d = JSON.parse(e.data);
|
|
const ts = new Date(d.t).toLocaleTimeString();
|
|
const div = document.createElement("div");
|
|
if (d.e) div.className = "err";
|
|
div.textContent = `[${ts}] ${d.m}`;
|
|
consoleEl().appendChild(div);
|
|
consoleEl().scrollTop = consoleEl().scrollHeight;
|
|
// Trim very old lines so the DOM doesn't grow unbounded
|
|
while (consoleEl().childNodes.length > 5000) {
|
|
consoleEl().removeChild(consoleEl().firstChild);
|
|
}
|
|
// Re-broadcast so other modules (e.g. pregen) can react to log lines
|
|
// without opening a second SSE connection.
|
|
document.dispatchEvent(new CustomEvent("serverlog", { detail: d }));
|
|
} catch {}
|
|
});
|
|
es.onerror = () => {
|
|
// EventSource will retry automatically.
|
|
};
|
|
|
|
// Command input
|
|
const cmdInput = document.getElementById("cmdInput");
|
|
document.getElementById("cmdSend").addEventListener("click", sendCommand);
|
|
cmdInput.addEventListener("keydown", onCmdKeyDown);
|
|
}
|
|
|
|
async function sendCommand() {
|
|
const cmdInput = document.getElementById("cmdInput");
|
|
const v = cmdInput.value.trim();
|
|
if (!v) return;
|
|
try {
|
|
await apiJson("/api/command", { command: v });
|
|
state.cmdHistory.push(v);
|
|
state.cmdHistoryIdx = state.cmdHistory.length;
|
|
cmdInput.value = "";
|
|
cmdInput.dispatchEvent(new Event("input")); // refresh ghost text
|
|
} catch (e) { alert(e.message); }
|
|
}
|
|
|
|
function onCmdKeyDown(e) {
|
|
const cmdInput = document.getElementById("cmdInput");
|
|
if (e.key === "Enter") {
|
|
sendCommand();
|
|
} else if (e.key === "ArrowUp") {
|
|
if (state.cmdHistory.length === 0) return;
|
|
e.preventDefault();
|
|
state.cmdHistoryIdx = Math.max(0, state.cmdHistoryIdx - 1);
|
|
cmdInput.value = state.cmdHistory[state.cmdHistoryIdx] || "";
|
|
cmdInput.dispatchEvent(new Event("input"));
|
|
} else if (e.key === "ArrowDown") {
|
|
if (state.cmdHistory.length === 0) return;
|
|
e.preventDefault();
|
|
state.cmdHistoryIdx = Math.min(state.cmdHistory.length, state.cmdHistoryIdx + 1);
|
|
cmdInput.value = state.cmdHistory[state.cmdHistoryIdx] || "";
|
|
cmdInput.dispatchEvent(new Event("input"));
|
|
}
|
|
// Note: Tab is handled by the autocomplete module's keydown listener.
|
|
}
|