mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-07-09 16:00:59 +00:00
Tighten v5-to-v6 upgrade safety, release installability, provider MSP mode handling, AI cost accounting, metrics flushing, and frontend guardrails for the v6.0.0 GA candidate.
108 lines
3.2 KiB
Go
108 lines
3.2 KiB
Go
package api
|
|
|
|
import (
|
|
"net/http"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/rcourtman/pulse-go-rewrite/internal/config"
|
|
"github.com/rcourtman/pulse-go-rewrite/internal/unifiedresources"
|
|
"github.com/rcourtman/pulse-go-rewrite/internal/utils"
|
|
"github.com/rs/zerolog/log"
|
|
)
|
|
|
|
type stateSummaryResponse struct {
|
|
ActiveAlerts int `json:"activeAlerts"`
|
|
Nodes int `json:"nodes"`
|
|
VMs int `json:"vms"`
|
|
Containers int `json:"containers"`
|
|
DockerHosts []stateSummaryDockerHost `json:"dockerHosts"`
|
|
LastUpdate time.Time `json:"lastUpdate"`
|
|
}
|
|
|
|
type stateSummaryDockerHost struct {
|
|
Name string `json:"name"`
|
|
Containers int `json:"containers"`
|
|
UptimeSeconds int64 `json:"uptimeSeconds"`
|
|
CPUUsagePercent float64 `json:"cpuUsagePercent"`
|
|
}
|
|
|
|
func buildStateSummary(readState unifiedresources.ReadState, activeAlerts int, lastUpdate time.Time) stateSummaryResponse {
|
|
if readState == nil {
|
|
return stateSummaryResponse{
|
|
ActiveAlerts: activeAlerts,
|
|
LastUpdate: lastUpdate,
|
|
}
|
|
}
|
|
|
|
dockerHosts := make([]stateSummaryDockerHost, 0, len(readState.DockerHosts()))
|
|
for _, host := range readState.DockerHosts() {
|
|
if host == nil {
|
|
continue
|
|
}
|
|
|
|
name := strings.TrimSpace(host.CustomDisplayName())
|
|
if name == "" {
|
|
name = strings.TrimSpace(host.DisplayName())
|
|
}
|
|
if name == "" {
|
|
name = strings.TrimSpace(host.Hostname())
|
|
}
|
|
if name == "" {
|
|
name = host.ID()
|
|
}
|
|
|
|
dockerHosts = append(dockerHosts, stateSummaryDockerHost{
|
|
Name: name,
|
|
Containers: len(host.Containers()),
|
|
UptimeSeconds: host.UptimeSeconds(),
|
|
CPUUsagePercent: host.CPUPercent(),
|
|
})
|
|
}
|
|
|
|
return stateSummaryResponse{
|
|
ActiveAlerts: activeAlerts,
|
|
Nodes: len(readState.Nodes()),
|
|
VMs: len(readState.VMs()),
|
|
Containers: len(readState.Containers()),
|
|
DockerHosts: dockerHosts,
|
|
LastUpdate: lastUpdate,
|
|
}
|
|
}
|
|
|
|
func (r *Router) handleStateSummary(w http.ResponseWriter, req *http.Request) {
|
|
if req.Method != http.MethodGet {
|
|
writeErrorResponse(w, http.StatusMethodNotAllowed, "method_not_allowed",
|
|
"Only GET method is allowed", nil)
|
|
return
|
|
}
|
|
|
|
authWriter := &responseCapture{ResponseWriter: w}
|
|
if !checkAuth(r.config, authWriter, req, false) {
|
|
if !authWriter.wrote {
|
|
writeErrorResponse(w, http.StatusUnauthorized, "unauthorized",
|
|
"Authentication required", nil)
|
|
}
|
|
return
|
|
}
|
|
|
|
if record := getAPITokenRecordFromRequest(req); record != nil && !record.HasScope(config.ScopeMonitoringRead) {
|
|
respondMissingScope(w, config.ScopeMonitoringRead)
|
|
return
|
|
}
|
|
|
|
monitor := r.getTenantMonitor(req.Context())
|
|
if monitor == nil {
|
|
writeErrorResponse(w, http.StatusInternalServerError, "no_monitor",
|
|
"Monitor not available", nil)
|
|
return
|
|
}
|
|
|
|
readState := monitor.GetUnifiedReadStateOrSnapshot()
|
|
snapshot := monitor.ReadSnapshot()
|
|
if err := utils.WriteJSONResponse(w, buildStateSummary(readState, len(snapshot.ActiveAlerts), snapshot.LastUpdate)); err != nil {
|
|
log.Error().Err(err).Msg("Failed to encode state summary response")
|
|
writeErrorResponse(w, http.StatusInternalServerError, "encoding_error",
|
|
"Failed to encode state summary", nil)
|
|
}
|
|
}
|