package api import ( "encoding/json" "net/http" "strings" "time" "github.com/rcourtman/pulse-go-rewrite/internal/monitoring" "github.com/rcourtman/pulse-go-rewrite/internal/utils" "github.com/rcourtman/pulse-go-rewrite/internal/websocket" agentsdocker "github.com/rcourtman/pulse-go-rewrite/pkg/agents/docker" "github.com/rs/zerolog/log" ) // DockerAgentHandlers manages ingest from the external Docker agent. type DockerAgentHandlers struct { monitor *monitoring.Monitor wsHub *websocket.Hub } // NewDockerAgentHandlers constructs a new Docker agent handler group. func NewDockerAgentHandlers(m *monitoring.Monitor, hub *websocket.Hub) *DockerAgentHandlers { return &DockerAgentHandlers{monitor: m, wsHub: hub} } // SetMonitor updates the monitor reference for docker agent handlers. func (h *DockerAgentHandlers) SetMonitor(m *monitoring.Monitor) { h.monitor = m } // HandleReport accepts heartbeat payloads from the Docker agent. func (h *DockerAgentHandlers) HandleReport(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { writeErrorResponse(w, http.StatusMethodNotAllowed, "method_not_allowed", "Only POST is allowed", nil) return } defer r.Body.Close() var report agentsdocker.Report if err := json.NewDecoder(r.Body).Decode(&report); err != nil { writeErrorResponse(w, http.StatusBadRequest, "invalid_json", "Failed to decode request body", map[string]string{"error": err.Error()}) return } if report.Timestamp.IsZero() { report.Timestamp = time.Now() } host, err := h.monitor.ApplyDockerReport(report) if err != nil { writeErrorResponse(w, http.StatusBadRequest, "invalid_report", err.Error(), nil) return } log.Debug(). Str("dockerHost", host.Hostname). Int("containers", len(host.Containers)). Msg("Docker agent report processed") // Broadcast the updated state for near-real-time UI updates go h.wsHub.BroadcastState(h.monitor.GetState().ToFrontend()) response := map[string]any{ "success": true, "hostId": host.ID, "containers": len(host.Containers), "lastSeen": host.LastSeen, } if err := utils.WriteJSONResponse(w, response); err != nil { log.Error().Err(err).Msg("Failed to serialize docker agent response") } } // HandleDeleteHost removes a docker host and its containers from the shared state. func (h *DockerAgentHandlers) HandleDeleteHost(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodDelete { writeErrorResponse(w, http.StatusMethodNotAllowed, "method_not_allowed", "Only DELETE is allowed", nil) return } trimmedPath := strings.TrimPrefix(r.URL.Path, "/api/agents/docker/hosts/") hostID := strings.TrimSpace(trimmedPath) if hostID == "" { writeErrorResponse(w, http.StatusBadRequest, "missing_host_id", "Docker host ID is required", nil) return } host, err := h.monitor.RemoveDockerHost(hostID) if err != nil { writeErrorResponse(w, http.StatusNotFound, "docker_host_not_found", err.Error(), nil) return } go h.wsHub.BroadcastState(h.monitor.GetState().ToFrontend()) if err := utils.WriteJSONResponse(w, map[string]any{ "success": true, "hostId": host.ID, "message": "Docker host removed", }); err != nil { log.Error().Err(err).Msg("Failed to serialize docker host removal response") } }