...
This commit is contained in:
213
pkg/servers/ui/DESIGN_PLAN_UI.md
Normal file
213
pkg/servers/ui/DESIGN_PLAN_UI.md
Normal file
@@ -0,0 +1,213 @@
|
||||
# Project Plan: Bootstrap UI with Fiber and Jet
|
||||
|
||||
**Goal:** Develop a new UI module using Go (Fiber framework), Jet templates, and Bootstrap 5, following an MVC pattern. The UI will include a dashboard and a process manager page.
|
||||
|
||||
**Location:** `pkg/servers/ui`
|
||||
|
||||
---
|
||||
|
||||
## 1. Directory Structure (MVC)
|
||||
|
||||
We'll establish a clear MVC structure within `pkg/servers/ui`:
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
A[pkg/servers/ui] --> B(app.go);
|
||||
A --> C(controllers);
|
||||
A --> M(models);
|
||||
A --> V(views);
|
||||
A --> S(static);
|
||||
A --> R(routes);
|
||||
|
||||
C --> C1(auth_controller.go);
|
||||
C --> C2(dashboard_controller.go);
|
||||
C --> C3(process_controller.go);
|
||||
|
||||
M --> M1(process_model.go);
|
||||
M --> M2(user_model.go);
|
||||
|
||||
V --> VL(layouts);
|
||||
V --> VP(pages);
|
||||
V --> VC(components);
|
||||
|
||||
VL --> VLL(base.jet);
|
||||
|
||||
VP --> VPD(dashboard.jet);
|
||||
VP --> VPP(process_manager.jet);
|
||||
VP --> VPL(login.jet);
|
||||
|
||||
VC --> VCN(navbar.jet);
|
||||
VC --> VCS(sidebar.jet);
|
||||
|
||||
S --> SCSS(css);
|
||||
S --> SJS(js);
|
||||
S --> SIMG(img);
|
||||
|
||||
SCSS --> custom.css; // For any custom styles
|
||||
|
||||
SJS --> custom.js; // For any custom JavaScript
|
||||
|
||||
R --> R1(router.go);
|
||||
```
|
||||
|
||||
**Detailed Breakdown:**
|
||||
|
||||
* **`pkg/servers/ui/app.go`:** Main application setup for this UI module. Initializes Fiber, Jet, routes, and middleware.
|
||||
* **`pkg/servers/ui/controllers/`**: Handles incoming requests, interacts with models, and selects views.
|
||||
* `auth_controller.go`: Handles login/logout (stubs for now).
|
||||
* `dashboard_controller.go`: Handles the dashboard page.
|
||||
* `process_controller.go`: Handles the process manager page.
|
||||
* **`pkg/servers/ui/models/`**: Business logic and data interaction.
|
||||
* `process_model.go`: Interacts with `pkg/system/processmanager` to fetch and manage process data.
|
||||
* `user_model.go`: (Placeholder for basic auth stubs).
|
||||
* **`pkg/servers/ui/views/`**: Jet templates.
|
||||
* **`layouts/`**: Base layout templates.
|
||||
* `base.jet`: Main site layout (includes Bootstrap, navbar, sidebar, main content area).
|
||||
* **`pages/`**: Specific page templates.
|
||||
* `dashboard.jet`: Dashboard content.
|
||||
* `process_manager.jet`: Process manager table and controls.
|
||||
* `login.jet`: (Placeholder login page).
|
||||
* **`components/`**: Reusable UI components.
|
||||
* `navbar.jet`: Top navigation bar.
|
||||
* `sidebar.jet`: Left tree menu.
|
||||
* **`pkg/servers/ui/static/`**: Local static assets.
|
||||
* **`css/`**: Custom CSS files.
|
||||
* `custom.css`
|
||||
* **`js/`**: Custom JavaScript files.
|
||||
* `custom.js`
|
||||
* **`img/`**: Image assets.
|
||||
* **`pkg/servers/ui/routes/router.go`:** Defines URL routes and maps them to controller actions.
|
||||
|
||||
---
|
||||
|
||||
## 2. Core UI Components
|
||||
|
||||
* **Base Layout (`views/layouts/base.jet`):**
|
||||
* HTML5 boilerplate.
|
||||
* Include Bootstrap 5 CSS from CDN:
|
||||
```html
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.6/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-4Q6Gf2aSP4eDXB8Miphtr37CMZZQ5oXLH2yaXMJ2w8e2ZtHTl7GptT4jmndRuHDT" crossorigin="anonymous">
|
||||
```
|
||||
* Include Bootstrap 5 JS Bundle from CDN (typically placed before the closing `</body>` tag):
|
||||
```html
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.6/dist/js/bootstrap.bundle.min.js" integrity="sha384-j1CDi7MgGQ12Z7Qab0qlWQ/Qqz24Gc6BM0thvEMVjHnfYGF0rmFCozFSxQBxwHKO" crossorigin="anonymous"></script>
|
||||
```
|
||||
* Include local custom CSS (e.g., `/static/css/custom.css`).
|
||||
* Include local custom JS (e.g., `/static/js/custom.js`).
|
||||
* Structure:
|
||||
* Navbar (include `components/navbar.jet`)
|
||||
* Main container (Bootstrap `container-fluid` or similar)
|
||||
* Sidebar (include `components/sidebar.jet`)
|
||||
* Content area (where page-specific content will be injected)
|
||||
* **Navbar (`views/components/navbar.jet`):**
|
||||
* Bootstrap Navbar component.
|
||||
* Site title/logo.
|
||||
* Login/Logout buttons (stubs, basic links for now).
|
||||
* **Sidebar/Tree Menu (`views/components/sidebar.jet`):**
|
||||
* Bootstrap navigation component (e.g., Navs, List group).
|
||||
* Links:
|
||||
* Dashboard
|
||||
* Process Manager
|
||||
|
||||
---
|
||||
|
||||
## 3. Pages
|
||||
|
||||
* **Dashboard Page:**
|
||||
* **Controller (`controllers/dashboard_controller.go`):**
|
||||
* `ShowDashboard()`: Renders the dashboard page.
|
||||
* **View (`views/pages/dashboard.jet`):**
|
||||
* Extends `layouts/base.jet`.
|
||||
* Simple placeholder content for now (e.g., "Welcome to the Dashboard!").
|
||||
* **Process Manager Page:**
|
||||
* **Model (`models/process_model.go`):**
|
||||
* `GetProcesses()`: Function to call `pkg/system/processmanager` to get a list of running processes. Will need to define a struct for process information (PID, Name, CPU, Memory).
|
||||
* `KillProcess(pid string)`: Function to call `pkg/system/processmanager` to terminate a process.
|
||||
* **Controller (`controllers/process_controller.go`):**
|
||||
* `ShowProcessManager()`:
|
||||
* Calls `models.GetProcesses()`.
|
||||
* Passes process data to the view.
|
||||
* Renders `views/pages/process_manager.jet`.
|
||||
* `HandleKillProcess()`: (Handles POST request to kill a process)
|
||||
* Extracts PID from request.
|
||||
* Calls `models.KillProcess(pid)`.
|
||||
* Redirects back to the process manager page or returns a status.
|
||||
* **View (`views/pages/process_manager.jet`):**
|
||||
* Extends `layouts/base.jet`.
|
||||
* Displays processes in a Bootstrap table:
|
||||
* Columns: PID, Name, CPU, Memory, Actions.
|
||||
* Actions column: "Kill" button for each process (linking to `HandleKillProcess` or using JS for an AJAX call).
|
||||
|
||||
---
|
||||
|
||||
## 4. Fiber & Jet Integration (`app.go` and `routes/router.go`)
|
||||
|
||||
* **`pkg/servers/ui/app.go`:**
|
||||
* `NewApp()` function:
|
||||
* Initialize Fiber app: `fiber.New()`.
|
||||
* Initialize Jet template engine:
|
||||
* `jet.NewSet(jet.NewOSFileSystemLoader("./pkg/servers/ui/views"), jet.InDevelopmentMode())` (or adjust path as needed).
|
||||
* Pass Jet views to Fiber: `app.Settings.Views = views`.
|
||||
* Setup static file serving: `app.Static("/static", "./pkg/servers/ui/static")`.
|
||||
* Setup routes: Call a function from `routes/router.go`.
|
||||
* Return the Fiber app instance.
|
||||
* This `app.go` can then be imported and run from your main application entry point (e.g., in `cmd/heroagent/main.go`).
|
||||
* **`pkg/servers/ui/routes/router.go`:**
|
||||
* `SetupRoutes(app *fiber.App, processController *controllers.ProcessController, ...)` function:
|
||||
* `app.Get("/", dashboardController.ShowDashboard)`
|
||||
* `app.Get("/processes", processController.ShowProcessManager)`
|
||||
* `app.Post("/processes/kill/:pid", processController.HandleKillProcess)` (or similar for kill action)
|
||||
* `app.Get("/login", authController.ShowLoginPage)` (stub)
|
||||
* `app.Post("/login", authController.HandleLogin)` (stub)
|
||||
* `app.Get("/logout", authController.HandleLogout)` (stub)
|
||||
|
||||
---
|
||||
|
||||
## 5. Authentication Stubs
|
||||
|
||||
* **`controllers/auth_controller.go`:**
|
||||
* `ShowLoginPage()`: Renders a simple login form.
|
||||
* `HandleLogin()`: Placeholder logic (e.g., always "logs in" or checks a hardcoded credential). Sets a dummy session/cookie.
|
||||
* `HandleLogout()`: Placeholder logic. Clears dummy session/cookie.
|
||||
* **`views/pages/login.jet`:**
|
||||
* Simple Bootstrap form for username/password.
|
||||
|
||||
---
|
||||
|
||||
## 6. Dependencies (go.mod)
|
||||
|
||||
Ensure these are added to your `go.mod` file:
|
||||
* `github.com/gofiber/fiber/v2`
|
||||
* `github.com/CloudyKit/jet/v6`
|
||||
|
||||
---
|
||||
|
||||
## Request Flow Example: Process Manager Page
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant User
|
||||
participant Browser
|
||||
participant FiberApp [Fiber App (pkg/servers/ui/app.go)]
|
||||
participant Router [routes/router.go]
|
||||
participant ProcessCtrl [controllers/process_controller.go]
|
||||
participant ProcessMdl [models/process_model.go]
|
||||
participant SysProcMgr [pkg/system/processmanager]
|
||||
participant JetEngine [CloudyKit/jet/v6]
|
||||
participant View [views/pages/process_manager.jet]
|
||||
|
||||
User->>Browser: Navigates to /processes
|
||||
Browser->>FiberApp: GET /processes
|
||||
FiberApp->>Router: Route request
|
||||
Router->>ProcessCtrl: Calls ShowProcessManager()
|
||||
ProcessCtrl->>ProcessMdl: Calls GetProcesses()
|
||||
ProcessMdl->>SysProcMgr: Fetches process list
|
||||
SysProcMgr-->>ProcessMdl: Returns process data
|
||||
ProcessMdl-->>ProcessCtrl: Returns process data
|
||||
ProcessCtrl->>JetEngine: Renders process_manager.jet with data
|
||||
JetEngine->>View: Populates template
|
||||
View-->>JetEngine: Rendered HTML
|
||||
JetEngine-->>ProcessCtrl: Rendered HTML
|
||||
ProcessCtrl-->>FiberApp: Returns HTML response
|
||||
FiberApp-->>Browser: Sends HTML response
|
||||
Browser->>User: Displays Process Manager page
|
43
pkg/servers/ui/app.go
Normal file
43
pkg/servers/ui/app.go
Normal file
@@ -0,0 +1,43 @@
|
||||
package ui
|
||||
|
||||
import (
|
||||
"git.ourworld.tf/herocode/heroagent/pkg/servers/ui/routes" // Import the routes package
|
||||
"github.com/gofiber/fiber/v2"
|
||||
jetadapter "github.com/gofiber/template/jet/v2" // Aliased for clarity
|
||||
)
|
||||
|
||||
// AppConfig holds the configuration for the UI application.
|
||||
type AppConfig struct {
|
||||
// Any specific configurations can be added here later
|
||||
}
|
||||
|
||||
// NewApp creates and configures a new Fiber application for the UI.
|
||||
func NewApp(config AppConfig) *fiber.App {
|
||||
// Initialize Jet template engine
|
||||
// Using OSFileSystemLoader to load templates from the filesystem.
|
||||
// The path is relative to where the application is run.
|
||||
// For development, InDevelopmentMode can be true to reload templates on each request.
|
||||
engine := jetadapter.New("./pkg/servers/ui/views", ".jet")
|
||||
|
||||
// Enable template reloading for development.
|
||||
// Set to false or remove this line for production.
|
||||
engine.Reload(true)
|
||||
|
||||
// If you need to add custom functions or global variables to Jet:
|
||||
// engine.AddFunc("myCustomFunc", func(arg jet.Arguments) reflect.Value { ... })
|
||||
// engine.AddGlobal("myGlobalVar", "someValue")
|
||||
|
||||
// Create a new Fiber app with the configured Jet engine
|
||||
app := fiber.New(fiber.Config{
|
||||
Views: engine,
|
||||
})
|
||||
|
||||
// Setup static file serving
|
||||
// Files in ./pkg/servers/ui/static will be accessible via /static URL path
|
||||
app.Static("/static", "./pkg/servers/ui/static")
|
||||
|
||||
// Setup routes
|
||||
routes.SetupRoutes(app)
|
||||
|
||||
return app
|
||||
}
|
1
pkg/servers/ui/controllers/.gitkeep
Normal file
1
pkg/servers/ui/controllers/.gitkeep
Normal file
@@ -0,0 +1 @@
|
||||
# This file is intentionally left blank to ensure the directory is tracked by Git.
|
71
pkg/servers/ui/controllers/auth_controller.go
Normal file
71
pkg/servers/ui/controllers/auth_controller.go
Normal file
@@ -0,0 +1,71 @@
|
||||
package controllers
|
||||
|
||||
import "github.com/gofiber/fiber/v2"
|
||||
|
||||
// AuthController handles authentication-related requests.
|
||||
type AuthController struct {
|
||||
// Add dependencies like a user service or session manager here
|
||||
}
|
||||
|
||||
// NewAuthController creates a new instance of AuthController.
|
||||
func NewAuthController() *AuthController {
|
||||
return &AuthController{}
|
||||
}
|
||||
|
||||
// ShowLoginPage renders the login page.
|
||||
// @Summary Show login page
|
||||
// @Description Displays the user login form.
|
||||
// @Tags auth
|
||||
// @Produce html
|
||||
// @Success 200 {string} html "Login page HTML"
|
||||
// @Router /login [get]
|
||||
func (ac *AuthController) ShowLoginPage(c *fiber.Ctx) error {
|
||||
return c.Render("pages/login", fiber.Map{
|
||||
"Title": "Login",
|
||||
})
|
||||
}
|
||||
|
||||
// HandleLogin processes the login form submission.
|
||||
// @Summary Process user login
|
||||
// @Description Authenticates the user based on submitted credentials.
|
||||
// @Tags auth
|
||||
// @Accept x-www-form-urlencoded
|
||||
// @Produce json
|
||||
// @Param username formData string true "Username"
|
||||
// @Param password formData string true "Password"
|
||||
// @Success 302 "Redirects to dashboard on successful login"
|
||||
// @Failure 400 {object} fiber.Map "Error for invalid input"
|
||||
// @Failure 401 {object} fiber.Map "Error for authentication failure"
|
||||
// @Router /login [post]
|
||||
func (ac *AuthController) HandleLogin(c *fiber.Ctx) error {
|
||||
// username := c.FormValue("username")
|
||||
// password := c.FormValue("password")
|
||||
|
||||
// TODO: Implement actual authentication logic here.
|
||||
// For now, we'll just simulate a successful login and redirect.
|
||||
// In a real app, you would:
|
||||
// 1. Validate username and password.
|
||||
// 2. Check credentials against a user store (e.g., database).
|
||||
// 3. Create a session or token.
|
||||
|
||||
// Simulate successful login
|
||||
// c.Cookie(&fiber.Cookie{Name: "session_token", Value: "dummy_token", HttpOnly: true, SameSite: "Lax"})
|
||||
return c.Redirect("/") // Redirect to dashboard
|
||||
}
|
||||
|
||||
// HandleLogout processes the logout request.
|
||||
// @Summary Process user logout
|
||||
// @Description Logs the user out by clearing their session.
|
||||
// @Tags auth
|
||||
// @Success 302 "Redirects to login page"
|
||||
// @Router /logout [get]
|
||||
func (ac *AuthController) HandleLogout(c *fiber.Ctx) error {
|
||||
// TODO: Implement actual logout logic here.
|
||||
// For now, we'll just simulate a logout and redirect.
|
||||
// In a real app, you would:
|
||||
// 1. Invalidate the session or token.
|
||||
// 2. Clear any session-related cookies.
|
||||
|
||||
// c.ClearCookie("session_token")
|
||||
return c.Redirect("/login")
|
||||
}
|
28
pkg/servers/ui/controllers/dashboard_controller.go
Normal file
28
pkg/servers/ui/controllers/dashboard_controller.go
Normal file
@@ -0,0 +1,28 @@
|
||||
package controllers
|
||||
|
||||
import "github.com/gofiber/fiber/v2"
|
||||
|
||||
// DashboardController handles requests related to the dashboard.
|
||||
type DashboardController struct {
|
||||
// Add any dependencies here, e.g., a service to fetch dashboard data
|
||||
}
|
||||
|
||||
// NewDashboardController creates a new instance of DashboardController.
|
||||
func NewDashboardController() *DashboardController {
|
||||
return &DashboardController{}
|
||||
}
|
||||
|
||||
// ShowDashboard renders the main dashboard page.
|
||||
// @Summary Show the main dashboard
|
||||
// @Description Displays the main dashboard page with an overview.
|
||||
// @Tags dashboard
|
||||
// @Produce html
|
||||
// @Success 200 {string} html "Dashboard page HTML"
|
||||
// @Router / [get]
|
||||
func (dc *DashboardController) ShowDashboard(c *fiber.Ctx) error {
|
||||
// For now, just render the dashboard template.
|
||||
// Later, you might pass data to the template.
|
||||
return c.Render("pages/dashboard", fiber.Map{
|
||||
"Title": "Dashboard", // This can be used in base.jet {{ .Title }}
|
||||
})
|
||||
}
|
76
pkg/servers/ui/controllers/process_controller.go
Normal file
76
pkg/servers/ui/controllers/process_controller.go
Normal file
@@ -0,0 +1,76 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"git.ourworld.tf/herocode/heroagent/pkg/servers/ui/models"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
)
|
||||
|
||||
// ProcessController handles requests related to process management.
|
||||
type ProcessController struct {
|
||||
ProcessService models.ProcessManagerService
|
||||
}
|
||||
|
||||
// NewProcessController creates a new instance of ProcessController.
|
||||
func NewProcessController(ps models.ProcessManagerService) *ProcessController {
|
||||
return &ProcessController{
|
||||
ProcessService: ps,
|
||||
}
|
||||
}
|
||||
|
||||
// ShowProcessManager renders the process manager page.
|
||||
// @Summary Show process manager
|
||||
// @Description Displays a list of running processes.
|
||||
// @Tags process
|
||||
// @Produce html
|
||||
// @Success 200 {string} html "Process manager page HTML"
|
||||
// @Failure 500 {object} fiber.Map "Error message if processes cannot be fetched"
|
||||
// @Router /processes [get]
|
||||
func (pc *ProcessController) ShowProcessManager(c *fiber.Ctx) error {
|
||||
processes, err := pc.ProcessService.GetProcesses()
|
||||
if err != nil {
|
||||
// Log the error appropriately in a real application
|
||||
fmt.Println("Error fetching processes:", err)
|
||||
return c.Status(fiber.StatusInternalServerError).Render("pages/process_manager", fiber.Map{
|
||||
"Title": "Process Manager",
|
||||
"Processes": []models.Process{}, // Empty list on error
|
||||
"Error": "Failed to retrieve process list.",
|
||||
})
|
||||
}
|
||||
|
||||
return c.Render("pages/process_manager", fiber.Map{
|
||||
"Title": "Process Manager",
|
||||
"Processes": processes,
|
||||
})
|
||||
}
|
||||
|
||||
// HandleKillProcess handles the request to kill a specific process.
|
||||
// @Summary Kill a process
|
||||
// @Description Terminates a process by its PID.
|
||||
// @Tags process
|
||||
// @Produce html
|
||||
// @Param pid path int true "Process ID"
|
||||
// @Success 302 "Redirects to process manager page"
|
||||
// @Failure 400 {object} fiber.Map "Error message if PID is invalid"
|
||||
// @Failure 500 {object} fiber.Map "Error message if process cannot be killed"
|
||||
// @Router /processes/kill/{pid} [post]
|
||||
func (pc *ProcessController) HandleKillProcess(c *fiber.Ctx) error {
|
||||
pidStr := c.Params("pid")
|
||||
pid, err := strconv.Atoi(pidStr)
|
||||
if err != nil {
|
||||
// Log error
|
||||
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Invalid PID format"})
|
||||
}
|
||||
|
||||
err = pc.ProcessService.KillProcess(pid)
|
||||
if err != nil {
|
||||
// Log error
|
||||
// In a real app, you might want to return a more user-friendly error page or message
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Failed to kill process"})
|
||||
}
|
||||
|
||||
// Redirect back to the process manager page
|
||||
return c.Redirect("/processes")
|
||||
}
|
1
pkg/servers/ui/models/.gitkeep
Normal file
1
pkg/servers/ui/models/.gitkeep
Normal file
@@ -0,0 +1 @@
|
||||
# This file is intentionally left blank to ensure the directory is tracked by Git.
|
48
pkg/servers/ui/models/process_model.go
Normal file
48
pkg/servers/ui/models/process_model.go
Normal file
@@ -0,0 +1,48 @@
|
||||
package models
|
||||
|
||||
// Process represents a single running process with its relevant details.
|
||||
type Process struct {
|
||||
PID int `json:"pid"`
|
||||
Name string `json:"name"`
|
||||
CPU float64 `json:"cpu"` // CPU usage percentage
|
||||
Memory float64 `json:"memory"` // Memory usage in MB
|
||||
// Add other fields if needed, e.g., User, Status, Path
|
||||
}
|
||||
|
||||
// ProcessManagerService defines the interface for interacting with the system's process manager.
|
||||
// This will be implemented by a struct that calls pkg/system/processmanager.
|
||||
type ProcessManagerService interface {
|
||||
GetProcesses() ([]Process, error)
|
||||
KillProcess(pid int) error
|
||||
}
|
||||
|
||||
// TODO: Implement a concrete ProcessManagerService that uses pkg/system/processmanager.
|
||||
// For now, we can create a mock implementation for development and testing of the UI.
|
||||
|
||||
// MockProcessManager is a mock implementation of ProcessManagerService for UI development.
|
||||
type MockProcessManager struct{}
|
||||
|
||||
// GetProcesses returns a list of mock processes.
|
||||
func (m *MockProcessManager) GetProcesses() ([]Process, error) {
|
||||
// Return some mock data
|
||||
return []Process{
|
||||
{PID: 1001, Name: "SystemIdleProcess", CPU: 95.5, Memory: 0.1},
|
||||
{PID: 1002, Name: "explorer.exe", CPU: 1.2, Memory: 150.7},
|
||||
{PID: 1003, Name: "chrome.exe", CPU: 25.8, Memory: 512.3},
|
||||
{PID: 1004, Name: "code.exe", CPU: 5.1, Memory: 350.0},
|
||||
{PID: 1005, Name: "go.exe", CPU: 0.5, Memory: 80.2},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// KillProcess simulates killing a process.
|
||||
func (m *MockProcessManager) KillProcess(pid int) error {
|
||||
// In a real implementation, this would call the system process manager.
|
||||
// For mock, we just print a message or do nothing.
|
||||
// fmt.Printf("Mock: Attempting to kill process %d\n", pid)
|
||||
return nil
|
||||
}
|
||||
|
||||
// NewMockProcessManager creates a new instance of MockProcessManager.
|
||||
func NewMockProcessManager() ProcessManagerService {
|
||||
return &MockProcessManager{}
|
||||
}
|
1
pkg/servers/ui/routes/.gitkeep
Normal file
1
pkg/servers/ui/routes/.gitkeep
Normal file
@@ -0,0 +1 @@
|
||||
# This file is intentionally left blank to ensure the directory is tracked by Git.
|
50
pkg/servers/ui/routes/router.go
Normal file
50
pkg/servers/ui/routes/router.go
Normal file
@@ -0,0 +1,50 @@
|
||||
package routes
|
||||
|
||||
import (
|
||||
"git.ourworld.tf/herocode/heroagent/pkg/servers/ui/controllers"
|
||||
"git.ourworld.tf/herocode/heroagent/pkg/servers/ui/models"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
)
|
||||
|
||||
// SetupRoutes configures the application's routes.
|
||||
func SetupRoutes(app *fiber.App) {
|
||||
// Initialize services and controllers
|
||||
// For now, using the mock process manager
|
||||
processManagerService := models.NewMockProcessManager()
|
||||
|
||||
dashboardController := controllers.NewDashboardController()
|
||||
processController := controllers.NewProcessController(processManagerService)
|
||||
authController := controllers.NewAuthController()
|
||||
|
||||
// --- Public Routes ---
|
||||
// Login and Logout
|
||||
app.Get("/login", authController.ShowLoginPage)
|
||||
app.Post("/login", authController.HandleLogin)
|
||||
app.Get("/logout", authController.HandleLogout)
|
||||
|
||||
// --- Authenticated Routes ---
|
||||
// TODO: Add middleware here to protect routes that require authentication.
|
||||
// For example:
|
||||
// authenticated := app.Group("/", authMiddleware) // Assuming authMiddleware is defined
|
||||
// authenticated.Get("/", dashboardController.ShowDashboard)
|
||||
// authenticated.Get("/processes", processController.ShowProcessManager)
|
||||
// authenticated.Post("/processes/kill/:pid", processController.HandleKillProcess)
|
||||
|
||||
// For now, routes are public for development ease
|
||||
app.Get("/", dashboardController.ShowDashboard)
|
||||
app.Get("/processes", processController.ShowProcessManager)
|
||||
app.Post("/processes/kill/:pid", processController.HandleKillProcess)
|
||||
|
||||
}
|
||||
|
||||
// TODO: Implement authMiddleware
|
||||
// func authMiddleware(c *fiber.Ctx) error {
|
||||
// // Check for session/token
|
||||
// // If not authenticated, redirect to /login
|
||||
// // If authenticated, c.Next()
|
||||
// // Example:
|
||||
// // if c.Cookies("session_token") == "" {
|
||||
// // return c.Redirect("/login")
|
||||
// // }
|
||||
// return c.Next()
|
||||
// }
|
1
pkg/servers/ui/static/css/.gitkeep
Normal file
1
pkg/servers/ui/static/css/.gitkeep
Normal file
@@ -0,0 +1 @@
|
||||
# This file is intentionally left blank to ensure the directory is tracked by Git.
|
48
pkg/servers/ui/static/css/custom.css
Normal file
48
pkg/servers/ui/static/css/custom.css
Normal file
@@ -0,0 +1,48 @@
|
||||
/* Custom CSS for HeroApp UI */
|
||||
|
||||
body {
|
||||
/* Example: Add some padding if needed, beyond what Bootstrap provides */
|
||||
/* padding-top: 5rem; */
|
||||
}
|
||||
|
||||
.sidebar {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
/* Sidenav can be customized further */
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
z-index: 100; /* Behind the navbar */
|
||||
padding: 56px 0 0; /* Height of navbar */
|
||||
box-shadow: inset -1px 0 0 rgba(0, 0, 0, .1);
|
||||
}
|
||||
|
||||
@media (max-width: 767.98px) {
|
||||
.sidebar {
|
||||
top: 5rem; /* Adjust if navbar height changes */
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.sidebar .nav-link {
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.sidebar .nav-link .feather {
|
||||
margin-right: 4px;
|
||||
color: #727272;
|
||||
}
|
||||
|
||||
.sidebar .nav-link.active {
|
||||
color: #007bff;
|
||||
}
|
||||
|
||||
.sidebar .nav-link:hover .feather,
|
||||
.sidebar .nav-link.active .feather {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.sidebar-heading {
|
||||
font-size: .75rem;
|
||||
text-transform: uppercase;
|
||||
}
|
1
pkg/servers/ui/static/img/.gitkeep
Normal file
1
pkg/servers/ui/static/img/.gitkeep
Normal file
@@ -0,0 +1 @@
|
||||
# This file is intentionally left blank to ensure the directory is tracked by Git.
|
1
pkg/servers/ui/static/js/.gitkeep
Normal file
1
pkg/servers/ui/static/js/.gitkeep
Normal file
@@ -0,0 +1 @@
|
||||
# This file is intentionally left blank to ensure the directory is tracked by Git.
|
15
pkg/servers/ui/static/js/custom.js
Normal file
15
pkg/servers/ui/static/js/custom.js
Normal file
@@ -0,0 +1,15 @@
|
||||
// Custom JavaScript for HeroApp UI
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
console.log('HeroApp UI custom.js loaded');
|
||||
|
||||
// Example: Add a click listener to a button with ID 'myButton'
|
||||
// const myButton = document.getElementById('myButton');
|
||||
// if (myButton) {
|
||||
// myButton.addEventListener('click', function() {
|
||||
// alert('Button clicked!');
|
||||
// });
|
||||
// }
|
||||
|
||||
// You can add more specific JavaScript interactions here as needed.
|
||||
});
|
1
pkg/servers/ui/views/components/.gitkeep
Normal file
1
pkg/servers/ui/views/components/.gitkeep
Normal file
@@ -0,0 +1 @@
|
||||
# This file is intentionally left blank to ensure the directory is tracked by Git.
|
24
pkg/servers/ui/views/components/navbar.jet
Normal file
24
pkg/servers/ui/views/components/navbar.jet
Normal file
@@ -0,0 +1,24 @@
|
||||
{{ block navbar() }}
|
||||
<nav class="navbar navbar-expand-lg navbar-dark bg-dark fixed-top">
|
||||
<div class="container-fluid">
|
||||
<a class="navbar-brand" href="/">HeroApp UI</a>
|
||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
<div class="collapse navbar-collapse" id="navbarNav">
|
||||
<ul class="navbar-nav ms-auto">
|
||||
<!-- Authentication status will determine which buttons to show -->
|
||||
<!-- For now, showing placeholder login/logout -->
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/login">Login</a> <!-- Placeholder Link -->
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/logout">Logout</a> <!-- Placeholder Link -->
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
<!-- Add some padding to the body to account for the fixed-top navbar -->
|
||||
<div style="padding-top: 56px;"></div>
|
||||
{{ end }}
|
17
pkg/servers/ui/views/components/sidebar.jet
Normal file
17
pkg/servers/ui/views/components/sidebar.jet
Normal file
@@ -0,0 +1,17 @@
|
||||
{{ block sidebar() }}
|
||||
<ul class="nav flex-column">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link active" aria-current="page" href="/">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-home"><path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"></path><polyline points="9 22 9 12 15 12 15 22"></polyline></svg>
|
||||
Dashboard
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/processes">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-activity"><polyline points="22 12 18 12 15 21 9 3 6 12 2 12"></polyline></svg>
|
||||
Process Manager
|
||||
</a>
|
||||
</li>
|
||||
<!-- Add more menu items here as needed -->
|
||||
</ul>
|
||||
{{ end }}
|
1
pkg/servers/ui/views/layouts/.gitkeep
Normal file
1
pkg/servers/ui/views/layouts/.gitkeep
Normal file
@@ -0,0 +1 @@
|
||||
# This file is intentionally left blank to ensure the directory is tracked by Git.
|
37
pkg/servers/ui/views/layouts/base.jet
Normal file
37
pkg/servers/ui/views/layouts/base.jet
Normal file
@@ -0,0 +1,37 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>{{ block "title" . }}My App{{ end }}</title>
|
||||
<!-- Bootstrap CSS from CDN -->
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.6/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-4Q6Gf2aSP4eDXB8Miphtr37CMZZQ5oXLH2yaXMJ2w8e2ZtHTl7GptT4jmndRuHDT" crossorigin="anonymous">
|
||||
<!-- Custom CSS -->
|
||||
<link rel="stylesheet" href="/static/css/custom.css">
|
||||
</head>
|
||||
<body>
|
||||
{{ import "pkg/servers/ui/views/components/navbar.jet" }}
|
||||
{{ yield navbar() }}
|
||||
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
<nav id="sidebarMenu" class="col-md-3 col-lg-2 d-md-block bg-light sidebar collapse">
|
||||
<div class="position-sticky pt-3">
|
||||
{{ import "pkg/servers/ui/views/components/sidebar.jet" }}
|
||||
{{ yield sidebar() }}
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<main class="col-md-9 ms-sm-auto col-lg-10 px-md-4">
|
||||
{{ yield body() }}
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Bootstrap JS Bundle from CDN -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.6/dist/js/bootstrap.bundle.min.js" integrity="sha384-j1CDi7MgGQ12Z7Qab0qlWQ/Qqz24Gc6BM0thvEMVjHnfYGF0rmFCozFSxQBxwHKO" crossorigin="anonymous"></script>
|
||||
<!-- Custom JS -->
|
||||
<script src="/static/js/custom.js"></script>
|
||||
{{ yield scripts() }}
|
||||
</body>
|
||||
</html>
|
1
pkg/servers/ui/views/pages/.gitkeep
Normal file
1
pkg/servers/ui/views/pages/.gitkeep
Normal file
@@ -0,0 +1 @@
|
||||
# This file is intentionally left blank to ensure the directory is tracked by Git.
|
22
pkg/servers/ui/views/pages/dashboard.jet
Normal file
22
pkg/servers/ui/views/pages/dashboard.jet
Normal file
@@ -0,0 +1,22 @@
|
||||
{{ extends "pkg/servers/ui/views/layouts/base.jet" }}
|
||||
|
||||
{{ block title() }}Dashboard - HeroApp UI{{ end }}
|
||||
|
||||
{{ block body() }}
|
||||
<div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom">
|
||||
<h1 class="h2">Dashboard</h1>
|
||||
</div>
|
||||
|
||||
<div class="container">
|
||||
<p>Welcome to the HeroApp UI Dashboard!</p>
|
||||
<p>This is a placeholder page. More content will be added here.</p>
|
||||
<!-- Example of using a Bootstrap component -->
|
||||
<div class="alert alert-info" role="alert">
|
||||
System status: All systems nominal.
|
||||
</div>
|
||||
</div>
|
||||
{{ end }}
|
||||
|
||||
{{ block scripts() }}
|
||||
<!-- Add any page-specific scripts here if needed -->
|
||||
{{ end }}
|
39
pkg/servers/ui/views/pages/login.jet
Normal file
39
pkg/servers/ui/views/pages/login.jet
Normal file
@@ -0,0 +1,39 @@
|
||||
{{ extends "pkg/servers/ui/views/layouts/base.jet" }}
|
||||
|
||||
{{ block title() }}Login - HeroApp UI{{ end }}
|
||||
|
||||
{{ block body() }}
|
||||
<div class="container mt-5">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6 col-lg-4">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h3 class="text-center">Login</h3>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<form action="/login" method="POST">
|
||||
<div class="mb-3">
|
||||
<label for="username" class="form-label">Username</label>
|
||||
<input type="text" class="form-control" id="username" name="username" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="password" class="form-label">Password</label>
|
||||
<input type="password" class="form-control" id="password" name="password" required>
|
||||
</div>
|
||||
<div class="d-grid">
|
||||
<button type="submit" class="btn btn-primary">Login</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="card-footer text-center">
|
||||
<small>© 2025 HeroApp</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{ end }}
|
||||
|
||||
{{ block scripts() }}
|
||||
<!-- Add any page-specific scripts here if needed -->
|
||||
{{ end }}
|
54
pkg/servers/ui/views/pages/process_manager.jet
Normal file
54
pkg/servers/ui/views/pages/process_manager.jet
Normal file
@@ -0,0 +1,54 @@
|
||||
{{ extends "pkg/servers/ui/views/layouts/base.jet" }}
|
||||
|
||||
{{ block title() }}Process Manager - HeroApp UI{{ end }}
|
||||
|
||||
{{ block body() }}
|
||||
<div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom">
|
||||
<h1 class="h2">Process Manager</h1>
|
||||
<div class="btn-toolbar mb-2 mb-md-0">
|
||||
<button type="button" class="btn btn-sm btn-outline-secondary" onclick="location.reload()">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-refresh-cw"><polyline points="23 4 23 10 17 10"></polyline><polyline points="1 20 1 14 7 14"></polyline><path d="M3.51 9a9 9 0 0 1 14.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0 0 20.49 15"></path></svg>
|
||||
Refresh
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped table-sm table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">PID</th>
|
||||
<th scope="col">Name</th>
|
||||
<th scope="col">CPU (%)</th>
|
||||
<th scope="col">Memory (MB)</th>
|
||||
<th scope="col">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{ if len(Processes) > 0 }}
|
||||
{{ range process := Processes }}
|
||||
<tr>
|
||||
<td>{{ process.PID }}</td>
|
||||
<td>{{ process.Name }}</td>
|
||||
<td>{{ printf "%.2f" process.CPU }}</td>
|
||||
<td>{{ printf "%.2f" process.Memory }}</td>
|
||||
<td>
|
||||
<form action="/processes/kill/{{ process.PID }}" method="POST" style="display:inline;">
|
||||
<button type="submit" class="btn btn-danger btn-sm" onclick="return confirm('Are you sure you want to kill process {{ process.PID }}?');">Kill</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
{{ end }}
|
||||
{{ else }}
|
||||
<tr>
|
||||
<td colspan="5" class="text-center">No processes found or unable to retrieve process list.</td>
|
||||
</tr>
|
||||
{{ end }}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{{ end }}
|
||||
|
||||
{{ block scripts() }}
|
||||
<!-- Add any page-specific scripts here if needed -->
|
||||
{{ end }}
|
Reference in New Issue
Block a user