package httpapi import ( "strings" "time" "evobgp/internal/birdfmt" "evobgp/internal/nodedispatch" "evobgp/internal/store" ) type agentHealthPoll struct { result nodedispatch.AgentHealthResult at time.Time lastSuccessAt time.Time observationOverride *liveObservation } func agentHealthObservation(poll *agentHealthPoll) liveObservation { if poll == nil { return liveObservation{Source: "none", Quality: "unknown", ErrorCode: "not_polled"} } if poll.observationOverride != nil { return *poll.observationOverride } o := liveObservation{ObservedAt: liveTime(poll.at), ReceivedAt: liveTime(poll.at), LastSuccessAt: liveTime(poll.lastSuccessAt), Source: "agent_health", Quality: "ok"} if !poll.result.OK { o.Quality = "unreachable" if !poll.lastSuccessAt.IsZero() { o.Quality = "stale" } o.ErrorCode = liveErrorCode(poll.result.Error) if poll.result.Error == "agent reported ok=false" { o.ErrorCode = "reported_unhealthy" } } return o } func countBGPSessions(sessions []birdfmt.BGPSession) (total, established int) { total = len(sessions) for _, s := range sessions { if strings.EqualFold(strings.TrimSpace(s.State), "Established") { established++ } } return total, established } func speakerLiveStatusJSON(sp *store.Speaker, view speakerBGPLive, health *nodedispatch.AgentHealthResult) map[string]any { var poll *agentHealthPoll if health != nil { poll = &agentHealthPoll{result: *health} } return speakerLiveStatusWithPoll(sp, view, poll) } func speakerLiveStatusWithPoll(sp *store.Speaker, view speakerBGPLive, health *agentHealthPoll) map[string]any { total, established := countBGPSessions(view.Sessions) bgpObservation := view.observation() agentObservation := agentHealthObservation(health) if sp != nil && strings.EqualFold(strings.TrimSpace(sp.Role), "master") && health == nil { agentObservation = bgpObservation } m := map[string]any{ "label": view.Label, "bgp_poll_ok": view.successful(), "bgp_sessions_total": nil, "bgp_established": nil, "bgp_observation": bgpObservation, "agent_observation": agentObservation, } if view.successful() { m["bgp_sessions_total"] = total m["bgp_established"] = established } if view.Error != "" { m["bgp_poll_error"] = view.Error } if health != nil { m["agent_ok"] = health.result.OK if health.result.Error != "" { m["agent_error"] = health.result.Error } if health.result.LastSyncAt != "" { m["agent_last_sync_at"] = health.result.LastSyncAt } if health.result.LastAppliedRevisionID != "" { m["agent_last_applied_revision_id"] = health.result.LastAppliedRevisionID } } else if sp != nil && strings.EqualFold(strings.TrimSpace(sp.Role), "master") { m["agent_ok"] = view.successful() if view.Error != "" { m["agent_error"] = view.Error } } else if sp != nil && store.SpeakerNeedsRemoteDispatch(sp.Role, store.ParseSpeakerMeta(sp.MetaJSON)) { m["agent_ok"] = false m["agent_error"] = "agent health not polled" } if view.successful() { sess := make([]map[string]any, 0, len(view.Sessions)) for _, s := range view.Sessions { row := map[string]any{ "name": s.Name, "state": s.State, } if strings.TrimSpace(s.Neighbor) != "" { row["neighbor"] = s.Neighbor } sess = append(sess, row) } m["sessions"] = sess } return m }