mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-05-15 01:07:32 +00:00
276 lines
8.7 KiB
Go
276 lines
8.7 KiB
Go
package api
|
|
|
|
import (
|
|
"encoding/json"
|
|
"net/http"
|
|
"time"
|
|
|
|
"github.com/rs/zerolog/log"
|
|
|
|
"github.com/rcourtman/pulse-go-rewrite/internal/unifiedresources"
|
|
)
|
|
|
|
// MonitoredSystemLedgerEntry represents a single counted top-level monitored
|
|
// system.
|
|
type MonitoredSystemLedgerEntry struct {
|
|
Name string `json:"name"`
|
|
Type string `json:"type"`
|
|
Status string `json:"status"` // "online", "warning", "offline", "unknown"
|
|
StatusExplanation MonitoredSystemLedgerStatusExplanation `json:"status_explanation"`
|
|
LatestIncludedSignal MonitoredSystemLedgerLatestSignal `json:"latest_included_signal"`
|
|
Source string `json:"source"`
|
|
Explanation MonitoredSystemLedgerExplanation `json:"explanation"`
|
|
}
|
|
|
|
type MonitoredSystemLedgerLatestSignal struct {
|
|
Name string `json:"name"`
|
|
Type string `json:"type"`
|
|
Source string `json:"source"`
|
|
At string `json:"at"`
|
|
}
|
|
|
|
type MonitoredSystemLedgerStatusExplanation struct {
|
|
Summary string `json:"summary"`
|
|
Reasons []MonitoredSystemLedgerStatusReason `json:"reasons"`
|
|
}
|
|
|
|
type MonitoredSystemLedgerStatusReason struct {
|
|
Kind string `json:"kind"`
|
|
Name string `json:"name"`
|
|
Type string `json:"type"`
|
|
Source string `json:"source"`
|
|
Status string `json:"status"`
|
|
ReportedAt string `json:"reported_at"`
|
|
Summary string `json:"summary"`
|
|
}
|
|
|
|
type MonitoredSystemLedgerExplanation struct {
|
|
Summary string `json:"summary"`
|
|
Reasons []MonitoredSystemLedgerExplanationReason `json:"reasons"`
|
|
Surfaces []MonitoredSystemLedgerExplanationSurface `json:"surfaces"`
|
|
}
|
|
|
|
type MonitoredSystemLedgerExplanationReason struct {
|
|
Kind string `json:"kind"`
|
|
Signal string `json:"signal"`
|
|
Summary string `json:"summary"`
|
|
}
|
|
|
|
type MonitoredSystemLedgerExplanationSurface struct {
|
|
Name string `json:"name"`
|
|
Type string `json:"type"`
|
|
Source string `json:"source"`
|
|
}
|
|
|
|
// MonitoredSystemLedgerResponse is the response for GET /api/license/monitored-system-ledger.
|
|
type MonitoredSystemLedgerResponse struct {
|
|
Systems []MonitoredSystemLedgerEntry `json:"systems"`
|
|
Total int `json:"total"`
|
|
Limit int `json:"limit"` // 0 = unlimited
|
|
}
|
|
|
|
func EmptyMonitoredSystemLedgerResponse() MonitoredSystemLedgerResponse {
|
|
return MonitoredSystemLedgerResponse{}.NormalizeCollections()
|
|
}
|
|
|
|
func (r MonitoredSystemLedgerResponse) NormalizeCollections() MonitoredSystemLedgerResponse {
|
|
if r.Systems == nil {
|
|
r.Systems = []MonitoredSystemLedgerEntry{}
|
|
}
|
|
for i := range r.Systems {
|
|
r.Systems[i] = r.Systems[i].NormalizeCollections()
|
|
}
|
|
return r
|
|
}
|
|
|
|
func (e MonitoredSystemLedgerEntry) NormalizeCollections() MonitoredSystemLedgerEntry {
|
|
if e.LatestIncludedSignal.Name == "" {
|
|
e.LatestIncludedSignal.Name = "Unnamed source"
|
|
}
|
|
if e.LatestIncludedSignal.Type == "" {
|
|
e.LatestIncludedSignal.Type = "system"
|
|
}
|
|
if e.StatusExplanation.Reasons == nil {
|
|
e.StatusExplanation.Reasons = []MonitoredSystemLedgerStatusReason{}
|
|
}
|
|
if e.Explanation.Reasons == nil {
|
|
e.Explanation.Reasons = []MonitoredSystemLedgerExplanationReason{}
|
|
}
|
|
if e.Explanation.Surfaces == nil {
|
|
e.Explanation.Surfaces = []MonitoredSystemLedgerExplanationSurface{}
|
|
}
|
|
return e
|
|
}
|
|
|
|
func (r *Router) handleMonitoredSystemLedger(w http.ResponseWriter, req *http.Request) {
|
|
orgID := GetOrgID(req.Context())
|
|
|
|
// Get canonical monitored systems from the unified ReadState surface.
|
|
var systems []unifiedresources.MonitoredSystemRecord
|
|
var monitorResolved bool
|
|
if r.mtMonitor != nil {
|
|
monitor, monErr := r.mtMonitor.GetMonitor(orgID)
|
|
if monErr != nil {
|
|
log.Warn().Err(monErr).Str("org", orgID).Msg("monitored-system-ledger: failed to resolve tenant monitor")
|
|
}
|
|
if monitor != nil {
|
|
if rs := monitor.GetUnifiedReadState(); rs != nil {
|
|
systems = unifiedresources.MonitoredSystems(rs)
|
|
}
|
|
monitorResolved = true
|
|
}
|
|
}
|
|
// Fallback to the default monitor only for the default org to avoid cross-tenant data leaks.
|
|
if !monitorResolved && orgID == "default" && r.monitor != nil {
|
|
if rs := r.monitor.GetUnifiedReadState(); rs != nil {
|
|
systems = unifiedresources.MonitoredSystems(rs)
|
|
}
|
|
}
|
|
|
|
entries := make([]MonitoredSystemLedgerEntry, 0, len(systems))
|
|
for _, system := range systems {
|
|
entries = append(entries, monitoredSystemLedgerEntry(system))
|
|
}
|
|
|
|
limit := maxMonitoredSystemsLimitForContext(req.Context())
|
|
|
|
resp := EmptyMonitoredSystemLedgerResponse()
|
|
resp.Systems = entries
|
|
resp.Total = len(entries)
|
|
resp.Limit = limit
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
json.NewEncoder(w).Encode(resp.NormalizeCollections())
|
|
}
|
|
|
|
func monitoredSystemLedgerEntry(system unifiedresources.MonitoredSystemRecord) MonitoredSystemLedgerEntry {
|
|
status := normalizeStatus(string(system.Status))
|
|
latestIncludedSignal := monitoredSystemLedgerLatestSignal(system.LatestIncludedSignal)
|
|
return MonitoredSystemLedgerEntry{
|
|
Name: system.Name,
|
|
Type: system.Type,
|
|
Status: status,
|
|
StatusExplanation: monitoredSystemLedgerStatusExplanation(system.StatusExplanation, status),
|
|
LatestIncludedSignal: latestIncludedSignal,
|
|
Source: system.Source,
|
|
Explanation: monitoredSystemLedgerExplanation(system.Explanation),
|
|
}
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Status helpers
|
|
// ---------------------------------------------------------------------------
|
|
|
|
func normalizeStatus(s string) string {
|
|
switch s {
|
|
case "online", "warning", "offline", "unknown":
|
|
return s
|
|
default:
|
|
return "unknown"
|
|
}
|
|
}
|
|
|
|
func monitoredSystemLedgerStatusExplanation(
|
|
explanation unifiedresources.MonitoredSystemStatusExplanation,
|
|
status string,
|
|
) MonitoredSystemLedgerStatusExplanation {
|
|
reasons := make([]MonitoredSystemLedgerStatusReason, 0, len(explanation.Reasons))
|
|
for _, reason := range explanation.Reasons {
|
|
reasons = append(reasons, MonitoredSystemLedgerStatusReason{
|
|
Kind: reason.Kind,
|
|
Name: reason.Name,
|
|
Type: reason.Type,
|
|
Source: reason.Source,
|
|
Status: normalizeMonitoredSystemLedgerReasonStatus(reason.Status),
|
|
ReportedAt: formatMonitoredSystemTime(reason.ReportedAt),
|
|
Summary: reason.Summary,
|
|
})
|
|
}
|
|
|
|
summary := explanation.Summary
|
|
if summary == "" {
|
|
summary = defaultMonitoredSystemLedgerStatusSummary(status)
|
|
}
|
|
|
|
return MonitoredSystemLedgerStatusExplanation{
|
|
Summary: summary,
|
|
Reasons: reasons,
|
|
}
|
|
}
|
|
|
|
func defaultMonitoredSystemLedgerStatusSummary(status string) string {
|
|
switch status {
|
|
case "online":
|
|
return "All included top-level collection paths currently report online status."
|
|
case "warning":
|
|
return "At least one included top-level collection path is degraded, so Pulse marks this monitored system as warning."
|
|
case "offline":
|
|
return "At least one included source is offline or disconnected, so Pulse marks this monitored system as offline."
|
|
default:
|
|
return "Pulse cannot determine a canonical runtime status for this monitored system yet."
|
|
}
|
|
}
|
|
|
|
func normalizeMonitoredSystemLedgerReasonStatus(status string) string {
|
|
switch status {
|
|
case "online", "stale", "offline", "unknown":
|
|
return status
|
|
default:
|
|
return "unknown"
|
|
}
|
|
}
|
|
|
|
func normalizeMonitoredSystemLedgerSource(source string) string {
|
|
switch source {
|
|
case "agent", "docker", "kubernetes", "pbs", "pmg", "proxmox", "truenas":
|
|
return source
|
|
default:
|
|
return ""
|
|
}
|
|
}
|
|
|
|
func monitoredSystemLedgerLatestSignal(
|
|
signal unifiedresources.MonitoredSystemLatestSignal,
|
|
) MonitoredSystemLedgerLatestSignal {
|
|
return MonitoredSystemLedgerLatestSignal{
|
|
Name: signal.Name,
|
|
Type: signal.Type,
|
|
Source: normalizeMonitoredSystemLedgerSource(signal.Source),
|
|
At: formatMonitoredSystemTime(signal.At),
|
|
}
|
|
}
|
|
|
|
func formatMonitoredSystemTime(t time.Time) string {
|
|
if t.IsZero() {
|
|
return ""
|
|
}
|
|
return t.UTC().Format(time.RFC3339)
|
|
}
|
|
|
|
func monitoredSystemLedgerExplanation(
|
|
explanation unifiedresources.MonitoredSystemGroupingExplanation,
|
|
) MonitoredSystemLedgerExplanation {
|
|
reasons := make([]MonitoredSystemLedgerExplanationReason, 0, len(explanation.Reasons))
|
|
for _, reason := range explanation.Reasons {
|
|
reasons = append(reasons, MonitoredSystemLedgerExplanationReason{
|
|
Kind: reason.Kind,
|
|
Signal: reason.Signal,
|
|
Summary: reason.Summary,
|
|
})
|
|
}
|
|
|
|
surfaces := make([]MonitoredSystemLedgerExplanationSurface, 0, len(explanation.Surfaces))
|
|
for _, surface := range explanation.Surfaces {
|
|
surfaces = append(surfaces, MonitoredSystemLedgerExplanationSurface{
|
|
Name: surface.Name,
|
|
Type: surface.Type,
|
|
Source: surface.Source,
|
|
})
|
|
}
|
|
|
|
return MonitoredSystemLedgerExplanation{
|
|
Summary: explanation.Summary,
|
|
Reasons: reasons,
|
|
Surfaces: surfaces,
|
|
}
|
|
}
|