...
This commit is contained in:
		
							
								
								
									
										541
									
								
								_pkg2_dont_use/heroagent/pages/admin.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										541
									
								
								_pkg2_dont_use/heroagent/pages/admin.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,541 @@ | ||||
| package pages | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"os" | ||||
| 	"path/filepath" | ||||
| 	"strings" | ||||
| 	"time" | ||||
|  | ||||
| 	"git.ourworld.tf/herocode/heroagent/pkg/heroagent/handlers" | ||||
| 	"git.ourworld.tf/herocode/heroagent/pkg/system/stats" | ||||
| 	"github.com/gofiber/fiber/v2" | ||||
| 	"github.com/shirou/gopsutil/v3/host" | ||||
| ) | ||||
|  | ||||
| // UptimeProvider defines an interface for getting system uptime | ||||
| type UptimeProvider interface { | ||||
| 	GetUptime() string | ||||
| } | ||||
|  | ||||
| // AdminHandler handles admin-related page routes | ||||
| type AdminHandler struct { | ||||
| 	uptimeProvider UptimeProvider | ||||
| 	statsManager   *stats.StatsManager | ||||
| 	pmSocketPath   string | ||||
| 	pmSecret       string | ||||
| } | ||||
|  | ||||
| // NewAdminHandler creates a new AdminHandler | ||||
| func NewAdminHandler(uptimeProvider UptimeProvider, statsManager *stats.StatsManager, pmSocketPath, pmSecret string) *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, | ||||
| 		pmSocketPath:   pmSocketPath, | ||||
| 		pmSecret:       pmSecret, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // RegisterRoutes registers all admin page routes | ||||
| func (h *AdminHandler) RegisterRoutes(app *fiber.App) { | ||||
| 	// Admin routes | ||||
| 	admin := app.Group("/admin") | ||||
|  | ||||
| 	// Dashboard | ||||
| 	admin.Get("/", h.getDashboard) | ||||
|  | ||||
| 	// Create service handler with the correct socket path and secret | ||||
| 	serviceHandler := handlers.NewServiceHandler(h.pmSocketPath, h.pmSecret) | ||||
| 	// Services routes | ||||
| 	admin.Get("/services", serviceHandler.GetServices) | ||||
| 	admin.Get("/services/data", serviceHandler.GetServicesFragment) | ||||
| 	admin.Post("/services/start", serviceHandler.StartService) | ||||
| 	admin.Post("/services/stop", serviceHandler.StopService) | ||||
| 	admin.Post("/services/restart", serviceHandler.RestartService) | ||||
| 	admin.Post("/services/delete", serviceHandler.DeleteService) | ||||
| 	admin.Get("/services/logs", serviceHandler.GetServiceLogs) | ||||
|  | ||||
| 	// 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 | ||||
| 	logDir := filepath.Join(os.TempDir(), "heroagent_logs") | ||||
| 	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) | ||||
| 		// Fallback to old implementation if log handler creation failed | ||||
| 		admin.Get("/system/logs", h.getSystemLogs) | ||||
| 		admin.Get("/system/logs-test", h.getSystemLogsTest) | ||||
| 	} else { | ||||
| 		fmt.Printf("Log handler created successfully\n") | ||||
| 		// Use the log handler for log routes | ||||
| 		admin.Get("/system/logs", logHandler.GetLogs) | ||||
| 		// Keep the fragment endpoint for backward compatibility | ||||
| 		// 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 | ||||
| 	admin.Get("/openrpc", h.getOpenRPCManager) | ||||
| 	admin.Get("/openrpc/vfs", h.getOpenRPCVFS) | ||||
| 	admin.Get("/openrpc/vfs/logs", h.getOpenRPCVFSLogs) | ||||
|  | ||||
| 	// Redirect root to admin | ||||
| 	app.Get("/", func(c *fiber.Ctx) error { | ||||
| 		return c.Redirect("/admin") | ||||
| 	}) | ||||
| } | ||||
|  | ||||
| // getDashboard renders the admin dashboard | ||||
| func (h *AdminHandler) getDashboard(c *fiber.Ctx) error { | ||||
| 	return c.Render("admin/index", fiber.Map{ | ||||
| 		"title": "Dashboard", | ||||
| 	}) | ||||
| } | ||||
|  | ||||
| // getSystemInfo renders the system info page | ||||
| func (h *AdminHandler) getSystemInfo(c *fiber.Ctx) error { | ||||
| 	// Initialize default values | ||||
| 	cpuInfo := "Unknown" | ||||
| 	memoryInfo := "Unknown" | ||||
| 	diskInfo := "Unknown" | ||||
| 	networkInfo := "Unknown" | ||||
| 	osInfo := "Unknown" | ||||
| 	uptimeInfo := "Unknown" | ||||
|  | ||||
| 	// Get hardware stats from the StatsManager | ||||
| 	var hardwareStats map[string]interface{} | ||||
| 	if h.statsManager != nil { | ||||
| 		hardwareStats = h.statsManager.GetHardwareStats() | ||||
| 	} else { | ||||
| 		// Fallback to direct function call if StatsManager is not available | ||||
| 		hardwareStats = stats.GetHardwareStats() | ||||
| 	} | ||||
|  | ||||
| 	// Extract the formatted strings - safely handle different return types | ||||
| 	if cpuVal, ok := hardwareStats["cpu"]; ok { | ||||
| 		switch v := cpuVal.(type) { | ||||
| 		case string: | ||||
| 			cpuInfo = v | ||||
| 		case map[string]interface{}: | ||||
| 			// Format the map into a string | ||||
| 			if model, ok := v["model"].(string); ok { | ||||
| 				usage := 0.0 | ||||
| 				if usagePercent, ok := v["usage_percent"].(float64); ok { | ||||
| 					usage = usagePercent | ||||
| 				} | ||||
| 				cpuInfo = fmt.Sprintf("%s (Usage: %.1f%%)", model, usage) | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if memVal, ok := hardwareStats["memory"]; ok { | ||||
| 		switch v := memVal.(type) { | ||||
| 		case string: | ||||
| 			memoryInfo = v | ||||
| 		case map[string]interface{}: | ||||
| 			// Format the map into a string | ||||
| 			total, used := 0.0, 0.0 | ||||
| 			if totalGB, ok := v["total_gb"].(float64); ok { | ||||
| 				total = totalGB | ||||
| 			} | ||||
| 			if usedGB, ok := v["used_gb"].(float64); ok { | ||||
| 				used = usedGB | ||||
| 			} | ||||
| 			usedPercent := 0.0 | ||||
| 			if percent, ok := v["used_percent"].(float64); ok { | ||||
| 				usedPercent = percent | ||||
| 			} | ||||
| 			memoryInfo = fmt.Sprintf("%.1f GB / %.1f GB (%.1f%% used)", used, total, usedPercent) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if diskVal, ok := hardwareStats["disk"]; ok { | ||||
| 		switch v := diskVal.(type) { | ||||
| 		case string: | ||||
| 			diskInfo = v | ||||
| 		case map[string]interface{}: | ||||
| 			// Format the map into a string | ||||
| 			total, used := 0.0, 0.0 | ||||
| 			if totalGB, ok := v["total_gb"].(float64); ok { | ||||
| 				total = totalGB | ||||
| 			} | ||||
| 			if usedGB, ok := v["used_gb"].(float64); ok { | ||||
| 				used = usedGB | ||||
| 			} | ||||
| 			usedPercent := 0.0 | ||||
| 			if percent, ok := v["used_percent"].(float64); ok { | ||||
| 				usedPercent = percent | ||||
| 			} | ||||
| 			diskInfo = fmt.Sprintf("%.1f GB / %.1f GB (%.1f%% used)", used, total, usedPercent) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if netVal, ok := hardwareStats["network"]; ok { | ||||
| 		switch v := netVal.(type) { | ||||
| 		case string: | ||||
| 			networkInfo = v | ||||
| 		case map[string]interface{}: | ||||
| 			// Format the map into a string | ||||
| 			var interfaces []string | ||||
| 			if ifaces, ok := v["interfaces"].([]interface{}); ok { | ||||
| 				for _, iface := range ifaces { | ||||
| 					if ifaceMap, ok := iface.(map[string]interface{}); ok { | ||||
| 						name := ifaceMap["name"].(string) | ||||
| 						ip := ifaceMap["ip"].(string) | ||||
| 						interfaces = append(interfaces, fmt.Sprintf("%s: %s", name, ip)) | ||||
| 					} | ||||
| 				} | ||||
| 				networkInfo = strings.Join(interfaces, ", ") | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	// Get OS info | ||||
| 	hostInfo, err := host.Info() | ||||
| 	if err == nil { | ||||
| 		osInfo = fmt.Sprintf("%s %s (%s)", hostInfo.Platform, hostInfo.PlatformVersion, hostInfo.KernelVersion) | ||||
| 	} | ||||
|  | ||||
| 	// Get uptime | ||||
| 	if h.uptimeProvider != nil { | ||||
| 		uptimeInfo = h.uptimeProvider.GetUptime() | ||||
| 	} | ||||
|  | ||||
| 	// Render the template with the system info | ||||
| 	return c.Render("admin/system/info", fiber.Map{ | ||||
| 		"title":       "System Information", | ||||
| 		"cpuInfo":     cpuInfo, | ||||
| 		"memoryInfo":  memoryInfo, | ||||
| 		"diskInfo":    diskInfo, | ||||
| 		"networkInfo": networkInfo, | ||||
| 		"osInfo":      osInfo, | ||||
| 		"uptimeInfo":  uptimeInfo, | ||||
| 	}) | ||||
| } | ||||
|  | ||||
| // getSystemLogs renders the system logs page | ||||
| func (h *AdminHandler) getSystemLogs(c *fiber.Ctx) error { | ||||
| 	return c.Render("admin/system/logs", fiber.Map{ | ||||
| 		"title": "System Logs", | ||||
| 	}) | ||||
| } | ||||
|  | ||||
| // getSystemLogsTest renders the test logs page | ||||
| func (h *AdminHandler) getSystemLogsTest(c *fiber.Ctx) error { | ||||
| 	return c.Render("admin/system/logs_test", fiber.Map{ | ||||
| 		"title": "Test Logs", | ||||
| 	}) | ||||
| } | ||||
|  | ||||
| // getSystemSettings renders the system settings page | ||||
| func (h *AdminHandler) getSystemSettings(c *fiber.Ctx) error { | ||||
| 	// Get system settings | ||||
| 	// This is a placeholder - in a real app, you would fetch settings from a database or config file | ||||
| 	settings := map[string]interface{}{ | ||||
| 		"logLevel":        "info", | ||||
| 		"enableDebugMode": false, | ||||
| 		"dataDirectory":   "/var/lib/heroagent", | ||||
| 		"maxLogSize":      "100MB", | ||||
| 	} | ||||
|  | ||||
| 	return c.Render("admin/system/settings", fiber.Map{ | ||||
| 		"title":    "System Settings", | ||||
| 		"settings": settings, | ||||
| 	}) | ||||
| } | ||||
|  | ||||
| // getHardwareStats returns only the hardware stats for Unpoly polling | ||||
| func (h *AdminHandler) getHardwareStats(c *fiber.Ctx) error { | ||||
| 	// Initialize default values | ||||
| 	cpuInfo := "Unknown" | ||||
| 	memoryInfo := "Unknown" | ||||
| 	diskInfo := "Unknown" | ||||
| 	networkInfo := "Unknown" | ||||
|  | ||||
| 	// Get hardware stats from the StatsManager | ||||
| 	var hardwareStats map[string]interface{} | ||||
| 	if h.statsManager != nil { | ||||
| 		hardwareStats = h.statsManager.GetHardwareStats() | ||||
| 	} else { | ||||
| 		// Fallback to direct function call if StatsManager is not available | ||||
| 		hardwareStats = stats.GetHardwareStats() | ||||
| 	} | ||||
|  | ||||
| 	// Extract the formatted strings - safely handle different return types | ||||
| 	if cpuVal, ok := hardwareStats["cpu"]; ok { | ||||
| 		switch v := cpuVal.(type) { | ||||
| 		case string: | ||||
| 			cpuInfo = v | ||||
| 		case map[string]interface{}: | ||||
| 			// Format the map into a string | ||||
| 			if model, ok := v["model"].(string); ok { | ||||
| 				cpuInfo = model | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if memVal, ok := hardwareStats["memory"]; ok { | ||||
| 		switch v := memVal.(type) { | ||||
| 		case string: | ||||
| 			memoryInfo = v | ||||
| 		case map[string]interface{}: | ||||
| 			// Format the map into a string | ||||
| 			total, used := 0.0, 0.0 | ||||
| 			if totalGB, ok := v["total_gb"].(float64); ok { | ||||
| 				total = totalGB | ||||
| 			} | ||||
| 			if usedGB, ok := v["used_gb"].(float64); ok { | ||||
| 				used = usedGB | ||||
| 			} | ||||
| 			memoryInfo = fmt.Sprintf("%.1f GB / %.1f GB", used, total) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if diskVal, ok := hardwareStats["disk"]; ok { | ||||
| 		switch v := diskVal.(type) { | ||||
| 		case string: | ||||
| 			diskInfo = v | ||||
| 		case map[string]interface{}: | ||||
| 			// Format the map into a string | ||||
| 			total, used := 0.0, 0.0 | ||||
| 			if totalGB, ok := v["total_gb"].(float64); ok { | ||||
| 				total = totalGB | ||||
| 			} | ||||
| 			if usedGB, ok := v["used_gb"].(float64); ok { | ||||
| 				used = usedGB | ||||
| 			} | ||||
| 			diskInfo = fmt.Sprintf("%.1f GB / %.1f GB", used, total) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if netVal, ok := hardwareStats["network"]; ok { | ||||
| 		switch v := netVal.(type) { | ||||
| 		case string: | ||||
| 			networkInfo = v | ||||
| 		case map[string]interface{}: | ||||
| 			// Format the map into a string | ||||
| 			var interfaces []string | ||||
| 			if ifaces, ok := v["interfaces"].([]interface{}); ok { | ||||
| 				for _, iface := range ifaces { | ||||
| 					if ifaceMap, ok := iface.(map[string]interface{}); ok { | ||||
| 						name := ifaceMap["name"].(string) | ||||
| 						ip := ifaceMap["ip"].(string) | ||||
| 						interfaces = append(interfaces, fmt.Sprintf("%s: %s", name, ip)) | ||||
| 					} | ||||
| 				} | ||||
| 				networkInfo = strings.Join(interfaces, ", ") | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	// Format for display | ||||
| 	cpuUsage := "0.0%" | ||||
| 	memUsage := "0.0%" | ||||
| 	diskUsage := "0.0%" | ||||
|  | ||||
| 	// Safely extract usage percentages | ||||
| 	if cpuVal, ok := hardwareStats["cpu"].(map[string]interface{}); ok { | ||||
| 		if usagePercent, ok := cpuVal["usage_percent"].(float64); ok { | ||||
| 			cpuUsage = fmt.Sprintf("%.1f%%", usagePercent) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if memVal, ok := hardwareStats["memory"].(map[string]interface{}); ok { | ||||
| 		if usedPercent, ok := memVal["used_percent"].(float64); ok { | ||||
| 			memUsage = fmt.Sprintf("%.1f%%", usedPercent) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if diskVal, ok := hardwareStats["disk"].(map[string]interface{}); ok { | ||||
| 		if usedPercent, ok := diskVal["used_percent"].(float64); ok { | ||||
| 			diskUsage = fmt.Sprintf("%.1f%%", usedPercent) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	// Render only the hardware stats fragment | ||||
| 	return c.Render("admin/system/hardware_stats_fragment", fiber.Map{ | ||||
| 		"cpuInfo":     cpuInfo, | ||||
| 		"memoryInfo":  memoryInfo, | ||||
| 		"diskInfo":    diskInfo, | ||||
| 		"networkInfo": networkInfo, | ||||
| 		"cpuUsage":    cpuUsage, | ||||
| 		"memUsage":    memUsage, | ||||
| 		"diskUsage":   diskUsage, | ||||
| 	}) | ||||
| } | ||||
|  | ||||
| // getProcesses has been moved to the handlers package | ||||
| // See handlers.ProcessHandler.GetProcesses | ||||
|  | ||||
| // getOpenRPCManager renders the OpenRPC Manager view page | ||||
| func (h *AdminHandler) getOpenRPCManager(c *fiber.Ctx) error { | ||||
| 	return c.Render("admin/openrpc/index", fiber.Map{ | ||||
| 		"title": "OpenRPC Manager", | ||||
| 	}) | ||||
| } | ||||
|  | ||||
| // getOpenRPCVFS renders the OpenRPC VFS view page | ||||
| func (h *AdminHandler) getOpenRPCVFS(c *fiber.Ctx) error { | ||||
| 	return c.Render("admin/openrpc/vfs", fiber.Map{ | ||||
| 		"title": "VFS OpenRPC Interface", | ||||
| 	}) | ||||
| } | ||||
|  | ||||
| // getOpenRPCVFSLogs renders the OpenRPC logs content for Unpoly or direct access | ||||
| func (h *AdminHandler) getOpenRPCVFSLogs(c *fiber.Ctx) error { | ||||
| 	// Get query parameters | ||||
| 	method := c.Query("method", "") | ||||
| 	params := c.Query("params", "") | ||||
|  | ||||
| 	// Define available methods and their display names | ||||
| 	methods := []string{ | ||||
| 		"vfs_ls", | ||||
| 		"vfs_read", | ||||
| 		"vfs_write", | ||||
| 		"vfs_mkdir", | ||||
| 		"vfs_rm", | ||||
| 		"vfs_mv", | ||||
| 		"vfs_cp", | ||||
| 		"vfs_exists", | ||||
| 		"vfs_isdir", | ||||
| 		"vfs_isfile", | ||||
| 	} | ||||
|  | ||||
| 	methodDisplayNames := map[string]string{ | ||||
| 		"vfs_ls":     "List Directory", | ||||
| 		"vfs_read":   "Read File", | ||||
| 		"vfs_write":  "Write File", | ||||
| 		"vfs_mkdir":  "Create Directory", | ||||
| 		"vfs_rm":     "Remove File/Directory", | ||||
| 		"vfs_mv":     "Move/Rename", | ||||
| 		"vfs_cp":     "Copy", | ||||
| 		"vfs_exists": "Check Exists", | ||||
| 		"vfs_isdir":  "Is Directory", | ||||
| 		"vfs_isfile": "Is File", | ||||
| 	} | ||||
|  | ||||
| 	// Generate method options HTML | ||||
| 	methodOptions := generateMethodOptions(methods, methodDisplayNames) | ||||
|  | ||||
| 	// Initialize variables | ||||
| 	var requestJSON, responseJSON, responseTime string | ||||
| 	var hasResponse bool | ||||
|  | ||||
| 	// If a method is selected, make the OpenRPC call | ||||
| 	if method != "" { | ||||
| 		// Prepare the request | ||||
| 		requestJSON = fmt.Sprintf(`{ | ||||
| 			"jsonrpc": "2.0", | ||||
| 			"method": "%s", | ||||
| 			"params": %s, | ||||
| 			"id": 1 | ||||
| 		}`, method, params) | ||||
|  | ||||
| 		// In a real implementation, we would make the actual OpenRPC call here | ||||
| 		// For now, we'll just simulate a response | ||||
|  | ||||
| 		// Simulate response time (would be real in production) | ||||
| 		time.Sleep(100 * time.Millisecond) | ||||
| 		responseTime = "100ms" | ||||
|  | ||||
| 		// Simulate a response based on the method | ||||
| 		switch method { | ||||
| 		case "vfs_ls": | ||||
| 			responseJSON = `{ | ||||
| 				"jsonrpc": "2.0", | ||||
| 				"result": [ | ||||
| 					{"name": "file1.txt", "size": 1024, "isDir": false, "modTime": "2023-01-01T12:00:00Z"}, | ||||
| 					{"name": "dir1", "size": 0, "isDir": true, "modTime": "2023-01-01T12:00:00Z"} | ||||
| 				], | ||||
| 				"id": 1 | ||||
| 			}` | ||||
| 		case "vfs_read": | ||||
| 			responseJSON = `{ | ||||
| 				"jsonrpc": "2.0", | ||||
| 				"result": "File content would be here", | ||||
| 				"id": 1 | ||||
| 			}` | ||||
| 		default: | ||||
| 			responseJSON = `{ | ||||
| 				"jsonrpc": "2.0", | ||||
| 				"result": "Operation completed successfully", | ||||
| 				"id": 1 | ||||
| 			}` | ||||
| 		} | ||||
|  | ||||
| 		hasResponse = true | ||||
| 	} | ||||
|  | ||||
| 	// Determine if this is an Unpoly request | ||||
| 	isUnpoly := c.Get("X-Up-Target") != "" | ||||
|  | ||||
| 	// If it's an Unpoly request, render just the logs fragment | ||||
| 	if isUnpoly { | ||||
| 		return c.Render("admin/openrpc/vfs_logs", fiber.Map{ | ||||
| 			"methodOptions":  methodOptions, | ||||
| 			"selectedMethod": method, | ||||
| 			"params":         params, | ||||
| 			"requestJSON":    requestJSON, | ||||
| 			"responseJSON":   responseJSON, | ||||
| 			"responseTime":   responseTime, | ||||
| 			"hasResponse":    hasResponse, | ||||
| 		}) | ||||
| 	} | ||||
|  | ||||
| 	// Otherwise render the full page | ||||
| 	return c.Render("admin/openrpc/vfs_overview", fiber.Map{ | ||||
| 		"title":          "VFS OpenRPC Logs", | ||||
| 		"methodOptions":  methodOptions, | ||||
| 		"selectedMethod": method, | ||||
| 		"params":         params, | ||||
| 		"requestJSON":    requestJSON, | ||||
| 		"responseJSON":   responseJSON, | ||||
| 		"responseTime":   responseTime, | ||||
| 		"hasResponse":    hasResponse, | ||||
| 	}) | ||||
| } | ||||
|  | ||||
| // generateMethodOptions generates HTML option tags for method dropdown | ||||
| func generateMethodOptions(methods []string, methodDisplayNames map[string]string) string { | ||||
| 	var options []string | ||||
| 	for _, method := range methods { | ||||
| 		displayName, ok := methodDisplayNames[method] | ||||
| 		if !ok { | ||||
| 			displayName = method | ||||
| 		} | ||||
| 		options = append(options, fmt.Sprintf(`<option value="%s">%s</option>`, method, displayName)) | ||||
| 	} | ||||
| 	return strings.Join(options, "\n") | ||||
| } | ||||
|  | ||||
| // Note: getProcessesData has been consolidated in the API routes file | ||||
| // to avoid duplication and ensure consistent behavior | ||||
							
								
								
									
										187
									
								
								_pkg2_dont_use/heroagent/pages/jobs.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										187
									
								
								_pkg2_dont_use/heroagent/pages/jobs.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,187 @@ | ||||
| package pages | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"log" | ||||
|  | ||||
| 	"git.ourworld.tf/herocode/heroagent/pkg/herojobs" | ||||
| 	"github.com/gofiber/fiber/v2" | ||||
| ) | ||||
|  | ||||
| // JobDisplayInfo represents information about a job for display purposes | ||||
| type JobDisplayInfo struct { | ||||
| 	JobID         string `json:"jobid"` | ||||
| 	CircleID      string `json:"circleid"` | ||||
| 	Topic         string `json:"topic"` | ||||
| 	Status        string `json:"status"` | ||||
| 	SessionKey    string `json:"sessionkey"` | ||||
| 	Params        string `json:"params"` | ||||
| 	ParamsType    string `json:"paramstype"` | ||||
| 	Result        string `json:"result"` | ||||
| 	Error         string `json:"error"` | ||||
| 	TimeScheduled int64  `json:"time_scheduled"` | ||||
| 	TimeStart     int64  `json:"time_start"` | ||||
| 	TimeEnd       int64  `json:"time_end"` | ||||
| 	Timeout       int64  `json:"timeout"` | ||||
| } | ||||
|  | ||||
| // JobHandler handles job-related page routes | ||||
| type JobHandler struct { | ||||
| 	client *herojobs.RedisClient | ||||
| 	logger *log.Logger | ||||
| } | ||||
|  | ||||
| // NewJobHandler creates a new job handler with the provided socket path | ||||
| 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 Redis client: %w", err) | ||||
| 	} | ||||
|  | ||||
| 	return &JobHandler{ | ||||
| 		client: client, | ||||
| 		logger: logger, | ||||
| 	}, nil | ||||
| } | ||||
|  | ||||
| // RegisterRoutes registers job page routes | ||||
| func (h *JobHandler) RegisterRoutes(app *fiber.App) { | ||||
| 	// Register routes for /jobs | ||||
| 	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) | ||||
| 	adminJobs.Get("/list", h.getJobsList) | ||||
| } | ||||
|  | ||||
| // getJobsPage renders the jobs page | ||||
| func (h *JobHandler) getJobsPage(c *fiber.Ctx) error { | ||||
| 	// 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 will be empty for now | ||||
| 		"error":   "", | ||||
| 	}) | ||||
| } | ||||
|  | ||||
| // getJobsList returns the jobs list fragment for AJAX updates | ||||
| func (h *JobHandler) getJobsList(c *fiber.Ctx) error { | ||||
| 	// Get parameters from query | ||||
| 	circleID := c.Query("circleid", "") | ||||
| 	topic := c.Query("topic", "") | ||||
|  | ||||
| 	// Get jobs | ||||
| 	jobs, err := h.getJobsData(circleID, topic) | ||||
| 	if err != nil { | ||||
| 		h.logger.Printf("Error getting jobs: %v", err) | ||||
| 		// Return the error in the template | ||||
| 		return c.Render("admin/jobs_list_fragment", fiber.Map{ | ||||
| 			"error": fmt.Sprintf("Failed to get jobs: %v", err), | ||||
| 			"jobs":  []JobDisplayInfo{}, | ||||
| 		}) | ||||
| 	} | ||||
|  | ||||
| 	// Render only the jobs fragment | ||||
| 	return c.Render("admin/jobs_list_fragment", fiber.Map{ | ||||
| 		"jobs": jobs, | ||||
| 	}) | ||||
| } | ||||
|  | ||||
| // getJobsData gets job data from the HeroJobs server | ||||
| func (h *JobHandler) getJobsData(circleID, topic string) ([]JobDisplayInfo, error) { | ||||
| 	// 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 { | ||||
| 				jobIDs, err := h.client.ListJobs(circle, t) | ||||
| 				if err != nil { | ||||
| 					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:         fmt.Sprintf("%d", job.JobID), | ||||
| 						CircleID:      job.CircleID, | ||||
| 						Topic:         job.Topic, | ||||
| 						Status:        string(job.Status), | ||||
| 						SessionKey:    job.SessionKey, | ||||
| 						Params:        job.Params, | ||||
| 						ParamsType:    string(job.ParamsType), | ||||
| 						Result:        job.Result, | ||||
| 						Error:         job.Error, | ||||
| 						TimeScheduled: job.TimeScheduled, | ||||
| 						TimeStart:     job.TimeStart, | ||||
| 						TimeEnd:       job.TimeEnd, | ||||
| 						Timeout:       job.Timeout, | ||||
| 					}) | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		return allJobs, nil | ||||
| 	} else if circleID == "" || topic == "" { | ||||
| 		// If only one of the parameters is provided, we can't list jobs | ||||
| 		return []JobDisplayInfo{}, nil | ||||
| 	} | ||||
|  | ||||
| 	// List jobs | ||||
| 	jobIDs, err := h.client.ListJobs(circleID, topic) | ||||
| 	if err != nil { | ||||
| 		return nil, fmt.Errorf("failed to list jobs: %w", err) | ||||
| 	} | ||||
|  | ||||
| 	// Get details for each job | ||||
| 	jobsList := make([]JobDisplayInfo, 0, len(jobIDs)) | ||||
| 	for _, jobID := range jobIDs { | ||||
| 		job, err := h.client.GetJob(jobID) | ||||
| 		if err != nil { | ||||
| 			h.logger.Printf("Error getting job %s: %v", jobID, err) | ||||
| 			continue | ||||
| 		} | ||||
|  | ||||
| 		jobInfo := JobDisplayInfo{ | ||||
| 			JobID:         fmt.Sprintf("%d", job.JobID), | ||||
| 			CircleID:      job.CircleID, | ||||
| 			Topic:         job.Topic, | ||||
| 			Status:        string(job.Status), | ||||
| 			SessionKey:    job.SessionKey, | ||||
| 			Params:        job.Params, | ||||
| 			ParamsType:    string(job.ParamsType), | ||||
| 			Result:        job.Result, | ||||
| 			Error:         job.Error, | ||||
| 			TimeScheduled: job.TimeScheduled, | ||||
| 			TimeStart:     job.TimeStart, | ||||
| 			TimeEnd:       job.TimeEnd, | ||||
| 			Timeout:       job.Timeout, | ||||
| 		} | ||||
| 		jobsList = append(jobsList, jobInfo) | ||||
| 	} | ||||
|  | ||||
| 	return jobsList, nil | ||||
| } | ||||
							
								
								
									
										111
									
								
								_pkg2_dont_use/heroagent/pages/services.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										111
									
								
								_pkg2_dont_use/heroagent/pages/services.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,111 @@ | ||||
| package pages | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"log" | ||||
|  | ||||
| 	"git.ourworld.tf/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 | ||||
| } | ||||
|  | ||||
| // 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 pages.ServiceHandler with socket path: %s and secret: %s\n", socketPath, secret) | ||||
| 	return &ServiceHandler{ | ||||
| 		client: openrpc.NewClient(socketPath, secret), | ||||
| 		logger: logger, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // RegisterRoutes registers service page routes | ||||
| func (h *ServiceHandler) RegisterRoutes(app *fiber.App) { | ||||
| 	services := app.Group("/services") | ||||
|  | ||||
| 	// Page routes | ||||
| 	services.Get("/", h.getServicesPage) | ||||
| 	services.Get("/data", h.getServicesData) | ||||
| } | ||||
|  | ||||
| // 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 we can connect to the process manager | ||||
| 	var warning string | ||||
| 	_, err := h.client.ListProcesses("json") | ||||
| 	if err != nil { | ||||
| 		warning = "Could not connect to process manager: " + err.Error() | ||||
| 		h.logger.Printf("Warning: %s", warning) | ||||
| 	} | ||||
|  | ||||
| 	return c.Render("admin/services", fiber.Map{ | ||||
| 		"title":     "Services", | ||||
| 		"processes": processes, | ||||
| 		"warning":   warning, | ||||
| 	}) | ||||
| } | ||||
|  | ||||
| // getServicesData returns only the services fragment for AJAX updates | ||||
| func (h *ServiceHandler) getServicesData(c *fiber.Ctx) error { | ||||
| 	// Get processes | ||||
| 	processes, _ := h.getProcessList() | ||||
|  | ||||
| 	// Render only the services fragment | ||||
| 	return c.Render("admin/services_fragment", fiber.Map{ | ||||
| 		"processes": processes, | ||||
| 	}) | ||||
| } | ||||
|  | ||||
| // getProcessList gets a list of processes from the process manager | ||||
| func (h *ServiceHandler) getProcessList() ([]ProcessDisplayInfo, error) { | ||||
| 	// Debug: Log the function entry | ||||
| 	h.logger.Printf("Entering getProcessList() function") | ||||
| 	fmt.Printf("DEBUG: 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 | ||||
| 	listResult, ok := result.([]interface{}) | ||||
| 	if !ok { | ||||
| 		h.logger.Printf("Error: unexpected result type from ListProcesses") | ||||
| 		return nil, fmt.Errorf("unexpected result type from ListProcesses") | ||||
| 	} | ||||
|  | ||||
| 	// Convert to display info format | ||||
| 	displayInfoList := make([]ProcessDisplayInfo, 0, len(listResult)) | ||||
| 	for _, item := range listResult { | ||||
| 		procMap, ok := item.(map[string]interface{}) | ||||
| 		if !ok { | ||||
| 			continue | ||||
| 		} | ||||
|  | ||||
| 		// Create a ProcessDisplayInfo from the map | ||||
| 		displayInfo := ProcessDisplayInfo{ | ||||
| 			ID:        fmt.Sprintf("%v", procMap["pid"]), | ||||
| 			Name:      fmt.Sprintf("%v", procMap["name"]), | ||||
| 			Status:    fmt.Sprintf("%v", procMap["status"]), | ||||
| 			Uptime:    fmt.Sprintf("%v", procMap["uptime"]), | ||||
| 			StartTime: fmt.Sprintf("%v", procMap["start_time"]), | ||||
| 			CPU:       fmt.Sprintf("%v%%", procMap["cpu"]), | ||||
| 			Memory:    fmt.Sprintf("%v MB", procMap["memory"]), | ||||
| 		} | ||||
| 		displayInfoList = append(displayInfoList, displayInfo) | ||||
| 	} | ||||
|  | ||||
| 	// Debug: Log the number of processes | ||||
| 	h.logger.Printf("Found %d processes", len(displayInfoList)) | ||||
|  | ||||
| 	return displayInfoList, nil | ||||
| } | ||||
							
								
								
									
										54
									
								
								_pkg2_dont_use/heroagent/pages/types.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										54
									
								
								_pkg2_dont_use/heroagent/pages/types.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,54 @@ | ||||
| package pages | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"time" | ||||
|  | ||||
| 	"git.ourworld.tf/herocode/heroagent/pkg/processmanager" | ||||
| ) | ||||
|  | ||||
| // 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), | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // 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) | ||||
| 	} | ||||
| } | ||||
		Reference in New Issue
	
	Block a user