Add AI service dashboard endpoint
All checks were successful
CI / test (push) Successful in 16s
Build and Deploy / build-and-deploy (push) Successful in 22s

This commit is contained in:
Grendgi
2026-06-08 17:23:01 +03:00
parent a525d7a1c4
commit 3ecdcd395e
3 changed files with 97 additions and 5 deletions

View File

@@ -0,0 +1,85 @@
package httpapi
import (
"net/http"
"time"
"ai-service/internal/model"
)
type dashboardResponse struct {
At time.Time `json:"at"`
Summary dashboardSummary `json:"summary"`
Stats *model.Stats `json:"stats"`
Providers providersStatusResponse `json:"providers"`
Infra infraStatusResponse `json:"infra"`
Jobs []*model.Job `json:"jobs"`
}
type dashboardSummary struct {
Pending int64 `json:"pending"`
Running int64 `json:"running"`
Done int64 `json:"done"`
Failed int64 `json:"failed"`
Cancelled int64 `json:"cancelled"`
Total int64 `json:"total"`
}
func (s *Server) handleDashboard(w http.ResponseWriter, r *http.Request) {
now := time.Now().UTC()
ctx, cancel := contextWithTimeout(r, 12*time.Second)
defer cancel()
stats, err := s.store.Stats(ctx)
if err != nil {
writeError(w, http.StatusInternalServerError, err.Error())
return
}
jobs, err := s.store.ListJobs(ctx, model.JobFilter{
Statuses: []string{model.StatusFailed, model.StatusRunning},
Limit: 40,
})
if err != nil {
writeError(w, http.StatusInternalServerError, err.Error())
return
}
resp := dashboardResponse{
At: now,
Summary: summarizeQueues(stats),
Stats: stats,
Providers: providersStatusResponse{
At: now,
Providers: []providerStatus{
s.checkLLM(ctx),
s.checkWhisperX(ctx),
},
},
Infra: loadInfraSnapshot(r, s.cfg),
Jobs: jobs,
}
writeJSON(w, http.StatusOK, resp)
}
func summarizeQueues(stats *model.Stats) dashboardSummary {
var out dashboardSummary
if stats == nil {
return out
}
for _, row := range stats.Queues {
switch row.Status {
case model.StatusPending:
out.Pending += row.Total
case model.StatusRunning:
out.Running += row.Total
case model.StatusDone:
out.Done += row.Total
case model.StatusFailed:
out.Failed += row.Total
case model.StatusCancelled:
out.Cancelled += row.Total
}
out.Total += row.Total
}
return out
}

View File

@@ -8,6 +8,8 @@ import (
"net/http"
"strings"
"time"
"ai-service/internal/config"
)
type infraStatusResponse struct {
@@ -17,15 +19,18 @@ type infraStatusResponse struct {
}
func (s *Server) handleInfraStatus(w http.ResponseWriter, r *http.Request) {
resp := infraStatusResponse{At: time.Now().UTC()}
baseURL := strings.TrimRight(strings.TrimSpace(s.cfg.AIStatsSidecarURL), "/")
if baseURL == "" {
resp.SidecarError = "AI stats sidecar is not configured"
writeJSON(w, http.StatusOK, resp)
return
writeJSON(w, http.StatusOK, loadInfraSnapshot(r, s.cfg))
}
timeout := s.cfg.AIStatsTimeout
func loadInfraSnapshot(r *http.Request, cfg config.Config) infraStatusResponse {
resp := infraStatusResponse{At: time.Now().UTC()}
baseURL := strings.TrimRight(strings.TrimSpace(cfg.AIStatsSidecarURL), "/")
if baseURL == "" {
resp.SidecarError = "AI stats sidecar is not configured"
return resp
}
timeout := cfg.AIStatsTimeout
if timeout <= 0 {
timeout = 8 * time.Second
}
@@ -37,7 +42,7 @@ func (s *Server) handleInfraStatus(w http.ResponseWriter, r *http.Request) {
} else {
resp.Sidecar = sidecar
}
writeJSON(w, http.StatusOK, resp)
return resp
}
func fetchAIStatsSidecar(ctx context.Context, baseURL string, timeout time.Duration) (map[string]any, error) {

View File

@@ -69,6 +69,8 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
s.handleProviderStatus(w, r)
case r.Method == http.MethodGet && path == "/api/v1/infra/status":
s.handleInfraStatus(w, r)
case r.Method == http.MethodGet && path == "/api/v1/dashboard":
s.handleDashboard(w, r)
default:
writeError(w, http.StatusNotFound, "not found")
}