Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
109 lines
3.4 KiB
Go
109 lines
3.4 KiB
Go
package web
|
|
|
|
import (
|
|
"context"
|
|
"net/http"
|
|
"slices"
|
|
"time"
|
|
|
|
"gitea.mynodes.xyz/ilker/llmgw/internal/guard"
|
|
"gitea.mynodes.xyz/ilker/llmgw/internal/store"
|
|
)
|
|
|
|
// GuardAdminSource is what the guardrails page reads.
|
|
type GuardAdminSource interface {
|
|
GuardEvents(ctx context.Context, since time.Time, limit int) ([]store.GuardEvent, error)
|
|
GuardCounts(ctx context.Context, since time.Time) ([]store.GuardCount, error)
|
|
GuardAttachments(ctx context.Context) ([]guard.Attachment, error)
|
|
AllProjects(ctx context.Context) ([]store.ProjectPath, error)
|
|
}
|
|
|
|
type guardPageData struct {
|
|
Nav Nav
|
|
Version string
|
|
Window string
|
|
Windows []string
|
|
// Policies are the library, Default those applied to every request.
|
|
Policies []string
|
|
Default []string
|
|
Attachments []guardAttachmentRow
|
|
Counts []guardCountRow
|
|
Events []guardEventRow
|
|
Error string
|
|
}
|
|
|
|
type guardAttachmentRow struct{ Scope, Who, Policy string }
|
|
|
|
type guardCountRow struct {
|
|
Where, Action, Policy, Category string
|
|
Count int64
|
|
}
|
|
|
|
type guardEventRow struct {
|
|
At, RequestID, Where, Model, Action, Policy, Category string
|
|
Status int
|
|
Ms int64
|
|
}
|
|
|
|
var guardWindows = map[string]time.Duration{"24h": 24 * time.Hour, "7d": 7 * 24 * time.Hour, "30d": 30 * 24 * time.Hour}
|
|
|
|
// RegisterAdminGuard mounts /admin/guard: what the guardrails did, where, by
|
|
// which policy — names and counts, never text.
|
|
func (p *AccountPages) RegisterAdminGuard(mux *http.ServeMux, s GuardAdminSource, library guard.Config) {
|
|
p.hasGuard = true
|
|
mux.HandleFunc("GET /admin/guard", p.adminOnly(func(w http.ResponseWriter, r *http.Request, admin store.User) {
|
|
ctx := r.Context()
|
|
d := guardPageData{Nav: p.navFor(r, admin, "guard"), Version: p.version, Window: r.URL.Query().Get("window"),
|
|
Windows: []string{"24h", "7d", "30d"}, Default: library.Default}
|
|
since, ok := guardWindows[d.Window]
|
|
if !ok {
|
|
d.Window, since = "7d", guardWindows["7d"]
|
|
}
|
|
for name := range library.Policies {
|
|
d.Policies = append(d.Policies, name)
|
|
}
|
|
slices.Sort(d.Policies)
|
|
|
|
where := map[string]string{"": "personal"}
|
|
if projects, err := s.AllProjects(ctx); err == nil {
|
|
for _, pp := range projects {
|
|
where[pp.ID] = pp.Path
|
|
}
|
|
}
|
|
name := func(id string) string {
|
|
if n, ok := where[id]; ok {
|
|
return n
|
|
}
|
|
return id
|
|
}
|
|
if att, err := s.GuardAttachments(ctx); err == nil {
|
|
for _, a := range att {
|
|
who := a.ScopeID
|
|
if a.Scope == "project" {
|
|
who = name(a.ScopeID)
|
|
}
|
|
d.Attachments = append(d.Attachments, guardAttachmentRow{Scope: a.Scope, Who: who, Policy: a.Policy})
|
|
}
|
|
}
|
|
counts, err := s.GuardCounts(ctx, time.Now().Add(-since))
|
|
if err != nil {
|
|
d.Error = err.Error()
|
|
}
|
|
for _, c := range counts {
|
|
d.Counts = append(d.Counts, guardCountRow{Where: name(c.ProjectID), Action: c.Action,
|
|
Policy: c.Policy, Category: c.Category, Count: c.Count})
|
|
}
|
|
events, err := s.GuardEvents(ctx, time.Now().Add(-since), 100)
|
|
if err != nil {
|
|
d.Error = err.Error()
|
|
}
|
|
for _, e := range events {
|
|
d.Events = append(d.Events, guardEventRow{At: e.At.UTC().Format("2006-01-02 15:04:05"),
|
|
RequestID: e.RequestID, Where: name(e.ProjectID), Model: e.Model, Action: e.Action,
|
|
Policy: e.Policy, Category: e.Category, Status: e.Status, Ms: e.GuardMs})
|
|
}
|
|
noStore(w)
|
|
render(w, "admin_guard", d)
|
|
}))
|
|
}
|