simpleadmin-web 0.1.0: web panel for CS2-SimpleAdmin
This commit is contained in:
commit
4f38daf0e6
31 changed files with 6870 additions and 0 deletions
244
internal/web/public.go
Normal file
244
internal/web/public.go
Normal file
|
|
@ -0,0 +1,244 @@
|
|||
package web
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
"slices"
|
||||
"time"
|
||||
|
||||
"git.zio.sh/cs2/simpleadmin-web/internal/live"
|
||||
"git.zio.sh/cs2/simpleadmin-web/internal/steam"
|
||||
"git.zio.sh/cs2/simpleadmin-web/internal/store"
|
||||
)
|
||||
|
||||
// publicPenalty is a penalty with everything about staff and IPs removed.
|
||||
type publicPenalty struct {
|
||||
ID int64 `json:"id"`
|
||||
Kind string `json:"kind"`
|
||||
Type string `json:"type,omitempty"`
|
||||
Name string `json:"name"`
|
||||
SteamID string `json:"steamid"`
|
||||
Reason string `json:"reason"`
|
||||
Duration int `json:"duration"`
|
||||
Passed *int `json:"passed,omitempty"`
|
||||
Ends *time.Time `json:"ends,omitempty"`
|
||||
Created time.Time `json:"created"`
|
||||
Status string `json:"status"`
|
||||
}
|
||||
|
||||
func toPublic(ps []store.Penalty) []publicPenalty {
|
||||
out := make([]publicPenalty, 0, len(ps))
|
||||
for _, p := range ps {
|
||||
out = append(out, publicPenalty{ID: p.ID, Kind: p.Kind, Type: p.Type, Name: p.Name, SteamID: p.SteamID,
|
||||
Reason: p.Reason, Duration: p.Duration, Passed: p.Passed, Ends: p.Ends, Created: p.Created, Status: p.Status})
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
type livePlayer struct {
|
||||
live.Player
|
||||
Rank *rankView `json:"rank,omitempty"`
|
||||
// Staff-only fields, left empty on the public page.
|
||||
Tags []string `json:"tags,omitempty"`
|
||||
CanTarget bool `json:"canTarget,omitempty"`
|
||||
}
|
||||
|
||||
type serverView struct {
|
||||
Online bool `json:"online"`
|
||||
Hostname string `json:"hostname"`
|
||||
Map string `json:"map"`
|
||||
Count int `json:"count"`
|
||||
MaxPlayers int `json:"maxPlayers"`
|
||||
ScoreT int `json:"scoreT"`
|
||||
ScoreCT int `json:"scoreCt"`
|
||||
Warmup bool `json:"warmup"`
|
||||
StaffOnline int `json:"staffOnline"`
|
||||
Address string `json:"address"`
|
||||
Players []livePlayer `json:"players"`
|
||||
Updated time.Time `json:"updated"`
|
||||
}
|
||||
|
||||
func (s *Server) serverView(x *staffIndex, st live.Status) serverView {
|
||||
v := serverView{Online: st.Online, Hostname: st.Hostname, Map: st.Map, MaxPlayers: st.MaxPlayers,
|
||||
ScoreT: st.ScoreT, ScoreCT: st.ScoreCT, Warmup: st.Warmup, Address: s.cfg.ServerAddress,
|
||||
Players: []livePlayer{}, Updated: st.Updated}
|
||||
for _, p := range st.Players {
|
||||
lp := livePlayer{Player: p}
|
||||
if !p.Bot {
|
||||
v.Count++
|
||||
lp.Rank = s.rankOf(x, p.SteamID)
|
||||
if s.isStaff(x, p.SteamID) {
|
||||
v.StaffOnline++
|
||||
}
|
||||
}
|
||||
v.Players = append(v.Players, lp)
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
func (s *Server) publicServer(w http.ResponseWriter, r *http.Request) {
|
||||
x, err := s.staffIdx(r.Context())
|
||||
if err != nil {
|
||||
internal(w, r, err)
|
||||
return
|
||||
}
|
||||
writeJSON(w, http.StatusOK, s.serverView(x, s.poll.Current()))
|
||||
}
|
||||
|
||||
// penaltyQuery reads ?status=&q=&page= (and for comms, a type in place of the status).
|
||||
func penaltyQuery(r *http.Request, comms bool) (store.Query, int) {
|
||||
lim, off, num := page(r)
|
||||
q := store.Query{Search: r.URL.Query().Get("q"), Limit: lim, Offset: off}
|
||||
switch st := r.URL.Query().Get("status"); st {
|
||||
case store.StatusActive, store.StatusExpired, store.StatusLifted:
|
||||
q.Status = st
|
||||
case "GAG", "MUTE", "SILENCE":
|
||||
if comms {
|
||||
q.Status, q.Type = store.StatusActive, st
|
||||
}
|
||||
case "all":
|
||||
default:
|
||||
q.Status = store.StatusActive
|
||||
}
|
||||
return q, num
|
||||
}
|
||||
|
||||
type penaltyPage struct {
|
||||
list[publicPenalty]
|
||||
Active int `json:"active"`
|
||||
}
|
||||
|
||||
func (s *Server) publicBans(w http.ResponseWriter, r *http.Request) {
|
||||
q, num := penaltyQuery(r, false)
|
||||
items, total, err := s.st.Bans(r.Context(), q)
|
||||
if err != nil {
|
||||
internal(w, r, err)
|
||||
return
|
||||
}
|
||||
_, active, err := s.st.Bans(r.Context(), store.Query{Status: store.StatusActive, Limit: 1})
|
||||
if err != nil {
|
||||
internal(w, r, err)
|
||||
return
|
||||
}
|
||||
writeJSON(w, http.StatusOK, penaltyPage{newList(toPublic(items), total, num), active})
|
||||
}
|
||||
|
||||
func (s *Server) publicComms(w http.ResponseWriter, r *http.Request) {
|
||||
q, num := penaltyQuery(r, true)
|
||||
items, total, err := s.st.Comms(r.Context(), q)
|
||||
if err != nil {
|
||||
internal(w, r, err)
|
||||
return
|
||||
}
|
||||
_, active, err := s.st.Comms(r.Context(), store.Query{Status: store.StatusActive, Limit: 1})
|
||||
if err != nil {
|
||||
internal(w, r, err)
|
||||
return
|
||||
}
|
||||
writeJSON(w, http.StatusOK, penaltyPage{newList(toPublic(items), total, num), active})
|
||||
}
|
||||
|
||||
type memberView struct {
|
||||
SteamID string `json:"steamid"`
|
||||
Name string `json:"name"`
|
||||
Online bool `json:"online"`
|
||||
Ends *time.Time `json:"ends,omitempty"`
|
||||
LastSeen *time.Time `json:"lastSeen,omitempty"`
|
||||
}
|
||||
|
||||
type groupView struct {
|
||||
ID int64 `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Label string `json:"label"`
|
||||
Color string `json:"color"`
|
||||
About string `json:"about,omitempty"`
|
||||
Supporter bool `json:"supporter"`
|
||||
Immunity int `json:"immunity,omitempty"`
|
||||
Flags []string `json:"flags,omitempty"`
|
||||
Members []memberView `json:"members"`
|
||||
}
|
||||
|
||||
// groupViews lists groups by immunity with their members. Staff get flags and expiry dates.
|
||||
func (s *Server) groupViews(x *staffIndex, st live.Status, forStaff bool) []groupView {
|
||||
out := make([]groupView, 0, len(x.groups))
|
||||
for i, g := range x.groups {
|
||||
r := s.site.rank(g.Name, i)
|
||||
gv := groupView{ID: g.ID, Name: g.Name, Label: r.Label, Color: r.Color, About: r.About,
|
||||
Supporter: r.Supporter, Immunity: g.Immunity, Members: []memberView{}}
|
||||
if forStaff {
|
||||
gv.Flags = g.Flags
|
||||
} else {
|
||||
gv.Immunity = 0
|
||||
}
|
||||
for _, a := range x.admins {
|
||||
if x.primaryGroup(&a) != g.Name {
|
||||
continue
|
||||
}
|
||||
_, online := st.Find(a.SteamID)
|
||||
m := memberView{SteamID: a.SteamID, Name: a.Name, Online: online}
|
||||
if forStaff {
|
||||
m.Ends = a.Ends
|
||||
}
|
||||
gv.Members = append(gv.Members, m)
|
||||
}
|
||||
slices.SortStableFunc(gv.Members, func(a, b memberView) int {
|
||||
if a.Online != b.Online {
|
||||
if a.Online {
|
||||
return -1
|
||||
}
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
})
|
||||
out = append(out, gv)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func (s *Server) publicStaff(w http.ResponseWriter, r *http.Request) {
|
||||
x, err := s.staffIdx(r.Context())
|
||||
if err != nil {
|
||||
internal(w, r, err)
|
||||
return
|
||||
}
|
||||
st := s.poll.Current()
|
||||
groups := s.groupViews(x, st, false)
|
||||
// Hide empty ranks from the public.
|
||||
groups = slices.DeleteFunc(groups, func(g groupView) bool { return len(g.Members) == 0 })
|
||||
writeJSON(w, http.StatusOK, map[string]any{"groups": groups, "staffOnline": s.serverView(x, st).StaffOnline})
|
||||
}
|
||||
|
||||
func (s *Server) publicPlayer(w http.ResponseWriter, r *http.Request) {
|
||||
steamid := r.PathValue("steamid")
|
||||
if !steam.ValidID(steamid) {
|
||||
fail(w, http.StatusBadRequest, "That isn't a SteamID64.")
|
||||
return
|
||||
}
|
||||
d, err := s.st.Player(r.Context(), steamid)
|
||||
if errors.Is(err, store.ErrNotFound) {
|
||||
d = store.PlayerDetail{SteamID: steamid}
|
||||
} else if err != nil {
|
||||
internal(w, r, err)
|
||||
return
|
||||
}
|
||||
bans, _, err := s.st.Bans(r.Context(), store.Query{SteamID: steamid, Limit: 200})
|
||||
if err != nil {
|
||||
internal(w, r, err)
|
||||
return
|
||||
}
|
||||
comms, _, err := s.st.Comms(r.Context(), store.Query{SteamID: steamid, Limit: 200})
|
||||
if err != nil {
|
||||
internal(w, r, err)
|
||||
return
|
||||
}
|
||||
x, err := s.staffIdx(r.Context())
|
||||
if err != nil {
|
||||
internal(w, r, err)
|
||||
return
|
||||
}
|
||||
_, online := s.poll.Current().Find(steamid)
|
||||
writeJSON(w, http.StatusOK, map[string]any{
|
||||
"steamid": steamid, "name": d.Name, "online": online, "rank": s.rankOf(x, steamid),
|
||||
"bans": toPublic(bans), "comms": toPublic(comms),
|
||||
})
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue