136 lines
3.6 KiB
Go
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)
|
|
}
|