Public rank change log: sa_rank_log, written on rank changes, listed on the Staff page
This commit is contained in:
parent
861692c534
commit
611b79cdba
11 changed files with 346 additions and 17 deletions
|
|
@ -517,7 +517,7 @@ func (s *Server) deleteGroup(w http.ResponseWriter, r *http.Request, id *Identit
|
|||
fail(w, http.StatusForbidden, "You can't delete your own rank.")
|
||||
return
|
||||
}
|
||||
if err := s.st.DeleteGroup(r.Context(), g.ID); err != nil {
|
||||
if err := s.st.DeleteGroup(r.Context(), id.actor(), g.ID); err != nil {
|
||||
internal(w, r, err)
|
||||
return
|
||||
}
|
||||
|
|
@ -576,7 +576,7 @@ func (s *Server) applyRank(w http.ResponseWriter, r *http.Request, id *Identity,
|
|||
if name == "" {
|
||||
name = req.SteamID
|
||||
}
|
||||
if err := s.st.SetAdminGroup(r.Context(), req.SteamID, name, req.Group, grp.Immunity, ends); err != nil {
|
||||
if err := s.st.SetAdminGroup(r.Context(), id.actor(), req.SteamID, name, req.Group, grp.Immunity, ends); err != nil {
|
||||
internal(w, r, err)
|
||||
return
|
||||
}
|
||||
|
|
@ -618,7 +618,7 @@ func (s *Server) removeStaff(w http.ResponseWriter, r *http.Request, id *Identit
|
|||
if !s.checkStaffTarget(w, id, x, steamid) {
|
||||
return
|
||||
}
|
||||
if err := s.st.RemoveAdmin(r.Context(), steamid); err != nil {
|
||||
if err := s.st.RemoveAdmin(r.Context(), id.actor(), steamid); err != nil {
|
||||
internal(w, r, err)
|
||||
return
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -928,6 +928,7 @@ dialog::backdrop { background: rgba(5, 6, 9, 0.65); }
|
|||
.crew small { color: var(--muted); font-size: 13px; }
|
||||
|
||||
.crew-sep { margin: 28px 0 12px; }
|
||||
.rank-move { display: flex; flex-wrap: wrap; align-items: center; gap: 6px 8px; }
|
||||
|
||||
@media (max-width: 820px) {
|
||||
.topbar-inner { gap: 12px; padding: 0 16px; }
|
||||
|
|
|
|||
|
|
@ -208,6 +208,39 @@ func (s *Server) publicStaff(w http.ResponseWriter, r *http.Request) {
|
|||
writeJSON(w, http.StatusOK, map[string]any{"groups": groups, "staffOnline": s.serverView(x, st).StaffOnline})
|
||||
}
|
||||
|
||||
type rankChangeView struct {
|
||||
store.RankChange
|
||||
Old *rankView `json:"old,omitempty"`
|
||||
New *rankView `json:"new,omitempty"`
|
||||
}
|
||||
|
||||
// publicRankChanges is the promotions and demotions list, with each rank's label and colour.
|
||||
func (s *Server) publicRankChanges(w http.ResponseWriter, r *http.Request) {
|
||||
lim, off, num := page(r)
|
||||
items, total, err := s.st.RankChanges(r.Context(), lim, off)
|
||||
if err != nil {
|
||||
internal(w, r, err)
|
||||
return
|
||||
}
|
||||
x, err := s.staffIdx(r.Context())
|
||||
if err != nil {
|
||||
internal(w, r, err)
|
||||
return
|
||||
}
|
||||
view := func(g string) *rankView {
|
||||
if g == "" {
|
||||
return nil
|
||||
}
|
||||
rk := s.site.rank(g, x.groupIndex(g))
|
||||
return &rankView{Name: g, Label: rk.Label, Color: rk.Color, Supporter: rk.Supporter}
|
||||
}
|
||||
out := make([]rankChangeView, 0, len(items))
|
||||
for _, c := range items {
|
||||
out = append(out, rankChangeView{RankChange: c, Old: view(c.OldRank), New: view(c.NewRank)})
|
||||
}
|
||||
writeJSON(w, http.StatusOK, newList(out, total, num))
|
||||
}
|
||||
|
||||
func (s *Server) publicPlayer(w http.ResponseWriter, r *http.Request) {
|
||||
steamid := r.PathValue("steamid")
|
||||
if !steam.ValidID(steamid) {
|
||||
|
|
|
|||
|
|
@ -89,6 +89,7 @@ func (s *Server) Handler() http.Handler {
|
|||
mux.HandleFunc("GET /api/public/bans", s.publicBans)
|
||||
mux.HandleFunc("GET /api/public/comms", s.publicComms)
|
||||
mux.HandleFunc("GET /api/public/staff", s.publicStaff)
|
||||
mux.HandleFunc("GET /api/public/ranks", s.publicRankChanges)
|
||||
mux.HandleFunc("GET /api/public/players/{steamid}", s.publicPlayer)
|
||||
mux.HandleFunc("GET /avatar/{steamid}", s.avatar)
|
||||
|
||||
|
|
|
|||
|
|
@ -124,6 +124,9 @@ func (id *Identity) Has(flag string) bool {
|
|||
return slices.Contains(id.Flags, "@css/root") || slices.Contains(id.Flags, flag)
|
||||
}
|
||||
|
||||
// actor is who the store credits for a change.
|
||||
func (id *Identity) actor() store.Actor { return store.Actor{SteamID: id.SteamID, Name: id.Name} }
|
||||
|
||||
// CanTarget follows CounterStrikeSharp: you can act on players whose immunity isn't above yours.
|
||||
// Nobody acts on themselves through the panel.
|
||||
func (id *Identity) CanTarget(x *staffIndex, steamid string) bool {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue