Files
brass-and-sigil/server/wwwroot/modules/console.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

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.
}