Compare commits

...

21 Commits

Author SHA1 Message Date
4a79011793 Update git remote URL from git.ourworld.tf to git.threefold.info 2025-06-15 16:21:09 +02:00
0b62ac9ecd ... 2025-05-24 10:42:24 +04:00
c9b14730ad ... 2025-05-24 10:33:50 +04:00
2ee8a95a90 ... 2025-05-24 09:52:43 +04:00
8bc1759dcb ... 2025-05-24 09:24:19 +04:00
e60b9f62f1 ... 2025-05-24 07:24:17 +04:00
5d241e9ade ... 2025-05-24 07:09:15 +04:00
b8c8da9e31 ... 2025-05-24 06:56:02 +04:00
55a05a5571 ... 2025-05-23 22:12:17 +04:00
2bfe4161b2 ... 2025-05-23 22:09:57 +04:00
0b1d9907a7 ... 2025-05-23 16:30:10 +04:00
c78761fe20 ... 2025-05-23 16:23:41 +04:00
2e8ec1735a ... 2025-05-23 16:19:07 +04:00
29d0d25a3b ... 2025-05-23 16:10:49 +04:00
3f01074e3f ... 2025-05-23 15:56:35 +04:00
532cda72d3 ... 2025-05-23 15:40:41 +04:00
0e545e56de ... 2025-05-23 15:28:30 +04:00
92b9c356b8 ... 2025-05-23 15:11:03 +04:00
c86165f88c ... 2025-05-23 14:05:09 +04:00
b2eb9d3116 ... 2025-05-23 13:44:18 +04:00
79d66e4b6b ... 2025-05-23 09:33:05 +04:00
248 changed files with 7990 additions and 11388 deletions

View File

@@ -11,7 +11,7 @@ import (
"syscall"
"time"
"github.com/freeflowuniverse/heroagent/pkg/mycelium_client"
"git.threefold.info/herocode/heroagent/pkg/mycelium_client"
)
type config struct {

View File

@@ -8,7 +8,7 @@ import (
"os"
"time"
"github.com/freeflowuniverse/heroagent/pkg/mycelium_client"
"git.threefold.info/herocode/heroagent/pkg/mycelium_client"
)
func main() {

View File

@@ -4,7 +4,7 @@ import (
"fmt"
"time"
"github.com/freeflowuniverse/heroagent/pkg/system/stats"
"git.threefold.info/herocode/heroagent/pkg/system/stats"
"github.com/gofiber/fiber/v2"
)
@@ -77,7 +77,7 @@ func (h *AdminHandler) getProcessStatsJSON(c *fiber.Ctx) error {
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
"success": false,
"error": "Failed to get process stats: " + err.Error(),
"error": "Failed to get process stats: " + err.Error(),
})
}

View File

@@ -3,7 +3,7 @@ package api
import (
"time"
"github.com/freeflowuniverse/heroagent/pkg/sal/executor"
"git.threefold.info/herocode/heroagent/pkg/sal/executor"
"github.com/gofiber/fiber/v2"
)

View File

