Playtime from PlaytimeHistory's database (SAW_PT_DSN)

A public Playtime tab and player-drawer playtime, and staff-only figures
(who's on, staff activity, disconnect reasons, countries, names, sessions)
for @css/generic on the staff panel and on the public site when signed in.
Hidden unless SAW_PT_DSN is set and its tables are there.
This commit is contained in:
Astra 2026-10-04 21:05:57 +01:00
parent daa7ec2dbc
commit 6d0fc6e463
18 changed files with 2238 additions and 12 deletions

View file

@ -14,6 +14,7 @@ import (
"time"
"git.zio.sh/cs2/simpleadmin-web/internal/live"
"git.zio.sh/cs2/simpleadmin-web/internal/playtime"
"git.zio.sh/cs2/simpleadmin-web/internal/push"
"git.zio.sh/cs2/simpleadmin-web/internal/rcon"
"git.zio.sh/cs2/simpleadmin-web/internal/saconfig"
@ -38,6 +39,8 @@ type Config struct {
AndroidCerts []string
// Push sends staff notifications; nil turns them off.
Push *push.Sender
// Playtime reads PlaytimeHistory's database; nil turns playtime off.
Playtime *playtime.Store
}
type Server struct {
@ -49,6 +52,8 @@ type Server struct {
sacfg *saconfig.File // nil when no config path is set
staff *staffCache
verify *verifyCache
pt *playtime.Store // nil without SAW_PT_DSN
ptc *ptCache // nil without SAW_PT_DSN
avatars *avatarCache
http *http.Client
secure bool
@ -63,6 +68,10 @@ func New(cfg Config, st *store.Store, rc *rcon.Client, poll *live.Poller, sacfg
if err != nil || u.Host == "" {
return nil, errors.New("base URL must be absolute, like https://bans.example.com")
}
var ptc *ptCache
if cfg.Playtime != nil {
ptc = &ptCache{pt: cfg.Playtime, ttl: time.Minute}
}
return &Server{
cfg: cfg,
site: site,
@ -72,6 +81,8 @@ func New(cfg Config, st *store.Store, rc *rcon.Client, poll *live.Poller, sacfg
sacfg: sacfg,
staff: &staffCache{st: st, ttl: 15 * time.Second},
verify: &verifyCache{st: st, ttl: 30 * time.Second},
pt: cfg.Playtime,
ptc: ptc,
avatars: newAvatarCache(&http.Client{Timeout: 10 * time.Second}),
http: &http.Client{Timeout: 10 * time.Second},
secure: u.Scheme == "https",
@ -107,6 +118,7 @@ func (s *Server) Handler() http.Handler {
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 /api/public/playtime", s.publicPlaytime)
mux.HandleFunc("GET /avatar/{steamid}", s.avatar)
admin := func(pattern string, h adminHandler) { mux.HandleFunc(pattern, s.requireStaff(h)) }
@ -118,6 +130,7 @@ func (s *Server) Handler() http.Handler {
admin("GET /api/admin/staff", s.adminStaff)
admin("GET /api/admin/settings", s.adminSettings)
admin("GET /api/admin/verify", s.adminVerify)
admin("GET /api/admin/playtime", s.adminPlaytime)
admin("POST /api/admin/penalties", s.addPenalty)
admin("POST /api/admin/penalties/lift", s.liftPenalty)