...
This commit is contained in:
		
							
								
								
									
										127
									
								
								_pkg2_dont_use/heroagent/api/admin.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										127
									
								
								_pkg2_dont_use/heroagent/api/admin.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,127 @@ | ||||
| package api | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"time" | ||||
|  | ||||
| 	"git.ourworld.tf/herocode/heroagent/pkg/system/stats" | ||||
| 	"github.com/gofiber/fiber/v2" | ||||
| ) | ||||
|  | ||||
| // UptimeProvider defines an interface for getting system uptime | ||||
| type UptimeProvider interface { | ||||
| 	GetUptime() string | ||||
| } | ||||
|  | ||||
| // AdminHandler handles admin-related API routes | ||||
| type AdminHandler struct { | ||||
| 	uptimeProvider UptimeProvider | ||||
| 	statsManager   *stats.StatsManager | ||||
| } | ||||
|  | ||||
| // NewAdminHandler creates a new AdminHandler | ||||
| func NewAdminHandler(uptimeProvider UptimeProvider, statsManager *stats.StatsManager) *AdminHandler { | ||||
| 	// If statsManager is nil, create a new one with default settings | ||||
| 	if statsManager == nil { | ||||
| 		var err error | ||||
| 		statsManager, err = stats.NewStatsManagerWithDefaults() | ||||
| 		if err != nil { | ||||
| 			// Log the error but continue with nil statsManager | ||||
| 			fmt.Printf("Error creating StatsManager: %v\n", err) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return &AdminHandler{ | ||||
| 		uptimeProvider: uptimeProvider, | ||||
| 		statsManager:   statsManager, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // RegisterRoutes registers all admin API routes | ||||
| func (h *AdminHandler) RegisterRoutes(app *fiber.App) { | ||||
| 	// API endpoints | ||||
| 	admin := app.Group("/api") | ||||
|  | ||||
| 	// @Summary Get hardware stats | ||||
| 	// @Description Get hardware statistics in JSON format | ||||
| 	// @Tags admin | ||||
| 	// @Accept json | ||||
| 	// @Produce json | ||||
| 	// @Success 200 {object} map[string]interface{} | ||||
| 	// @Failure 500 {object} ErrorResponse | ||||
| 	// @Router /api/hardware-stats [get] | ||||
| 	admin.Get("/hardware-stats", h.getHardwareStatsJSON) | ||||
|  | ||||
| 	// @Summary Get process stats | ||||
| 	// @Description Get process statistics in JSON format | ||||
| 	// @Tags admin | ||||
| 	// @Accept json | ||||
| 	// @Produce json | ||||
| 	// @Success 200 {object} map[string]interface{} | ||||
| 	// @Failure 500 {object} ErrorResponse | ||||
| 	// @Router /api/process-stats [get] | ||||
| 	admin.Get("/process-stats", h.getProcessStatsJSON) | ||||
| } | ||||
|  | ||||
| // getProcessStatsJSON returns process statistics in JSON format for API consumption | ||||
| func (h *AdminHandler) getProcessStatsJSON(c *fiber.Ctx) error { | ||||
| 	// Get process stats from the StatsManager (limit to top 30 processes) | ||||
| 	var processData *stats.ProcessStats | ||||
| 	var err error | ||||
| 	if h.statsManager != nil { | ||||
| 		processData, err = h.statsManager.GetProcessStats(30) | ||||
| 	} else { | ||||
| 		// Fallback to direct function call if StatsManager is not available | ||||
| 		processData, err = stats.GetProcessStats(30) | ||||
| 	} | ||||
| 	if err != nil { | ||||
| 		return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ | ||||
| 			"success": false, | ||||
| 			"error":   "Failed to get process stats: " + err.Error(), | ||||
| 		}) | ||||
| 	} | ||||
|  | ||||
| 	// Convert to []fiber.Map for JSON response | ||||
| 	processStats := make([]fiber.Map, len(processData.Processes)) | ||||
| 	for i, proc := range processData.Processes { | ||||
| 		processStats[i] = fiber.Map{ | ||||
| 			"pid":             proc.PID, | ||||
| 			"name":            proc.Name, | ||||
| 			"status":          proc.Status, | ||||
| 			"cpu_percent":     proc.CPUPercent, | ||||
| 			"memory_mb":       proc.MemoryMB, | ||||
| 			"create_time_str": proc.CreateTime, | ||||
| 			"is_current":      proc.IsCurrent, | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	// Return JSON response | ||||
| 	return c.JSON(fiber.Map{ | ||||
| 		"success":   true, | ||||
| 		"processes": processStats, | ||||
| 		"timestamp": time.Now().Unix(), | ||||
| 	}) | ||||
| } | ||||
|  | ||||
| // getHardwareStatsJSON returns hardware stats in JSON format for API consumption | ||||
| func (h *AdminHandler) getHardwareStatsJSON(c *fiber.Ctx) error { | ||||
| 	// Get hardware stats from the StatsManager | ||||
| 	var hardwareStats map[string]interface{} | ||||
| 	if h.statsManager != nil { | ||||
| 		hardwareStats = h.statsManager.GetHardwareStatsJSON() | ||||
| 	} else { | ||||
| 		// Fallback to direct function call if StatsManager is not available | ||||
| 		hardwareStats = stats.GetHardwareStatsJSON() | ||||
| 	} | ||||
|  | ||||
| 	// Convert to fiber.Map for JSON response | ||||
| 	response := fiber.Map{ | ||||
| 		"success": true, | ||||
| 	} | ||||
| 	for k, v := range hardwareStats { | ||||
| 		response[k] = v | ||||
| 	} | ||||
|  | ||||
| 	// Return JSON response | ||||
| 	return c.JSON(response) | ||||
| } | ||||
							
								
								
									
										149
									
								
								_pkg2_dont_use/heroagent/api/executor.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										149
									
								
								_pkg2_dont_use/heroagent/api/executor.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,149 @@ | ||||
| package api | ||||
|  | ||||
| import ( | ||||
| 	"time" | ||||
|  | ||||
| 	"git.ourworld.tf/herocode/heroagent/pkg/sal/executor" | ||||
|  | ||||
| 	"github.com/gofiber/fiber/v2" | ||||
| ) | ||||
|  | ||||
| // ExecutorHandler handles executor-related API endpoints | ||||
| type ExecutorHandler struct { | ||||
| 	executor *executor.Executor | ||||
| } | ||||
|  | ||||
| // NewExecutorHandler creates a new executor handler | ||||
| func NewExecutorHandler(exec *executor.Executor) *ExecutorHandler { | ||||
| 	return &ExecutorHandler{ | ||||
| 		executor: exec, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // RegisterRoutes registers executor routes to the fiber app | ||||
| func (h *ExecutorHandler) RegisterRoutes(app *fiber.App) { | ||||
| 	group := app.Group("/api/executor") | ||||
|  | ||||
| 	// @Summary Execute a command | ||||
| 	// @Description Execute a command and return a job ID | ||||
| 	// @Tags executor | ||||
| 	// @Accept json | ||||
| 	// @Produce json | ||||
| 	// @Param command body ExecuteCommandRequest true "Command to execute" | ||||
| 	// @Success 200 {object} ExecuteCommandResponse | ||||
| 	// @Failure 400 {object} ErrorResponse | ||||
| 	// @Router /api/executor/execute [post] | ||||
| 	group.Post("/execute", h.executeCommand) | ||||
|  | ||||
| 	// @Summary List all jobs | ||||
| 	// @Description Get a list of all command execution jobs | ||||
| 	// @Tags executor | ||||
| 	// @Produce json | ||||
| 	// @Success 200 {array} JobResponse | ||||
| 	// @Router /api/executor/jobs [get] | ||||
| 	group.Get("/jobs", h.listJobs) | ||||
|  | ||||
| 	// @Summary Get job details | ||||
| 	// @Description Get details of a specific job by ID | ||||
| 	// @Tags executor | ||||
| 	// @Produce json | ||||
| 	// @Param id path string true "Job ID" | ||||
| 	// @Success 200 {object} JobResponse | ||||
| 	// @Failure 404 {object} ErrorResponse | ||||
| 	// @Router /api/executor/jobs/{id} [get] | ||||
| 	group.Get("/jobs/:id", h.getJob) | ||||
| } | ||||
|  | ||||
| // @Summary Execute a command | ||||
| // @Description Execute a command and return a job ID | ||||
| // @Tags executor | ||||
| // @Accept json | ||||
| // @Produce json | ||||
| // @Param command body ExecuteCommandRequest true "Command to execute" | ||||
| // @Success 200 {object} ExecuteCommandResponse | ||||
| // @Failure 400 {object} ErrorResponse | ||||
| // @Router /api/executor/execute [post] | ||||
| func (h *ExecutorHandler) executeCommand(c *fiber.Ctx) error { | ||||
| 	var req ExecuteCommandRequest | ||||
| 	if err := c.BodyParser(&req); err != nil { | ||||
| 		return c.Status(fiber.StatusBadRequest).JSON(ErrorResponse{ | ||||
| 			Error: "Invalid request: " + err.Error(), | ||||
| 		}) | ||||
| 	} | ||||
|  | ||||
| 	jobID, err := h.executor.ExecuteCommand(req.Command, req.Args) | ||||
| 	if err != nil { | ||||
| 		return c.Status(fiber.StatusInternalServerError).JSON(ErrorResponse{ | ||||
| 			Error: "Failed to execute command: " + err.Error(), | ||||
| 		}) | ||||
| 	} | ||||
|  | ||||
| 	return c.JSON(ExecuteCommandResponse{ | ||||
| 		JobID: jobID, | ||||
| 	}) | ||||
| } | ||||
|  | ||||
| // @Summary List all jobs | ||||
| // @Description Get a list of all command execution jobs | ||||
| // @Tags executor | ||||
| // @Produce json | ||||
| // @Success 200 {array} JobResponse | ||||
| // @Router /api/executor/jobs [get] | ||||
| func (h *ExecutorHandler) listJobs(c *fiber.Ctx) error { | ||||
| 	jobs := h.executor.ListJobs() | ||||
|  | ||||
| 	response := make([]JobResponse, 0, len(jobs)) | ||||
| 	for _, job := range jobs { | ||||
| 		var endTime time.Time | ||||
| 		if job.Status == "completed" || job.Status == "failed" { | ||||
| 			endTime = job.EndTime | ||||
| 		} | ||||
| 		response = append(response, JobResponse{ | ||||
| 			ID:        job.ID, | ||||
| 			Command:   job.Command, | ||||
| 			Args:      job.Args, | ||||
| 			StartTime: job.StartTime, | ||||
| 			EndTime:   endTime, | ||||
| 			Status:    job.Status, | ||||
| 			Output:    job.Output, | ||||
| 			Error:     job.Error, | ||||
| 		}) | ||||
| 	} | ||||
|  | ||||
| 	return c.JSON(response) | ||||
| } | ||||
|  | ||||
| // @Summary Get job details | ||||
| // @Description Get details of a specific job by ID | ||||
| // @Tags executor | ||||
| // @Produce json | ||||
| // @Param id path string true "Job ID" | ||||
| // @Success 200 {object} JobResponse | ||||
| // @Failure 404 {object} ErrorResponse | ||||
| // @Router /api/executor/jobs/{id} [get] | ||||
| func (h *ExecutorHandler) getJob(c *fiber.Ctx) error { | ||||
| 	jobID := c.Params("id") | ||||
|  | ||||
| 	job, err := h.executor.GetJob(jobID) | ||||
| 	if err != nil { | ||||
| 		return c.Status(fiber.StatusNotFound).JSON(ErrorResponse{ | ||||
| 			Error: err.Error(), | ||||
| 		}) | ||||
| 	} | ||||
|  | ||||
| 	var endTime time.Time | ||||
| 	if job.Status == "completed" || job.Status == "failed" { | ||||
| 		endTime = job.EndTime | ||||
| 	} | ||||
|  | ||||
| 	return c.JSON(JobResponse{ | ||||
| 		ID:        job.ID, | ||||
| 		Command:   job.Command, | ||||
| 		Args:      job.Args, | ||||
| 		StartTime: job.StartTime, | ||||
| 		EndTime:   endTime, | ||||
| 		Status:    job.Status, | ||||
| 		Output:    job.Output, | ||||
| 		Error:     job.Error, | ||||
| 	}) | ||||
| } | ||||
							
								
								
									
										112
									
								
								_pkg2_dont_use/heroagent/api/jet.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										112
									
								
								_pkg2_dont_use/heroagent/api/jet.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,112 @@ | ||||
| package api | ||||
|  | ||||
| import ( | ||||
| 	"strings" | ||||
|  | ||||
| 	"github.com/CloudyKit/jet/v6" | ||||
| 	"github.com/gofiber/fiber/v2" | ||||
| ) | ||||
|  | ||||
| // JetTemplateRequest represents the request body for the checkjet endpoint | ||||
| type JetTemplateRequest struct { | ||||
| 	Template string `json:"template"` | ||||
| } | ||||
|  | ||||
| // JetTemplateResponse represents the response for the checkjet endpoint | ||||
| type JetTemplateResponse struct { | ||||
| 	Valid   bool   `json:"valid"` | ||||
| 	Message string `json:"message,omitempty"` | ||||
| 	Error   string `json:"error,omitempty"` | ||||
| } | ||||
|  | ||||
| // JetHandler handles Jet template-related API endpoints | ||||
| type JetHandler struct { | ||||
| 	// No dependencies needed for this handler | ||||
| } | ||||
|  | ||||
| // NewJetHandler creates a new Jet template handler | ||||
| func NewJetHandler() *JetHandler { | ||||
| 	return &JetHandler{} | ||||
| } | ||||
|  | ||||
| // RegisterRoutes registers Jet template routes to the fiber app | ||||
| func (h *JetHandler) RegisterRoutes(app *fiber.App) { | ||||
| 	// Create a group for Jet API endpoints | ||||
| 	jetGroup := app.Group("/api/jet") | ||||
|  | ||||
| 	// Register the checkjet endpoint | ||||
| 	jetGroup.Post("/validate", h.validateTemplate) | ||||
| } | ||||
|  | ||||
| // @Summary Validate a Jet template | ||||
| // @Description Validates a Jet template and returns detailed error information if invalid | ||||
| // @Tags jet | ||||
| // @Accept json | ||||
| // @Produce json | ||||
| // @Param template body JetTemplateRequest true "Jet template to validate" | ||||
| // @Success 200 {object} JetTemplateResponse | ||||
| // @Failure 400 {object} map[string]interface{} | ||||
| // @Router /api/jet/validate [post] | ||||
| func (h *JetHandler) validateTemplate(c *fiber.Ctx) error { | ||||
| 	var req JetTemplateRequest | ||||
| 	if err := c.BodyParser(&req); err != nil { | ||||
| 		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ | ||||
| 			"success": false, | ||||
| 			"error":   "Invalid request: " + err.Error(), | ||||
| 		}) | ||||
| 	} | ||||
|  | ||||
| 	if req.Template == "" { | ||||
| 		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ | ||||
| 			"success": false, | ||||
| 			"error":   "Template cannot be empty", | ||||
| 		}) | ||||
| 	} | ||||
|  | ||||
| 	// Create a temporary in-memory loader for the template | ||||
| 	loader := jet.NewInMemLoader() | ||||
|  | ||||
| 	// Add the template to the loader | ||||
| 	loader.Set("test.jet", req.Template) | ||||
|  | ||||
| 	// Create a new Jet set with the loader and enable development mode for better error reporting | ||||
| 	set := jet.NewSet(loader, jet.InDevelopmentMode()) | ||||
|  | ||||
| 	// Get the template to parse it | ||||
| 	_, err := set.GetTemplate("test.jet") | ||||
|  | ||||
| 	// Check if the template is valid | ||||
| 	if err != nil { | ||||
| 		// Extract meaningful error information | ||||
| 		errMsg := err.Error() | ||||
|  | ||||
| 		// Ignore errors related to extended or included files not found | ||||
| 		// These aren't syntax errors but dependency errors we want to ignore | ||||
| 		if strings.Contains(errMsg, "no template") || | ||||
| 			strings.Contains(errMsg, "unable to locate template") || | ||||
| 			strings.Contains(errMsg, "template not found") || | ||||
| 			strings.Contains(errMsg, "extends|import") || | ||||
| 			strings.Contains(errMsg, "could not be found") || | ||||
| 			strings.Contains(errMsg, "template /") { | ||||
| 			// Still valid since it's only a dependency error, not a syntax error | ||||
| 			return c.JSON(fiber.Map{ | ||||
| 				"success": true, | ||||
| 				"valid":   true, | ||||
| 				"message": "Template syntax is valid (ignoring extends/include errors)", | ||||
| 			}) | ||||
| 		} | ||||
|  | ||||
| 		return c.JSON(fiber.Map{ | ||||
| 			"success": false, | ||||
| 			"valid":   false, | ||||
| 			"error":   errMsg, | ||||
| 		}) | ||||
| 	} | ||||
|  | ||||
| 	// If no error, the template is valid | ||||
| 	return c.JSON(fiber.Map{ | ||||
| 		"success": true, | ||||
| 		"valid":   true, | ||||
| 		"message": "Template is valid", | ||||
| 	}) | ||||
| } | ||||
							
								
								
									
										74
									
								
								_pkg2_dont_use/heroagent/api/main.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										74
									
								
								_pkg2_dont_use/heroagent/api/main.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,74 @@ | ||||
| // Package api contains API handlers for HeroLauncher | ||||
| package api | ||||
|  | ||||
| // @title HeroLauncher API | ||||
| // @version 1.0 | ||||
| // @description API for HeroLauncher - a modular service manager | ||||
| // @termsOfService http://swagger.io/terms/ | ||||
| // @contact.name API Support | ||||
| // @contact.email support@freeflowuniverse.org | ||||
| // @license.name Apache 2.0 | ||||
| // @license.url http://www.apache.org/licenses/LICENSE-2.0.html | ||||
| // @host localhost:9001 | ||||
| // @BasePath /api | ||||
| // @schemes http https | ||||
|  | ||||
| // This file exists solely to provide Swagger documentation | ||||
| // and to ensure all API handlers are included in the documentation | ||||
|  | ||||
| // AdminHandler handles admin-related API routes | ||||
| // @Router /api/hardware-stats [get] | ||||
| // @Router /api/process-stats [get] | ||||
|  | ||||
| // ServiceHandler handles service-related API routes | ||||
| // @Router /api/services/running [get] | ||||
| // @Router /api/services/start [post] | ||||
| // @Router /api/services/stop [post] | ||||
| // @Router /api/services/restart [post] | ||||
| // @Router /api/services/delete [post] | ||||
| // @Router /api/services/logs [post] | ||||
| // @Router /admin/services/ [get] | ||||
| // @Router /admin/services/data [get] | ||||
| // @Router /admin/services/running [get] | ||||
| // @Router /admin/services/start [post] | ||||
| // @Router /admin/services/stop [post] | ||||
| // @Router /admin/services/restart [post] | ||||
| // @Router /admin/services/delete [post] | ||||
| // @Router /admin/services/logs [post] | ||||
|  | ||||
| // ExecutorHandler handles command execution API routes | ||||
| // @Router /api/executor/execute [post] | ||||
| // @Router /api/executor/jobs [get] | ||||
| // @Router /api/executor/jobs/{id} [get] | ||||
|  | ||||
| // JetHandler handles Jet template API routes | ||||
| // @Router /api/jet/validate [post] | ||||
|  | ||||
| // RedisHandler handles Redis API routes | ||||
| // @Router /api/redis/set [post] | ||||
| // @Router /api/redis/get/{key} [get] | ||||
| // @Router /api/redis/del/{key} [delete] | ||||
| // @Router /api/redis/keys/{pattern} [get] | ||||
| // @Router /api/redis/hset [post] | ||||
| // @Router /api/redis/hget/{key}/{field} [get] | ||||
| // @Router /api/redis/hdel [post] | ||||
| // @Router /api/redis/hkeys/{key} [get] | ||||
| // @Router /api/redis/hgetall/{key} [get] | ||||
|  | ||||
| // JobHandler handles HeroJobs API routes | ||||
| // @Router /api/jobs/submit [post] | ||||
| // @Router /api/jobs/get/{id} [get] | ||||
| // @Router /api/jobs/delete/{id} [delete] | ||||
| // @Router /api/jobs/list [get] | ||||
| // @Router /api/jobs/queue/size [get] | ||||
| // @Router /api/jobs/queue/empty [post] | ||||
| // @Router /api/jobs/queue/get [get] | ||||
| // @Router /api/jobs/create [post] | ||||
| // @Router /admin/jobs/submit [post] | ||||
| // @Router /admin/jobs/get/{id} [get] | ||||
| // @Router /admin/jobs/delete/{id} [delete] | ||||
| // @Router /admin/jobs/list [get] | ||||
| // @Router /admin/jobs/queue/size [get] | ||||
| // @Router /admin/jobs/queue/empty [post] | ||||
| // @Router /admin/jobs/queue/get [get] | ||||
| // @Router /admin/jobs/create [post] | ||||
							
								
								
									
										105
									
								
								_pkg2_dont_use/heroagent/api/models.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										105
									
								
								_pkg2_dont_use/heroagent/api/models.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,105 @@ | ||||
| package api | ||||
|  | ||||
| import "time" | ||||
|  | ||||
| // ErrorResponse represents an error response | ||||
| type ErrorResponse struct { | ||||
| 	Error string `json:"error"` | ||||
| } | ||||
|  | ||||
| // Executor Models | ||||
|  | ||||
| // ExecuteCommandRequest represents a request to execute a command | ||||
| type ExecuteCommandRequest struct { | ||||
| 	Command string   `json:"command"` | ||||
| 	Args    []string `json:"args"` | ||||
| } | ||||
|  | ||||
| // ExecuteCommandResponse represents the response from executing a command | ||||
| type ExecuteCommandResponse struct { | ||||
| 	JobID string `json:"job_id"` | ||||
| } | ||||
|  | ||||
| // JobResponse represents a job response | ||||
| type JobResponse struct { | ||||
| 	ID        string    `json:"id"` | ||||
| 	Command   string    `json:"command"` | ||||
| 	Args      []string  `json:"args"` | ||||
| 	StartTime time.Time `json:"start_time"` | ||||
| 	EndTime   time.Time `json:"end_time"` | ||||
| 	Status    string    `json:"status"` | ||||
| 	Output    string    `json:"output"` | ||||
| 	Error     string    `json:"error"` | ||||
| } | ||||
|  | ||||
| // Redis Models | ||||
|  | ||||
| // SetKeyRequest represents a request to set a key | ||||
| type SetKeyRequest struct { | ||||
| 	Key               string `json:"key"` | ||||
| 	Value             string `json:"value"` | ||||
| 	ExpirationSeconds int    `json:"expiration_seconds"` | ||||
| } | ||||
|  | ||||
| // SetKeyResponse represents the response from setting a key | ||||
| type SetKeyResponse struct { | ||||
| 	Success bool `json:"success"` | ||||
| } | ||||
|  | ||||
| // GetKeyResponse represents the response from getting a key | ||||
| type GetKeyResponse struct { | ||||
| 	Value string `json:"value"` | ||||
| } | ||||
|  | ||||
| // DeleteKeyResponse represents the response from deleting a key | ||||
| type DeleteKeyResponse struct { | ||||
| 	Count int `json:"count"` | ||||
| } | ||||
|  | ||||
| // GetKeysResponse represents the response from getting keys | ||||
| type GetKeysResponse struct { | ||||
| 	Keys []string `json:"keys"` | ||||
| } | ||||
|  | ||||
| // HSetKeyRequest represents a request to set a hash field | ||||
| type HSetKeyRequest struct { | ||||
| 	Key   string `json:"key"` | ||||
| 	Field string `json:"field"` | ||||
| 	Value string `json:"value"` | ||||
| } | ||||
|  | ||||
| // HSetKeyResponse represents the response from setting a hash field | ||||
| type HSetKeyResponse struct { | ||||
| 	Added bool `json:"added"` | ||||
| } | ||||
|  | ||||
| // HGetKeyResponse represents the response from getting a hash field | ||||
| type HGetKeyResponse struct { | ||||
| 	Value string `json:"value"` | ||||
| } | ||||
|  | ||||
| // HDelKeyRequest represents a request to delete hash fields | ||||
| type HDelKeyRequest struct { | ||||
| 	Key    string   `json:"key"` | ||||
| 	Fields []string `json:"fields"` | ||||
| } | ||||
|  | ||||
| // HDelKeyResponse represents the response from deleting hash fields | ||||
| type HDelKeyResponse struct { | ||||
| 	Count int `json:"count"` | ||||
| } | ||||
|  | ||||
| // HKeysResponse represents the response from getting hash keys | ||||
| type HKeysResponse struct { | ||||
| 	Fields []string `json:"fields"` | ||||
| } | ||||
|  | ||||
| // HLenResponse represents the response from getting hash length | ||||
| type HLenResponse struct { | ||||
| 	Length int `json:"length"` | ||||
| } | ||||
|  | ||||
| // IncrKeyResponse represents the response from incrementing a key | ||||
| type IncrKeyResponse struct { | ||||
| 	Value int64 `json:"value"` | ||||
| } | ||||
							
								
								
									
										544
									
								
								_pkg2_dont_use/heroagent/api/processmanager.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										544
									
								
								_pkg2_dont_use/heroagent/api/processmanager.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,544 @@ | ||||
| package api | ||||
|  | ||||
| import ( | ||||
| 	"encoding/json" | ||||
| 	"fmt" | ||||
| 	"log" | ||||
| 	"strconv" | ||||
| 	"time" | ||||
|  | ||||
| 	"git.ourworld.tf/herocode/heroagent/pkg/processmanager" | ||||
| 	"git.ourworld.tf/herocode/heroagent/pkg/processmanager/interfaces" | ||||
| 	"git.ourworld.tf/herocode/heroagent/pkg/processmanager/interfaces/openrpc" | ||||
| 	"github.com/gofiber/fiber/v2" | ||||
| ) | ||||
|  | ||||
| // ProcessDisplayInfo represents information about a process for display purposes | ||||
| type ProcessDisplayInfo struct { | ||||
| 	ID        string `json:"id"` | ||||
| 	Name      string `json:"name"` | ||||
| 	Status    string `json:"status"` | ||||
| 	Uptime    string `json:"uptime"` | ||||
| 	StartTime string `json:"start_time"` | ||||
| 	CPU       string `json:"cpu"` | ||||
| 	Memory    string `json:"memory"` | ||||
| } | ||||
|  | ||||
| // ConvertToDisplayInfo converts a ProcessInfo from the processmanager package to ProcessDisplayInfo | ||||
| func ConvertToDisplayInfo(info *processmanager.ProcessInfo) ProcessDisplayInfo { | ||||
| 	// Calculate uptime from start time | ||||
| 	uptime := formatUptime(time.Since(info.StartTime)) | ||||
|  | ||||
| 	return ProcessDisplayInfo{ | ||||
| 		ID:        fmt.Sprintf("%d", info.PID), | ||||
| 		Name:      info.Name, | ||||
| 		Status:    string(info.Status), | ||||
| 		Uptime:    uptime, | ||||
| 		StartTime: info.StartTime.Format("2006-01-02 15:04:05"), | ||||
| 		CPU:       fmt.Sprintf("%.2f%%", info.CPUPercent), | ||||
| 		Memory:    fmt.Sprintf("%.2f MB", info.MemoryMB), | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // ServiceHandler handles service-related API routes | ||||
| type ServiceHandler struct { | ||||
| 	client *openrpc.Client | ||||
| 	logger *log.Logger | ||||
| } | ||||
|  | ||||
| // default number of log lines to retrieve - use a high value to essentially show all logs | ||||
| const DefaultLogLines = 10000 | ||||
|  | ||||
| // NewServiceHandler creates a new service handler with the provided socket path and secret | ||||
| func NewServiceHandler(socketPath, secret string, logger *log.Logger) *ServiceHandler { | ||||
| 	fmt.Printf("DEBUG: Creating new api.ServiceHandler with socket path: %s and secret: %s\n", socketPath, secret) | ||||
| 	return &ServiceHandler{ | ||||
| 		client: openrpc.NewClient(socketPath, secret), | ||||
| 		logger: logger, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // RegisterRoutes registers service API routes | ||||
| func (h *ServiceHandler) RegisterRoutes(app *fiber.App) { | ||||
| 	// Register common routes to both API and admin groups | ||||
| 	serviceRoutes := func(group fiber.Router) { | ||||
| 		group.Get("/running", h.getRunningServices) | ||||
| 		group.Post("/start", h.startService) | ||||
| 		group.Post("/stop", h.stopService) | ||||
| 		group.Post("/restart", h.restartService) | ||||
| 		group.Post("/delete", h.deleteService) | ||||
| 		group.Post("/logs", h.getProcessLogs) | ||||
| 	} | ||||
|  | ||||
| 	// Apply common routes to API group | ||||
| 	apiServices := app.Group("/api/services") | ||||
| 	serviceRoutes(apiServices) | ||||
|  | ||||
| 	// Apply common routes to admin group and add admin-specific routes | ||||
| 	adminServices := app.Group("/admin/services") | ||||
| 	serviceRoutes(adminServices) | ||||
|  | ||||
| 	// Admin-only routes | ||||
| 	adminServices.Get("/", h.getServicesPage) | ||||
| 	adminServices.Get("/data", h.getServicesData) | ||||
| } | ||||
|  | ||||
| // getProcessList gets a list of processes from the process manager | ||||
| // TODO: add swagger annotations | ||||
| func (h *ServiceHandler) getProcessList() ([]ProcessDisplayInfo, error) { | ||||
| 	// Debug: Log the function entry | ||||
| 	h.logger.Printf("Entering getProcessList() function") | ||||
| 	fmt.Printf("DEBUG: API getProcessList called using client: %p\n", h.client) | ||||
|  | ||||
| 	// Get the list of processes via the client | ||||
| 	result, err := h.client.ListProcesses("json") | ||||
| 	if err != nil { | ||||
| 		h.logger.Printf("Error listing processes: %v", err) | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	// Convert the result to a slice of ProcessStatus | ||||
| 	processStatuses, ok := result.([]interfaces.ProcessStatus) | ||||
| 	if !ok { | ||||
| 		// Try to handle the result as a map or other structure | ||||
| 		h.logger.Printf("Warning: unexpected result type from ListProcesses, trying alternative parsing") | ||||
|  | ||||
| 		// Try to convert the result to JSON and then parse it | ||||
| 		resultJSON, err := json.Marshal(result) | ||||
| 		if err != nil { | ||||
| 			h.logger.Printf("Error marshaling result to JSON: %v", err) | ||||
| 			return nil, fmt.Errorf("failed to marshal result: %w", err) | ||||
| 		} | ||||
|  | ||||
| 		var processStatuses []interfaces.ProcessStatus | ||||
| 		if err := json.Unmarshal(resultJSON, &processStatuses); err != nil { | ||||
| 			h.logger.Printf("Error unmarshaling result to ProcessStatus: %v", err) | ||||
| 			return nil, fmt.Errorf("failed to unmarshal process list result: %w", err) | ||||
| 		} | ||||
|  | ||||
| 		// Convert to display info format | ||||
| 		displayInfoList := make([]ProcessDisplayInfo, 0, len(processStatuses)) | ||||
| 		for _, proc := range processStatuses { | ||||
| 			// Calculate uptime based on start time | ||||
| 			uptime := formatUptime(time.Since(proc.StartTime)) | ||||
|  | ||||
| 			displayInfo := ProcessDisplayInfo{ | ||||
| 				ID:        fmt.Sprintf("%d", proc.PID), | ||||
| 				Name:      proc.Name, | ||||
| 				Status:    string(proc.Status), | ||||
| 				Uptime:    uptime, | ||||
| 				StartTime: proc.StartTime.Format("2006-01-02 15:04:05"), | ||||
| 				CPU:       fmt.Sprintf("%.2f%%", proc.CPUPercent), | ||||
| 				Memory:    fmt.Sprintf("%.2f MB", proc.MemoryMB), | ||||
| 			} | ||||
| 			displayInfoList = append(displayInfoList, displayInfo) | ||||
| 		} | ||||
|  | ||||
| 		// Debug: Log the number of processes | ||||
| 		h.logger.Printf("Found %d processes", len(displayInfoList)) | ||||
| 		return displayInfoList, nil | ||||
| 	} | ||||
|  | ||||
| 	// Convert to display info format | ||||
| 	displayInfoList := make([]ProcessDisplayInfo, 0, len(processStatuses)) | ||||
| 	for _, proc := range processStatuses { | ||||
| 		// Calculate uptime based on start time | ||||
| 		uptime := formatUptime(time.Since(proc.StartTime)) | ||||
|  | ||||
| 		displayInfo := ProcessDisplayInfo{ | ||||
| 			ID:        fmt.Sprintf("%d", proc.PID), | ||||
| 			Name:      proc.Name, | ||||
| 			Status:    string(proc.Status), | ||||
| 			Uptime:    uptime, | ||||
| 			StartTime: proc.StartTime.Format("2006-01-02 15:04:05"), | ||||
| 			CPU:       fmt.Sprintf("%.2f%%", proc.CPUPercent), | ||||
| 			Memory:    fmt.Sprintf("%.2f MB", proc.MemoryMB), | ||||
| 		} | ||||
| 		displayInfoList = append(displayInfoList, displayInfo) | ||||
| 	} | ||||
|  | ||||
| 	// Debug: Log the number of processes | ||||
| 	h.logger.Printf("Found %d processes", len(displayInfoList)) | ||||
|  | ||||
| 	return displayInfoList, nil | ||||
| } | ||||
|  | ||||
| // formatUptime formats a duration as a human-readable uptime string | ||||
| func formatUptime(duration time.Duration) string { | ||||
| 	totalSeconds := int(duration.Seconds()) | ||||
| 	days := totalSeconds / (24 * 3600) | ||||
| 	hours := (totalSeconds % (24 * 3600)) / 3600 | ||||
| 	minutes := (totalSeconds % 3600) / 60 | ||||
| 	seconds := totalSeconds % 60 | ||||
|  | ||||
| 	if days > 0 { | ||||
| 		return fmt.Sprintf("%d days, %d hours", days, hours) | ||||
| 	} else if hours > 0 { | ||||
| 		return fmt.Sprintf("%d hours, %d minutes", hours, minutes) | ||||
| 	} else if minutes > 0 { | ||||
| 		return fmt.Sprintf("%d minutes, %d seconds", minutes, seconds) | ||||
| 	} else { | ||||
| 		return fmt.Sprintf("%d seconds", seconds) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // @Summary Start a service | ||||
| // @Description Start a new service with the given name and command | ||||
| // @Tags services | ||||
| // @Accept x-www-form-urlencoded | ||||
| // @Produce json | ||||
| // @Param name formData string true "Service name" | ||||
| // @Param command formData string true "Command to run" | ||||
| // @Success 200 {object} map[string]interface{} | ||||
| // @Failure 400 {object} map[string]string | ||||
| // @Failure 500 {object} map[string]string | ||||
| // @Router /api/services/start [post] | ||||
| // @Router /admin/services/start [post] | ||||
| func (h *ServiceHandler) startService(c *fiber.Ctx) error { | ||||
| 	// Get form values | ||||
| 	name := c.FormValue("name") | ||||
| 	command := c.FormValue("command") | ||||
|  | ||||
| 	// Validate inputs | ||||
| 	if name == "" || command == "" { | ||||
| 		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ | ||||
| 			"success": false, | ||||
| 			"error":   "Name and command are required", | ||||
| 		}) | ||||
| 	} | ||||
|  | ||||
| 	// Start the process with default values | ||||
| 	// logEnabled=true, deadline=0 (no deadline), no cron, no jobID | ||||
| 	fmt.Printf("DEBUG: API startService called for '%s' using client: %p\n", name, h.client) | ||||
| 	result, err := h.client.StartProcess(name, command, true, 0, "", "") | ||||
| 	if err != nil { | ||||
| 		return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ | ||||
| 			"success": false, | ||||
| 			"error":   fmt.Sprintf("Failed to start service: %v", err), | ||||
| 		}) | ||||
| 	} | ||||
|  | ||||
| 	// Check if the result indicates success | ||||
| 	if !result.Success { | ||||
| 		return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ | ||||
| 			"success": false, | ||||
| 			"error":   result.Message, | ||||
| 		}) | ||||
| 	} | ||||
|  | ||||
| 	// Get the PID from the result | ||||
| 	pid := result.PID | ||||
|  | ||||
| 	return c.JSON(fiber.Map{ | ||||
| 		"success": true, | ||||
| 		"message": fmt.Sprintf("Service '%s' started with PID %d", name, pid), | ||||
| 		"pid":     pid, | ||||
| 	}) | ||||
| } | ||||
|  | ||||
| // @Summary Stop a service | ||||
| // @Description Stop a running service by name | ||||
| // @Tags services | ||||
| // @Accept x-www-form-urlencoded | ||||
| // @Produce json | ||||
| // @Param name formData string true "Service name" | ||||
| // @Success 200 {object} map[string]interface{} | ||||
| // @Failure 400 {object} map[string]string | ||||
| // @Failure 500 {object} map[string]string | ||||
| // @Router /api/services/stop [post] | ||||
| // @Router /admin/services/stop [post] | ||||
| // stopService stops a service | ||||
| func (h *ServiceHandler) stopService(c *fiber.Ctx) error { | ||||
| 	// Get form values | ||||
| 	name := c.FormValue("name") | ||||
|  | ||||
| 	// For backward compatibility, try ID field if name is empty | ||||
| 	if name == "" { | ||||
| 		name = c.FormValue("id") | ||||
| 		if name == "" { | ||||
| 			return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ | ||||
| 				"success": false, | ||||
| 				"error":   "Process name is required", | ||||
| 			}) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	// Log the stop request | ||||
| 	h.logger.Printf("Stopping process with name: %s", name) | ||||
|  | ||||
| 	// Stop the process | ||||
| 	fmt.Printf("DEBUG: API stopService called for '%s' using client: %p\n", name, h.client) | ||||
| 	result, err := h.client.StopProcess(name) | ||||
| 	if err != nil { | ||||
| 		h.logger.Printf("Error stopping process: %v", err) | ||||
| 		return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ | ||||
| 			"success": false, | ||||
| 			"error":   fmt.Sprintf("Failed to stop service: %v", err), | ||||
| 		}) | ||||
| 	} | ||||
|  | ||||
| 	// Check if the result indicates success | ||||
| 	if !result.Success { | ||||
| 		return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ | ||||
| 			"success": false, | ||||
| 			"error":   result.Message, | ||||
| 		}) | ||||
| 	} | ||||
|  | ||||
| 	return c.JSON(fiber.Map{ | ||||
| 		"success": true, | ||||
| 		"message": fmt.Sprintf("Service '%s' stopped successfully", name), | ||||
| 	}) | ||||
| } | ||||
|  | ||||
| // @Summary Restart a service | ||||
| // @Description Restart a running service by name | ||||
| // @Tags services | ||||
| // @Accept x-www-form-urlencoded | ||||
| // @Produce json | ||||
| // @Param name formData string true "Service name" | ||||
| // @Success 200 {object} map[string]interface{} | ||||
| // @Failure 400 {object} map[string]string | ||||
| // @Failure 500 {object} map[string]string | ||||
| // @Router /api/services/restart [post] | ||||
| // @Router /admin/services/restart [post] | ||||
| // restartService restarts a service | ||||
| func (h *ServiceHandler) restartService(c *fiber.Ctx) error { | ||||
| 	// Get form values | ||||
| 	name := c.FormValue("name") | ||||
|  | ||||
| 	// For backward compatibility, try ID field if name is empty | ||||
| 	if name == "" { | ||||
| 		name = c.FormValue("id") | ||||
| 		if name == "" { | ||||
| 			return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ | ||||
| 				"success": false, | ||||
| 				"error":   "Process name is required", | ||||
| 			}) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	// Log the restart request | ||||
| 	h.logger.Printf("Restarting process with name: %s", name) | ||||
|  | ||||
| 	// Restart the process | ||||
| 	fmt.Printf("DEBUG: API restartService called for '%s' using client: %p\n", name, h.client) | ||||
| 	result, err := h.client.RestartProcess(name) | ||||
| 	if err != nil { | ||||
| 		h.logger.Printf("Error restarting process: %v", err) | ||||
| 		return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ | ||||
| 			"success": false, | ||||
| 			"error":   fmt.Sprintf("Failed to restart service: %v", err), | ||||
| 		}) | ||||
| 	} | ||||
|  | ||||
| 	// Check if the result indicates success | ||||
| 	if !result.Success { | ||||
| 		return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ | ||||
| 			"success": false, | ||||
| 			"error":   result.Message, | ||||
| 		}) | ||||
| 	} | ||||
|  | ||||
| 	return c.JSON(fiber.Map{ | ||||
| 		"success": true, | ||||
| 		"message": fmt.Sprintf("Service '%s' restarted successfully", name), | ||||
| 	}) | ||||
| } | ||||
|  | ||||
| // @Summary Delete a service | ||||
| // @Description Delete a service by name | ||||
| // @Tags services | ||||
| // @Accept x-www-form-urlencoded | ||||
| // @Produce json | ||||
| // @Param name formData string true "Service name" | ||||
| // @Success 200 {object} map[string]interface{} | ||||
| // @Failure 400 {object} map[string]string | ||||
| // @Failure 500 {object} map[string]string | ||||
| // @Router /api/services/delete [post] | ||||
| // @Router /admin/services/delete [post] | ||||
| // deleteService deletes a service | ||||
| func (h *ServiceHandler) deleteService(c *fiber.Ctx) error { | ||||
| 	// Get form values | ||||
| 	name := c.FormValue("name") | ||||
|  | ||||
| 	// Validate inputs | ||||
| 	if name == "" { | ||||
| 		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ | ||||
| 			"success": false, | ||||
| 			"error":   "Service name is required", | ||||
| 		}) | ||||
| 	} | ||||
|  | ||||
| 	// Debug: Log the delete request | ||||
| 	h.logger.Printf("Deleting process with name: %s", name) | ||||
|  | ||||
| 	// Delete the process | ||||
| 	fmt.Printf("DEBUG: API deleteService called for '%s' using client: %p\n", name, h.client) | ||||
| 	result, err := h.client.DeleteProcess(name) | ||||
| 	if err != nil { | ||||
| 		return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ | ||||
| 			"success": false, | ||||
| 			"error":   fmt.Sprintf("Failed to delete service: %v", err), | ||||
| 		}) | ||||
| 	} | ||||
|  | ||||
| 	// Check if the result indicates success | ||||
| 	if !result.Success { | ||||
| 		return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ | ||||
| 			"success": false, | ||||
| 			"error":   result.Message, | ||||
| 		}) | ||||
| 	} | ||||
|  | ||||
| 	return c.JSON(fiber.Map{ | ||||
| 		"success": true, | ||||
| 		"message": fmt.Sprintf("Service '%s' deleted successfully", name), | ||||
| 	}) | ||||
| } | ||||
|  | ||||
| // @Summary Get running services | ||||
| // @Description Get a list of all currently running services | ||||
| // @Tags services | ||||
| // @Accept json | ||||
| // @Produce json | ||||
| // @Success 200 {object} map[string][]ProcessDisplayInfo | ||||
| // @Failure 500 {object} map[string]string | ||||
| // @Router /api/services/running [get] | ||||
| // @Router /admin/services/running [get] | ||||
| func (h *ServiceHandler) getRunningServices(c *fiber.Ctx) error { | ||||
| 	// Get the list of processes | ||||
| 	processes, err := h.getProcessList() | ||||
| 	if err != nil { | ||||
| 		h.logger.Printf("Error getting process list: %v", err) | ||||
| 		return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ | ||||
| 			"success": false, | ||||
| 			"error":   fmt.Sprintf("Failed to get process list: %v", err), | ||||
| 		}) | ||||
| 	} | ||||
|  | ||||
| 	// Filter to only include running processes | ||||
| 	runningProcesses := make([]ProcessDisplayInfo, 0) | ||||
| 	for _, proc := range processes { | ||||
| 		if proc.Status == "running" { | ||||
| 			runningProcesses = append(runningProcesses, proc) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	// Return the processes as JSON | ||||
| 	return c.JSON(fiber.Map{ | ||||
| 		"success":   true, | ||||
| 		"services":  runningProcesses, | ||||
| 		"processes": processes, // Keep for backward compatibility | ||||
| 	}) | ||||
| } | ||||
|  | ||||
| // @Summary Get process logs | ||||
| // @Description Get logs for a specific process | ||||
| // @Tags services | ||||
| // @Accept x-www-form-urlencoded | ||||
| // @Produce json | ||||
| // @Param name formData string true "Service name" | ||||
| // @Param lines formData integer false "Number of log lines to retrieve" | ||||
| // @Success 200 {object} map[string]string | ||||
| // @Failure 400 {object} map[string]string | ||||
| // @Failure 500 {object} map[string]string | ||||
| // @Router /api/services/logs [post] | ||||
| // @Router /admin/services/logs [post] | ||||
| // getProcessLogs retrieves logs for a specific process | ||||
| func (h *ServiceHandler) getProcessLogs(c *fiber.Ctx) error { | ||||
| 	// Get form values | ||||
| 	name := c.FormValue("name") | ||||
|  | ||||
| 	// For backward compatibility, try ID field if name is empty | ||||
| 	if name == "" { | ||||
| 		name = c.FormValue("id") | ||||
| 		if name == "" { | ||||
| 			return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ | ||||
| 				"success": false, | ||||
| 				"error":   "Process name is required", | ||||
| 			}) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	// Get the number of lines to retrieve | ||||
| 	linesStr := c.FormValue("lines") | ||||
| 	lines := DefaultLogLines | ||||
| 	if linesStr != "" { | ||||
| 		if parsedLines, err := strconv.Atoi(linesStr); err == nil && parsedLines > 0 { | ||||
| 			lines = parsedLines | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	// Log the request | ||||
| 	h.logger.Printf("Getting logs for process: %s (lines: %d)", name, lines) | ||||
|  | ||||
| 	// Get logs | ||||
| 	fmt.Printf("DEBUG: API getProcessLogs called for '%s' using client: %p\n", name, h.client) | ||||
| 	logs, err := h.client.GetProcessLogs(name, lines) | ||||
| 	if err != nil { | ||||
| 		h.logger.Printf("Error getting process logs: %v", err) | ||||
| 		return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ | ||||
| 			"success": false, | ||||
| 			"error":   fmt.Sprintf("Failed to get logs: %v", err), | ||||
| 		}) | ||||
| 	} | ||||
|  | ||||
| 	return c.JSON(fiber.Map{ | ||||
| 		"success": true, | ||||
| 		"logs":    logs, | ||||
| 	}) | ||||
| } | ||||
|  | ||||
| // @Summary Get services page | ||||
| // @Description Get the services management page | ||||
| // @Tags admin | ||||
| // @Produce html | ||||
| // @Success 200 {string} string "HTML content" | ||||
| // @Failure 500 {object} map[string]string | ||||
| // @Router /admin/services/ [get] | ||||
| // getServicesPage renders the services page | ||||
| func (h *ServiceHandler) getServicesPage(c *fiber.Ctx) error { | ||||
| 	// Get processes to display on the initial page load | ||||
| 	processes, _ := h.getProcessList() | ||||
|  | ||||
| 	// Check if client is properly initialized | ||||
| 	var warning string | ||||
| 	if h.client == nil { | ||||
| 		warning = "Process manager client is not properly initialized." | ||||
| 		h.logger.Printf("Warning: %s", warning) | ||||
| 	} | ||||
|  | ||||
| 	return c.Render("admin/services", fiber.Map{ | ||||
| 		"title":     "Services", | ||||
| 		"processes": processes, | ||||
| 		"warning":   warning, | ||||
| 	}) | ||||
| } | ||||
|  | ||||
| // @Summary Get services data | ||||
| // @Description Get services data for AJAX updates | ||||
| // @Tags admin | ||||
| // @Produce html | ||||
| // @Success 200 {string} string "HTML content" | ||||
| // @Failure 500 {object} map[string]string | ||||
| // @Router /admin/services/data [get] | ||||
| // getServicesData returns only the services fragment for AJAX updates | ||||
| func (h *ServiceHandler) getServicesData(c *fiber.Ctx) error { | ||||
| 	// Get processes | ||||
| 	processes, _ := h.getProcessList() | ||||
|  | ||||
| 	// Check if client is properly initialized | ||||
| 	var warning string | ||||
| 	if h.client == nil { | ||||
| 		warning = "Process manager client is not properly initialized." | ||||
| 		h.logger.Printf("Warning: %s", warning) | ||||
| 	} | ||||
|  | ||||
| 	// Return the fragment with process data and optional warning | ||||
| 	return c.Render("admin/services_fragment", fiber.Map{ | ||||
| 		"processes": processes, | ||||
| 		"warning":   warning, | ||||
| 		"layout":    "", | ||||
| 	}) | ||||
| } | ||||
							
								
								
									
										449
									
								
								_pkg2_dont_use/heroagent/api/redisserver.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										449
									
								
								_pkg2_dont_use/heroagent/api/redisserver.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,449 @@ | ||||
| package api | ||||
|  | ||||
| import ( | ||||
| 	"context" | ||||
| 	"time" | ||||
|  | ||||
| 	"github.com/gofiber/fiber/v2" | ||||
| 	"github.com/redis/go-redis/v9" | ||||
| ) | ||||
|  | ||||
| // RedisHandler handles Redis-related API endpoints | ||||
| type RedisHandler struct { | ||||
| 	redisClient *redis.Client | ||||
| } | ||||
|  | ||||
| // NewRedisHandler creates a new Redis handler | ||||
| func NewRedisHandler(redisAddr string, isUnixSocket bool) *RedisHandler { | ||||
| 	// Determine network type | ||||
| 	networkType := "tcp" | ||||
| 	if isUnixSocket { | ||||
| 		networkType = "unix" | ||||
| 	} | ||||
|  | ||||
| 	// Create Redis client | ||||
| 	client := redis.NewClient(&redis.Options{ | ||||
| 		Network:      networkType, | ||||
| 		Addr:         redisAddr, | ||||
| 		DB:           0, | ||||
| 		DialTimeout:  5 * time.Second, | ||||
| 		ReadTimeout:  5 * time.Second, | ||||
| 		WriteTimeout: 5 * time.Second, | ||||
| 	}) | ||||
|  | ||||
| 	return &RedisHandler{ | ||||
| 		redisClient: client, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // RegisterRoutes registers Redis routes to the fiber app | ||||
| func (h *RedisHandler) RegisterRoutes(app *fiber.App) { | ||||
| 	group := app.Group("/api/redis") | ||||
|  | ||||
| 	// @Summary Set a Redis key | ||||
| 	// @Description Set a key-value pair in Redis with optional expiration | ||||
| 	// @Tags redis | ||||
| 	// @Accept json | ||||
| 	// @Produce json | ||||
| 	// @Param request body SetKeyRequest true "Key-value data" | ||||
| 	// @Success 200 {object} SetKeyResponse | ||||
| 	// @Failure 400 {object} ErrorResponse | ||||
| 	// @Failure 500 {object} ErrorResponse | ||||
| 	// @Router /api/redis/set [post] | ||||
| 	group.Post("/set", h.setKey) | ||||
|  | ||||
| 	// @Summary Get a Redis key | ||||
| 	// @Description Get a value by key from Redis | ||||
| 	// @Tags redis | ||||
| 	// @Produce json | ||||
| 	// @Param key path string true "Key to retrieve" | ||||
| 	// @Success 200 {object} GetKeyResponse | ||||
| 	// @Failure 400 {object} ErrorResponse | ||||
| 	// @Failure 404 {object} ErrorResponse | ||||
| 	// @Failure 500 {object} ErrorResponse | ||||
| 	// @Router /api/redis/get/{key} [get] | ||||
| 	group.Get("/get/:key", h.getKey) | ||||
|  | ||||
| 	// @Summary Delete a Redis key | ||||
| 	// @Description Delete a key from Redis | ||||
| 	// @Tags redis | ||||
| 	// @Produce json | ||||
| 	// @Param key path string true "Key to delete" | ||||
| 	// @Success 200 {object} DeleteKeyResponse | ||||
| 	// @Failure 400 {object} ErrorResponse | ||||
| 	// @Failure 500 {object} ErrorResponse | ||||
| 	// @Router /api/redis/del/{key} [delete] | ||||
| 	group.Delete("/del/:key", h.deleteKey) | ||||
|  | ||||
| 	// @Summary Get Redis keys by pattern | ||||
| 	// @Description Get keys matching a pattern from Redis | ||||
| 	// @Tags redis | ||||
| 	// @Produce json | ||||
| 	// @Param pattern path string true "Pattern to match keys" | ||||
| 	// @Success 200 {object} GetKeysResponse | ||||
| 	// @Failure 500 {object} ErrorResponse | ||||
| 	// @Router /api/redis/keys/{pattern} [get] | ||||
| 	group.Get("/keys/:pattern", h.getKeys) | ||||
|  | ||||
| 	// @Summary Set hash fields | ||||
| 	// @Description Set one or more fields in a Redis hash | ||||
| 	// @Tags redis | ||||
| 	// @Accept json | ||||
| 	// @Produce json | ||||
| 	// @Param request body HSetKeyRequest true "Hash field data" | ||||
| 	// @Success 200 {object} HSetKeyResponse | ||||
| 	// @Failure 400 {object} ErrorResponse | ||||
| 	// @Failure 500 {object} ErrorResponse | ||||
| 	// @Router /api/redis/hset [post] | ||||
| 	group.Post("/hset", h.hsetKey) | ||||
|  | ||||
| 	// @Summary Get hash field | ||||
| 	// @Description Get a field from a Redis hash | ||||
| 	// @Tags redis | ||||
| 	// @Produce json | ||||
| 	// @Param key path string true "Hash key" | ||||
| 	// @Param field path string true "Field to retrieve" | ||||
| 	// @Success 200 {object} HGetKeyResponse | ||||
| 	// @Failure 400 {object} ErrorResponse | ||||
| 	// @Failure 404 {object} ErrorResponse | ||||
| 	// @Failure 500 {object} ErrorResponse | ||||
| 	// @Router /api/redis/hget/{key}/{field} [get] | ||||
| 	group.Get("/hget/:key/:field", h.hgetKey) | ||||
|  | ||||
| 	// @Summary Delete hash fields | ||||
| 	// @Description Delete one or more fields from a Redis hash | ||||
| 	// @Tags redis | ||||
| 	// @Accept json | ||||
| 	// @Produce json | ||||
| 	// @Param request body HDelKeyRequest true "Fields to delete" | ||||
| 	// @Success 200 {object} HDelKeyResponse | ||||
| 	// @Failure 400 {object} ErrorResponse | ||||
| 	// @Failure 500 {object} ErrorResponse | ||||
| 	// @Router /api/redis/hdel [post] | ||||
| 	group.Post("/hdel", h.hdelKey) | ||||
|  | ||||
| 	// @Summary Get hash fields | ||||
| 	// @Description Get all field names in a Redis hash | ||||
| 	// @Tags redis | ||||
| 	// @Produce json | ||||
| 	// @Param key path string true "Hash key" | ||||
| 	// @Success 200 {object} HKeysResponse | ||||
| 	// @Failure 400 {object} ErrorResponse | ||||
| 	// @Failure 500 {object} ErrorResponse | ||||
| 	// @Router /api/redis/hkeys/{key} [get] | ||||
| 	group.Get("/hkeys/:key", h.hkeysKey) | ||||
|  | ||||
| 	// @Summary Get all hash fields and values | ||||
| 	// @Description Get all fields and values in a Redis hash | ||||
| 	// @Tags redis | ||||
| 	// @Produce json | ||||
| 	// @Param key path string true "Hash key" | ||||
| 	// @Success 200 {object} map[string]string | ||||
| 	// @Failure 400 {object} ErrorResponse | ||||
| 	// @Failure 500 {object} ErrorResponse | ||||
| 	// @Router /api/redis/hgetall/{key} [get] | ||||
| 	group.Get("/hgetall/:key", h.hgetallKey) | ||||
| } | ||||
|  | ||||
| // setKey sets a key-value pair in Redis | ||||
| func (h *RedisHandler) setKey(c *fiber.Ctx) error { | ||||
| 	// Parse request | ||||
| 	var req struct { | ||||
| 		Key     string `json:"key"` | ||||
| 		Value   string `json:"value"` | ||||
| 		Expires int    `json:"expires,omitempty"` // Expiration in seconds, optional | ||||
| 	} | ||||
|  | ||||
| 	if err := c.BodyParser(&req); err != nil { | ||||
| 		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ | ||||
| 			"success": false, | ||||
| 			"error":   "Invalid request format: " + err.Error(), | ||||
| 		}) | ||||
| 	} | ||||
|  | ||||
| 	// Validate required fields | ||||
| 	if req.Key == "" || req.Value == "" { | ||||
| 		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ | ||||
| 			"success": false, | ||||
| 			"error":   "Key and value are required", | ||||
| 		}) | ||||
| 	} | ||||
|  | ||||
| 	ctx := context.Background() | ||||
| 	var err error | ||||
|  | ||||
| 	// Set with or without expiration | ||||
| 	if req.Expires > 0 { | ||||
| 		err = h.redisClient.Set(ctx, req.Key, req.Value, time.Duration(req.Expires)*time.Second).Err() | ||||
| 	} else { | ||||
| 		err = h.redisClient.Set(ctx, req.Key, req.Value, 0).Err() | ||||
| 	} | ||||
|  | ||||
| 	if err != nil { | ||||
| 		return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ | ||||
| 			"success": false, | ||||
| 			"error":   "Failed to set key: " + err.Error(), | ||||
| 		}) | ||||
| 	} | ||||
|  | ||||
| 	return c.JSON(fiber.Map{ | ||||
| 		"success": true, | ||||
| 		"message": "Key set successfully", | ||||
| 	}) | ||||
| } | ||||
|  | ||||
| // getKey retrieves a value by key from Redis | ||||
| func (h *RedisHandler) getKey(c *fiber.Ctx) error { | ||||
| 	key := c.Params("key") | ||||
| 	if key == "" { | ||||
| 		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ | ||||
| 			"success": false, | ||||
| 			"error":   "Key is required", | ||||
| 		}) | ||||
| 	} | ||||
|  | ||||
| 	ctx := context.Background() | ||||
| 	val, err := h.redisClient.Get(ctx, key).Result() | ||||
|  | ||||
| 	if err == redis.Nil { | ||||
| 		return c.Status(fiber.StatusNotFound).JSON(fiber.Map{ | ||||
| 			"success": false, | ||||
| 			"error":   "Key not found", | ||||
| 		}) | ||||
| 	} else if err != nil { | ||||
| 		return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ | ||||
| 			"success": false, | ||||
| 			"error":   "Failed to get key: " + err.Error(), | ||||
| 		}) | ||||
| 	} | ||||
|  | ||||
| 	return c.JSON(fiber.Map{ | ||||
| 		"success": true, | ||||
| 		"key":     key, | ||||
| 		"value":   val, | ||||
| 	}) | ||||
| } | ||||
|  | ||||
| // deleteKey deletes a key from Redis | ||||
| func (h *RedisHandler) deleteKey(c *fiber.Ctx) error { | ||||
| 	key := c.Params("key") | ||||
| 	if key == "" { | ||||
| 		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ | ||||
| 			"success": false, | ||||
| 			"error":   "Key is required", | ||||
| 		}) | ||||
| 	} | ||||
|  | ||||
| 	ctx := context.Background() | ||||
| 	result, err := h.redisClient.Del(ctx, key).Result() | ||||
|  | ||||
| 	if err != nil { | ||||
| 		return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ | ||||
| 			"success": false, | ||||
| 			"error":   "Failed to delete key: " + err.Error(), | ||||
| 		}) | ||||
| 	} | ||||
|  | ||||
| 	return c.JSON(fiber.Map{ | ||||
| 		"success": true, | ||||
| 		"deleted": result > 0, | ||||
| 		"count":   result, | ||||
| 	}) | ||||
| } | ||||
|  | ||||
| // getKeys retrieves keys matching a pattern from Redis | ||||
| func (h *RedisHandler) getKeys(c *fiber.Ctx) error { | ||||
| 	pattern := c.Params("pattern", "*") | ||||
|  | ||||
| 	ctx := context.Background() | ||||
| 	keys, err := h.redisClient.Keys(ctx, pattern).Result() | ||||
|  | ||||
| 	if err != nil { | ||||
| 		return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ | ||||
| 			"success": false, | ||||
| 			"error":   "Failed to get keys: " + err.Error(), | ||||
| 		}) | ||||
| 	} | ||||
|  | ||||
| 	return c.JSON(fiber.Map{ | ||||
| 		"success": true, | ||||
| 		"keys":    keys, | ||||
| 		"count":   len(keys), | ||||
| 	}) | ||||
| } | ||||
|  | ||||
| // hsetKey sets a field in a hash stored at key | ||||
| func (h *RedisHandler) hsetKey(c *fiber.Ctx) error { | ||||
| 	// Parse request | ||||
| 	var req struct { | ||||
| 		Key    string            `json:"key"` | ||||
| 		Fields map[string]string `json:"fields"` | ||||
| 	} | ||||
|  | ||||
| 	if err := c.BodyParser(&req); err != nil { | ||||
| 		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ | ||||
| 			"success": false, | ||||
| 			"error":   "Invalid request format: " + err.Error(), | ||||
| 		}) | ||||
| 	} | ||||
|  | ||||
| 	// Validate required fields | ||||
| 	if req.Key == "" || len(req.Fields) == 0 { | ||||
| 		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ | ||||
| 			"success": false, | ||||
| 			"error":   "Key and at least one field are required", | ||||
| 		}) | ||||
| 	} | ||||
|  | ||||
| 	ctx := context.Background() | ||||
| 	totalAdded := 0 | ||||
|  | ||||
| 	// Use HSet to set multiple fields at once | ||||
| 	for field, value := range req.Fields { | ||||
| 		added, err := h.redisClient.HSet(ctx, req.Key, field, value).Result() | ||||
| 		if err != nil { | ||||
| 			return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ | ||||
| 				"success": false, | ||||
| 				"error":   "Failed to set hash field: " + err.Error(), | ||||
| 			}) | ||||
| 		} | ||||
| 		totalAdded += int(added) | ||||
| 	} | ||||
|  | ||||
| 	return c.JSON(fiber.Map{ | ||||
| 		"success": true, | ||||
| 		"added":   totalAdded, | ||||
| 	}) | ||||
| } | ||||
|  | ||||
| // hgetKey retrieves a field from a hash stored at key | ||||
| func (h *RedisHandler) hgetKey(c *fiber.Ctx) error { | ||||
| 	key := c.Params("key") | ||||
| 	field := c.Params("field") | ||||
|  | ||||
| 	if key == "" || field == "" { | ||||
| 		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ | ||||
| 			"success": false, | ||||
| 			"error":   "Key and field are required", | ||||
| 		}) | ||||
| 	} | ||||
|  | ||||
| 	ctx := context.Background() | ||||
| 	val, err := h.redisClient.HGet(ctx, key, field).Result() | ||||
|  | ||||
| 	if err == redis.Nil { | ||||
| 		return c.Status(fiber.StatusNotFound).JSON(fiber.Map{ | ||||
| 			"success": false, | ||||
| 			"error":   "Field not found in hash", | ||||
| 		}) | ||||
| 	} else if err != nil { | ||||
| 		return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ | ||||
| 			"success": false, | ||||
| 			"error":   "Failed to get hash field: " + err.Error(), | ||||
| 		}) | ||||
| 	} | ||||
|  | ||||
| 	return c.JSON(fiber.Map{ | ||||
| 		"success": true, | ||||
| 		"key":     key, | ||||
| 		"field":   field, | ||||
| 		"value":   val, | ||||
| 	}) | ||||
| } | ||||
|  | ||||
| // hdelKey deletes fields from a hash stored at key | ||||
| func (h *RedisHandler) hdelKey(c *fiber.Ctx) error { | ||||
| 	// Parse request | ||||
| 	var req struct { | ||||
| 		Key    string   `json:"key"` | ||||
| 		Fields []string `json:"fields"` | ||||
| 	} | ||||
|  | ||||
| 	if err := c.BodyParser(&req); err != nil { | ||||
| 		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ | ||||
| 			"success": false, | ||||
| 			"error":   "Invalid request format: " + err.Error(), | ||||
| 		}) | ||||
| 	} | ||||
|  | ||||
| 	// Validate required fields | ||||
| 	if req.Key == "" || len(req.Fields) == 0 { | ||||
| 		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ | ||||
| 			"success": false, | ||||
| 			"error":   "Key and at least one field are required", | ||||
| 		}) | ||||
| 	} | ||||
|  | ||||
| 	ctx := context.Background() | ||||
| 	fields := make([]string, len(req.Fields)) | ||||
| 	copy(fields, req.Fields) | ||||
|  | ||||
| 	removed, err := h.redisClient.HDel(ctx, req.Key, fields...).Result() | ||||
| 	if err != nil { | ||||
| 		return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ | ||||
| 			"success": false, | ||||
| 			"error":   "Failed to delete hash fields: " + err.Error(), | ||||
| 		}) | ||||
| 	} | ||||
|  | ||||
| 	return c.JSON(fiber.Map{ | ||||
| 		"success": true, | ||||
| 		"removed": removed, | ||||
| 	}) | ||||
| } | ||||
|  | ||||
| // hkeysKey retrieves all field names in a hash stored at key | ||||
| func (h *RedisHandler) hkeysKey(c *fiber.Ctx) error { | ||||
| 	key := c.Params("key") | ||||
| 	if key == "" { | ||||
| 		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ | ||||
| 			"success": false, | ||||
| 			"error":   "Key is required", | ||||
| 		}) | ||||
| 	} | ||||
|  | ||||
| 	ctx := context.Background() | ||||
| 	fields, err := h.redisClient.HKeys(ctx, key).Result() | ||||
|  | ||||
| 	if err != nil { | ||||
| 		return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ | ||||
| 			"success": false, | ||||
| 			"error":   "Failed to get hash keys: " + err.Error(), | ||||
| 		}) | ||||
| 	} | ||||
|  | ||||
| 	return c.JSON(fiber.Map{ | ||||
| 		"success": true, | ||||
| 		"key":     key, | ||||
| 		"fields":  fields, | ||||
| 		"count":   len(fields), | ||||
| 	}) | ||||
| } | ||||
|  | ||||
| // hgetallKey retrieves all fields and values in a hash stored at key | ||||
| func (h *RedisHandler) hgetallKey(c *fiber.Ctx) error { | ||||
| 	key := c.Params("key") | ||||
| 	if key == "" { | ||||
| 		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ | ||||
| 			"success": false, | ||||
| 			"error":   "Key is required", | ||||
| 		}) | ||||
| 	} | ||||
|  | ||||
| 	ctx := context.Background() | ||||
| 	values, err := h.redisClient.HGetAll(ctx, key).Result() | ||||
|  | ||||
| 	if err != nil { | ||||
| 		return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ | ||||
| 			"success": false, | ||||
| 			"error":   "Failed to get hash: " + err.Error(), | ||||
| 		}) | ||||
| 	} | ||||
|  | ||||
| 	return c.JSON(fiber.Map{ | ||||
| 		"success": true, | ||||
| 		"key":     key, | ||||
| 		"hash":    values, | ||||
| 		"count":   len(values), | ||||
| 	}) | ||||
| } | ||||
							
								
								
									
										57
									
								
								_pkg2_dont_use/heroagent/api/tests/test_utils.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										57
									
								
								_pkg2_dont_use/heroagent/api/tests/test_utils.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,57 @@ | ||||
