fix: AUTO tier order ascending + stats/CSV export completeness + UI key-view toggle & exit affordance

- scheduler: BuildChain sorts tiers ascending so tier 1 (highest priority) is tried first; previously descending inverted the chain (P10-1..P10-5)
- stats/api: handleStatsAPI limit→20000; CSV reads full audit via new Stats.AuditRecords (includes *.old rotation); key_names mapped by keyID() masked key; non-admin filter and keys-csv use masked keys
- server: ring buffer NewStats(10000)
- ui: dash-row card scroll area moved to table container (fix overflow below card); key view toggle (re-click same key returns to global) + kpi-exit affordance; rec-exit span
- chat: chain-failure record now surfaces first failed tier; AUTO comment sync
This commit is contained in:
root
2026-08-11 13:36:23 +08:00
parent 32e2fd521a
commit 854b3e2e37
7 changed files with 104 additions and 43 deletions

View File

@ -395,6 +395,51 @@ func (s *Stats) Records(from, to int64, key string) []Req {
return out
}
// AuditRecords returns every request row from the audit file plus its rotated
// .old files within the [from,to] unix-millisecond window, optionally for one
// masked key id. Unlike Records it is not bounded by the in-memory ring, so it
// yields a complete history for CSV export regardless of restarts or churn.
func (s *Stats) AuditRecords(from, to int64, key string) []Req {
s.mu.Lock()
path := s.auditPath
s.mu.Unlock()
if path == "" {
return nil
}
files := []string{path}
if olds, err := filepath.Glob(path + ".*.old"); err == nil {
files = append(files, olds...)
}
var out []Req
for _, p := range files {
f, err := os.Open(p)
if err != nil {
continue
}
sc := bufio.NewScanner(f)
sc.Buffer(make([]byte, 64*1024), 16*1024*1024)
for sc.Scan() {
var r Req
if json.Unmarshal(sc.Bytes(), &r) != nil || r.Type == "" {
continue
}
if key != "" && r.Key != key {
continue
}
if from > 0 && r.Time < from {
continue
}
if to > 0 && r.Time > to {
continue
}
out = append(out, r)
}
f.Close()
}
sort.Slice(out, func(i, j int) bool { return out[i].Time < out[j].Time })
return out
}
// 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{} {