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

4
go.mod
View File

@ -9,7 +9,7 @@ require (
github.com/emersion/go-message v0.18.2
github.com/emersion/go-smtp v0.21.3
github.com/emersion/go-webdav v0.6.0
github.com/gofiber/fiber/v2 v2.52.6
github.com/gofiber/fiber/v2 v2.52.8
github.com/gofiber/template/pug/v2 v2.1.8
github.com/knusbaum/go9p v1.18.0
github.com/redis/go-redis/v9 v9.8.0
@ -47,7 +47,7 @@ require (
github.com/go-openapi/jsonreference v0.21.0 // indirect
github.com/go-openapi/spec v0.21.0 // indirect
github.com/gofiber/template v1.8.3 // indirect
github.com/gofiber/template/jet/v2 v2.1.11 // indirect
github.com/gofiber/template/jet/v2 v2.1.12 // indirect
github.com/gofiber/utils v1.1.0 // indirect
github.com/golang/snappy v0.0.2 // indirect
github.com/google/uuid v1.6.0 // indirect

18
go.sum
View File

@ -1,5 +1,7 @@
9fans.net/go v0.0.2/go.mod h1:lfPdxjq9v8pVQXUMBCx5EO5oLXWQFlKRQgs1kEkjoIM=
github.com/CloudyKit/fastprinter v0.0.0-20200109182630-33d98a066a53 h1:sR+/8Yb4slttB4vD+b9btVEnWgL3Q00OBTzVT8B9C0c=
github.com/CloudyKit/fastprinter v0.0.0-20200109182630-33d98a066a53/go.mod h1:+3IMCy2vIlbG1XG/0ggNQv0SvxCAIpPM5b1nCz56Xno=
github.com/CloudyKit/jet/v6 v6.3.1 h1:6IAo5Cx21xrHVaR8zzXN5gJatKV/wO7Nf6bfCnCSbUw=
github.com/CloudyKit/jet/v6 v6.3.1/go.mod h1:lf8ksdNsxZt7/yH/3n4vJQWA9RUq4wpaHtArHhGVMOw=
github.com/Joker/hpp v1.0.0/go.mod h1:8x5n+M1Hp5hC0g8okX3sR3vFQwynaX/UgSOM9MeBKzY=
github.com/Joker/jade v1.1.3/go.mod h1:T+2WLyt7VH6Lp0TRxQrUYEs64nRc83wkMQrfeIQKduM=
@ -7,6 +9,7 @@ github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6Xge
github.com/Plan9-Archive/libauth v0.0.0-20180917063427-d1ca9e94969d/go.mod h1:UKp8dv9aeaZoQFWin7eQXtz89iHly1YAFZNn3MCutmQ=
github.com/PuerkitoBio/purell v1.2.1/go.mod h1:ZwHcC/82TOaovDi//J/804umJFFmbOHPngi8iYYv/Eo=
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M=
github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY=
github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg=
github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0=
@ -40,14 +43,21 @@ github.com/go-openapi/jsonpointer v0.21.1/go.mod h1:50I1STOfbY1ycR8jGz8DaMeLCdXi
github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4=
github.com/go-openapi/spec v0.21.0/go.mod h1:78u6VdPw81XU44qEWGhtr982gJ5BWg2c0I5XwVMotYk=
github.com/gofiber/fiber/v2 v2.52.6/go.mod h1:YEcBbO/FB+5M1IZNBP9FO3J9281zgPAreiI1oqg8nDw=
github.com/gofiber/fiber/v2 v2.52.8 h1:xl4jJQ0BV5EJTA2aWiKw/VddRpHrKeZLF0QPUxqn0x4=
github.com/gofiber/fiber/v2 v2.52.8/go.mod h1:YEcBbO/FB+5M1IZNBP9FO3J9281zgPAreiI1oqg8nDw=
github.com/gofiber/template v1.8.3 h1:hzHdvMwMo/T2kouz2pPCA0zGiLCeMnoGsQZBTSYgZxc=
github.com/gofiber/template v1.8.3/go.mod h1:bs/2n0pSNPOkRa5VJ8zTIvedcI/lEYxzV3+YPXdBvq8=
github.com/gofiber/template/jet/v2 v2.1.11/go.mod h1:Kb1oBdrx90oEvP71MDTUB9k+IWRF082Td5OPW7SoUMQ=
github.com/gofiber/template/jet/v2 v2.1.12 h1:poRJ6Lv0lffuGqt6FCLXh4rcavVrIytLgCruzAslmhU=
github.com/gofiber/template/jet/v2 v2.1.12/go.mod h1:kBQYWT0JbtwYgbMPkpHnzOeZtVYrqrCipjMVi9KOjSs=
github.com/gofiber/template/pug/v2 v2.1.8/go.mod h1:e0Sg0YBMtC+RQMRm0swaAvqIBDJmhhDIKfFFtQRjvlQ=
github.com/gofiber/utils v1.1.0 h1:vdEBpn7AzIUJRhe+CiTOJdUcTg4Q9RK+pEa0KPbLdrM=
github.com/gofiber/utils v1.1.0/go.mod h1:poZpsnhBykfnY1Mc0KeEa6mSHrS3dV0+oBWyeQmb2e0=
github.com/golang/snappy v0.0.2/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/hanwen/go-fuse v1.0.0/go.mod h1:unqXarDXqzAk0rt98O2tVndEPIpUgLD9+rwFisZH3Ok=
github.com/hanwen/go-fuse/v2 v2.0.3/go.mod h1:0EQM6aH2ctVpvZ6a+onrQ/vaykxh2GH7hy3e13vzTUY=
@ -56,6 +66,7 @@ github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkr
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc=
github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0=
github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
github.com/klauspost/pgzip v1.2.5/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=
@ -69,9 +80,12 @@ github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348/go.mod h1:B69LE
github.com/lufia/plan9stats v0.0.0-20231016141302-07b5767bb0ed h1:036IscGBfJsFIgJQzlui7nK1Ncm0tp2ktmPj8xO4N/0=
github.com/lufia/plan9stats v0.0.0-20231016141302-07b5767bb0ed/go.mod h1:ilwx/Dta8jXAgpFYFvSWEMwxmbWXyiUHkd5FwyKhb5k=
github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/mattn/go-sqlite3 v1.14.24/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
github.com/metoro-io/mcp-golang v0.8.0/go.mod h1:ifLP9ZzKpN1UqFWNTpAHOqSvNkMK6b7d1FSZ5Lu0lN0=
@ -91,6 +105,7 @@ github.com/redis/go-redis/v9 v9.8.0/go.mod h1:huWgSWd8mW6+m0VPhJjSSQ+d6Nh1VICQ6Q
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
@ -120,8 +135,11 @@ github.com/tklauser/numcpus v0.7.0/go.mod h1:bb6dMVcj8A42tSE7i32fsIUCbQNllK5iDgu
github.com/ulikunitz/xz v0.5.8/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
github.com/ulikunitz/xz v0.5.9/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
github.com/urfave/cli/v2 v2.27.6/go.mod h1:3Sevf16NykTbInEnD0yKkjDAeZDS0A6bzhBH5hrMvTQ=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasthttp v1.51.0 h1:8b30A5JlZ6C7AS81RsWjYMQmrZG6feChmgAolCl1SqA=
github.com/valyala/fasthttp v1.51.0/go.mod h1:oI2XroL+lI7vdXyYoQk03bXBThfFl2cVdIA3Xl7cH8g=
github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8=
github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc=
github.com/wk8/go-ordered-map/v2 v2.1.9-0.20240815153524-6ea36470d1bd/go.mod h1:DbzwytT4g/odXquuOCqroKvtxxldI4nb3nuesHF/Exo=
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8/go.mod h1:HUYIGzjTL3rfEspMxjDjgmT5uz5wzYJKVo23qUhYTos=

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