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) })) }