@@ -7,9 +7,9 @@ import (
"strconv"
"time"
"github.com/freeflowuniverse/heroagent/pkg/processmanager"
"github.com/freeflowuniverse/heroagent/pkg/processmanager/interfaces"
"github.com/freeflowuniverse/heroagent/pkg/processmanager/interfaces/openrpc"
"git.threefold.info/herocode/heroagent/pkg/processmanager"
"git.threefold.info/herocode/heroagent/pkg/processmanager/interfaces"
"git.threefold.info/herocode/heroagent/pkg/processmanager/interfaces/openrpc"
"github.com/gofiber/fiber/v2"
)
@@ -102,26 +102,26 @@ func (h *ServiceHandler) getProcessList() ([]ProcessDisplayInfo, error) {
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,
@@ -133,7 +133,7 @@ func (h *ServiceHandler) getProcessList() ([]ProcessDisplayInfo, error) {
}
displayInfoList = append(displayInfoList, displayInfo)
}
// Debug: Log the number of processes
h.logger.Printf("Found %d processes", len(displayInfoList))
return displayInfoList, nil
@@ -144,7 +144,7 @@ func (h *ServiceHandler) getProcessList() ([]ProcessDisplayInfo, error) {
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,
@@ -276,7 +276,7 @@ func (h *ServiceHandler) stopService(c *fiber.Ctx) error {
"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{
@@ -331,7 +331,7 @@ func (h *ServiceHandler) restartService(c *fiber.Ctx) error {
"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{
@@ -382,7 +382,7 @@ func (h *ServiceHandler) deleteService(c *fiber.Ctx) error {
"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{

View File

@@ -12,16 +12,16 @@ import (
"syscall"
"time"
"github.com/freeflowuniverse/heroagent/pkg/heroagent/api"
"github.com/freeflowuniverse/heroagent/pkg/heroagent/handlers"
"github.com/freeflowuniverse/heroagent/pkg/heroagent/pages"
"github.com/freeflowuniverse/heroagent/pkg/processmanager"
"github.com/freeflowuniverse/heroagent/pkg/sal/executor"
"github.com/freeflowuniverse/heroagent/pkg/servers/redisserver"
"github.com/freeflowuniverse/heroagent/pkg/system/stats"
"git.threefold.info/herocode/heroagent/pkg/heroagent/api"
"git.threefold.info/herocode/heroagent/pkg/heroagent/handlers"
"git.threefold.info/herocode/heroagent/pkg/heroagent/pages"
"git.threefold.info/herocode/heroagent/pkg/processmanager"
"git.threefold.info/herocode/heroagent/pkg/sal/executor"
"git.threefold.info/herocode/heroagent/pkg/servers/redisserver"
"git.threefold.info/herocode/heroagent/pkg/system/stats"
// "github.com/freeflowuniverse/heroagent/pkg/vfs/interfaces"
// "github.com/freeflowuniverse/heroagent/pkg/vfs/interfaces/mock"
// "git.threefold.info/herocode/heroagent/pkg/vfs/interfaces"
// "git.threefold.info/herocode/heroagent/pkg/vfs/interfaces/mock"
"github.com/gofiber/fiber/v2"
"github.com/gofiber/fiber/v2/middleware/cors"
"github.com/gofiber/fiber/v2/middleware/logger"
@@ -239,7 +239,7 @@ func (hl *HeroLauncher) GetUptime() string {
func (hl *HeroLauncher) startProcessManager() error {
_, filename, _, _ := runtime.Caller(0)
projectRoot := filepath.Join(filepath.Dir(filename), "../..")
processManagerPath := filepath.Join(projectRoot, "cmd/processmanager/main.go")
processManagerPath := filepath.Join(projectRoot, "pkg/processmanager/examples/openrpc/main.go")
log.Printf("Starting process manager from: %s", processManagerPath)

View File

@@ -3,40 +3,40 @@ package handlers
import (
"fmt"
"log"
"strconv" // Added strconv for JobID parsing
"github.com/freeflowuniverse/heroagent/pkg/herojobs"
"git.threefold.info/herocode/heroagent/pkg/herojobs"
"github.com/gofiber/fiber/v2"
)
// HeroJobsClientInterface defines the interface for the HeroJobs client
type HeroJobsClientInterface interface {
Connect() error
Close() error
SubmitJob(job *herojobs.Job) (*herojobs.Job, error)
GetJob(jobID string) (*herojobs.Job, error)
DeleteJob(jobID string) error
ListJobs(circleID, topic string) ([]string, error)
// RedisClientInterface defines the methods JobHandler needs from a HeroJobs Redis client.
type RedisClientInterface interface {
StoreJob(job *herojobs.Job) error
EnqueueJob(job *herojobs.Job) error
GetJob(jobID interface{}) (*herojobs.Job, error) // Changed jobID type to interface{}
ListJobs(circleID, topic string) ([]uint32, error)
QueueSize(circleID, topic string) (int64, error)
QueueEmpty(circleID, topic string) error
QueueGet(circleID, topic string) (*herojobs.Job, error)
CreateJob(circleID, topic, sessionKey, heroScript, rhaiScript string) (*herojobs.Job, error)
// herojobs.Job also has Load() and Save() methods, but those are on the Job object itself,
// not typically part of the client interface unless the client is a facade for all job operations.
}
// JobHandler handles job-related routes
type JobHandler struct {
client HeroJobsClientInterface
client RedisClientInterface // Changed to use the interface
logger *log.Logger
}
// NewJobHandler creates a new JobHandler
func NewJobHandler(socketPath string, logger *log.Logger) (*JobHandler, error) {
client, err := herojobs.NewClient(socketPath)
func NewJobHandler(redisAddr string, logger *log.Logger) (*JobHandler, error) {
redisClient, err := herojobs.NewRedisClient(redisAddr, false)
if err != nil {
return nil, fmt.Errorf("failed to create HeroJobs client: %w", err)
return nil, fmt.Errorf("failed to create HeroJobs Redis client: %w", err)
}
// *herojobs.RedisClient must implement RedisClientInterface.
// This assignment is valid if *herojobs.RedisClient has all methods of RedisClientInterface.
return &JobHandler{
client: client,
client: redisClient,
logger: logger,
}, nil
}
@@ -76,14 +76,6 @@ func (h *JobHandler) RegisterRoutes(app *fiber.App) {
// @Router /api/jobs/submit [post]
// @Router /admin/jobs/submit [post]
func (h *JobHandler) submitJob(c *fiber.Ctx) error {
// Connect to the HeroJobs server
if err := h.client.Connect(); err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
"error": fmt.Sprintf("Failed to connect to HeroJobs server: %v", err),
})
}
defer h.client.Close()
// Parse job from request body
var job herojobs.Job
if err := c.BodyParser(&job); err != nil {
@@ -92,15 +84,32 @@ func (h *JobHandler) submitJob(c *fiber.Ctx) error {
})
}
// Submit job
submittedJob, err := h.client.SubmitJob(&job)
if err != nil {
// Save job to OurDB (this assigns/confirms JobID)
if err := job.Save(); err != nil {
h.logger.Printf("Failed to save job to OurDB: %v", err)
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
"error": fmt.Sprintf("Failed to submit job: %v", err),
"error": fmt.Sprintf("Failed to save job: %v", err),
})
}
return c.JSON(submittedJob)
// Store job in Redis
if err := h.client.StoreJob(&job); err != nil {
h.logger.Printf("Failed to store job in Redis: %v", err)
// Attempt to roll back or log, but proceed to enqueue if critical
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
"error": fmt.Sprintf("Failed to store job in Redis: %v", err),
})
}
// Enqueue job in Redis
if err := h.client.EnqueueJob(&job); err != nil {
h.logger.Printf("Failed to enqueue job in Redis: %v", err)
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
"error": fmt.Sprintf("Failed to enqueue job: %v", err),
})
}
return c.JSON(job)
}
// @Summary Get a job
@@ -114,28 +123,36 @@ func (h *JobHandler) submitJob(c *fiber.Ctx) error {
// @Router /api/jobs/get/{id} [get]
// @Router /admin/jobs/get/{id} [get]
func (h *JobHandler) getJob(c *fiber.Ctx) error {
// Connect to the HeroJobs server
if err := h.client.Connect(); err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
"error": fmt.Sprintf("Failed to connect to HeroJobs server: %v", err),
})
}
defer h.client.Close()
// Get job ID from path parameter
jobID := c.Params("id")
if jobID == "" {
jobIDStr := c.Params("id")
if jobIDStr == "" {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
"error": "Job ID is required",
})
}
// Get job
// Convert jobID string to uint32
jobID64, err := strconv.ParseUint(jobIDStr, 10, 32)
if err != nil {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
"error": fmt.Sprintf("Invalid Job ID format: %s. %v", jobIDStr, err),
})
}
jobID := uint32(jobID64)
// Get job from Redis first
job, err := h.client.GetJob(jobID)
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
"error": fmt.Sprintf("Failed to get job: %v", err),
})
// If not found in Redis (e.g. redis.Nil or other error), try OurDB
h.logger.Printf("Job %d not found in Redis or error: %v. Trying OurDB.", jobID, err)
retrievedJob := &herojobs.Job{JobID: jobID}
if loadErr := retrievedJob.Load(); loadErr != nil {
h.logger.Printf("Failed to load job %d from OurDB: %v", jobID, loadErr)
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
"error": fmt.Sprintf("Failed to get job %d: %v / %v", jobID, err, loadErr),
})
}
job = retrievedJob // Use the job loaded from OurDB
}
return c.JSON(job)
@@ -152,32 +169,22 @@ func (h *JobHandler) getJob(c *fiber.Ctx) error {
// @Router /api/jobs/delete/{id} [delete]
// @Router /admin/jobs/delete/{id} [delete]
func (h *JobHandler) deleteJob(c *fiber.Ctx) error {
// Connect to the HeroJobs server
if err := h.client.Connect(); err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
"error": fmt.Sprintf("Failed to connect to HeroJobs server: %v", err),
})
}
defer h.client.Close()
// Get job ID from path parameter
jobID := c.Params("id")
if jobID == "" {
jobIDStr := c.Params("id")
if jobIDStr == "" {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
"error": "Job ID is required",
})
}
// Delete job
if err := h.client.DeleteJob(jobID); err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
"error": fmt.Sprintf("Failed to delete job: %v", err),
})
}
return c.JSON(fiber.Map{
"status": "success",
"message": fmt.Sprintf("Job %s deleted successfully", jobID),
// Deleting jobs requires removing from OurDB and Redis.
// This functionality is not directly provided by RedisClient.DeleteJob
// and OurDB job deletion is not specified in README.
// For now, returning not implemented.
h.logger.Printf("Attempt to delete job %s - not implemented", jobIDStr)
return c.Status(fiber.StatusNotImplemented).JSON(fiber.Map{
"error": "Job deletion is not implemented",
"message": fmt.Sprintf("Job %s deletion requested but not implemented.", jobIDStr),
})
}
@@ -193,14 +200,6 @@ func (h *JobHandler) deleteJob(c *fiber.Ctx) error {
// @Router /api/jobs/list [get]
// @Router /admin/jobs/list [get]
func (h *JobHandler) listJobs(c *fiber.Ctx) error {
// Connect to the HeroJobs server
if err := h.client.Connect(); err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
"error": fmt.Sprintf("Failed to connect to HeroJobs server: %v", err),
})
}
defer h.client.Close()
// Get parameters from query
circleID := c.Query("circleid")
if circleID == "" {
@@ -242,14 +241,6 @@ func (h *JobHandler) listJobs(c *fiber.Ctx) error {
// @Router /api/jobs/queue/size [get]
// @Router /admin/jobs/queue/size [get]
func (h *JobHandler) queueSize(c *fiber.Ctx) error {
// Connect to the HeroJobs server
if err := h.client.Connect(); err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
"error": fmt.Sprintf("Failed to connect to HeroJobs server: %v", err),
})
}
defer h.client.Close()
// Get parameters from query
circleID := c.Query("circleid")
if circleID == "" {
@@ -291,14 +282,6 @@ func (h *JobHandler) queueSize(c *fiber.Ctx) error {
// @Router /api/jobs/queue/empty [post]
// @Router /admin/jobs/queue/empty [post]
func (h *JobHandler) queueEmpty(c *fiber.Ctx) error {
// Connect to the HeroJobs server
if err := h.client.Connect(); err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
"error": fmt.Sprintf("Failed to connect to HeroJobs server: %v", err),
})
}
defer h.client.Close()
// Parse parameters from request body
var params struct {
CircleID string `json:"circleid"`
@@ -347,14 +330,6 @@ func (h *JobHandler) queueEmpty(c *fiber.Ctx) error {
// @Router /api/jobs/queue/get [get]
// @Router /admin/jobs/queue/get [get]
func (h *JobHandler) queueGet(c *fiber.Ctx) error {
// Connect to the HeroJobs server
if err := h.client.Connect(); err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
"error": fmt.Sprintf("Failed to connect to HeroJobs server: %v", err),
})
}
defer h.client.Close()
// Get parameters from query
circleID := c.Query("circleid")
if circleID == "" {
@@ -370,14 +345,40 @@ func (h *JobHandler) queueGet(c *fiber.Ctx) error {
})
}
// Get job from queue
job, err := h.client.QueueGet(circleID, topic)
// Get list of job IDs (uint32) from the queue (non-destructive)
jobIDs, err := h.client.ListJobs(circleID, topic)
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
"error": fmt.Sprintf("Failed to get job from queue: %v", err),
"error": fmt.Sprintf("Failed to list jobs in queue: %v", err),
})
}
if len(jobIDs) == 0 {
return c.Status(fiber.StatusNotFound).JSON(fiber.Map{
"error": "Queue is empty or no jobs found",
})
}
// Take the first job ID from the list (it's already uint32)
jobIDToFetch := jobIDs[0]
// Get the actual job details using the ID
job, err := h.client.GetJob(jobIDToFetch)
if err != nil {
// If not found in Redis (e.g. redis.Nil or other error), try OurDB
h.logger.Printf("Job %d (from queue list) not found in Redis or error: %v. Trying OurDB.", jobIDToFetch, err)
retrievedJob := &herojobs.Job{JobID: jobIDToFetch} // Ensure CircleID and Topic are set if Load needs them
retrievedJob.CircleID = circleID // Needed for Load if path depends on it
retrievedJob.Topic = topic // Needed for Load if path depends on it
if loadErr := retrievedJob.Load(); loadErr != nil {
h.logger.Printf("Failed to load job %d from OurDB: %v", jobIDToFetch, loadErr)
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
"error": fmt.Sprintf("Failed to get job %d from queue (Redis err: %v / OurDB err: %v)", jobIDToFetch, err, loadErr),
})
}
job = retrievedJob // Use the job loaded from OurDB
}
return c.JSON(job)
}
@@ -393,51 +394,92 @@ func (h *JobHandler) queueGet(c *fiber.Ctx) error {
// @Router /api/jobs/create [post]
// @Router /admin/jobs/create [post]
func (h *JobHandler) createJob(c *fiber.Ctx) error {
// Connect to the HeroJobs server
if err := h.client.Connect(); err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
"error": fmt.Sprintf("Failed to connect to HeroJobs server: %v", err),
})
}
defer h.client.Close()
// Parse parameters from request body
var params struct {
var reqBody struct {
CircleID string `json:"circleid"`
Topic string `json:"topic"`
SessionKey string `json:"sessionkey"`
HeroScript string `json:"heroscript"`
RhaiScript string `json:"rhaiscript"`
Params string `json:"params"`
ParamsType string `json:"paramstype"`
Timeout int64 `json:"timeout"` // Optional: allow timeout override
Log bool `json:"log"` // Optional: allow log enabling
}
if err := c.BodyParser(&params); err != nil {
if err := c.BodyParser(&reqBody); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
"error": fmt.Sprintf("Failed to parse parameters: %v", err),
})
}
if params.CircleID == "" {
if reqBody.CircleID == "" {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
"error": "Circle ID is required",
})
}
if params.Topic == "" {
if reqBody.Topic == "" {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
"error": "Topic is required",
})
}
if reqBody.Params == "" {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
"error": "Params are required",
})
}
if reqBody.ParamsType == "" {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
"error": "ParamsType is required",
})
}
// Create job
job, err := h.client.CreateJob(
params.CircleID,
params.Topic,
params.SessionKey,
params.HeroScript,
params.RhaiScript,
)
if err != nil {
// Create a new job instance
job := herojobs.NewJob() // Initializes with defaults
job.CircleID = reqBody.CircleID
job.Topic = reqBody.Topic
job.SessionKey = reqBody.SessionKey
job.Params = reqBody.Params
// Convert ParamsType string to herojobs.ParamsType
switch herojobs.ParamsType(reqBody.ParamsType) {
case herojobs.ParamsTypeHeroScript:
job.ParamsType = herojobs.ParamsTypeHeroScript
case herojobs.ParamsTypeRhaiScript:
job.ParamsType = herojobs.ParamsTypeRhaiScript
case herojobs.ParamsTypeOpenRPC:
job.ParamsType = herojobs.ParamsTypeOpenRPC
case herojobs.ParamsTypeAI:
job.ParamsType = herojobs.ParamsTypeAI
default:
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
"error": fmt.Sprintf("Invalid ParamsType: %s", reqBody.ParamsType),
})
}
if reqBody.Timeout > 0 {
job.Timeout = reqBody.Timeout
}
job.Log = reqBody.Log
// Save job to OurDB (this assigns JobID)
if err := job.Save(); err != nil {
h.logger.Printf("Failed to save new job to OurDB: %v", err)
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
"error": fmt.Sprintf("Failed to create job: %v", err),
"error": fmt.Sprintf("Failed to save new job: %v", err),
})
}
// Store job in Redis
if err := h.client.StoreJob(job); err != nil {
h.logger.Printf("Failed to store new job in Redis: %v", err)
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
"error": fmt.Sprintf("Failed to store new job in Redis: %v", err),
})
}
// Enqueue job in Redis
if err := h.client.EnqueueJob(job); err != nil {
h.logger.Printf("Failed to enqueue new job in Redis: %v", err)
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
"error": fmt.Sprintf("Failed to enqueue new job: %v", err),
})
}

