...
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