Names on offline penalties, mobile menu close, player drawer tidy, IP-ban bypass flag

Bans and comms saved without a player name (SimpleAdmin's offline css_addban etc.) show the
player's latest name from sa_players_ips, and the panel writes that name onto new rows it claims.
The mobile sidebar closes from a Close button, the backdrop or Escape, since it covers the Menu
button. The public player drawer drops "Right now" and "Free to play and talk." and puts the
Steam profile link after the SteamID. Adds @css/bypassipban to the permission checkboxes.
This commit is contained in:
Astra 2026-09-28 11:23:14 +01:00
parent 657ead4c5a
commit 6324fc7f81
7 changed files with 45 additions and 16 deletions

View file

@ -183,7 +183,7 @@ func (s *Server) addPenalty(w http.ResponseWriter, r *http.Request, id *Identity
if req.Kind == "comm" {
commType = req.Type
}
claimed := s.st.ClaimConsoleRow(ctx, table, before, req.SteamID, commType, id.SteamID, id.Name)
claimed := s.st.ClaimConsoleRow(ctx, table, before, req.SteamID, commType, id.SteamID, id.Name, s.st.LatestName(ctx, req.SteamID))
go s.poll.Refresh()
slog.Info("penalty issued", "admin", id.SteamID, "kind", req.Kind, "type", req.Type, "player", req.SteamID, "minutes", req.Duration)
if claimed == 0 {

View file

@ -281,6 +281,7 @@ var knownFlags = [][3]string{
{"@css/config", "Config", "Exec config files"},
{"@css/rcon", "RCON", "Run raw console commands"},
{"@css/cheats", "Cheats", "sv_cheats-protected commands"},
{"@css/bypassipban", "Bypass IP bans", "Joins even when their IP, or another account on it, is banned"},
{"@css/vip", "VIP", "VIP perks in other plugins"},
{"@css/root", "Root", "Every permission, and managing ranks and staff"},
}

View file

@ -12,6 +12,7 @@
<body>
<div class="app">
<nav class="nav" id="nav" aria-label="Main">
<button class="btn sm ghost nav-close" type="button" id="nav-close" aria-label="Close menu">Close</button>
<a class="brand" href="#overview">
<svg class="brand-mark" viewBox="0 0 32 32" aria-hidden="true">
<path d="M16 2 28 7v9c0 7-5 12-12 14C9 28 4 23 4 16V7z" fill="none" stroke="#fe113d" stroke-width="2.5"/>
@ -35,6 +36,7 @@
<button class="btn sm ghost" type="button" id="signout">Sign out</button>
</div>
</nav>
<div class="nav-backdrop" id="nav-backdrop" aria-hidden="true"></div>
<div>
<div class="mobile-bar">

View file

@ -648,12 +648,18 @@ function renderNav() {
$("#nav-links").innerHTML = PAGES.map((p) => `<li><a class="link" href="#${p.id}" ${p.id === state.page ? 'aria-current="page"' : ""}>${p.label}${count[p.id] !== undefined && count[p.id] !== "" ? `<span class="count">${esc(count[p.id])}</span>` : ""}</a></li>`).join("");
}
// The mobile sidebar covers the Menu button, so it closes from its own Close button, the
// backdrop, Escape, or picking a page.
function setNav(open) {
$("#nav").classList.toggle("open", open);
$("#menu-btn").setAttribute("aria-expanded", String(open));
}
function route() {
const page = location.hash.slice(1);
state.page = RENDER[page] ? page : "overview";
renderNav();
$("#nav").classList.remove("open");
$("#menu-btn").setAttribute("aria-expanded", "false");
setNav(false);
if (!$("#view").innerHTML) $("#view").innerHTML = loading();
RENDER[state.page]();
}
@ -676,8 +682,11 @@ document.addEventListener("click", (e) => {
const t = e.target;
if (t.closest("#menu-btn")) {
const open = $("#nav").classList.toggle("open");
$("#menu-btn").setAttribute("aria-expanded", String(open));
setNav(!$("#nav").classList.contains("open"));
return;
}
if (t.closest("#nav-close, #nav-backdrop")) {
setNav(false);
return;
}
if (t.closest("#signout")) {
@ -806,6 +815,7 @@ document.addEventListener("submit", async (e) => {
});
document.addEventListener("keydown", (e) => {
if (e.key === "Escape" && $("#nav").classList.contains("open")) { setNav(false); return; }
if (e.key === "Escape" && $("#drawer-root").innerHTML && !$("#modal").open) closeDrawer();
});

View file

@ -196,15 +196,12 @@ async function openPlayer(sid) {
<aside class="drawer" role="dialog" aria-modal="true" aria-label="${esc(name)}">
<div class="drawer-head">
<span class="avatar lg">${initial(name)}</span>
<div><h2>${esc(name)}</h2><span class="sid">${esc(p.steamid)}</span>${p.rank ? `<div>${rankTag(p.rank)}</div>` : ""}</div>
<div><h2>${esc(name)}</h2><span class="sid">${esc(p.steamid)} · <a href="${steamProfile(p.steamid)}" target="_blank" rel="noopener">Steam profile</a></span>${p.rank ? `<div>${rankTag(p.rank)}</div>` : ""}</div>
<button class="close" type="button" data-close aria-label="Close">×</button>
</div>
<section>
<h3>Right now</h3>
${now.length ? `<div class="chips">${now.map((h) => `<span class="tag ${h.kind === "ban" ? "red" : "amber"}">${h.kind === "ban" ? "Banned" : COMM_HELP[h.type]}${h.duration ? `, ${fmtLeft(served(h).left)}` : ", permanently"}</span>`).join("")}</div>`
: `<p class="muted" style="margin:0">${p.online ? "On the server, free to play and talk." : "Free to play and talk."}</p>`}
<p style="margin:12px 0 0"><a href="${steamProfile(p.steamid)}" target="_blank" rel="noopener">Steam profile</a></p>
</section>
${now.length || p.online ? `<section>
<div class="chips">${p.online ? '<span class="tag">On the server</span>' : ""}${now.map((h) => `<span class="tag ${h.kind === "ban" ? "red" : "amber"}">${h.kind === "ban" ? "Banned" : COMM_HELP[h.type]}${h.duration ? `, ${fmtLeft(served(h).left)}` : ", permanently"}</span>`).join("")}</div>
</section>` : ""}
<section>
<h3>History</h3>
${history.length ? `<ul class="history">${history.map((h) => `<li>

View file

@ -772,6 +772,7 @@ dialog::backdrop { background: rgba(5, 6, 9, 0.65); }
.empty { padding: 40px 18px; text-align: center; color: var(--muted); }
.mobile-bar { display: none; }
.nav-close, .nav-backdrop { display: none; }
/* ---------- Responsive ---------- */
@ -790,6 +791,14 @@ dialog::backdrop { background: rgba(5, 6, 9, 0.65); }
transition: transform 0.2s ease;
}
.nav.open { transform: none; box-shadow: 0 0 60px rgba(0, 0, 0, 0.6); }
.nav-close { display: inline-flex; align-self: flex-end; margin-bottom: -12px; }
.nav.open ~ .nav-backdrop {
display: block;
position: fixed;
inset: 0;
z-index: 29;
background: rgba(0, 0, 0, 0.5);
}
.mobile-bar {
display: flex;
align-items: center;