Files
oikos/internal/httpapi/dashboard.go
dtoro 2908b0a377
Some checks failed
ci / build-test (push) Has been cancelled
ci / docker-build (push) Has been cancelled
feat(ui): M4 — agent activity, knowledge search, audit, correlation grouping
Add three new pages completing the control-room web UI:
- Agent activity: polls /agent-activity every 5s, filterable by type/agent
- Knowledge search: FTS over /knowledge/search with snippet + entity links
- Audit trail: browseable audit log with actor/action/entity filters

Enhanced live events page with correlation-id clustering (Groups toggle).
Added fetchAgentActivity/searchKnowledge/fetchAudit to the API client.
11 nav items now cover all planned control-room views.
2026-07-08 17:02:09 +02:00

161 lines
3.6 KiB
Go

package httpapi
import (
"context"
"time"
"github.com/dtoro/oikos/internal/httpapi/gen"
)
// GetDashboardSummary returns one round-trip overview for the control room
// home page: entity counts, health rollup, open signals, pending approvals,
// executions in the last 24h, and an event-rate sparkline.
func (s *Server) GetDashboardSummary(ctx context.Context, req gen.GetDashboardSummaryRequestObject) (gen.GetDashboardSummaryResponseObject, error) {
resp := gen.DashboardSummary{
EntitiesByType: map[string]int{},
EntitiesByState: map[string]int{},
SignalsBySeverity: map[string]int{},
ExecutionsByState: map[string]int{},
}
rows, err := s.pool.Query(ctx, `SELECT type, count(*) FROM entities GROUP BY type`)
if err != nil {
return nil, err
}
for rows.Next() {
var typ string
var n int
if err := rows.Scan(&typ, &n); err != nil {
rows.Close()
return nil, err
}
resp.EntitiesByType[typ] = n
}
rows.Close()
if err := rows.Err(); err != nil {
return nil, err
}
rows, err = s.pool.Query(ctx, `SELECT coalesce(state, 'unknown'), count(*) FROM entities GROUP BY state`)
if err != nil {
return nil, err
}
for rows.Next() {
var state string
var n int
if err := rows.Scan(&state, &n); err != nil {
rows.Close()
return nil, err
}
resp.EntitiesByState[state] = n
}
rows.Close()
if err := rows.Err(); err != nil {
return nil, err
}
rows, err = s.pool.Query(ctx, `SELECT health, count(*) FROM entity_status GROUP BY health`)
if err != nil {
return nil, err
}
for rows.Next() {
var health string
var n int
if err := rows.Scan(&health, &n); err != nil {
rows.Close()
return nil, err
}
switch health {
case "healthy":
resp.Health.Healthy = n
case "degraded":
resp.Health.Degraded = n
case "down":
resp.Health.Down = n
default:
resp.Health.Unknown = n
}
}
rows.Close()
if err := rows.Err(); err != nil {
return nil, err
}
rows, err = s.pool.Query(ctx, `
SELECT severity, count(*) FROM signals
WHERE state NOT IN ('resolved', 'failed')
GROUP BY severity`)
if err != nil {
return nil, err
}
for rows.Next() {
var severity string
var n int
if err := rows.Scan(&severity, &n); err != nil {
rows.Close()
return nil, err
}
resp.SignalsBySeverity[severity] = n
}
rows.Close()
if err := rows.Err(); err != nil {
return nil, err
}
if err := s.pool.QueryRow(ctx,
`SELECT count(*) FROM approvals WHERE status = 'pending'`,
).Scan(&resp.ApprovalsPending); err != nil {
return nil, err
}
rows, err = s.pool.Query(ctx, `
SELECT status, count(*) FROM executions
WHERE created_at > now() - interval '24 hours'
GROUP BY status`)
if err != nil {
return nil, err
}
for rows.Next() {
var status string
var n int
if err := rows.Scan(&status, &n); err != nil {
rows.Close()
return nil, err
}
resp.ExecutionsByState[status] = n
}
rows.Close()
if err := rows.Err(); err != nil {
return nil, err
}
rows, err = s.pool.Query(ctx, `
SELECT date_trunc('hour', ts) + (extract(minute FROM ts)::int / 5) * interval '5 minutes' AS bucket,
count(*)
FROM events
WHERE ts > now() - interval '6 hours'
GROUP BY bucket
ORDER BY bucket`)
if err != nil {
return nil, err
}
for rows.Next() {
var bucket time.Time
var n int
if err := rows.Scan(&bucket, &n); err != nil {
rows.Close()
return nil, err
}
resp.EventRate = append(resp.EventRate, struct {
Bucket time.Time `json:"bucket"`
Count int `json:"count"`
}{Bucket: bucket, Count: n})
}
rows.Close()
if err := rows.Err(); err != nil {
return nil, err
}
return gen.GetDashboardSummary200JSONResponse(resp), nil
}