Files

136 lines
3.6 KiB
Go

package api
import (
"context"
"linkport/internal/gateway"
"linkport/internal/model"
"net/http"
"time"
)
// monitoring handles read-only gateway metrics and explicit target probes.
// Register it from Server.serve for the `monitoring` path.
func (s *Server) monitoring(w http.ResponseWriter, r *http.Request, path string) {
if path == "monitoring" && r.Method == "GET" {
scopes, ok := s.scopeIndex(w, r)
if !ok {
return
}
proxies, err := s.store.List()
if err != nil {
fail(w, 500, "cannot load gateways")
return
}
out := make([]any, 0, len(proxies))
interval, health := s.manager.HealthSnapshot()
filteredHealth := health[:0]
for _, h := range health {
if scopes == nil || scopes.Matches("targets", h.TargetID, requestScope(r)) {
filteredHealth = append(filteredHealth, h)
}
}
health = filteredHealth
byAccount := make(map[string]gateway.HealthResult, len(health))
for _, h := range health {
byAccount[h.TargetID+"/"+h.AccountID] = h
}
for _, p := range proxies {
if !requestScope(r).Matches(p.Project, p.Environment) {
continue
}
metrics := s.manager.Metrics(p.ID)
var latest any
accountHealth := []gateway.HealthResult{}
seen := map[string]bool{}
add := func(selected model.Proxy) {
key := selected.TargetID + "/" + selected.TargetAccountID
if h, ok := byAccount[key]; ok && !seen[key] {
seen[key] = true
accountHealth = append(accountHealth, h)
if latest == nil || !h.Healthy {
latest = h
}
}
}
if len(p.GatewayAccounts) == 0 {
add(p)
}
for _, a := range p.GatewayAccounts {
if !a.Disabled {
add(a.Apply(p))
}
}
out = append(out, map[string]any{"id": p.ID, "metrics": metrics, "health": latest, "accountHealth": accountHealth})
}
slow := []gateway.SlowRequest{}
for _, entry := range s.manager.SlowSnapshot() {
if scopes == nil || scopes.Matches("targets", entry.TargetID, requestScope(r)) {
slow = append(slow, entry)
}
}
respond(w, 200, map[string]any{"gateways": out, "health": health, "slowRequests": slow, "intervalSeconds": interval, "auditQueue": s.store.AuditQueueSnapshot()})
return
}
if r.Method == "POST" && path == "monitoring/settings" {
var body struct {
IntervalSeconds int `json:"intervalSeconds"`
}
if !decode(w, r, &body) {
return
}
if err := s.manager.SetHealthInterval(body.IntervalSeconds); err != nil {
fail(w, 400, err.Error())
return
}
respond(w, 200, map[string]int{"intervalSeconds": body.IntervalSeconds})
return
}
if path != "monitoring/check" && path != "monitoring/database" {
fail(w, 404, "not found")
return
}
if r.Method != "POST" {
fail(w, 405, "method not allowed")
return
}
var body struct {
TargetID string `json:"targetId"`
AccountID string `json:"accountId"`
Attempts int `json:"attempts"`
}
if !decode(w, r, &body) {
return
}
target, err := s.store.GetTarget(body.TargetID)
if err != nil {
fail(w, 404, "target not found")
return
}
p := target.Runtime()
p.TargetAccountID = body.AccountID
p, err = target.Resolve(p)
if err != nil {
fail(w, 400, "target account not found")
return
}
if body.Attempts == 0 {
body.Attempts = 1
}
ctx, cancel := context.WithTimeout(r.Context(), 25*time.Second)
defer cancel()
result, err := s.manager.CheckHealth(ctx, p, body.Attempts)
if err != nil {
fail(w, 400, err.Error())
return
}
if path == "monitoring/database" {
diagnostics, e := s.manager.DatabaseMonitoring(ctx, p)
if e != nil {
diagnostics.Notices = append(diagnostics.Notices, e.Error())
}
respond(w, 200, map[string]any{"health": result, "diagnostics": diagnostics})
return
}
respond(w, 200, result)
}