package api import ( "crypto/rand" "encoding/hex" "encoding/json" "log" "net/http" "strconv" "strings" "time" "github.com/cynfo/rmm-backend/db" "github.com/cynfo/rmm-backend/models" "github.com/cynfo/rmm-backend/ws" ) type Handler struct { db *db.Database } func NewHandler(database *db.Database) *Handler { return &Handler{db: database} } // SetupRoutes - Alle API-Routes registrieren func (h *Handler) SetupRoutes(mux *http.ServeMux, hub *ws.Hub) { // Bestehende Agent-Routes mux.HandleFunc("POST /api/v1/agent/register", h.registerAgent) mux.HandleFunc("POST /api/v1/agent/heartbeat", h.heartbeat) mux.HandleFunc("GET /api/v1/agents", h.listAgents) mux.HandleFunc("GET /api/v1/agents/events", h.getAllAgentEvents) mux.HandleFunc("GET /api/v1/agents/{id}", h.getAgent) mux.HandleFunc("GET /api/v1/agents/{id}/events", h.getAgentEvents) mux.HandleFunc("GET /api/v1/agents/{id}/system", h.getSystemData) mux.HandleFunc("DELETE /api/v1/agents/{id}", h.deleteAgent) // Customer-Routes mux.HandleFunc("GET /api/v1/customers", h.listCustomers) mux.HandleFunc("POST /api/v1/customers", h.createCustomer) mux.HandleFunc("GET /api/v1/customers/{id}", h.getCustomer) mux.HandleFunc("PUT /api/v1/customers/{id}", h.updateCustomer) mux.HandleFunc("DELETE /api/v1/customers/{id}", h.deleteCustomer) mux.HandleFunc("GET /api/v1/customers/{id}/agents", h.getCustomerAgents) // Agent-Customer Zuordnung mux.HandleFunc("PUT /api/v1/agents/{id}/customer", h.assignAgentCustomer) mux.HandleFunc("DELETE /api/v1/agents/{id}/customer", h.unassignAgentCustomer) // User-Routes mux.HandleFunc("GET /api/v1/users", h.listUsers) mux.HandleFunc("POST /api/v1/users", h.createUser) mux.HandleFunc("PUT /api/v1/users/{id}/password", h.changePassword) mux.HandleFunc("DELETE /api/v1/users/{id}", h.deleteUser) // WebSocket und Tunnel-Routes h.setupTunnelRoutes(mux, hub) } func generateID() string { b := make([]byte, 16) rand.Read(b) return hex.EncodeToString(b) } func writeJSON(w http.ResponseWriter, status int, v interface{}) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(status) json.NewEncoder(w).Encode(v) } func writeError(w http.ResponseWriter, status int, msg string) { writeJSON(w, status, map[string]string{"error": msg}) } // POST /api/v1/agent/register func (h *Handler) registerAgent(w http.ResponseWriter, r *http.Request) { var req models.RegisterRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { writeError(w, http.StatusBadRequest, "Ungueltige Anfrage") return } if req.Name == "" || req.Hostname == "" { writeError(w, http.StatusBadRequest, "Name und Hostname sind erforderlich") return } // Agent-ID bestimmen: vom Agent mitgeliefert > nach Hostname suchen > neu generieren agentID := req.AgentID isExisting := false if agentID != "" { // Pruefen ob die ID schon in der DB existiert if existing, _, err := h.db.GetAgent(agentID); err == nil && existing != nil { isExisting = true } } if agentID == "" { // Nach bestehendem Agent mit gleichem Hostname suchen if existing, err := h.db.GetAgentByHostname(req.Hostname); err == nil && existing != nil { agentID = existing.ID isExisting = true } } if agentID == "" { agentID = generateID() } agent := &models.Agent{ ID: agentID, Name: req.Name, Hostname: req.Hostname, IP: req.IP, OPNsenseVersion: req.OPNsenseVersion, RegisteredAt: time.Now().UTC(), } if err := h.db.RegisterAgent(agent); err != nil { log.Printf("Fehler bei Registrierung: %v", err) writeError(w, http.StatusInternalServerError, "Registrierung fehlgeschlagen") return } action := "registriert" if isExisting { action = "re-registriert" } log.Printf("Agent %s: %s (%s)", action, agent.Name, agent.ID) writeJSON(w, http.StatusCreated, models.RegisterResponse{ ID: agent.ID, Message: "Agent erfolgreich registriert", }) } // POST /api/v1/agent/heartbeat func (h *Handler) heartbeat(w http.ResponseWriter, r *http.Request) { var req models.HeartbeatRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { writeError(w, http.StatusBadRequest, "Ungueltige Anfrage") return } if req.AgentID == "" { writeError(w, http.StatusBadRequest, "agent_id erforderlich") return } if err := h.db.SaveSystemData(req.AgentID, &req.SystemData); err != nil { if strings.Contains(err.Error(), "nicht gefunden") { writeError(w, http.StatusNotFound, "Agent nicht gefunden") return } log.Printf("Fehler bei Heartbeat: %v", err) writeError(w, http.StatusInternalServerError, "Heartbeat fehlgeschlagen") return } writeJSON(w, http.StatusOK, models.HeartbeatResponse{Message: "OK"}) } // GET /api/v1/agents func (h *Handler) listAgents(w http.ResponseWriter, r *http.Request) { agents, err := h.db.GetAgents() if err != nil { log.Printf("Fehler beim Laden der Agents: %v", err) writeError(w, http.StatusInternalServerError, "Fehler beim Laden") return } if agents == nil { agents = []models.Agent{} } // Status fuer jeden Agent berechnen var response []models.AgentResponse for _, a := range agents { response = append(response, models.AgentResponse{ Agent: a, Status: models.CalculateAgentStatus(a.LastHeartbeat), }) } if response == nil { response = []models.AgentResponse{} } writeJSON(w, http.StatusOK, response) } // GET /api/v1/agents/{id} func (h *Handler) getAgent(w http.ResponseWriter, r *http.Request) { id := r.PathValue("id") agent, sysData, err := h.db.GetAgent(id) if err != nil { log.Printf("Fehler: %v", err) writeError(w, http.StatusInternalServerError, "Fehler beim Laden") return } if agent == nil { writeError(w, http.StatusNotFound, "Agent nicht gefunden") return } result := map[string]interface{}{ "agent": agent, "status": models.CalculateAgentStatus(agent.LastHeartbeat), "system_data": sysData, } writeJSON(w, http.StatusOK, result) } // GET /api/v1/agents/{id}/system func (h *Handler) getSystemData(w http.ResponseWriter, r *http.Request) { id := r.PathValue("id") data, err := h.db.GetSystemData(id) if err != nil { log.Printf("Fehler: %v", err) writeError(w, http.StatusInternalServerError, "Fehler beim Laden") return } if data == nil { writeError(w, http.StatusNotFound, "Keine Systemdaten vorhanden") return } writeJSON(w, http.StatusOK, data) } // DELETE /api/v1/agents/{id} func (h *Handler) deleteAgent(w http.ResponseWriter, r *http.Request) { id := r.PathValue("id") deleted, err := h.db.DeleteAgent(id) if err != nil { log.Printf("Fehler beim Loeschen: %v", err) writeError(w, http.StatusInternalServerError, "Loeschen fehlgeschlagen") return } if !deleted { writeError(w, http.StatusNotFound, "Agent nicht gefunden") return } log.Printf("Agent geloescht: %s", id) writeJSON(w, http.StatusOK, map[string]string{"message": "Agent geloescht"}) } // GET /api/v1/agents/{id}/events func (h *Handler) getAgentEvents(w http.ResponseWriter, r *http.Request) { agentID := r.PathValue("id") eventType := r.URL.Query().Get("type") limit := 100 if l := r.URL.Query().Get("limit"); l != "" { if n, err := strconv.Atoi(l); err == nil && n > 0 { limit = n } } events, err := h.db.GetAgentEvents(agentID, eventType, limit) if err != nil { log.Printf("Fehler beim Laden der Events: %v", err) writeError(w, http.StatusInternalServerError, "Fehler beim Laden") return } writeJSON(w, http.StatusOK, events) } // GET /api/v1/agents/events func (h *Handler) getAllAgentEvents(w http.ResponseWriter, r *http.Request) { eventType := r.URL.Query().Get("type") limit := 100 if l := r.URL.Query().Get("limit"); l != "" { if n, err := strconv.Atoi(l); err == nil && n > 0 { limit = n } } events, err := h.db.GetAllAgentEvents(eventType, limit) if err != nil { log.Printf("Fehler beim Laden der Events: %v", err) writeError(w, http.StatusInternalServerError, "Fehler beim Laden") return } writeJSON(w, http.StatusOK, events) }