This repository has been archived on 2025-08-04. You can view files and clone it, but cannot push or open issues or pull requests.
heroagent_go_old/_pkg2_dont_use/heroagent/pages/admin.go

542 lines
15 KiB
Go

package pages
import (
"fmt"
"os"
"path/filepath"
"strings"
"time"
"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"
)
// 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