diff --git a/.gitea/workflows/build-multi-plaform.yaml b/.gitea/workflows/build-multi-plaform.yaml index 9e55b95..c5b1b08 100644 --- a/.gitea/workflows/build-multi-plaform.yaml +++ b/.gitea/workflows/build-multi-plaform.yaml @@ -1,16 +1,16 @@ -name: Build Go Multi-Platform +name: Build Process Supervisor -on: - push: - branches: - - main +on: [push] + +env: + APP_NAME: processSupervisor jobs: build: runs-on: ubuntu-latest steps: - - name: Checkout repository + - name: Checkout repo uses: actions/checkout@v4 - name: Set up Go @@ -20,20 +20,32 @@ jobs: - name: Build for Windows amd64 run: | - GOOS=windows GOARCH=amd64 go build -o build/myapp-windows-amd64.exe ./... + GOOS=windows GOARCH=amd64 go build -ldflags="-s -w" -trimpath -o bin/${APP_NAME}-windows-amd64.exe ./... + uses: actions/upload-artifact@v3 + with: + name: ${{env.APP_NAME}}-windows-amd64.exe + path: bin/${{env.APP_NAME}}-windows-amd64.exe - name: Build for Linux amd64 run: | - GOOS=linux GOARCH=amd64 go build -o build/myapp-linux-amd64 ./... + GOOS=linux GOARCH=amd64 go build -ldflags="-s -w" -trimpath -o bin/${APP_NAME}-linux-amd64 ./... + uses: actions/upload-artifact@v3 + with: + name: ${{env.APP_NAME}}-linux-amd64 + path: bin/${{env.APP_NAME}}-linux-amd64 - name: Build for Linux ARM64 run: | - GOOS=linux GOARCH=arm64 go build -o build/myapp-linux-arm64 ./... + GOOS=linux GOARCH=arm64 go build -ldflags="-s -w" -trimpath -o bin/${APP_NAME}-linux-arm64 ./... + uses: actions/upload-artifact@v3 + with: + name: ${{env.APP_NAME}}-linux-arm64 + path: bin/${{env.APP_NAME}}-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 + GOOS=linux GOARCH=arm GOARM=6 go build -ldflags="-s -w" -trimpath -o bin/${APP_NAME}-linux-armv6 ./... + uses: actions/upload-artifact@v3 + with: + name: ${{env.APP_NAME}}-linux-armv6 + path: bin/${{env.APP_NAME}}-linux-armv6 \ No newline at end of file diff --git a/go.mod b/go.mod index deded74..c8396f2 100644 --- a/go.mod +++ b/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 diff --git a/go.sum b/go.sum index bba3242..61e6f38 100644 --- a/go.sum +++ b/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= diff --git a/handlers/htopHandler.go b/handlers/htopHandler.go index 6deed5a..11d1eb7 100644 --- a/handlers/htopHandler.go +++ b/handlers/htopHandler.go @@ -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) +} diff --git a/handlers/killHandler.go b/handlers/killHandler.go index 0b54231..d495faa 100644 --- a/handlers/killHandler.go +++ b/handlers/killHandler.go @@ -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) } diff --git a/handlers/mainPage.go b/handlers/mainPage.go new file mode 100644 index 0000000..1233acd --- /dev/null +++ b/handlers/mainPage.go @@ -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) +} diff --git a/main.go b/main.go index 5b8db7f..775f3c9 100644 --- a/main.go +++ b/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)) } diff --git a/models/htopTable.go b/models/htopTable.go index ffdee8e..213c132 100644 --- a/models/htopTable.go +++ b/models/htopTable.go @@ -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 +} diff --git a/models/process.go b/models/process.go index ce8af4c..0fbaeb1 100644 --- a/models/process.go +++ b/models/process.go @@ -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 - } - }) -} diff --git a/templates/htop.html b/templates/htop/htop.html similarity index 52% rename from templates/htop.html rename to templates/htop/htop.html index 300369f..ae26566 100644 --- a/templates/htop.html +++ b/templates/htop/htop.html @@ -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); +}
+