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.
This commit is contained in:
@@ -0,0 +1,117 @@
|
||||
// Status / players / whitelist sidebar panels. Polled (not streamed) because the
|
||||
// data they show changes infrequently. Logs use SSE -- see console.js.
|
||||
"use strict";
|
||||
|
||||
import { api, escapeHtml } from "./api.js";
|
||||
import { state, rebuildKnownPlayers } from "./state.js";
|
||||
|
||||
export async function tickStatus() {
|
||||
const pill = document.getElementById("statusPill");
|
||||
const text = document.getElementById("statusText");
|
||||
const memEl = document.getElementById("memUsage");
|
||||
const memBar = document.getElementById("memBar");
|
||||
const cpuCur = document.getElementById("cpuCurrent");
|
||||
const cpuBar = document.getElementById("cpuBar");
|
||||
const cpuMax = document.getElementById("cpuMax");
|
||||
const cpuAvg = document.getElementById("cpuAvg");
|
||||
|
||||
function renderResources(s) {
|
||||
if (s.memoryBytes != null) {
|
||||
const usedGB = s.memoryBytes / (1024 ** 3);
|
||||
const maxGB = s.memoryMaxMB ? s.memoryMaxMB / 1024 : null;
|
||||
memEl.textContent = maxGB
|
||||
? `${usedGB.toFixed(2)} / ${maxGB.toFixed(1)} GB`
|
||||
: `${usedGB.toFixed(2)} GB`;
|
||||
memBar.style.width = maxGB ? `${Math.min(100, (usedGB / maxGB) * 100)}%` : "0%";
|
||||
} else {
|
||||
memEl.textContent = "--";
|
||||
memBar.style.width = "0%";
|
||||
}
|
||||
if (s.cpu) {
|
||||
cpuCur.textContent = `${s.cpu.current.toFixed(1)} %`;
|
||||
cpuBar.style.width = `${Math.min(100, s.cpu.current)}%`;
|
||||
cpuMax.textContent = `${s.cpu.max.toFixed(1)}%`;
|
||||
cpuAvg.textContent = `${s.cpu.avg.toFixed(1)}%`;
|
||||
} else {
|
||||
cpuCur.textContent = "--";
|
||||
cpuBar.style.width = "0%";
|
||||
cpuMax.textContent = "--";
|
||||
cpuAvg.textContent = "--";
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
const s = await api("/api/status");
|
||||
if (s.running) {
|
||||
pill.className = "status-pill online";
|
||||
text.textContent = "Online";
|
||||
document.getElementById("pid").textContent = s.pid ?? "--";
|
||||
const secs = Math.floor(s.uptime ?? 0);
|
||||
const h = Math.floor(secs / 3600), m = Math.floor((secs % 3600) / 60);
|
||||
document.getElementById("uptime").textContent = `${h}h ${m}m`;
|
||||
renderResources(s);
|
||||
} else {
|
||||
pill.className = "status-pill offline";
|
||||
text.textContent = "Offline";
|
||||
document.getElementById("pid").textContent = "--";
|
||||
document.getElementById("uptime").textContent = "--";
|
||||
renderResources({ memoryBytes: null, cpu: null, memoryMaxMB: null });
|
||||
}
|
||||
const pv = s.packVersion;
|
||||
document.getElementById("packVersion").textContent = pv?.name ? `${pv.name} v${pv.version}` : "--";
|
||||
|
||||
// World size -- even when server is offline we can still report disk usage.
|
||||
const worldEl = document.getElementById("worldSize");
|
||||
if (worldEl) {
|
||||
const b = s.worldSizeBytes;
|
||||
if (b == null || b === 0) worldEl.textContent = "--";
|
||||
else if (b < 1024 * 1024) worldEl.textContent = `${(b / 1024).toFixed(0)} KB`;
|
||||
else if (b < 1024 * 1024 * 1024) worldEl.textContent = `${(b / (1024 * 1024)).toFixed(1)} MB`;
|
||||
else worldEl.textContent = `${(b / (1024 * 1024 * 1024)).toFixed(2)} GB`;
|
||||
}
|
||||
} catch {
|
||||
pill.className = "status-pill offline";
|
||||
text.textContent = "Disconnected";
|
||||
}
|
||||
}
|
||||
|
||||
export async function tickPlayers() {
|
||||
try {
|
||||
const p = await api("/api/players");
|
||||
state.onlinePlayers = (p.players || []).slice();
|
||||
document.getElementById("playerCount").textContent = p.online >= 0 ? p.online : "?";
|
||||
const list = document.getElementById("players");
|
||||
if (state.onlinePlayers.length === 0) {
|
||||
list.innerHTML = '<li class="empty-state">No-one online</li>';
|
||||
} else {
|
||||
list.innerHTML = state.onlinePlayers.map(n => `<li>${escapeHtml(n)}<span></span></li>`).join("");
|
||||
}
|
||||
} catch {}
|
||||
rebuildKnownPlayers();
|
||||
}
|
||||
|
||||
export async function tickWhitelist() {
|
||||
try {
|
||||
const w = await api("/api/whitelist");
|
||||
state.whitelistedPlayers = (w.players || []).slice();
|
||||
const list = document.getElementById("whitelist");
|
||||
if (state.whitelistedPlayers.length === 0) {
|
||||
list.innerHTML = '<li class="empty-state">No players whitelisted yet</li>';
|
||||
} else {
|
||||
list.innerHTML = state.whitelistedPlayers.map(n =>
|
||||
`<li>${escapeHtml(n)}<button data-name="${escapeHtml(n)}" class="wl-remove">Remove</button></li>`
|
||||
).join("");
|
||||
}
|
||||
} catch {}
|
||||
rebuildKnownPlayers();
|
||||
}
|
||||
|
||||
// MC takes ~1-2 s to look up a UUID via Mojang and write whitelist.json.
|
||||
// Refresh shortly after a user-triggered add/remove instead of waiting for the
|
||||
// 30-second polling tick.
|
||||
let pendingRefresh;
|
||||
export function refreshWhitelistSoon() {
|
||||
clearTimeout(pendingRefresh);
|
||||
pendingRefresh = setTimeout(tickWhitelist, 1500);
|
||||
setTimeout(tickWhitelist, 4000); // belt-and-braces if Mojang is slow
|
||||
}
|
||||
Reference in New Issue
Block a user