View File

@@ -0,0 +1,572 @@
package handlers
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"net/http/httptest"
"testing"
"git.threefold.info/herocode/heroagent/pkg/herojobs"
"github.com/gofiber/fiber/v2"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
)
// MockRedisClient is a mock implementation of the RedisClientInterface
type MockRedisClient struct {
mock.Mock
}
// StoreJob mocks the StoreJob method
func (m *MockRedisClient) StoreJob(job *herojobs.Job) error {
args := m.Called(job)
return args.Error(0)
}
// EnqueueJob mocks the EnqueueJob method
func (m *MockRedisClient) EnqueueJob(job *herojobs.Job) error {
args := m.Called(job)
return args.Error(0)
}
// GetJob mocks the GetJob method
func (m *MockRedisClient) GetJob(jobID interface{}) (*herojobs.Job, error) { // jobID is interface{}
args := m.Called(jobID)
if args.Get(0) == nil {
return nil, args.Error(1)
}
return args.Get(0).(*herojobs.Job), args.Error(1)
}
// ListJobs mocks the ListJobs method
func (m *MockRedisClient) ListJobs(circleID, topic string) ([]uint32, error) { // Returns []uint32
args := m.Called(circleID, topic)
if args.Get(0) == nil {
return nil, args.Error(1)
}
return args.Get(0).([]uint32), args.Error(1)
}
// QueueSize mocks the QueueSize method
func (m *MockRedisClient) QueueSize(circleID, topic string) (int64, error) {
args := m.Called(circleID, topic)
// Ensure Get(0) is not nil before type assertion if it can be nil in some error cases
if args.Get(0) == nil && args.Error(1) != nil { // If error is set, result might be nil
return 0, args.Error(1)
}
return args.Get(0).(int64), args.Error(1)
}
// QueueEmpty mocks the QueueEmpty method
func (m *MockRedisClient) QueueEmpty(circleID, topic string) error {
args := m.Called(circleID, topic)
return args.Error(0)
}
// setupTest initializes a test environment with a mock client
func setupTest() (*JobHandler, *MockRedisClient, *fiber.App) {
mockClient := new(MockRedisClient)
handler := &JobHandler{
client: mockClient, // Assign the mock that implements RedisClientInterface
}
app := fiber.New()
// Register routes (ensure these match the actual routes in job_handlers.go)
apiJobs := app.Group("/api/jobs") // Assuming routes are under /api/jobs
apiJobs.Post("/submit", handler.submitJob)
apiJobs.Get("/get/:id", handler.getJob) // :id as per job_handlers.go
apiJobs.Delete("/delete/:id", handler.deleteJob) // :id as per job_handlers.go
apiJobs.Get("/list", handler.listJobs)
apiJobs.Get("/queue/size", handler.queueSize)
apiJobs.Post("/queue/empty", handler.queueEmpty)
apiJobs.Get("/queue/get", handler.queueGet)
apiJobs.Post("/create", handler.createJob)
// If admin routes are also tested, they need to be registered here too
// adminJobs := app.Group("/admin/jobs")
// jobRoutes(adminJobs) // if using the same handler instance
return handler, mockClient, app
}
// createTestRequest creates a test request with the given method, path, and body
func createTestRequest(method, path string, body io.Reader) (*http.Request, error) {
req := httptest.NewRequest(method, path, body)
req.Header.Set("Content-Type", "application/json")
return req, nil
}
// TestQueueEmpty tests the queueEmpty handler
func TestQueueEmpty(t *testing.T) {
// Test cases
tests := []struct {
name string
circleID string
topic string
emptyError error
expectedStatus int
expectedBody string
}{
{
name: "Success",
circleID: "test-circle",
topic: "test-topic",
emptyError: nil,
expectedStatus: fiber.StatusOK,
expectedBody: `{"status":"success","message":"Queue for circle test-circle and topic test-topic emptied successfully"}`,
},
// Removed "Connection Error" test case as Connect is no longer directly called per op
{
name: "Empty Error",
circleID: "test-circle",
topic: "test-topic",
emptyError: errors.New("empty error"),
expectedStatus: fiber.StatusInternalServerError,
expectedBody: `{"error":"Failed to empty queue: empty error"}`,
},
{
name: "Empty Circle ID",
circleID: "",
topic: "test-topic",
emptyError: nil,
expectedStatus: fiber.StatusBadRequest,
expectedBody: `{"error":"Circle ID is required"}`,
},
{
name: "Empty Topic",
circleID: "test-circle",
topic: "",
emptyError: nil,
expectedStatus: fiber.StatusBadRequest,
expectedBody: `{"error":"Topic is required"}`,
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
// Create a new mock client for each test and setup app
_, mockClient, app := setupTest() // Use setupTest to get handler with mock
// Setup mock expectations
if tc.circleID != "" && tc.topic != "" { // Only expect call if params are valid
mockClient.On("QueueEmpty", tc.circleID, tc.topic).Return(tc.emptyError)
}
// Create request body
reqBody := map[string]string{
"circleid": tc.circleID,
"topic": tc.topic,
}
reqBodyBytes, err := json.Marshal(reqBody)
assert.NoError(t, err)
// Create test request
req, err := createTestRequest(http.MethodPost, "/api/jobs/queue/empty", bytes.NewReader(reqBodyBytes))
assert.NoError(t, err)
req.Header.Set("Content-Type", "application/json")
// Perform the request
resp, err := app.Test(req)
assert.NoError(t, err)
// Check status code
assert.Equal(t, tc.expectedStatus, resp.StatusCode)
// Check response body
body, err := io.ReadAll(resp.Body)
assert.NoError(t, err)
assert.JSONEq(t, tc.expectedBody, string(body))
// Verify that all expectations were met
mockClient.AssertExpectations(t)
})
}
}
// TestQueueGet tests the queueGet handler
func TestQueueGet(t *testing.T) {
// Create a test job
testJob := herojobs.NewJob()
testJob.JobID = 10 // This will be a number in JSON
testJob.CircleID = "test-circle"
testJob.Topic = "test-topic"
testJob.Params = "some script"
testJob.ParamsType = herojobs.ParamsTypeHeroScript
testJob.Status = herojobs.JobStatusNew
// Test cases
tests := []struct {
name string
circleID string
topic string
listJobsError error
listJobsResp []uint32
getJobError error
getJobResp *herojobs.Job
expectedStatus int
expectedBody string // This will need to be updated to match the actual job structure
}{
{
name: "Success",
circleID: "test-circle",
topic: "test-topic",
listJobsError: nil,
listJobsResp: []uint32{10},
getJobError: nil,
getJobResp: testJob,
expectedStatus: fiber.StatusOK,
expectedBody: `{"jobid":10,"circleid":"test-circle","topic":"test-topic","params":"some script","paramstype":"HeroScript","status":"new","sessionkey":"","result":"","error":"","timeout":60,"log":false,"timescheduled":0,"timestart":0,"timeend":0}`,
},
// Removed "Connection Error"
{
name: "ListJobs Error",
circleID: "test-circle",
topic: "test-topic",
listJobsError: errors.New("list error"),
listJobsResp: nil,
getJobError: nil, // Not reached
getJobResp: nil, // Not reached
expectedStatus: fiber.StatusInternalServerError,
expectedBody: `{"error":"Failed to list jobs in queue: list error"}`,
},
{
name: "GetJob Error after ListJobs success",
circleID: "test-circle",
topic: "test-topic",
listJobsError: nil,
listJobsResp: []uint32{10},
getJobError: errors.New("get error"),
getJobResp: nil,
expectedStatus: fiber.StatusInternalServerError, // Or based on how GetJob error is handled (e.g. fallback to OurDB)
// The error message might be more complex if OurDB load is also attempted and fails
expectedBody: `{"error":"Failed to get job 10 from queue (Redis err: get error / OurDB err: record not found)"}`, // Adjusted expected error
},
{
name: "Queue Empty (ListJobs returns empty)",
circleID: "test-circle",
topic: "test-topic",
listJobsError: nil,
listJobsResp: []uint32{}, // Empty list
getJobError: nil,
getJobResp: nil,
expectedStatus: fiber.StatusNotFound,
expectedBody: `{"error":"Queue is empty or no jobs found"}`,
},
{
name: "Empty Circle ID",
circleID: "",
topic: "test-topic",
listJobsError: nil,
listJobsResp: nil,
getJobError: nil,
getJobResp: nil,
expectedStatus: fiber.StatusBadRequest,
expectedBody: `{"error":"Circle ID is required"}`,
},
{
name: "Empty Topic",
circleID: "test-circle",
topic: "",
listJobsError: nil,
listJobsResp: nil,
getJobError: nil,
getJobResp: nil,
expectedStatus: fiber.StatusBadRequest,
expectedBody: `{"error":"Topic is required"}`,
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
// Create a new mock client for each test and setup app
_, mockClient, app := setupTest()
// Setup mock expectations
if tc.circleID != "" && tc.topic != "" {
mockClient.On("ListJobs", tc.circleID, tc.topic).Return(tc.listJobsResp, tc.listJobsError)
if tc.listJobsError == nil && len(tc.listJobsResp) > 0 {
// Expect GetJob to be called with the first ID from listJobsResp
// The handler passes uint32 to client.GetJob, which matches interface{}
mockClient.On("GetJob", tc.listJobsResp[0]).Return(tc.getJobResp, tc.getJobError).Maybe()
// If GetJob from Redis fails, a Load from OurDB is attempted.
// We are not mocking job.Load() here as it's on the job object.
// The error message in the test case reflects this potential dual failure.
}
}
// Create test request
path := fmt.Sprintf("/api/jobs/queue/get?circleid=%s&topic=%s", tc.circleID, tc.topic)
req, err := createTestRequest(http.MethodGet, path, nil)
assert.NoError(t, err)
// Perform the request
resp, err := app.Test(req)
assert.NoError(t, err)
// Check status code
assert.Equal(t, tc.expectedStatus, resp.StatusCode)
// Check response body
body, err := io.ReadAll(resp.Body)
assert.NoError(t, err)
assert.JSONEq(t, tc.expectedBody, string(body))
// Verify that all expectations were met
mockClient.AssertExpectations(t)
})
}
}
// TestCreateJob tests the createJob handler
func TestCreateJob(t *testing.T) {
// Test cases
createdJob := herojobs.NewJob()
createdJob.JobID = 10 // Assuming Save will populate this; for mock, we set it
createdJob.CircleID = "test-circle"
createdJob.Topic = "test-topic"
createdJob.SessionKey = "test-key"
createdJob.Params = "test-params"
createdJob.ParamsType = herojobs.ParamsTypeHeroScript // Match "HeroScript" string
createdJob.Status = herojobs.JobStatusNew // Default status after NewJob and Save
tests := []struct {
name string
reqBody map[string]interface{} // Use map for flexibility
storeError error
enqueueError error
expectedStatus int
expectedBody string // Will be the createdJob marshaled
}{
{
name: "Success",
reqBody: map[string]interface{}{
"circleid": "test-circle",
"topic": "test-topic",
"sessionkey": "test-key",
"params": "test-params",
"paramstype": "HeroScript",
"timeout": 30,
"log": true,
},
storeError: nil,
enqueueError: nil,
expectedStatus: fiber.StatusOK,
// Expected body should match the 'createdJob' structure after Save, Store, Enqueue
// JobID is assigned by Save(), which we are not mocking here.
// The handler returns the job object.
// For the test, we assume Save() works and populates JobID if it were a real DB.
// The mock will return the job passed to it.
expectedBody: `{"jobid":0,"circleid":"test-circle","topic":"test-topic","params":"test-params","paramstype":"HeroScript","status":"new","sessionkey":"test-key","result":"","error":"","timeout":30,"log":true,"timescheduled":0,"timestart":0,"timeend":0}`,
},
// Removed "Connection Error"
{
name: "StoreJob Error",
reqBody: map[string]interface{}{
"circleid": "test-circle", "topic": "test-topic", "params": "p", "paramstype": "HeroScript",
},
storeError: errors.New("store error"),
enqueueError: nil,
expectedStatus: fiber.StatusInternalServerError,
expectedBody: `{"error":"Failed to store new job in Redis: store error"}`,
},
{
name: "EnqueueJob Error",
reqBody: map[string]interface{}{
"circleid": "test-circle", "topic": "test-topic", "params": "p", "paramstype": "HeroScript",
},
storeError: nil,
enqueueError: errors.New("enqueue error"),
expectedStatus: fiber.StatusInternalServerError,
expectedBody: `{"error":"Failed to enqueue new job in Redis: enqueue error"}`,
},
{
name: "Empty Circle ID",
reqBody: map[string]interface{}{
"circleid": "", "topic": "test-topic", "params": "p", "paramstype": "HeroScript",
},
expectedStatus: fiber.StatusBadRequest,
expectedBody: `{"error":"Circle ID is required"}`,
},
{
name: "Empty Topic",
reqBody: map[string]interface{}{
"circleid": "c", "topic": "", "params": "p", "paramstype": "HeroScript",
},
expectedStatus: fiber.StatusBadRequest,
expectedBody: `{"error":"Topic is required"}`,
},
{
name: "Empty Params",
reqBody: map[string]interface{}{
"circleid": "c", "topic": "t", "params": "", "paramstype": "HeroScript",
},
expectedStatus: fiber.StatusBadRequest,
expectedBody: `{"error":"Params are required"}`,
},
{
name: "Empty ParamsType",
reqBody: map[string]interface{}{
"circleid": "c", "topic": "t", "params": "p", "paramstype": "",
},
expectedStatus: fiber.StatusBadRequest,
expectedBody: `{"error":"ParamsType is required"}`,
},
{
name: "Invalid ParamsType",
reqBody: map[string]interface{}{
"circleid": "c", "topic": "t", "params": "p", "paramstype": "InvalidType",
},
expectedStatus: fiber.StatusBadRequest,
expectedBody: `{"error":"Invalid ParamsType: InvalidType"}`,
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
_, mockClient, app := setupTest()
// Setup mock expectations
// job.Save() is called before client interactions. We assume it succeeds for these tests.
// The mock will be called with a job object. We use mock.AnythingOfType for the job
// because the JobID might be populated by Save() in a real scenario, making exact match hard.
if tc.reqBody["circleid"] != "" && tc.reqBody["topic"] != "" &&
tc.reqBody["params"] != "" && tc.reqBody["paramstype"] != "" &&
herojobs.ParamsType(tc.reqBody["paramstype"].(string)) != "" { // Basic validation check
// We expect StoreJob to be called with a *herojobs.Job.
// The actual JobID is set by job.Save() which is not mocked here.
// So we use mock.AnythingOfType to match the argument.
mockClient.On("StoreJob", mock.AnythingOfType("*herojobs.Job")).Return(tc.storeError).Once().Maybe()
if tc.storeError == nil {
mockClient.On("EnqueueJob", mock.AnythingOfType("*herojobs.Job")).Return(tc.enqueueError).Once().Maybe()
}
}
reqBodyBytes, err := json.Marshal(tc.reqBody)
assert.NoError(t, err)
req, err := createTestRequest(http.MethodPost, "/api/jobs/create", bytes.NewReader(reqBodyBytes)) // Use /api/jobs/create
assert.NoError(t, err)
// Content-Type is set by createTestRequest
// Perform the request
resp, err := app.Test(req)
assert.NoError(t, err)
// Check status code
assert.Equal(t, tc.expectedStatus, resp.StatusCode)
// Check response body
body, err := io.ReadAll(resp.Body)
assert.NoError(t, err)
assert.JSONEq(t, tc.expectedBody, string(body))
// Verify that all expectations were met
mockClient.AssertExpectations(t)
})
}
}
// TestSubmitJob tests the submitJob handler
func TestSubmitJob(t *testing.T) {
// Test cases
submittedJob := herojobs.NewJob()
submittedJob.JobID = 10 // Assume Save populates this
submittedJob.CircleID = "test-circle"
submittedJob.Topic = "test-topic"
submittedJob.Params = "submitted params"
submittedJob.ParamsType = herojobs.ParamsTypeHeroScript
submittedJob.Status = herojobs.JobStatusNew
tests := []struct {
name string
jobToSubmit *herojobs.Job // This is the job in the request body
storeError error
enqueueError error
expectedStatus int
expectedBody string // Will be the jobToSubmit marshaled (after potential Save)
}{
{
name: "Success",
jobToSubmit: submittedJob,
storeError: nil,
enqueueError: nil,
expectedStatus: fiber.StatusOK,
// The handler returns the job object from the request after Save(), Store(), Enqueue()
// For the mock, the JobID from jobToSubmit will be used.
expectedBody: `{"jobid":10,"circleid":"test-circle","topic":"test-topic","params":"submitted params","paramstype":"HeroScript","status":"new","sessionkey":"","result":"","error":"","timeout":60,"log":false,"timescheduled":0,"timestart":0,"timeend":0}`,
},
// Removed "Connection Error"
{
name: "StoreJob Error",
jobToSubmit: submittedJob,
storeError: errors.New("store error"),
enqueueError: nil,
expectedStatus: fiber.StatusInternalServerError,
expectedBody: `{"error":"Failed to store job in Redis: store error"}`,
},
{
name: "EnqueueJob Error",
jobToSubmit: submittedJob,
storeError: nil,
enqueueError: errors.New("enqueue error"),
expectedStatus: fiber.StatusInternalServerError,
expectedBody: `{"error":"Failed to enqueue job: enqueue error"}`,
},
{
name: "Empty Job in request (parsing error)",
jobToSubmit: nil, // Simulates empty or malformed request body
expectedStatus: fiber.StatusBadRequest,
expectedBody: `{"error":"Failed to parse job data: unexpected end of JSON input"}`, // Or similar based on actual parsing
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
_, mockClient, app := setupTest()
// Setup mock expectations
// job.Save() is called before client interactions.
if tc.jobToSubmit != nil { // If job is parsable from request
// We expect StoreJob to be called with the job from the request.
// The JobID might be modified by Save() in a real scenario.
mockClient.On("StoreJob", tc.jobToSubmit).Return(tc.storeError).Once().Maybe()
if tc.storeError == nil {
mockClient.On("EnqueueJob", tc.jobToSubmit).Return(tc.enqueueError).Once().Maybe()
}
}
var reqBodyBytes []byte
var err error
if tc.jobToSubmit != nil {
reqBodyBytes, err = json.Marshal(tc.jobToSubmit)
assert.NoError(t, err)
}
req, err := createTestRequest(http.MethodPost, "/api/jobs/submit", bytes.NewReader(reqBodyBytes)) // Use /api/jobs/submit
assert.NoError(t, err)
// Content-Type is set by createTestRequest
// Perform the request
resp, err := app.Test(req)
assert.NoError(t, err)
// Check status code
assert.Equal(t, tc.expectedStatus, resp.StatusCode)
// Check response body
body, err := io.ReadAll(resp.Body)
assert.NoError(t, err)
assert.JSONEq(t, tc.expectedBody, string(body))
// Verify that all expectations were met
mockClient.AssertExpectations(t)
})
}
}

