Public rank change log: sa_rank_log, written on rank changes, listed on the Staff page

This commit is contained in:
Astra 2026-09-29 16:56:37 +01:00
parent 861692c534
commit 611b79cdba
11 changed files with 346 additions and 17 deletions

View file

@ -1,5 +1,5 @@
// Public, read-only site: the live server, bans, gags/mutes and staff. The API leaves out admin
// names and IPs; nothing here can change anything.
// Public, read-only site: the live server, bans, gags/mutes, and staff with their rank changes. The
// API leaves out who issued penalties and IPs; nothing here can change anything.
const PAGES = [
{ id: "server", label: "Server" },
@ -175,7 +175,46 @@ async function staff() {
<div><h1>Staff</h1><p class="sub">${online ? `${online} staff ${online === 1 ? "is" : "are"} on the server right now.` : "No staff are on the server right now."}</p></div>
</div>
${staffGroups.length ? `<section class="panel crew-panel">${staffGroups.map(rankRow).join("")}</section>` : '<div class="empty">No staff are listed yet.</div>'}
${supporters.length ? `<h2 class="crew-sep">Supporters</h2><section class="panel crew-panel">${supporters.map(rankRow).join("")}</section>` : ""}`;
${supporters.length ? `<h2 class="crew-sep">Supporters</h2><section class="panel crew-panel">${supporters.map(rankRow).join("")}</section>` : ""}
<h2 class="crew-sep">Rank changes</h2>
<section class="panel"><div class="table-wrap" id="rank-log">${loading()}</div></section>`;
loadRankLog();
}
const RANK_CHANGE = {
added: ["Joined", "green"],
promoted: ["Promoted", "green"],
demoted: ["Demoted", "amber"],
changed: ["Moved", ""],
removed: ["Removed", "red"],
};
async function loadRankLog() {
const box = $("#rank-log");
if (!box) return;
const want = state.num;
loadRankLog.want = want;
let list;
try { list = await api("/api/public/ranks" + qs({ page: state.num })); } catch (err) { box.innerHTML = errorBox(err); return; }
if (loadRankLog.want !== want || !$("#rank-log")) return;
if (!list.items.length) { box.innerHTML = '<div class="empty">No rank changes recorded yet.</div>'; return; }
box.innerHTML = `<table>
<thead><tr><th>Player</th><th>Change</th><th class="hide-sm">By</th><th>When</th></tr></thead>
<tbody>
${list.items.map((c) => {
const [label, tone] = RANK_CHANGE[c.kind] || [c.kind, ""];
const by = c.adminSteamid
? `<a href="${steamProfile(c.adminSteamid)}" target="_blank" rel="noopener">${esc(c.admin)}</a>`
: esc(c.admin);
return `<tr class="clickable" data-player="${esc(c.steamid)}">
<td><div class="who"><div><b>${esc(c.name || "Unknown")}</b><span class="sid">${esc(c.steamid)}</span></div></div></td>
<td><div class="rank-move"><span class="tag ${tone}">${label}</span>${rankTag(c.old)}<span class="faint" aria-label="to">→</span>${rankTag(c.new)}</div></td>
<td class="hide-sm">${by}${c.source === "game" ? ' <span class="faint">in game</span>' : ""}</td>
<td>${fmtDate(c.created)}</td>
</tr>`;
}).join("")}
</tbody>
</table>` + pager(list);
}
/* ---------- Player lookup (read-only) ---------- */
@ -243,8 +282,9 @@ document.addEventListener("click", (e) => {
return;
}
const pg = t.closest("[data-page]");
if (pg) { state.num = Number(pg.dataset.page); loadResults(); return; }
if (pg) { state.num = Number(pg.dataset.page); (state.page === "staff" ? loadRankLog : loadResults)(); return; }
if (t.closest("[data-close]")) { closeDrawer(); return; }
if (t.closest("a[href]")) return;
const row = t.closest("[data-player]");
if (row) openPlayer(row.dataset.player);
});