Compare commits
10 Commits
Author | SHA1 | Date | |
---|---|---|---|
29d91d1133 | |||
f40c54ebc1 | |||
![]() |
c47a664f1f | ||
9a1bb814ee | |||
d2a563588c | |||
![]() |
fabbb230d8 | ||
![]() |
b9efeb670d | ||
![]() |
5712929f23 | ||
![]() |
afb44caf52 | ||
![]() |
784b812c99 |
@@ -1,40 +0,0 @@
|
||||
name: Build Go Multi-Platform
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
run: |
|
||||
git clone https://gitea.tecamino.com/paadi/tecamino-proccessSupervisor.git .
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version: 'stable'
|
||||
|
||||
- name: Build for Windows amd64
|
||||
run: |
|
||||
GOOS=windows GOARCH=amd64 go build -o build/myapp-windows-amd64.exe ./...
|
||||
|
||||
- name: Build for Linux amd64
|
||||
run: |
|
||||
GOOS=linux GOARCH=amd64 go build -o build/myapp-linux-amd64 ./...
|
||||
|
||||
- name: Build for Linux ARM64
|
||||
run: |
|
||||
GOOS=linux GOARCH=arm64 go build -o build/myapp-linux-arm64 ./...
|
||||
|
||||
- name: Build for Linux ARMv6 (Raspberry Pi)
|
||||
run: |
|
||||
GOOS=linux GOARCH=arm GOARM=6 go build -o build/myapp-linux-armv6 ./...
|
||||
|
||||
- name: List build artifacts
|
||||
run: |
|
||||
ls -lh build
|
57
.gitea/workflows/build.yaml
Normal file
57
.gitea/workflows/build.yaml
Normal file
@@ -0,0 +1,57 @@
|
||||
name: Build Process Supervisor
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- '*'
|
||||
|
||||
env:
|
||||
APP_NAME: processSupervisor
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- os: windows
|
||||
arch: amd64
|
||||
ext: .exe
|
||||
- os: linux
|
||||
arch: amd64
|
||||
ext: ""
|
||||
- os: linux
|
||||
arch: arm64
|
||||
ext: ""
|
||||
- os: linux
|
||||
arch: arm
|
||||
arm_version: 6
|
||||
ext: ""
|
||||
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version: '1.24.5'
|
||||
|
||||
- name: Build binaries
|
||||
run: |
|
||||
OUTPUT="bin/${APP_NAME}-${{ matrix.os }}-${{ matrix.arch }}"
|
||||
if [ -n "${{ matrix.arm_version }}" ]; then
|
||||
OUTPUT="${OUTPUT}v${{ matrix.arm_version }}"
|
||||
export GOARM=${{ matrix.arm_version }}
|
||||
fi
|
||||
OUTPUT="${OUTPUT}${{ matrix.ext }}"
|
||||
echo "Building $OUTPUT"
|
||||
GOOS=${{ matrix.os }} GOARCH=${{ matrix.arch }} go build -ldflags="-s -w" -trimpath -o "$OUTPUT"
|
||||
shell: bash
|
||||
|
||||
- name: Upload artifacts
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: ${{ env.APP_NAME }}-${{ matrix.os }}-${{ matrix.arch }}${{ matrix.arm_version && format('v{0}', matrix.arm_version) || '' }}${{ matrix.ext }}
|
||||
path: bin/${{ env.APP_NAME }}-${{ matrix.os }}-${{ matrix.arch }}${{ matrix.arm_version && format('v{0}', matrix.arm_version) || '' }}${{ matrix.ext }}
|
3
go.mod
3
go.mod
@@ -2,11 +2,12 @@ module processSupervisor
|
||||
|
||||
go 1.24.5
|
||||
|
||||
require github.com/shirou/gopsutil/v3 v3.24.5
|
||||
|
||||
require (
|
||||
github.com/go-ole/go-ole v1.2.6 // indirect
|
||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
|
||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
|
||||
github.com/shirou/gopsutil/v3 v3.24.5 // indirect
|
||||
github.com/shoenig/go-m1cpu v0.1.6 // indirect
|
||||
github.com/tklauser/go-sysconf v0.3.12 // indirect
|
||||
github.com/tklauser/numcpus v0.6.1 // indirect
|
||||
|
12
go.sum
12
go.sum
@@ -1,14 +1,24 @@
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
|
||||
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
|
||||
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4=
|
||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw=
|
||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
|
||||
github.com/shirou/gopsutil/v3 v3.24.5 h1:i0t8kL+kQTvpAYToeuiVk3TgDeKOFioZO3Ztz/iZ9pI=
|
||||
github.com/shirou/gopsutil/v3 v3.24.5/go.mod h1:bsoOS1aStSs9ErQ1WWfxllSeS1K5D+U30r2NfcubMVk=
|
||||
github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM=
|
||||
github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ=
|
||||
github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU=
|
||||
github.com/shoenig/test v0.6.4/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k=
|
||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU=
|
||||
github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI=
|
||||
github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk=
|
||||
@@ -22,3 +32,5 @@ golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
|
||||
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
|
@@ -1,18 +1,33 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"html/template"
|
||||
"net/http"
|
||||
"processSupervisor/models"
|
||||
)
|
||||
|
||||
func HtopHandler(w http.ResponseWriter, r *http.Request) {
|
||||
table, err := models.GetTable()
|
||||
type HTopHandler struct {
|
||||
Table *models.HtopTable
|
||||
}
|
||||
|
||||
func NewHTopHandler() (*HTopHandler, error) {
|
||||
table, err := models.NewTable()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &HTopHandler{
|
||||
Table: table,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (h *HTopHandler) UpdateHTop(w http.ResponseWriter, r *http.Request) {
|
||||
err := h.Table.UpdateTable()
|
||||
if err != nil {
|
||||
http.Error(w, "Failed to get processes", 500)
|
||||
return
|
||||
}
|
||||
table.Sort(r)
|
||||
h.Table.Sort(r)
|
||||
|
||||
funcMap := template.FuncMap{
|
||||
"div": divide,
|
||||
@@ -23,19 +38,19 @@ func HtopHandler(w http.ResponseWriter, r *http.Request) {
|
||||
// Detect HTMX request via the HX-Request header
|
||||
if r.Header.Get("HX-Request") == "true" {
|
||||
tmpl := template.Must(
|
||||
template.New("table.html").Funcs(funcMap).ParseFiles("templates/partials/table.html"),
|
||||
template.New("table.html").Funcs(funcMap).ParseFiles("templates/htop/table.html"),
|
||||
)
|
||||
tmpl.Execute(w, table)
|
||||
tmpl.Execute(w, h.Table)
|
||||
return
|
||||
}
|
||||
|
||||
tmpl := template.Must(
|
||||
template.New("htop.html").Funcs(funcMap).ParseFiles(
|
||||
"templates/htop.html",
|
||||
"templates/partials/table.html",
|
||||
"templates/htop/htop.html",
|
||||
"templates/htop/table.html",
|
||||
),
|
||||
)
|
||||
tmpl.Execute(w, table)
|
||||
tmpl.Execute(w, h.Table)
|
||||
}
|
||||
|
||||
func divide(a any, b any) float64 {
|
||||
@@ -65,3 +80,21 @@ func toFloat64(v any) float64 {
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
// GET /taskmanager/usage
|
||||
func UsageHandler(w http.ResponseWriter, r *http.Request) {
|
||||
cpu, mem, err := models.GetSystemUsage()
|
||||
if err != nil {
|
||||
http.Error(w, "Failed to get usage", 500)
|
||||
return
|
||||
}
|
||||
|
||||
usage := struct {
|
||||
CPU float64 `json:"cpu"`
|
||||
Mem float64 `json:"mem"`
|
||||
}{
|
||||
CPU: cpu,
|
||||
Mem: mem,
|
||||
}
|
||||
json.NewEncoder(w).Encode(usage)
|
||||
}
|
||||
|
@@ -35,6 +35,6 @@ func KillHandler(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
tmpl := template.Must(template.ParseFiles("templates/partials/table.html"))
|
||||
tmpl := template.Must(template.ParseFiles("templates/htop/table.html"))
|
||||
tmpl.Execute(w, processes)
|
||||
}
|
||||
|
17
handlers/mainPage.go
Normal file
17
handlers/mainPage.go
Normal file
@@ -0,0 +1,17 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"html/template"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type MainPage struct {
|
||||
}
|
||||
|
||||
func UpdateMainPage(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||
tmpl := template.Must(
|
||||
template.New("index.html").ParseFiles("templates/index.html"),
|
||||
)
|
||||
tmpl.Execute(w, nil)
|
||||
}
|
19
main.go
19
main.go
@@ -1,18 +1,31 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"processSupervisor/handlers"
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
||||
port := flag.Uint("port", 9400, "listenig port")
|
||||
flag.Parse()
|
||||
|
||||
htop, err := handlers.NewHTopHandler()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fs := http.FileServer(http.Dir("static"))
|
||||
http.Handle("/static/", http.StripPrefix("/static/", fs))
|
||||
|
||||
http.HandleFunc("/taskmanager/htop", handlers.HtopHandler)
|
||||
http.HandleFunc("/taskmanager/htop", htop.UpdateHTop)
|
||||
http.HandleFunc("/", handlers.UpdateMainPage)
|
||||
http.HandleFunc("/taskmanager/kill", handlers.KillHandler)
|
||||
http.HandleFunc("/taskmanager/usage", handlers.UsageHandler)
|
||||
|
||||
log.Println("Listening on http://localhost:8080/taskmanager/htop")
|
||||
log.Fatal(http.ListenAndServe(":8080", nil))
|
||||
log.Printf("Listening on http://localhost:%d/taskmanager/htop\n", *port)
|
||||
log.Fatal(http.ListenAndServe(fmt.Sprintf(":%d", *port), nil))
|
||||
}
|
||||
|
@@ -1,12 +1,22 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"sort"
|
||||
"strconv"
|
||||
|
||||
"github.com/shirou/gopsutil/v3/cpu"
|
||||
"github.com/shirou/gopsutil/v3/mem"
|
||||
)
|
||||
|
||||
type HtopTable struct {
|
||||
Processes []Process
|
||||
CurrentSort string
|
||||
CurrentOrder string
|
||||
}
|
||||
|
||||
func GetTable() (*HtopTable, error) {
|
||||
func NewTable() (*HtopTable, error) {
|
||||
processes, err := GetProcesses()
|
||||
if err != nil {
|
||||
return &HtopTable{}, err
|
||||
@@ -15,3 +25,77 @@ func GetTable() (*HtopTable, error) {
|
||||
Processes: processes,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (t *HtopTable) UpdateTable() error {
|
||||
var err error
|
||||
t.Processes, err = GetProcesses()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *HtopTable) Sort(r *http.Request) {
|
||||
sortBy := r.URL.Query().Get("sort")
|
||||
order := r.URL.Query().Get("order")
|
||||
|
||||
if order == "desc" {
|
||||
t.CurrentOrder = "desc"
|
||||
} else {
|
||||
t.CurrentOrder = "asc"
|
||||
}
|
||||
|
||||
t.CurrentSort = sortBy
|
||||
|
||||
sort.Slice(t.Processes, func(i, j int) bool {
|
||||
switch sortBy {
|
||||
case "pid":
|
||||
pidI, _ := strconv.Atoi(t.Processes[i].PID)
|
||||
pidJ, _ := strconv.Atoi(t.Processes[j].PID)
|
||||
if t.CurrentOrder == "desc" {
|
||||
return pidI > pidJ
|
||||
}
|
||||
return pidI < pidJ
|
||||
case "user":
|
||||
if t.CurrentOrder == "desc" {
|
||||
return t.Processes[i].User > t.Processes[j].User
|
||||
}
|
||||
return t.Processes[i].User < t.Processes[j].User
|
||||
case "cmd":
|
||||
if t.CurrentOrder == "desc" {
|
||||
return t.Processes[i].Cmd > t.Processes[j].Cmd
|
||||
}
|
||||
return t.Processes[i].Cmd < t.Processes[j].Cmd
|
||||
case "cpu":
|
||||
if t.CurrentOrder == "desc" {
|
||||
return t.Processes[i].CPU > t.Processes[j].CPU
|
||||
}
|
||||
return t.Processes[i].CPU < t.Processes[j].CPU
|
||||
case "memory":
|
||||
if t.CurrentOrder == "desc" {
|
||||
return t.Processes[i].Memory > t.Processes[j].Memory
|
||||
}
|
||||
return t.Processes[i].Memory < t.Processes[j].Memory
|
||||
default:
|
||||
return true
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func GetSystemUsage() (cpuPercent float64, memPercent float64, err error) {
|
||||
cpuPercents, err := cpu.Percent(0, false)
|
||||
if err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
|
||||
if len(cpuPercents) == 0 {
|
||||
return 0, 0, fmt.Errorf("cpu.Percent returned empty slice")
|
||||
}
|
||||
|
||||
vmStat, err := mem.VirtualMemory()
|
||||
if err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
|
||||
return cpuPercents[0], vmStat.UsedPercent, nil
|
||||
}
|
||||
|
@@ -2,9 +2,6 @@ package models
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"sort"
|
||||
"strconv"
|
||||
|
||||
ps "github.com/shirou/gopsutil/v3/process"
|
||||
)
|
||||
@@ -59,50 +56,3 @@ func GetProcesses() ([]Process, error) {
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (t *HtopTable) Sort(r *http.Request) {
|
||||
sortBy := r.URL.Query().Get("sort")
|
||||
order := r.URL.Query().Get("order")
|
||||
|
||||
if order == "desc" {
|
||||
t.CurrentOrder = "desc"
|
||||
} else {
|
||||
t.CurrentOrder = "asc"
|
||||
}
|
||||
|
||||
t.CurrentSort = sortBy
|
||||
|
||||
sort.Slice(t.Processes, func(i, j int) bool {
|
||||
switch sortBy {
|
||||
case "pid":
|
||||
pidI, _ := strconv.Atoi(t.Processes[i].PID)
|
||||
pidJ, _ := strconv.Atoi(t.Processes[j].PID)
|
||||
if t.CurrentOrder == "desc" {
|
||||
return pidI > pidJ
|
||||
}
|
||||
return pidI < pidJ
|
||||
case "user":
|
||||
if t.CurrentOrder == "desc" {
|
||||
return t.Processes[i].User > t.Processes[j].User
|
||||
}
|
||||
return t.Processes[i].User < t.Processes[j].User
|
||||
case "cmd":
|
||||
if t.CurrentOrder == "desc" {
|
||||
return t.Processes[i].Cmd > t.Processes[j].Cmd
|
||||
}
|
||||
return t.Processes[i].Cmd < t.Processes[j].Cmd
|
||||
case "cpu":
|
||||
if t.CurrentOrder == "desc" {
|
||||
return t.Processes[i].CPU > t.Processes[j].CPU
|
||||
}
|
||||
return t.Processes[i].CPU < t.Processes[j].CPU
|
||||
case "memory":
|
||||
if t.CurrentOrder == "desc" {
|
||||
return t.Processes[i].Memory > t.Processes[j].Memory
|
||||
}
|
||||
return t.Processes[i].Memory < t.Processes[j].Memory
|
||||
default:
|
||||
return true
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@@ -92,19 +92,104 @@
|
||||
a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
.usage-bar {
|
||||
position: relative;
|
||||
background-color: #333;
|
||||
border-radius: 8px;
|
||||
height: 20px;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.usage-fill {
|
||||
height: 100%;
|
||||
width: 0%;
|
||||
background-color: #00e676; /* initial */
|
||||
transition: width 0.5s ease, background-color 0.4s ease;
|
||||
border-radius: 8px 0 0 8px;
|
||||
}
|
||||
|
||||
.usage-text {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
font-weight: bold;
|
||||
color: #fff;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
user-select: none;
|
||||
text-shadow: 0 0 5px rgba(0,0,0,0.7);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="system-stats" class="card" style="margin-bottom: 1.5rem;">
|
||||
<div>
|
||||
<label>CPU Usage</label>
|
||||
<div class="usage-bar">
|
||||
<div id="cpu-bar" class="usage-fill"></div>
|
||||
<span id="cpu-percent" class="usage-text">0%</span>
|
||||
</div>
|
||||
</div>
|
||||
<div style="margin-top: 1rem;">
|
||||
<label>Memory Usage</label>
|
||||
<div class="usage-bar">
|
||||
<div id="mem-bar" class="usage-fill"></div>
|
||||
<span id="mem-percent" class="usage-text">0%</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<h1 class="page-title">🧠 Task Manager</h1>
|
||||
<div
|
||||
id="htop-wrapper"
|
||||
hx-get="/taskmanager/htop?sort={{.CurrentSort}}&order={{.CurrentOrder}}"
|
||||
hx-trigger="load, every 1s"
|
||||
hx-target="#process-table"
|
||||
hx-swap="innerHTML">
|
||||
|
||||
<div id="process-table" class="card">
|
||||
{{template "table.html" .}}
|
||||
</div>
|
||||
</body>
|
||||
<script>
|
||||
function updateUsage() {
|
||||
fetch("/taskmanager/usage")
|
||||
.then(res => res.json())
|
||||
.then(data => {
|
||||
const cpu = Math.round(data.cpu);
|
||||
const mem = Math.round(data.mem);
|
||||
|
||||
updateBar('cpu-bar', 'cpu-percent', cpu);
|
||||
updateBar('mem-bar', 'mem-percent', mem);
|
||||
})
|
||||
.catch(console.error);
|
||||
}
|
||||
|
||||
function updateBar(barId, textId, percent) {
|
||||
const bar = document.getElementById(barId);
|
||||
const text = document.getElementById(textId);
|
||||
|
||||
bar.style.width = percent + '%';
|
||||
text.textContent = percent + '%';
|
||||
|
||||
// Set interpolated color from green to red
|
||||
bar.style.backgroundColor = getInterpolatedColor(percent);
|
||||
}
|
||||
|
||||
function getInterpolatedColor(percent) {
|
||||
// Clamp between 0–100
|
||||
percent = Math.max(0, Math.min(100, percent));
|
||||
|
||||
let r, g;
|
||||
|
||||
if (percent <= 50) {
|
||||
// green (0%) to yellow (50%)
|
||||
r = Math.floor(255 * (percent / 50)); // 0 → 255
|
||||
g = 230; // stay green
|
||||
} else {
|
||||
// yellow (50%) to red (100%)
|
||||
r = 255;
|
||||
g = Math.floor(230 - 230 * ((percent - 50) / 50)); // 230 → 0
|
||||
}
|
||||
|
||||
return `rgb(${r},${g},0)`;
|
||||
}
|
||||
|
||||
setInterval(updateUsage, 5000);
|
||||
updateUsage();
|
||||
</script>
|
||||
</html>
|
@@ -1,39 +1,43 @@
|
||||
<div id="process-table" class="card">
|
||||
<div id="process-table" class="card"
|
||||
hx-get="/taskmanager/htop?sort={{.CurrentSort}}&order={{.CurrentOrder}}"
|
||||
hx-trigger="every 2s"
|
||||
hx-target="#process-table"
|
||||
hx-swap="innerHTML">
|
||||
<table >
|
||||
<thead>
|
||||
<tr>
|
||||
<th>
|
||||
<a hx-get="/taskmanager/htop?sort=pid&order={{if and (eq .CurrentSort "pid") (eq .CurrentOrder "asc")}}desc{{else}}asc{{end}}"
|
||||
hx-target="#process-table"
|
||||
hx-swap="outerHTML">
|
||||
hx-swap="innerHTML">
|
||||
PID {{if eq .CurrentSort "pid"}}{{if eq .CurrentOrder "asc"}}↑{{else}}↓{{end}}{{end}}
|
||||
</a>
|
||||
</th>
|
||||
<th>
|
||||
<a hx-get="/taskmanager/htop?sort=user&order={{if and (eq .CurrentSort "user") (eq .CurrentOrder "asc")}}desc{{else}}asc{{end}}"
|
||||
hx-target="#process-table"
|
||||
hx-swap="outerHTML">
|
||||
hx-swap="innerHTML">
|
||||
User {{if eq .CurrentSort "user"}}{{if eq .CurrentOrder "asc"}}↑{{else}}↓{{end}}{{end}}
|
||||
</a>
|
||||
</th>
|
||||
<th>
|
||||
<a hx-get="/taskmanager/htop?sort=cmd&order={{if and (eq .CurrentSort "cmd") (eq .CurrentOrder "asc")}}desc{{else}}asc{{end}}"
|
||||
hx-target="#process-table"
|
||||
hx-swap="outerHTML">
|
||||
hx-swap="innerHTML">
|
||||
Command {{if eq .CurrentSort "cmd"}}{{if eq .CurrentOrder "asc"}}↑{{else}}↓{{end}}{{end}}
|
||||
</a>
|
||||
</th>
|
||||
<th>
|
||||
<a hx-get="/taskmanager/htop?sort=cpu&order={{if and (eq .CurrentSort "cpu") (eq .CurrentOrder "asc")}}desc{{else}}asc{{end}}"
|
||||
hx-target="#process-table"
|
||||
hx-swap="outerHTML">
|
||||
hx-swap="innerHTML">
|
||||
CPU %{{if eq .CurrentSort "cpu"}}{{if eq .CurrentOrder "asc"}}↑{{else}}↓{{end}}{{end}}
|
||||
</a>
|
||||
</th>
|
||||
<th>
|
||||
<a hx-get="/taskmanager/htop?sort=memory&order={{if and (eq .CurrentSort "memory") (eq .CurrentOrder "asc")}}desc{{else}}asc{{end}}"
|
||||
hx-target="#process-table"
|
||||
hx-swap="outerHTML">
|
||||
hx-swap="innerHTML">
|
||||
Memory {{if eq .CurrentSort "memory"}}{{if eq .CurrentOrder "asc"}}↑{{else}}↓{{end}}{{end}}
|
||||
</a>
|
||||
</th>
|
||||
@@ -67,4 +71,4 @@
|
||||
{{end}}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
62
templates/index.html
Normal file
62
templates/index.html
Normal file
@@ -0,0 +1,62 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Process Dashboard</title>
|
||||
<script src="https://unpkg.com/htmx.org@1.9.5"></script>
|
||||
<style>
|
||||
body {
|
||||
font-family: sans-serif;
|
||||
margin: 2rem;
|
||||
}
|
||||
.top-link {
|
||||
display: block;
|
||||
margin-bottom: 1rem;
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
ul#process-list {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
}
|
||||
ul#process-list li {
|
||||
background: #f4f4f4;
|
||||
margin: 0.5rem 0;
|
||||
padding: 0.75rem;
|
||||
border-radius: 5px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="top-link-container">
|
||||
<a href="/taskmanager/htop" class="top-link" target="_blank" rel="noopener noreferrer">
|
||||
📈 Task Manager
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- Header -->
|
||||
<h2>Future Processes</h2>
|
||||
|
||||
<!-- Placeholder List for Future Processes -->
|
||||
<ul id="process-list">
|
||||
<li>📌 Process A (placeholder)</li>
|
||||
<li>📌 Process B (placeholder)</li>
|
||||
<li>📌 Process C (placeholder)</li>
|
||||
</ul>
|
||||
|
||||
<!-- Example Button for Future HTMX Action -->
|
||||
<button
|
||||
hx-get="/get-latest-processes"
|
||||
hx-target="#process-list"
|
||||
hx-swap="innerHTML">
|
||||
🔄 Load Latest Processes
|
||||
</button>
|
||||
|
||||
</body>
|
||||
<style>
|
||||
.top-link-container {
|
||||
text-align: center;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
</style>
|
||||
</html>
|
Reference in New Issue
Block a user