package handlers import ( "time" "github.com/gin-gonic/gin" "git.netcell-it.de/projekte/edgeguard-native/internal/cluster" "git.netcell-it.de/projekte/edgeguard-native/internal/handlers/response" "git.netcell-it.de/projekte/edgeguard-native/internal/models" ) // ClusterHandler exposes cluster-state endpoints. v1 ist read-only; // /status liefert eine strukturierte UI-Sicht (local + peers + health), // /nodes bleibt als simpler list-endpoint für Tools/Scripts. type ClusterHandler struct { Store *cluster.Store LocalID string } func NewClusterHandler(store *cluster.Store, localID string) *ClusterHandler { return &ClusterHandler{Store: store, LocalID: localID} } func (h *ClusterHandler) Register(rg *gin.RouterGroup) { g := rg.Group("/cluster") g.GET("/nodes", h.ListNodes) g.GET("/status", h.Status) } func (h *ClusterHandler) ListNodes(c *gin.Context) { nodes, err := h.Store.List(c.Request.Context()) if err != nil { response.Internal(c, err) return } response.OK(c, gin.H{"nodes": nodes, "local_id": h.LocalID}) } // ClusterStatus ist die UI-zentrierte Sicht: local-Node hervorgehoben, // peers separat, mode + health-flag. type ClusterStatus struct { LocalID string `json:"local_id"` LocalNode *models.HANode `json:"local_node,omitempty"` Peers []models.HANode `json:"peers"` Mode string `json:"mode"` // "single-node" | "cluster" Health string `json:"health"` // "ok" | "degraded" | "split-brain" DriftFound bool `json:"drift_found"` UpdatedAt time.Time `json:"updated_at"` } // Status splittet alle Nodes in local + peers, berechnet mode + health. func (h *ClusterHandler) Status(c *gin.Context) { all, err := h.Store.List(c.Request.Context()) if err != nil { response.Internal(c, err) return } out := ClusterStatus{ LocalID: h.LocalID, Peers: []models.HANode{}, Mode: "single-node", Health: "ok", UpdatedAt: time.Now().UTC(), } var localHash *string for i := range all { n := all[i] if n.ID == h.LocalID { ln := n out.LocalNode = &ln localHash = ln.ConfigHash continue } out.Peers = append(out.Peers, n) } if len(out.Peers) > 0 { out.Mode = "cluster" } // Drift-Detection: jeder peer mit anderem config_hash als unser // lokaler → Banner-Trigger im UI. if localHash != nil && *localHash != "" { for _, p := range out.Peers { if p.ConfigHash == nil || *p.ConfigHash == "" { continue } if *p.ConfigHash != *localHash { out.DriftFound = true out.Health = "degraded" break } } } // Offline-Peers → degraded. if !out.DriftFound { for _, p := range out.Peers { if p.Status != "online" { out.Health = "degraded" break } } } response.OK(c, out) }