| package tests | ||||
|  | ||||
| import ( | ||||
| 	"bytes" | ||||
| 	"encoding/json" | ||||
| 	"net/http" | ||||
| 	"net/http/httptest" | ||||
| 	"testing" | ||||
|  | ||||
| 	"github.com/gofiber/fiber/v2" | ||||
| 	"github.com/stretchr/testify/assert" | ||||
| ) | ||||
|  | ||||
| // TestSetup represents the common test setup | ||||
| type TestSetup struct { | ||||
| 	App    *fiber.App | ||||
| 	Assert *assert.Assertions | ||||
| } | ||||
|  | ||||
| // NewTestSetup creates a new test setup | ||||
| func NewTestSetup(t *testing.T) *TestSetup { | ||||
| 	return &TestSetup{ | ||||
| 		App:    fiber.New(), | ||||
| 		Assert: assert.New(t), | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // PerformRequest performs an HTTP request and returns the response | ||||
| func (ts *TestSetup) PerformRequest(method, path string, body interface{}) *http.Response { | ||||
| 	// Convert body to JSON if it's not nil | ||||
| 	var reqBody *bytes.Buffer | ||||
| 	if body != nil { | ||||
| 		jsonBody, _ := json.Marshal(body) | ||||
| 		reqBody = bytes.NewBuffer(jsonBody) | ||||
| 	} else { | ||||
| 		reqBody = bytes.NewBuffer(nil) | ||||
| 	} | ||||
|  | ||||
| 	// Create a new HTTP request | ||||
| 	req := httptest.NewRequest(method, path, reqBody) | ||||
| 	req.Header.Set("Content-Type", "application/json") | ||||
|  | ||||
| 	// Perform the request | ||||
| 	resp, _ := ts.App.Test(req) | ||||
| 	return resp | ||||
| } | ||||
|  | ||||
| // AssertStatusCode asserts that the response has the expected status code | ||||
| func (ts *TestSetup) AssertStatusCode(resp *http.Response, expected int) { | ||||
| 	ts.Assert.Equal(expected, resp.StatusCode, "Expected status code %d but got %d", expected, resp.StatusCode) | ||||
| } | ||||
|  | ||||
| // ParseResponseBody parses the response body into the given struct | ||||
| func (ts *TestSetup) ParseResponseBody(resp *http.Response, v interface{}) { | ||||
| 	defer resp.Body.Close() | ||||
| 	ts.Assert.NoError(json.NewDecoder(resp.Body).Decode(v), "Failed to parse response body") | ||||
| } | ||||
		Reference in New Issue
	
	Block a user