Compare commits
17 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
ca9d8af13f | ||
![]() |
2537d5f1c8 | ||
![]() |
79ad631494 | ||
![]() |
a0046e1848 | ||
![]() |
d5f314dad2 | ||
![]() |
988a332249 | ||
29d91d1133 | |||
f40c54ebc1 | |||
![]() |
c47a664f1f | ||
9a1bb814ee | |||
d2a563588c | |||
![]() |
fabbb230d8 | ||
![]() |
b9efeb670d | ||
![]() |
5712929f23 | ||
![]() |
afb44caf52 | ||
![]() |
784b812c99 | ||
![]() |
a770875e2a |
@@ -1,37 +0,0 @@
|
|||||||
name: Build
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches: [ main ]
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
runs-on: self-hosted
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Install Latest Go
|
|
||||||
run: |
|
|
||||||
GO_VERSION=$(curl -s https://go.dev/VERSION?m=text)
|
|
||||||
wget https://go.dev/dl/${GO_VERSION}.linux-amd64.tar.gz
|
|
||||||
sudo rm -rf /usr/local/go
|
|
||||||
sudo tar -C /usr/local -xzf ${GO_VERSION}.linux-amd64.tar.gz
|
|
||||||
export PATH=/usr/local/go/bin:$PATH
|
|
||||||
echo "Go version installed:"
|
|
||||||
go version
|
|
||||||
|
|
||||||
- name: Checkout code
|
|
||||||
run: git clone https://your.gitea.server/user/repo.git .
|
|
||||||
|
|
||||||
- name: Build for Linux/Windows (amd64 + arm)
|
|
||||||
run: |
|
|
||||||
mkdir -p build
|
|
||||||
for GOOS in linux windows; do
|
|
||||||
for GOARCH in amd64 arm64; do
|
|
||||||
OUT=build/app-${GOOS}-${GOARCH}
|
|
||||||
[[ $GOOS == "windows" ]] && OUT="${OUT}.exe"
|
|
||||||
GOOS=$GOOS GOARCH=$GOARCH go build -o $OUT .
|
|
||||||
done
|
|
||||||
done
|
|
||||||
|
|
||||||
- name: List artifacts
|
|
||||||
run: ls -lh build
|
|
97
.gitea/workflows/build.yaml
Normal file
97
.gitea/workflows/build.yaml
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
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: Ensure latest Go is installed in /data/go
|
||||||
|
run: |
|
||||||
|
export GOROOT=/data/go/go
|
||||||
|
export PATH=$GOROOT/bin:$PATH
|
||||||
|
export GOCACHE=/data/gocache
|
||||||
|
export GOMODCACHE=/data/gomodcache
|
||||||
|
mkdir -p $GOCACHE $GOMODCACHE
|
||||||
|
|
||||||
|
if [ ! -x "$GOROOT/bin/go" ]; then
|
||||||
|
echo "Go not found in $GOROOT, downloading latest stable..."
|
||||||
|
|
||||||
|
GO_VERSION=$(curl -s https://go.dev/VERSION?m=text | head -n1)
|
||||||
|
echo "Latest version is $GO_VERSION"
|
||||||
|
|
||||||
|
mkdir -p /data/go
|
||||||
|
curl -sSL "https://go.dev/dl/${GO_VERSION}.linux-amd64.tar.gz" -o /tmp/go.tar.gz
|
||||||
|
tar -C /data/go -xzf /tmp/go.tar.gz
|
||||||
|
else
|
||||||
|
echo "Using cached Go from $GOROOT"
|
||||||
|
fi
|
||||||
|
|
||||||
|
go version
|
||||||
|
|
||||||
|
- name: Download Go dependencies
|
||||||
|
run: |
|
||||||
|
export GOROOT=/data/go/go
|
||||||
|
export PATH=$GOROOT/bin:$PATH
|
||||||
|
export GOCACHE=/data/gocache
|
||||||
|
export GOMODCACHE=/data/gomodcache
|
||||||
|
mkdir -p $GOCACHE $GOMODCACHE
|
||||||
|
go mod download
|
||||||
|
|
||||||
|
- name: Build binary
|
||||||
|
run: |
|
||||||
|
export GOROOT=/data/go/go
|
||||||
|
export PATH=$GOROOT/bin:$PATH
|
||||||
|
export GOCACHE=/data/gocache
|
||||||
|
export GOMODCACHE=/data/gomodcache
|
||||||
|
mkdir -p $GOCACHE $GOMODCACHE
|
||||||
|
|
||||||
|
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: Install zip
|
||||||
|
run: sudo apt-get update && sudo apt-get install -y zip
|
||||||
|
|
||||||
|
- name: Zip artifact
|
||||||
|
run: |
|
||||||
|
zip bin/${{ env.APP_NAME }}-${{ matrix.os }}-${{ matrix.arch }}.zip bin/${{ env.APP_NAME }}-*
|
||||||
|
|
||||||
|
- name: Upload zipped artifact
|
||||||
|
uses: actions/upload-artifact@v3
|
||||||
|
with:
|
||||||
|
name: ${{ env.APP_NAME }}-${{ matrix.os }}-${{ matrix.arch }}
|
||||||
|
path: bin/${{ env.APP_NAME }}-${{ matrix.os }}-${{ matrix.arch }}.zip
|
3
go.mod
3
go.mod
@@ -2,11 +2,12 @@ module processSupervisor
|
|||||||
|
|
||||||
go 1.24.5
|
go 1.24.5
|
||||||
|
|
||||||
|
require github.com/shirou/gopsutil/v3 v3.24.5
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/go-ole/go-ole v1.2.6 // indirect
|
github.com/go-ole/go-ole v1.2.6 // indirect
|
||||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
|
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
|
||||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // 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/shoenig/go-m1cpu v0.1.6 // indirect
|
||||||
github.com/tklauser/go-sysconf v0.3.12 // indirect
|
github.com/tklauser/go-sysconf v0.3.12 // indirect
|
||||||
github.com/tklauser/numcpus v0.6.1 // 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 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
|
||||||
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
|
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.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 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4=
|
||||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
|
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 h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw=
|
||||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
|
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 h1:i0t8kL+kQTvpAYToeuiVk3TgDeKOFioZO3Ztz/iZ9pI=
|
||||||
github.com/shirou/gopsutil/v3 v3.24.5/go.mod h1:bsoOS1aStSs9ErQ1WWfxllSeS1K5D+U30r2NfcubMVk=
|
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 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM=
|
||||||
github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ=
|
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 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU=
|
||||||
github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI=
|
github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI=
|
||||||
github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk=
|
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 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
|
||||||
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
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=
|
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
|
package handlers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"html/template"
|
"html/template"
|
||||||
"net/http"
|
"net/http"
|
||||||
"processSupervisor/models"
|
"processSupervisor/models"
|
||||||
)
|
)
|
||||||
|
|
||||||
func HtopHandler(w http.ResponseWriter, r *http.Request) {
|
type HTopHandler struct {
|
||||||
table, err := models.GetTable()
|
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 {
|
if err != nil {
|
||||||
http.Error(w, "Failed to get processes", 500)
|
http.Error(w, "Failed to get processes", 500)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
table.Sort(r)
|
h.Table.Sort(r)
|
||||||
|
|
||||||
funcMap := template.FuncMap{
|
funcMap := template.FuncMap{
|
||||||
"div": divide,
|
"div": divide,
|
||||||
@@ -23,19 +38,19 @@ func HtopHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
// Detect HTMX request via the HX-Request header
|
// Detect HTMX request via the HX-Request header
|
||||||
if r.Header.Get("HX-Request") == "true" {
|
if r.Header.Get("HX-Request") == "true" {
|
||||||
tmpl := template.Must(
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
tmpl := template.Must(
|
tmpl := template.Must(
|
||||||
template.New("htop.html").Funcs(funcMap).ParseFiles(
|
template.New("htop.html").Funcs(funcMap).ParseFiles(
|
||||||
"templates/htop.html",
|
"templates/htop/htop.html",
|
||||||
"templates/partials/table.html",
|
"templates/htop/table.html",
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
tmpl.Execute(w, table)
|
tmpl.Execute(w, h.Table)
|
||||||
}
|
}
|
||||||
|
|
||||||
func divide(a any, b any) float64 {
|
func divide(a any, b any) float64 {
|
||||||
@@ -65,3 +80,21 @@ func toFloat64(v any) float64 {
|
|||||||
return 0
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
tmpl := template.Must(template.ParseFiles("templates/partials/table.html"))
|
tmpl := template.Must(template.ParseFiles("templates/htop/table.html"))
|
||||||
tmpl.Execute(w, processes)
|
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
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"processSupervisor/handlers"
|
"processSupervisor/handlers"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
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"))
|
fs := http.FileServer(http.Dir("static"))
|
||||||
http.Handle("/static/", http.StripPrefix("/static/", fs))
|
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/kill", handlers.KillHandler)
|
||||||
|
http.HandleFunc("/taskmanager/usage", handlers.UsageHandler)
|
||||||
|
|
||||||
log.Println("Listening on http://localhost:8080/taskmanager/htop")
|
log.Printf("Listening on http://localhost:%d/taskmanager/htop\n", *port)
|
||||||
log.Fatal(http.ListenAndServe(":8080", nil))
|
log.Fatal(http.ListenAndServe(fmt.Sprintf(":%d", *port), nil))
|
||||||
}
|
}
|
||||||
|
@@ -1,12 +1,22 @@
|
|||||||
package models
|
package models
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"sort"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/shirou/gopsutil/v3/cpu"
|
||||||
|
"github.com/shirou/gopsutil/v3/mem"
|
||||||
|
)
|
||||||
|
|
||||||
type HtopTable struct {
|
type HtopTable struct {
|
||||||
Processes []Process
|
Processes []Process
|
||||||
CurrentSort string
|
CurrentSort string
|
||||||
CurrentOrder string
|
CurrentOrder string
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetTable() (*HtopTable, error) {
|
func NewTable() (*HtopTable, error) {
|
||||||
processes, err := GetProcesses()
|
processes, err := GetProcesses()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &HtopTable{}, err
|
return &HtopTable{}, err
|
||||||
@@ -15,3 +25,77 @@ func GetTable() (*HtopTable, error) {
|
|||||||
Processes: processes,
|
Processes: processes,
|
||||||
}, nil
|
}, 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 (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
|
||||||
"sort"
|
|
||||||
"strconv"
|
|
||||||
|
|
||||||
ps "github.com/shirou/gopsutil/v3/process"
|
ps "github.com/shirou/gopsutil/v3/process"
|
||||||
)
|
)
|
||||||
@@ -59,50 +56,3 @@ func GetProcesses() ([]Process, error) {
|
|||||||
|
|
||||||
return result, nil
|
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 {
|
a:hover {
|
||||||
text-decoration: underline;
|
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>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<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>
|
<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">
|
<div id="process-table" class="card">
|
||||||
{{template "table.html" .}}
|
{{template "table.html" .}}
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</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>
|
</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 >
|
<table >
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>
|
<th>
|
||||||
<a hx-get="/taskmanager/htop?sort=pid&order={{if and (eq .CurrentSort "pid") (eq .CurrentOrder "asc")}}desc{{else}}asc{{end}}"
|
<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-target="#process-table"
|
||||||
hx-swap="outerHTML">
|
hx-swap="innerHTML">
|
||||||
PID {{if eq .CurrentSort "pid"}}{{if eq .CurrentOrder "asc"}}↑{{else}}↓{{end}}{{end}}
|
PID {{if eq .CurrentSort "pid"}}{{if eq .CurrentOrder "asc"}}↑{{else}}↓{{end}}{{end}}
|
||||||
</a>
|
</a>
|
||||||
</th>
|
</th>
|
||||||
<th>
|
<th>
|
||||||
<a hx-get="/taskmanager/htop?sort=user&order={{if and (eq .CurrentSort "user") (eq .CurrentOrder "asc")}}desc{{else}}asc{{end}}"
|
<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-target="#process-table"
|
||||||
hx-swap="outerHTML">
|
hx-swap="innerHTML">
|
||||||
User {{if eq .CurrentSort "user"}}{{if eq .CurrentOrder "asc"}}↑{{else}}↓{{end}}{{end}}
|
User {{if eq .CurrentSort "user"}}{{if eq .CurrentOrder "asc"}}↑{{else}}↓{{end}}{{end}}
|
||||||
</a>
|
</a>
|
||||||
</th>
|
</th>
|
||||||
<th>
|
<th>
|
||||||
<a hx-get="/taskmanager/htop?sort=cmd&order={{if and (eq .CurrentSort "cmd") (eq .CurrentOrder "asc")}}desc{{else}}asc{{end}}"
|
<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-target="#process-table"
|
||||||
hx-swap="outerHTML">
|
hx-swap="innerHTML">
|
||||||
Command {{if eq .CurrentSort "cmd"}}{{if eq .CurrentOrder "asc"}}↑{{else}}↓{{end}}{{end}}
|
Command {{if eq .CurrentSort "cmd"}}{{if eq .CurrentOrder "asc"}}↑{{else}}↓{{end}}{{end}}
|
||||||
</a>
|
</a>
|
||||||
</th>
|
</th>
|
||||||
<th>
|
<th>
|
||||||
<a hx-get="/taskmanager/htop?sort=cpu&order={{if and (eq .CurrentSort "cpu") (eq .CurrentOrder "asc")}}desc{{else}}asc{{end}}"
|
<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-target="#process-table"
|
||||||
hx-swap="outerHTML">
|
hx-swap="innerHTML">
|
||||||
CPU %{{if eq .CurrentSort "cpu"}}{{if eq .CurrentOrder "asc"}}↑{{else}}↓{{end}}{{end}}
|
CPU %{{if eq .CurrentSort "cpu"}}{{if eq .CurrentOrder "asc"}}↑{{else}}↓{{end}}{{end}}
|
||||||
</a>
|
</a>
|
||||||
</th>
|
</th>
|
||||||
<th>
|
<th>
|
||||||
<a hx-get="/taskmanager/htop?sort=memory&order={{if and (eq .CurrentSort "memory") (eq .CurrentOrder "asc")}}desc{{else}}asc{{end}}"
|
<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-target="#process-table"
|
||||||
hx-swap="outerHTML">
|
hx-swap="innerHTML">
|
||||||
Memory {{if eq .CurrentSort "memory"}}{{if eq .CurrentOrder "asc"}}↑{{else}}↓{{end}}{{end}}
|
Memory {{if eq .CurrentSort "memory"}}{{if eq .CurrentOrder "asc"}}↑{{else}}↓{{end}}{{end}}
|
||||||
</a>
|
</a>
|
||||||
</th>
|
</th>
|
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