View File

@@ -7,7 +7,7 @@ import (
"strings"
"time"
"github.com/freeflowuniverse/heroagent/pkg/logger"
"git.threefold.info/herocode/heroagent/pkg/logger"
"github.com/gofiber/fiber/v2"
)
@@ -65,40 +65,40 @@ func NewLogHandler(logPath string) (*LogHandler, error) {
type LogType string
const (
LogTypeSystem LogType = "system"
LogTypeService LogType = "service"
LogTypeJob LogType = "job"
LogTypeProcess LogType = "process"
LogTypeAll LogType = "all" // Special type to retrieve logs from all sources
LogTypeSystem LogType = "system"
LogTypeService LogType = "service"
LogTypeJob LogType = "job"
LogTypeProcess LogType = "process"
LogTypeAll LogType = "all" // Special type to retrieve logs from all sources
)
// GetLogs renders the logs page with logs content
func (h *LogHandler) GetLogs(c *fiber.Ctx) error {
// Check which logger to use based on the log type parameter
logTypeParam := c.Query("log_type", string(LogTypeSystem))
// Parse query parameters
category := c.Query("category", "")
logItemType := parseLogType(c.Query("type", ""))
maxItems := c.QueryInt("max_items", 100)
page := c.QueryInt("page", 1)
itemsPerPage := 20 // Default items per page
// Parse time range
fromTime := parseTimeParam(c.Query("from", ""))
toTime := parseTimeParam(c.Query("to", ""))
// Create search arguments
searchArgs := logger.SearchArgs{
Category: category,
LogType: logItemType,
MaxItems: maxItems,
Category: category,
LogType: logItemType,
MaxItems: maxItems,
}
if !fromTime.IsZero() {
searchArgs.TimestampFrom = &fromTime
}
if !toTime.IsZero() {
searchArgs.TimestampTo = &toTime
}
@@ -107,7 +107,7 @@ func (h *LogHandler) GetLogs(c *fiber.Ctx) error {
var logs []logger.LogItem
var err error
var logTypeTitle string
// Check if we want to merge logs from all sources
if LogType(logTypeParam) == LogTypeAll {
// Get merged logs from all loggers
@@ -116,7 +116,7 @@ func (h *LogHandler) GetLogs(c *fiber.Ctx) error {
} else {
// Select the appropriate logger based on the log type
var selectedLogger *logger.Logger
switch LogType(logTypeParam) {
case LogTypeService:
selectedLogger = h.serviceLogger
@@ -131,13 +131,13 @@ func (h *LogHandler) GetLogs(c *fiber.Ctx) error {
selectedLogger = h.systemLogger
logTypeTitle = "System Logs"
}
// Check if the selected logger is properly initialized
if selectedLogger == nil {
return c.Render("admin/system/logs", fiber.Map{
"title": logTypeTitle,
"error": "Logger not initialized",
"logTypes": []LogType{LogTypeAll, LogTypeSystem, LogTypeService, LogTypeJob, LogTypeProcess},
"title": logTypeTitle,
"error": "Logger not initialized",
"logTypes": []LogType{LogTypeAll, LogTypeSystem, LogTypeService, LogTypeJob, LogTypeProcess},
"selectedLogType": logTypeParam,
})
}
@@ -149,25 +149,24 @@ func (h *LogHandler) GetLogs(c *fiber.Ctx) error {
// Handle search error
if err != nil {
return c.Render("admin/system/logs", fiber.Map{
"title": logTypeTitle,
"error": err.Error(),
"logTypes": []LogType{LogTypeAll, LogTypeSystem, LogTypeService, LogTypeJob, LogTypeProcess},
"title": logTypeTitle,
"error": err.Error(),
"logTypes": []LogType{LogTypeAll, LogTypeSystem, LogTypeService, LogTypeJob, LogTypeProcess},
"selectedLogType": logTypeParam,
})
}
// Calculate total pages
totalLogs := len(logs)
totalPages := (totalLogs + itemsPerPage - 1) / itemsPerPage
// Apply pagination
startIndex := (page - 1) * itemsPerPage
endIndex := startIndex + itemsPerPage
if endIndex > totalLogs {
endIndex = totalLogs
}
// Slice logs for current page
pagedLogs := logs
if startIndex < totalLogs {
@@ -175,7 +174,7 @@ func (h *LogHandler) GetLogs(c *fiber.Ctx) error {
} else {
pagedLogs = []logger.LogItem{}
}
// Convert logs to a format suitable for the UI
formattedLogs := make([]fiber.Map, 0, len(pagedLogs))
for _, log := range pagedLogs {
@@ -185,7 +184,7 @@ func (h *LogHandler) GetLogs(c *fiber.Ctx) error {
logTypeStr = "ERROR"
logTypeClass = "log-error"
}
formattedLogs = append(formattedLogs, fiber.Map{
"timestamp": log.Timestamp.Format("2006-01-02T15:04:05"),
"category": log.Category,
@@ -194,20 +193,20 @@ func (h *LogHandler) GetLogs(c *fiber.Ctx) error {
"typeClass": logTypeClass,
})
}
return c.Render("admin/system/logs", fiber.Map{
"title": logTypeTitle,
"logTypes": []LogType{LogTypeAll, LogTypeSystem, LogTypeService, LogTypeJob, LogTypeProcess},
"title": logTypeTitle,
"logTypes": []LogType{LogTypeAll, LogTypeSystem, LogTypeService, LogTypeJob, LogTypeProcess},
"selectedLogType": logTypeParam,
"logs": formattedLogs,
"total": totalLogs,
"showing": len(formattedLogs),
"page": page,
"totalPages": totalPages,
"categoryParam": category,
"typeParam": c.Query("type", ""),
"fromParam": c.Query("from", ""),
"toParam": c.Query("to", ""),
"logs": formattedLogs,
"total": totalLogs,
"showing": len(formattedLogs),
"page": page,
"totalPages": totalPages,
"categoryParam": category,
"typeParam": c.Query("type", ""),
"fromParam": c.Query("from", ""),
"toParam": c.Query("to", ""),
})
}
@@ -215,27 +214,27 @@ func (h *LogHandler) GetLogs(c *fiber.Ctx) error {
func (h *LogHandler) GetLogsAPI(c *fiber.Ctx) error {
// Check which logger to use based on the log type parameter
logTypeParam := c.Query("log_type", string(LogTypeSystem))
// Parse query parameters
category := c.Query("category", "")
logItemType := parseLogType(c.Query("type", ""))
maxItems := c.QueryInt("max_items", 100)
// Parse time range
fromTime := parseTimeParam(c.Query("from", ""))
toTime := parseTimeParam(c.Query("to", ""))
// Create search arguments
searchArgs := logger.SearchArgs{
Category: category,
LogType: logItemType,
MaxItems: maxItems,
Category: category,
LogType: logItemType,
MaxItems: maxItems,
}
if !fromTime.IsZero() {
searchArgs.TimestampFrom = &fromTime
}
if !toTime.IsZero() {
searchArgs.TimestampTo = &toTime
}
@@ -243,7 +242,7 @@ func (h *LogHandler) GetLogsAPI(c *fiber.Ctx) error {
// Variables for logs and error
var logs []logger.LogItem
var err error
// Check if we want to merge logs from all sources
if LogType(logTypeParam) == LogTypeAll {
// Get merged logs from all loggers
@@ -251,7 +250,7 @@ func (h *LogHandler) GetLogsAPI(c *fiber.Ctx) error {
} else {
// Select the appropriate logger based on the log type
var selectedLogger *logger.Logger
switch LogType(logTypeParam) {
case LogTypeService:
selectedLogger = h.serviceLogger
@@ -262,7 +261,7 @@ func (h *LogHandler) GetLogsAPI(c *fiber.Ctx) error {
default:
selectedLogger = h.systemLogger
}
// Check if the selected logger is properly initialized
if selectedLogger == nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
@@ -280,7 +279,7 @@ func (h *LogHandler) GetLogsAPI(c *fiber.Ctx) error {
"error": err.Error(),
})
}
// Convert logs to a format suitable for the UI
response := make([]fiber.Map, 0, len(logs))
for _, log := range logs {
@@ -288,7 +287,7 @@ func (h *LogHandler) GetLogsAPI(c *fiber.Ctx) error {
if log.LogType == logger.LogTypeError {
logTypeStr = "ERROR"
}
response = append(response, fiber.Map{
"timestamp": log.Timestamp.Format(time.RFC3339),
"category": log.Category,
@@ -296,9 +295,9 @@ func (h *LogHandler) GetLogsAPI(c *fiber.Ctx) error {
"type": logTypeStr,
})
}
return c.JSON(fiber.Map{
"logs": response,
"logs": response,
"total": len(logs),
})
}
@@ -309,29 +308,29 @@ func (h *LogHandler) GetLogsFragment(c *fiber.Ctx) error {
// Check which logger to use based on the log type parameter
logTypeParam := c.Query("log_type", string(LogTypeSystem))
// Parse query parameters
category := c.Query("category", "")
logItemType := parseLogType(c.Query("type", ""))
maxItems := c.QueryInt("max_items", 100)
page := c.QueryInt("page", 1)
itemsPerPage := 20 // Default items per page
// Parse time range
fromTime := parseTimeParam(c.Query("from", ""))
toTime := parseTimeParam(c.Query("to", ""))
// Create search arguments
searchArgs := logger.SearchArgs{
Category: category,
LogType: logItemType,
MaxItems: maxItems,
Category: category,
LogType: logItemType,
MaxItems: maxItems,
}
if !fromTime.IsZero() {
searchArgs.TimestampFrom = &fromTime
}
if !toTime.IsZero() {
searchArgs.TimestampTo = &toTime
}
@@ -340,7 +339,7 @@ func (h *LogHandler) GetLogsFragment(c *fiber.Ctx) error {
var logs []logger.LogItem
var err error
var logTypeTitle string
// Check if we want to merge logs from all sources
if LogType(logTypeParam) == LogTypeAll {
// Get merged logs from all loggers
@@ -349,7 +348,7 @@ func (h *LogHandler) GetLogsFragment(c *fiber.Ctx) error {
} else {
// Select the appropriate logger based on the log type
var selectedLogger *logger.Logger
switch LogType(logTypeParam) {
case LogTypeService:
selectedLogger = h.serviceLogger
@@ -364,13 +363,13 @@ func (h *LogHandler) GetLogsFragment(c *fiber.Ctx) error {
selectedLogger = h.systemLogger
logTypeTitle = "System Logs"
}
// Check if the selected logger is properly initialized
if selectedLogger == nil {
return c.Render("admin/system/logs_fragment", fiber.Map{
"title": logTypeTitle,
"error": "Logger not initialized",
"logTypes": []LogType{LogTypeAll, LogTypeSystem, LogTypeService, LogTypeJob, LogTypeProcess},
"title": logTypeTitle,
"error": "Logger not initialized",
"logTypes": []LogType{LogTypeAll, LogTypeSystem, LogTypeService, LogTypeJob, LogTypeProcess},
"selectedLogType": logTypeParam,
})
}
@@ -382,24 +381,24 @@ func (h *LogHandler) GetLogsFragment(c *fiber.Ctx) error {
// Handle search error
if err != nil {
return c.Render("admin/system/logs_fragment", fiber.Map{
"title": logTypeTitle,
"error": err.Error(),
"logTypes": []LogType{LogTypeAll, LogTypeSystem, LogTypeService, LogTypeJob, LogTypeProcess},
"title": logTypeTitle,
"error": err.Error(),
"logTypes": []LogType{LogTypeAll, LogTypeSystem, LogTypeService, LogTypeJob, LogTypeProcess},
"selectedLogType": logTypeParam,
})
}
// Calculate total pages
totalLogs := len(logs)
totalPages := (totalLogs + itemsPerPage - 1) / itemsPerPage
// Apply pagination
startIndex := (page - 1) * itemsPerPage
endIndex := startIndex + itemsPerPage
if endIndex > totalLogs {
endIndex = totalLogs
}
// Slice logs for current page
pagedLogs := logs
if startIndex < totalLogs {
@@ -407,7 +406,7 @@ func (h *LogHandler) GetLogsFragment(c *fiber.Ctx) error {
} else {
pagedLogs = []logger.LogItem{}
}
// Convert logs to a format suitable for the UI
formattedLogs := make([]fiber.Map, 0, len(pagedLogs))
for _, log := range pagedLogs {
@@ -417,7 +416,7 @@ func (h *LogHandler) GetLogsFragment(c *fiber.Ctx) error {
logTypeStr = "ERROR"
logTypeClass = "log-error"
}
formattedLogs = append(formattedLogs, fiber.Map{
"timestamp": log.Timestamp.Format("2006-01-02T15:04:05"),
"category": log.Category,
@@ -426,18 +425,18 @@ func (h *LogHandler) GetLogsFragment(c *fiber.Ctx) error {
"typeClass": logTypeClass,
})
}
// Set layout to empty to disable the layout for fragment responses
return c.Render("admin/system/logs_fragment", fiber.Map{
"title": logTypeTitle,
"logTypes": []LogType{LogTypeAll, LogTypeSystem, LogTypeService, LogTypeJob, LogTypeProcess},
"title": logTypeTitle,
"logTypes": []LogType{LogTypeAll, LogTypeSystem, LogTypeService, LogTypeJob, LogTypeProcess},
"selectedLogType": logTypeParam,
"logs": formattedLogs,
"total": totalLogs,
"showing": len(formattedLogs),
"page": page,
"totalPages": totalPages,
"layout": "", // Disable layout for partial template
"logs": formattedLogs,
"total": totalLogs,
"showing": len(formattedLogs),
"page": page,
"totalPages": totalPages,
"layout": "", // Disable layout for partial template
})
}
@@ -458,12 +457,12 @@ func parseTimeParam(timeStr string) time.Time {
if timeStr == "" {
return time.Time{}
}
t, err := time.Parse(time.RFC3339, timeStr)
if err != nil {
return time.Time{}
}
return t
}
@@ -471,10 +470,10 @@ func parseTimeParam(timeStr string) time.Time {
func (h *LogHandler) getMergedLogs(args logger.SearchArgs) ([]logger.LogItem, error) {
// Create a slice to hold all logs
allLogs := make([]logger.LogItem, 0)
// Create a map to track errors
errors := make(map[string]error)
// Get logs from system logger if available
if h.systemLogger != nil {
systemLogs, err := h.systemLogger.Search(args)
@@ -488,7 +487,7 @@ func (h *LogHandler) getMergedLogs(args logger.SearchArgs) ([]logger.LogItem, er
allLogs = append(allLogs, systemLogs...)
}
}
// Get logs from service logger if available
if h.serviceLogger != nil {
serviceLogs, err := h.serviceLogger.Search(args)
@@ -502,7 +501,7 @@ func (h *LogHandler) getMergedLogs(args logger.SearchArgs) ([]logger.LogItem, er
allLogs = append(allLogs, serviceLogs...)
}
}
// Get logs from job logger if available
if h.jobLogger != nil {
jobLogs, err := h.jobLogger.Search(args)
@@ -516,7 +515,7 @@ func (h *LogHandler) getMergedLogs(args logger.SearchArgs) ([]logger.LogItem, er
allLogs = append(allLogs, jobLogs...)
}
}
// Get logs from process logger if available
if h.processLogger != nil {
processLogs, err := h.processLogger.Search(args)
@@ -530,7 +529,7 @@ func (h *LogHandler) getMergedLogs(args logger.SearchArgs) ([]logger.LogItem, er
allLogs = append(allLogs, processLogs...)
}
}
// Check if we have any logs
if len(allLogs) == 0 && len(errors) > 0 {
// Combine error messages
@@ -540,16 +539,16 @@ func (h *LogHandler) getMergedLogs(args logger.SearchArgs) ([]logger.LogItem, er
}
return nil, fmt.Errorf("failed to retrieve logs: %s", strings.Join(errorMsgs, "; "))
}
// Sort logs by timestamp (newest first)
sort.Slice(allLogs, func(i, j int) bool {
return allLogs[i].Timestamp.After(allLogs[j].Timestamp)
})
// Apply max items limit if specified
if args.MaxItems > 0 && len(allLogs) > args.MaxItems {
allLogs = allLogs[:args.MaxItems]
}
return allLogs, nil
}

View File

@@ -4,7 +4,7 @@ import (
"fmt"
"time"
"github.com/freeflowuniverse/heroagent/pkg/system/stats"
"git.threefold.info/herocode/heroagent/pkg/system/stats"
"github.com/gofiber/fiber/v2"
)
@@ -43,8 +43,8 @@ func (h *ProcessHandler) GetProcessStatsJSON(c *fiber.Ctx) error {
// Convert to fiber.Map for JSON response
response := fiber.Map{
"total": processData.Total,
"filtered": processData.Filtered,
"total": processData.Total,
"filtered": processData.Filtered,
"timestamp": time.Now().Unix(),
}
@@ -61,7 +61,7 @@ func (h *ProcessHandler) GetProcessStatsJSON(c *fiber.Ctx) error {
"is_current": proc.IsCurrent,
}
}
response["processes"] = processes
// Return JSON response
@@ -127,8 +127,8 @@ func (h *ProcessHandler) GetProcessesData(c *fiber.Ctx) error {
// Check if StatsManager is properly initialized
if h.statsManager == nil {
return c.Render("admin/system/processes_data", fiber.Map{
"error": "System error: Stats manager not initialized",
"layout": "",
"error": "System error: Stats manager not initialized",
"layout": "",
})
}
@@ -156,7 +156,7 @@ func (h *ProcessHandler) GetProcessesData(c *fiber.Ctx) error {
} else {
processData, err = h.statsManager.GetProcessStatsFresh(0)
}
if err != nil {
// Handle AJAX requests differently from regular requests
isAjax := c.Get("X-Requested-With") == "XMLHttpRequest"
@@ -165,8 +165,8 @@ func (h *ProcessHandler) GetProcessesData(c *fiber.Ctx) error {
}
// For regular requests, render the error within the fragment
return c.Render("admin/system/processes_data", fiber.Map{
"error": "Failed to get process data: " + err.Error(),
"layout": "",
"error": "Failed to get process data: " + err.Error(),
"layout": "",
})
}
}
@@ -197,9 +197,7 @@ func (h *ProcessHandler) GetProcessesData(c *fiber.Ctx) error {
"processStats": processStats,
"layout": "", // Disable layout for partial template
}
// Return only the table HTML content directly to be injected into the processes-table-content div
return c.Render("admin/system/processes_data", templateData)
}

View File

@@ -4,8 +4,8 @@ import (
"fmt"
"time"
"github.com/freeflowuniverse/heroagent/pkg/processmanager/interfaces"
"github.com/freeflowuniverse/heroagent/pkg/processmanager/interfaces/openrpc"
"git.threefold.info/herocode/heroagent/pkg/processmanager/interfaces"
"git.threefold.info/herocode/heroagent/pkg/processmanager/interfaces/openrpc"
"github.com/gofiber/fiber/v2"
)

View File

@@ -5,7 +5,7 @@ import (
"strings"
"time"
"github.com/freeflowuniverse/heroagent/pkg/system/stats"
"git.threefold.info/herocode/heroagent/pkg/system/stats"
"github.com/gofiber/fiber/v2"
"github.com/shirou/gopsutil/v3/host"
)
@@ -335,7 +335,7 @@ func (h *SystemHandler) GetProcessStatsAPI(c *fiber.Ctx) error {
"is_current": proc.IsCurrent,
}
}
response["processes"] = processes
// Return JSON response

View File

@@ -7,8 +7,8 @@ import (
"strings"
"time"
"github.com/freeflowuniverse/heroagent/pkg/heroagent/handlers"
"github.com/freeflowuniverse/heroagent/pkg/system/stats"
"git.threefold.info/herocode/heroagent/pkg/heroagent/handlers"
"git.threefold.info/herocode/heroagent/pkg/system/stats"
"github.com/gofiber/fiber/v2"
"github.com/shirou/gopsutil/v3/host"
)
@@ -68,12 +68,12 @@ func (h *AdminHandler) RegisterRoutes(app *fiber.App) {
// System routes
admin.Get("/system/info", h.getSystemInfo)
admin.Get("/system/hardware-stats", h.getHardwareStats)
// Create process handler
processHandler := handlers.NewProcessHandler(h.statsManager)
admin.Get("/system/processes", processHandler.GetProcesses)
admin.Get("/system/processes-data", processHandler.GetProcessesData)
// Create log handler
// Ensure log directory exists
// Using the same shared logs path as process manager
@@ -81,7 +81,7 @@ func (h *AdminHandler) RegisterRoutes(app *fiber.App) {
if err := os.MkdirAll(logDir, 0755); err != nil {
fmt.Printf("Error creating log directory: %v\n", err)
}
logHandler, err := handlers.NewLogHandler(logDir)
if err != nil {
fmt.Printf("Error creating log handler: %v\n", err)
@@ -96,11 +96,11 @@ func (h *AdminHandler) RegisterRoutes(app *fiber.App) {
// but it now just redirects to the main logs endpoint
admin.Get("/system/logs-fragment", logHandler.GetLogsFragment)
admin.Get("/system/logs-test", h.getSystemLogsTest) // Keep the test logs route
// Log API endpoints
app.Get("/api/logs", logHandler.GetLogsAPI)
}
admin.Get("/system/settings", h.getSystemSettings)
// OpenRPC routes

View File

@@ -4,7 +4,7 @@ import (
"fmt"
"log"
"github.com/freeflowuniverse/heroagent/pkg/herojobs"
"git.threefold.info/herocode/heroagent/pkg/herojobs"
"github.com/gofiber/fiber/v2"
)
@@ -15,8 +15,8 @@ type JobDisplayInfo struct {
Topic string `json:"topic"`
Status string `json:"status"`
SessionKey string `json:"sessionkey"`
HeroScript string `json:"heroscript"`
RhaiScript string `json:"rhaiscript"`
Params string `json:"params"`
ParamsType string `json:"paramstype"`
Result string `json:"result"`
Error string `json:"error"`
TimeScheduled int64 `json:"time_scheduled"`
@@ -27,15 +27,17 @@ type JobDisplayInfo struct {
// JobHandler handles job-related page routes
type JobHandler struct {
client *herojobs.Client
client *herojobs.RedisClient
logger *log.Logger
}
// NewJobHandler creates a new job handler with the provided socket path
func NewJobHandler(socketPath string, logger *log.Logger) (*JobHandler, error) {
client, err := herojobs.NewClient(socketPath)
func NewJobHandler(redisAddr string, logger *log.Logger) (*JobHandler, error) {
// Assuming SSL is false as per README example herojobs.NewRedisClient("localhost:6379", false)
// This might need to be configurable later.
client, err := herojobs.NewRedisClient(redisAddr, false)
if err != nil {
return nil, fmt.Errorf("failed to create HeroJobs client: %w", err)
return nil, fmt.Errorf("failed to create HeroJobs Redis client: %w", err)
}
return &JobHandler{
@@ -50,7 +52,7 @@ func (h *JobHandler) RegisterRoutes(app *fiber.App) {
jobs := app.Group("/jobs")
jobs.Get("/", h.getJobsPage)
jobs.Get("/list", h.getJobsList)
// Register the same routes under /admin/jobs for consistency
adminJobs := app.Group("/admin/jobs")
adminJobs.Get("/", h.getJobsPage)
@@ -59,18 +61,13 @@ func (h *JobHandler) RegisterRoutes(app *fiber.App) {
// getJobsPage renders the jobs page
func (h *JobHandler) getJobsPage(c *fiber.Ctx) error {
// Check if we can connect to the HeroJobs server
var warning string
if err := h.client.Connect(); err != nil {
warning = "Could not connect to HeroJobs server: " + err.Error()
h.logger.Printf("Warning: %s", warning)
} else {
h.client.Close()
}
// Assuming h.client (RedisClient) is valid if NewJobHandler succeeded.
// The client is connected on creation. A Ping method could be used here for a health check if available.
// The previous connect/close logic per-request is removed.
var warning string // This will be empty unless a new check (e.g., Ping) sets it.
return c.Render("admin/jobs", fiber.Map{
"title": "Jobs",
"warning": warning,
"warning": warning, // warning will be empty for now
"error": "",
})
}
@@ -100,20 +97,18 @@ func (h *JobHandler) getJobsList(c *fiber.Ctx) error {
// getJobsData gets job data from the HeroJobs server
func (h *JobHandler) getJobsData(circleID, topic string) ([]JobDisplayInfo, error) {
// Connect to the HeroJobs server
if err := h.client.Connect(); err != nil {
return nil, fmt.Errorf("failed to connect to HeroJobs server: %w", err)
}
defer h.client.Close()
// Assuming h.client (RedisClient) is already connected (established by NewJobHandler).
// It should not be closed here as it's a long-lived client.
// Connect() and Close() calls per-request are removed.
// If circleID and topic are not provided, try to list all jobs
if circleID == "" && topic == "" {
// Try to get some default jobs
defaultCircles := []string{"default", "system"}
defaultTopics := []string{"default", "system"}
var allJobs []JobDisplayInfo
// Try each combination
for _, circle := range defaultCircles {
for _, t := range defaultTopics {
@@ -122,22 +117,22 @@ func (h *JobHandler) getJobsData(circleID, topic string) ([]JobDisplayInfo, erro
h.logger.Printf("Could not list jobs for circle=%s, topic=%s: %v", circle, t, err)
continue
}
for _, jobID := range jobIDs {
job, err := h.client.GetJob(jobID)
if err != nil {
h.logger.Printf("Error getting job %s: %v", jobID, err)
continue
}
allJobs = append(allJobs, JobDisplayInfo{
JobID: job.JobID,
JobID: fmt.Sprintf("%d", job.JobID),
CircleID: job.CircleID,
Topic: job.Topic,
Status: string(job.Status),
SessionKey: job.SessionKey,
HeroScript: job.HeroScript,
RhaiScript: job.RhaiScript,
Params: job.Params,
ParamsType: string(job.ParamsType),
Result: job.Result,
Error: job.Error,
TimeScheduled: job.TimeScheduled,
@@ -148,7 +143,7 @@ func (h *JobHandler) getJobsData(circleID, topic string) ([]JobDisplayInfo, erro
}
}
}
return allJobs, nil
} else if circleID == "" || topic == "" {
// If only one of the parameters is provided, we can't list jobs
@@ -171,13 +166,13 @@ func (h *JobHandler) getJobsData(circleID, topic string) ([]JobDisplayInfo, erro
}
jobInfo := JobDisplayInfo{
JobID: job.JobID,
JobID: fmt.Sprintf("%d", job.JobID),
CircleID: job.CircleID,
Topic: job.Topic,
Status: string(job.Status),
SessionKey: job.SessionKey,
HeroScript: job.HeroScript,
RhaiScript: job.RhaiScript,
Params: job.Params,
ParamsType: string(job.ParamsType),
Result: job.Result,
Error: job.Error,
TimeScheduled: job.TimeScheduled,

View File

@@ -4,14 +4,14 @@ import (
"fmt"
"log"
"github.com/freeflowuniverse/heroagent/pkg/processmanager/interfaces/openrpc"
"git.threefold.info/herocode/heroagent/pkg/processmanager/interfaces/openrpc"
"github.com/gofiber/fiber/v2"
)
// ServiceHandler handles service-related page routes
type ServiceHandler struct {
client *openrpc.Client
logger *log.Logger
client *openrpc.Client
logger *log.Logger
}
// NewServiceHandler creates a new service handler with the provided socket path and secret
@@ -90,7 +90,7 @@ func (h *ServiceHandler) getProcessList() ([]ProcessDisplayInfo, error) {
if !ok {
continue
}
// Create a ProcessDisplayInfo from the map
displayInfo := ProcessDisplayInfo{
ID: fmt.Sprintf("%v", procMap["pid"]),

View File

@@ -4,7 +4,7 @@ import (
"fmt"
"time"
"github.com/freeflowuniverse/heroagent/pkg/processmanager"
"git.threefold.info/herocode/heroagent/pkg/processmanager"
)
// ProcessDisplayInfo represents information about a process for display purposes
@@ -22,7 +22,7 @@ type ProcessDisplayInfo struct {
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,

View File

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

Before

Width:  |  Height:  |  Size: 1.0 KiB

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

Before

Width:  |  Height:  |  Size: 844 B

After

Width:  |  Height:  |  Size: 844 B

View File

@@ -35,7 +35,7 @@ Key features:
```go
import (
"fmt"
"github.com/freeflowuniverse/heroagent/pkg/heroscript/playbook"
"git.threefold.info/herocode/heroagent/pkg/heroscript/playbook"
)
// Create a new playbook from HeroScript text

View File

@@ -7,7 +7,7 @@ import (
"os/signal"
"syscall"
"github.com/freeflowuniverse/heroagent/pkg/handlerfactory/herohandler"
"git.threefold.info/herocode/heroagent/pkg/handlerfactory/herohandler"
)
func main() {
@@ -24,7 +24,7 @@ func main() {
fmt.Printf("Failed to initialize hero handler: %v\n", err)
os.Exit(1)
}
// Get the default instance
handler := herohandler.DefaultInstance
@@ -43,7 +43,7 @@ func main() {
tcpAddressStr = *tcpAddress
fmt.Printf("TCP address: %s\n", tcpAddressStr)
}
err = handler.StartTelnet(socketPathStr, tcpAddressStr)
if err != nil {
fmt.Printf("Failed to start telnet server: %v\n", err)

View File

@@ -8,7 +8,7 @@ import (
"os"
"strings"
"github.com/freeflowuniverse/heroagent/pkg/heroscript/playbook"
"git.threefold.info/herocode/heroagent/pkg/heroscript/playbook"
)
func main() {

View File

@@ -3,8 +3,8 @@ package internal
import (
"fmt"
"github.com/freeflowuniverse/heroagent/pkg/handlerfactory"
"github.com/freeflowuniverse/heroagent/pkg/heroscript/handlers"
"git.threefold.info/herocode/heroagent/pkg/handlerfactory"
"git.threefold.info/herocode/heroagent/pkg/heroscript/handlers"
)
// ExampleHandler handles example actions

View File

@@ -7,7 +7,7 @@ import (
"os"
"strings"
"github.com/freeflowuniverse/heroagent/pkg/heroscript/cmd/herohandler/internal"
"git.threefold.info/herocode/heroagent/pkg/heroscript/cmd/herohandler/internal"
)
func main() {
@@ -30,7 +30,7 @@ func main() {
// Get the command from arguments
command := strings.Join(os.Args[1:], " ")
// Format as proper HeroScript with !! prefix if not already prefixed
script := command
if !strings.HasPrefix(script, "!!") {

View File

@@ -10,7 +10,7 @@ import (
"syscall"
"time"
"github.com/freeflowuniverse/heroagent/pkg/handlerfactory/herohandler"
"git.threefold.info/herocode/heroagent/pkg/handlerfactory/herohandler"
)
func main() {
@@ -92,7 +92,7 @@ func showSupportedActions() {
// We need to implement this function to get supported actions
// Since we can't directly access the factory field, we'll use the telnet interface
script := "!!core.actions"
// Try TCP first, then Unix socket if TCP fails
result, err := Send(script, "localhost:8023", false)
if err != nil {
@@ -103,7 +103,7 @@ func showSupportedActions() {
return
}
}
fmt.Println("Supported actions by actor:")
fmt.Println(result)
}
@@ -151,7 +151,7 @@ func Send(command string, address string, isUnixSocket bool) (string, error) {
return
}
response.WriteString(line)
// If we've received a complete response, break
if strings.Contains(line, "\n") && strings.TrimSpace(line) == "" {
break
@@ -187,7 +187,7 @@ func runTestScript() {
result, err = Send(script, "/tmp/hero.sock", true)
if err != nil {
fmt.Printf("Unix socket connection failed: %v\n", err)
// We can't directly access the factory field, so we'll just report the error
fmt.Printf("Error: %v\n", err)
return

View File

@@ -4,7 +4,7 @@ import (
"fmt"
"log"
"github.com/freeflowuniverse/heroagent/pkg/heroscript/playbook"
"git.threefold.info/herocode/heroagent/pkg/heroscript/playbook"
)
const exampleScript = `

View File

@@ -16,7 +16,7 @@ The VM handler example shows how to:
To run the example:
```bash
cd ~/code/github/freeflowuniverse/heroagent/pkg/handlerfactory/cmd/vmhandler
cd ~/code/github/freeflowuniverse/herocode/heroagent/pkg/handlerfactory/cmd/vmhandler
go run . tutorial
#to run just the server do
go run .

View File

@@ -6,7 +6,7 @@ import (
"os"
"time"
"github.com/freeflowuniverse/heroagent/pkg/handlerfactory"
"git.threefold.info/herocode/heroagent/pkg/handlerfactory"
)
// runTutorial runs an interactive tutorial demonstrating the VM handler
@@ -50,7 +50,7 @@ func runTutorial() {
// Process heroscript commands
fmt.Println("\nStep 5: Process heroscript commands")
// Define a VM
defineScript := `!!vm.define name:'tutorial_vm' cpu:2 memory:'4GB' storage:'50GB'
description: 'A tutorial VM for demonstration purposes'`

View File

@@ -4,7 +4,7 @@ import (
"fmt"
"strings"
"github.com/freeflowuniverse/heroagent/pkg/handlerfactory"
"git.threefold.info/herocode/heroagent/pkg/handlerfactory"
)
// VMHandler handles VM-related actions

View File

@@ -8,7 +8,7 @@ import (
"path/filepath"
"syscall"
"github.com/freeflowuniverse/heroagent/pkg/handlerfactory"
"git.threefold.info/herocode/heroagent/pkg/handlerfactory"
)
// The tutorial functions are defined in tutorial.go

View File

@@ -30,7 +30,7 @@ The Handler Factory exposes two interfaces for communication:
to get started
```bash
cd /root/code/github/freeflowuniverse/heroagent/pkg/handlerfactory/herohandler/cmd
cd /root/code/github/freeflowuniverse/herocode/heroagent/pkg/handlerfactory/herohandler/cmd
go run .
```

View File

@@ -5,8 +5,8 @@ import (
"reflect"
"strings"
"github.com/freeflowuniverse/heroagent/pkg/heroscript/paramsparser"
"github.com/freeflowuniverse/heroagent/pkg/heroscript/playbook"
"git.threefold.info/herocode/heroagent/pkg/heroscript/paramsparser"
"git.threefold.info/herocode/heroagent/pkg/heroscript/playbook"
)
// Handler interface defines methods that all handlers must implement

View File

@@ -5,7 +5,7 @@ import (
"reflect"
"strings"
"github.com/freeflowuniverse/heroagent/pkg/heroscript/playbook"
"git.threefold.info/herocode/heroagent/pkg/heroscript/playbook"
)
// HandlerFactory manages a collection of handlers

View File

@@ -12,7 +12,7 @@ import (
"sync"
"syscall"
"github.com/freeflowuniverse/heroagent/pkg/heroscript/playbook"
"git.threefold.info/herocode/heroagent/pkg/heroscript/playbook"
)
// ANSI color codes for terminal output
@@ -43,23 +43,23 @@ type TelnetServer struct {
sigCh chan os.Signal
onShutdown func()
// Map to store client preferences (like json formatting)
clientPrefs map[net.Conn]map[string]bool
prefsMutex sync.RWMutex
clientPrefs map[net.Conn]map[string]bool
prefsMutex sync.RWMutex
}
// NewTelnetServer creates a new telnet server
func NewTelnetServer(factory *HandlerFactory, secrets ...string) *TelnetServer {
ctx, cancel := context.WithCancel(context.Background())
return &TelnetServer{
factory: factory,
secrets: secrets,
clients: make(map[net.Conn]bool),
factory: factory,
secrets: secrets,
clients: make(map[net.Conn]bool),
clientPrefs: make(map[net.Conn]map[string]bool),
running: false,
ctx: ctx,
cancel: cancel,
sigCh: make(chan os.Signal, 1),
onShutdown: func() {},
running: false,
ctx: ctx,
cancel: cancel,
sigCh: make(chan os.Signal, 1),
onShutdown: func() {},
}
}
@@ -205,7 +205,7 @@ func (ts *TelnetServer) handleConnection(conn net.Conn) {
ts.clientsMutex.Lock()
ts.clients[conn] = false
ts.clientsMutex.Unlock()
// Initialize client preferences
ts.prefsMutex.Lock()
ts.clientPrefs[conn] = make(map[string]bool)
@@ -284,7 +284,7 @@ func (ts *TelnetServer) handleConnection(conn net.Conn) {
}
continue
}
// Handle JSON format toggle
if line == "!!json" {
ts.prefsMutex.Lock()
@@ -293,12 +293,12 @@ func (ts *TelnetServer) handleConnection(conn net.Conn) {
prefs = make(map[string]bool)
ts.clientPrefs[conn] = prefs
}
// Toggle JSON format preference
currentSetting := prefs["json"]
prefs["json"] = !currentSetting
ts.prefsMutex.Unlock()
if prefs["json"] {
conn.Write([]byte("JSON format will be automatically added to all heroscripts.\n"))
} else {
@@ -416,7 +416,7 @@ func (ts *TelnetServer) executeHeroscript(script string, conn net.Conn, interact
ts.prefsMutex.RLock()
prefs, exists := ts.clientPrefs[conn]
ts.prefsMutex.RUnlock()
if exists && prefs["json"] {
// Add format:json if not already present
if !strings.Contains(script, "format:json") {
@@ -424,7 +424,7 @@ func (ts *TelnetServer) executeHeroscript(script string, conn net.Conn, interact
}
}
}
if interactive {
// Format the script with colors
formattedScript := formatHeroscript(script)
@@ -466,8 +466,6 @@ func (ts *TelnetServer) addJsonFormat(script string) string {
return strings.Join(lines, "\n")
}
// formatHeroscript formats heroscript with colors for console output only
// This is not used for telnet responses, only for server-side logging
func formatHeroscript(script string) string {
@@ -611,12 +609,12 @@ func (ts *TelnetServer) generateHelpText(interactive bool) string {
// Try to call the Help method on each handler using reflection
handlerValue := reflect.ValueOf(handler)
helpMethod := handlerValue.MethodByName("Help")
if helpMethod.IsValid() {
// Call the Help method
args := []reflect.Value{reflect.ValueOf("")}
result := helpMethod.Call(args)
// Get the result
if len(result) > 0 && result[0].Kind() == reflect.String {
helpText := result[0].String()

View File

@@ -3,7 +3,7 @@ package handlers
import (
"fmt"
"github.com/freeflowuniverse/heroagent/pkg/heroscript/handlerfactory/core"
"git.threefold.info/herocode/heroagent/pkg/heroscript/handlerfactory/core"
)
// AuthHandler handles authentication actions

View File

@@ -5,9 +5,9 @@ import (
"reflect"
"strings"
"github.com/freeflowuniverse/heroagent/pkg/heroscript/handlerfactory/core"
"github.com/freeflowuniverse/heroagent/pkg/heroscript/paramsparser"
"github.com/freeflowuniverse/heroagent/pkg/heroscript/playbook"
"git.threefold.info/herocode/heroagent/pkg/heroscript/handlerfactory/core"
"git.threefold.info/herocode/heroagent/pkg/heroscript/paramsparser"
"git.threefold.info/herocode/heroagent/pkg/heroscript/playbook"
)
// BaseHandler provides common functionality for all handlers

View File

@@ -4,8 +4,8 @@ import (
"fmt"
"strings"
"github.com/freeflowuniverse/heroagent/pkg/heroscript/handlerfactory/core"
"github.com/freeflowuniverse/heroagent/pkg/heroscript/playbook"
"git.threefold.info/herocode/heroagent/pkg/heroscript/handlerfactory/core"
"git.threefold.info/herocode/heroagent/pkg/heroscript/playbook"
)
// HandlerFactory manages a collection of handlers for processing HeroScript commands

View File

@@ -1,7 +1,7 @@
package herohandler
import (
"github.com/freeflowuniverse/heroagent/pkg/heroscript/handlerfactory/core"
"git.threefold.info/herocode/heroagent/pkg/heroscript/handlerfactory/core"
)
// GetFactory returns the handler factory

View File

@@ -4,7 +4,7 @@ import (
"log"
"sync"
"github.com/freeflowuniverse/heroagent/pkg/handlerfactory/herohandler"
"git.threefold.info/herocode/heroagent/pkg/heroscript/handlerfactory/herohandler"
)
func main() {

View File

@@ -4,10 +4,10 @@ import (
"fmt"
"log"
"github.com/freeflowuniverse/heroagent/pkg/heroscript/handlerfactory/core"
"git.threefold.info/herocode/heroagent/pkg/heroscript/handlerfactory/core"
// "github.com/freeflowuniverse/heroagent/pkg/handlerfactory/heroscript/handlerfactory/fakehandler"
"github.com/freeflowuniverse/heroagent/pkg/heroscript/handlerfactory/processmanagerhandler"
// "git.threefold.info/herocode/heroagent/pkg/handlerfactory/heroscript/handlerfactory/fakehandler"
"git.threefold.info/herocode/heroagent/pkg/heroscript/handlerfactory/processmanagerhandler"
)
// HeroHandler is the main handler factory that manages all registered handlers

View File

@@ -2,10 +2,8 @@ package main
import (
"fmt"
"log"
"os"
"github.com/freeflowuniverse/heroagent/pkg/heroscript/playbook"
"git.threefold.info/herocode/heroagent/pkg/heroscript/playbook"
)
func main() {
@@ -15,26 +13,26 @@ func main() {
pb := playbook.New()
// Start a simple process
startAction := pb.NewAction(1, "start", "process", 0, playbook.ActionTypeUnknown)
startAction := pb.NewAction("1", "start", "process", 0, playbook.ActionTypeUnknown)
startAction.Params.Set("name", "example_process")
startAction.Params.Set("command", "ping -c 60 localhost")
startAction.Params.Set("log", "true")
// List all processes
listAction := pb.NewAction(2, "list", "process", 0, playbook.ActionTypeUnknown)
listAction := pb.NewAction("2", "list", "process", 0, playbook.ActionTypeUnknown)
listAction.Params.Set("format", "table")
// Get status of a specific process
statusAction := pb.NewAction(3, "status", "process", 0, playbook.ActionTypeUnknown)
statusAction := pb.NewAction("3", "status", "process", 0, playbook.ActionTypeUnknown)
statusAction.Params.Set("name", "example_process")
// Get logs of a specific process
logsAction := pb.NewAction(4, "logs", "process", 0, playbook.ActionTypeUnknown)
logsAction := pb.NewAction("4", "logs", "process", 0, playbook.ActionTypeUnknown)
logsAction.Params.Set("name", "example_process")
logsAction.Params.Set("lines", "10")
// Stop a process
stopAction := pb.NewAction(5, "stop", "process", 0, playbook.ActionTypeUnknown)
stopAction := pb.NewAction("5", "stop", "process", 0, playbook.ActionTypeUnknown)
stopAction.Params.Set("name", "example_process")
// Generate the heroscript

View File

@@ -3,8 +3,8 @@ package processmanagerhandler
import (
"fmt"
"github.com/freeflowuniverse/heroagent/pkg/heroscript/handlerfactory/core"
"github.com/freeflowuniverse/heroagent/pkg/processmanager"
"git.threefold.info/herocode/heroagent/pkg/heroscript/handlerfactory/core"
"git.threefold.info/herocode/heroagent/pkg/processmanager"
)
// ProcessManagerHandler handles process manager-related actions

Some files were not shown because too many files have changed in this diff Show More