This commit is contained in:
2025-05-23 15:56:35 +04:00
parent 532cda72d3
commit 3f01074e3f
26 changed files with 814 additions and 2 deletions

View 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
View 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
}

View File

@@ -0,0 +1 @@
# This file is intentionally left blank to ensure the directory is tracked by Git.

View 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")
}

View 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 }}
})
}

View 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")
}

View File

@@ -0,0 +1 @@
# This file is intentionally left blank to ensure the directory is tracked by Git.

View 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{}
}

View File

@@ -0,0 +1 @@
# This file is intentionally left blank to ensure the directory is tracked by Git.

View 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()
// }

View File

@@ -0,0 +1 @@
# This file is intentionally left blank to ensure the directory is tracked by Git.

View 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;
}

View File

@@ -0,0 +1 @@
# This file is intentionally left blank to ensure the directory is tracked by Git.

View File

@@ -0,0 +1 @@
# This file is intentionally left blank to ensure the directory is tracked by Git.

View 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.
});

View File

@@ -0,0 +1 @@
# This file is intentionally left blank to ensure the directory is tracked by Git.

View 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 }}

View 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 }}

View File

@@ -0,0 +1 @@
# This file is intentionally left blank to ensure the directory is tracked by Git.

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

View File

@@ -0,0 +1 @@
# This file is intentionally left blank to ensure the directory is tracked by Git.

View 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 }}

View 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>&copy; 2025 HeroApp</small>
</div>
</div>
</div>
</div>
</div>
{{ end }}
{{ block scripts() }}
<!-- Add any page-specific scripts here if needed -->
{{ end }}

View 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 }}