feat(keys): role-based gateway keys with admin management UI and per-user model scope

This commit is contained in:
root
2026-08-09 10:01:40 +08:00
parent dec03238dd
commit 3408c9cb1f
10 changed files with 901 additions and 82 deletions

View File

@ -45,13 +45,15 @@ type agrRow struct {
// Stats collects per-key / per-model / per-source aggregates plus a bounded
// ring of raw request records, all guarded by one mutex.
type Stats struct {
mu sync.Mutex
active int64
byKey map[string]*Stat
byModel map[string]*Stat
bySrc map[string]*Stat
recs []Req
maxRecs int
mu sync.Mutex
active int64
byKey map[string]*Stat
byModel map[string]*Stat
bySrc map[string]*Stat
byKeyModel map[string]map[string]*Stat
byKeySrc map[string]map[string]*Stat
recs []Req
maxRecs int
}
func NewStats(maxRecords int) *Stats {
@ -59,10 +61,12 @@ func NewStats(maxRecords int) *Stats {
maxRecords = 3000
}
return &Stats{
byKey: map[string]*Stat{},
byModel: map[string]*Stat{},
bySrc: map[string]*Stat{},
maxRecs: maxRecords,
byKey: map[string]*Stat{},
byModel: map[string]*Stat{},
bySrc: map[string]*Stat{},
byKeyModel: map[string]map[string]*Stat{},
byKeySrc: map[string]map[string]*Stat{},
maxRecs: maxRecords,
}
}
@ -107,6 +111,18 @@ func (s *Stats) Record(r Req) {
inc(s.byKey, r.Key, r)
inc(s.byModel, r.Model, r)
inc(s.bySrc, r.Source, r)
km := s.byKeyModel[r.Key]
if km == nil {
km = map[string]*Stat{}
s.byKeyModel[r.Key] = km
}
inc(km, r.Model, r)
ks := s.byKeySrc[r.Key]
if ks == nil {
ks = map[string]*Stat{}
s.byKeySrc[r.Key] = ks
}
inc(ks, r.Source, r)
s.recs = append(s.recs, r)
if len(s.recs) > s.maxRecs {
s.recs = s.recs[len(s.recs)-s.maxRecs:]
@ -132,8 +148,9 @@ type StatsRow struct {
Stat
}
// Snapshot returns the whole dashboard payload.
func (s *Stats) Snapshot(limit int) map[string]interface{} {
// Snapshot returns the whole dashboard payload; when key != "" the records
// and aggregate views are restricted to that gateway key.
func (s *Stats) Snapshot(limit int, key string) map[string]interface{} {
s.mu.Lock()
defer s.mu.Unlock()
if limit <= 0 {
@ -143,8 +160,26 @@ func (s *Stats) Snapshot(limit int) map[string]interface{} {
if len(s.recs) > limit {
start = len(s.recs) - limit
}
recs := s.recs[start:]
if key != "" {
filt := recs[:0]
for _, r := range recs {
if r.Key == key {
filt = append(filt, r)
}
}
recs = filt
}
var total Stat
for _, a := range s.byModel {
var byModel, byKey, bySrc map[string]*Stat
if key == "" {
byKey, byModel, bySrc = s.byKey, s.byModel, s.bySrc
} else {
byKey = map[string]*Stat{key: s.byKey[key]}
byModel = s.byKeyModel[key]
bySrc = s.byKeySrc[key]
}
for _, a := range byModel {
total.Reqs += a.Reqs
total.OK += a.OK
total.Err += a.Err
@ -159,9 +194,9 @@ func (s *Stats) Snapshot(limit int) map[string]interface{} {
return map[string]interface{}{
"active": s.active,
"total": total,
"by_key": rows(s.byKey),
"by_model": rows(s.byModel),
"by_source": rows(s.bySrc),
"records": append([]Req(nil), s.recs[start:]...),
"by_key": rows(byKey),
"by_model": rows(byModel),
"by_source": rows(bySrc),
"records": append([]Req(nil), recs...),
}
}