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
161
internal/web/staff.go
Normal file
161
internal/web/staff.go
Normal file
|
|
@ -0,0 +1,161 @@
|
|||
package web
|
||||
|
||||
import (
|
||||
"context"
|
||||
"slices"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"git.zio.sh/cs2/simpleadmin-web/internal/store"
|
||||
)
|
||||
|
||||
// staffIndex is SimpleAdmin's admins and groups for this server, merged the way CounterStrikeSharp's
|
||||
// AdminManager sees them: an admin's permissions are their own @flags plus their groups' flags, and
|
||||
// their immunity is the highest of their own and their groups'.
|
||||
type staffIndex struct {
|
||||
groups []store.Group
|
||||
byName map[string]*store.Group
|
||||
admins []store.Admin
|
||||
bySteam map[string]*store.Admin
|
||||
}
|
||||
|
||||
type staffCache struct {
|
||||
st *store.Store
|
||||
ttl time.Duration
|
||||
|
||||
mu sync.Mutex
|
||||
idx *staffIndex
|
||||
fetched time.Time
|
||||
}
|
||||
|
||||
func (c *staffCache) get(ctx context.Context) (*staffIndex, error) {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
if c.idx != nil && time.Since(c.fetched) < c.ttl {
|
||||
return c.idx, nil
|
||||
}
|
||||
groups, err := c.st.Groups(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
admins, err := c.st.Admins(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
idx := &staffIndex{groups: groups, admins: admins, byName: map[string]*store.Group{}, bySteam: map[string]*store.Admin{}}
|
||||
for i := range idx.groups {
|
||||
idx.byName[idx.groups[i].Name] = &idx.groups[i]
|
||||
}
|
||||
for i := range idx.admins {
|
||||
idx.bySteam[idx.admins[i].SteamID] = &idx.admins[i]
|
||||
}
|
||||
c.idx, c.fetched = idx, time.Now()
|
||||
return idx, nil
|
||||
}
|
||||
|
||||
func (c *staffCache) invalidate() {
|
||||
c.mu.Lock()
|
||||
c.idx = nil
|
||||
c.mu.Unlock()
|
||||
}
|
||||
|
||||
// flags returns an admin's effective permissions.
|
||||
func (x *staffIndex) flags(a *store.Admin) []string {
|
||||
out := slices.Clone(a.Flags)
|
||||
for _, g := range a.Groups {
|
||||
if grp, ok := x.byName[g]; ok {
|
||||
for _, f := range grp.Flags {
|
||||
if !slices.Contains(out, f) {
|
||||
out = append(out, f)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// immunity returns a player's effective immunity, 0 for non-staff.
|
||||
func (x *staffIndex) immunity(steamid string) int {
|
||||
a, ok := x.bySteam[steamid]
|
||||
if !ok {
|
||||
return 0
|
||||
}
|
||||
imm := a.Immunity
|
||||
for _, g := range a.Groups {
|
||||
if grp, ok := x.byName[g]; ok {
|
||||
imm = max(imm, grp.Immunity)
|
||||
}
|
||||
}
|
||||
return imm
|
||||
}
|
||||
|
||||
// primaryGroup is the admin's highest-immunity group that exists, or "".
|
||||
func (x *staffIndex) primaryGroup(a *store.Admin) string {
|
||||
best, bestImm := "", -1
|
||||
for _, g := range a.Groups {
|
||||
if grp, ok := x.byName[g]; ok && grp.Immunity > bestImm {
|
||||
best, bestImm = g, grp.Immunity
|
||||
}
|
||||
}
|
||||
return best
|
||||
}
|
||||
|
||||
// groupIndex is a group's position, used to pick a stable fallback colour.
|
||||
func (x *staffIndex) groupIndex(name string) int {
|
||||
for i, g := range x.groups {
|
||||
if g.Name == name {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// Identity is a signed-in staff member.
|
||||
type Identity struct {
|
||||
SteamID string
|
||||
Name string
|
||||
Flags []string
|
||||
Immunity int
|
||||
RowID int64
|
||||
}
|
||||
|
||||
// Has reports whether the identity holds a permission. @css/root grants everything.
|
||||
func (id *Identity) Has(flag string) bool {
|
||||
return slices.Contains(id.Flags, "@css/root") || slices.Contains(id.Flags, flag)
|
||||
}
|
||||
|
||||
// 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 {
|
||||
if steamid == id.SteamID {
|
||||
return false
|
||||
}
|
||||
return x.immunity(steamid) <= id.Immunity
|
||||
}
|
||||
|
||||
// rankView is how a rank is shown to the browser.
|
||||
type rankView struct {
|
||||
Name string `json:"name"`
|
||||
Label string `json:"label"`
|
||||
Color string `json:"color"`
|
||||
Supporter bool `json:"supporter,omitempty"`
|
||||
}
|
||||
|
||||
func (s *Server) rankOf(x *staffIndex, steamid string) *rankView {
|
||||
a, ok := x.bySteam[steamid]
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
g := x.primaryGroup(a)
|
||||
if g == "" {
|
||||
return &rankView{Label: "Staff", Color: "#8a91a0"}
|
||||
}
|
||||
r := s.site.rank(g, x.groupIndex(g))
|
||||
return &rankView{Name: g, Label: r.Label, Color: r.Color, Supporter: r.Supporter}
|
||||
}
|
||||
|
||||
// isStaff is true for admins whose rank isn't a supporter rank.
|
||||
func (s *Server) isStaff(x *staffIndex, steamid string) bool {
|
||||
r := s.rankOf(x, steamid)
|
||||
return r != nil && !r.Supporter
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue