diff --git a/pkg/server/handlers.go b/pkg/server/handlers.go index 5f93f14..a0f9ff8 100644 --- a/pkg/server/handlers.go +++ b/pkg/server/handlers.go @@ -784,6 +784,12 @@ func (h *Handler) RemoteOpenAIProxy(w http.ResponseWriter, r *http.Request, mode io.Copy(w, resp.Body) } +// NodeResponse represents a sanitized node configuration for API responses +type NodeResponse struct { + Name string `json:"name"` + Address string `json:"address"` +} + // ParseCommandRequest represents the request body for command parsing type ParseCommandRequest struct { Command string `json:"command"` @@ -864,21 +870,21 @@ func (h *Handler) ParseMlxCommand() http.HandlerFunc { writeError(w, http.StatusBadRequest, "invalid_request", "Invalid JSON body") return } - + if strings.TrimSpace(req.Command) == "" { writeError(w, http.StatusBadRequest, "invalid_command", "Command cannot be empty") return } - + mlxOptions, err := mlx.ParseMlxCommand(req.Command) if err != nil { writeError(w, http.StatusBadRequest, "parse_error", err.Error()) return } - + // Currently only support mlx_lm backend type backendType := backends.BackendTypeMlxLm - + options := &instance.CreateInstanceOptions{ BackendType: backendType, MlxServerOptions: mlxOptions, @@ -943,3 +949,78 @@ func (h *Handler) ParseVllmCommand() http.HandlerFunc { } } } + +// ListNodes godoc +// @Summary List all configured nodes +// @Description Returns a list of all nodes configured in the server +// @Tags nodes +// @Security ApiKeyAuth +// @Produces json +// @Success 200 {array} NodeResponse "List of nodes" +// @Failure 500 {string} string "Internal Server Error" +// @Router /nodes [get] +func (h *Handler) ListNodes() http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + // Convert to sanitized response format + nodeResponses := make([]NodeResponse, len(h.cfg.Nodes)) + for i, node := range h.cfg.Nodes { + nodeResponses[i] = NodeResponse{ + Name: node.Name, + Address: node.Address, + } + } + + w.Header().Set("Content-Type", "application/json") + if err := json.NewEncoder(w).Encode(nodeResponses); err != nil { + http.Error(w, "Failed to encode nodes: "+err.Error(), http.StatusInternalServerError) + return + } + } +} + +// GetNode godoc +// @Summary Get details of a specific node +// @Description Returns the details of a specific node by name +// @Tags nodes +// @Security ApiKeyAuth +// @Produces json +// @Param name path string true "Node Name" +// @Success 200 {object} NodeResponse "Node details" +// @Failure 400 {string} string "Invalid name format" +// @Failure 404 {string} string "Node not found" +// @Failure 500 {string} string "Internal Server Error" +// @Router /nodes/{name} [get] +func (h *Handler) GetNode() http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + name := chi.URLParam(r, "name") + if name == "" { + http.Error(w, "Node name cannot be empty", http.StatusBadRequest) + return + } + + var nodeConfig *config.NodeConfig + for i := range h.cfg.Nodes { + if h.cfg.Nodes[i].Name == name { + nodeConfig = &h.cfg.Nodes[i] + break + } + } + + if nodeConfig == nil { + http.Error(w, "Node not found", http.StatusNotFound) + return + } + + // Convert to sanitized response format + nodeResponse := NodeResponse{ + Name: nodeConfig.Name, + Address: nodeConfig.Address, + } + + w.Header().Set("Content-Type", "application/json") + if err := json.NewEncoder(w).Encode(nodeResponse); err != nil { + http.Error(w, "Failed to encode node: "+err.Error(), http.StatusInternalServerError) + return + } + } +} diff --git a/pkg/server/routes.go b/pkg/server/routes.go index 6af6a5c..d14baec 100644 --- a/pkg/server/routes.go +++ b/pkg/server/routes.go @@ -60,6 +60,15 @@ func SetupRouter(handler *Handler) *chi.Mux { }) }) + // Node management endpoints + r.Route("/nodes", func(r chi.Router) { + r.Get("/", handler.ListNodes()) // List all nodes + + r.Route("/{name}", func(r chi.Router) { + r.Get("/", handler.GetNode()) + }) + }) + // Instance management endpoints r.Route("/instances", func(r chi.Router) { r.Get("/", handler.ListInstances()) // List all instances