Add system usage monitoring to admin dashboard

This commit is contained in:
2024-09-03 21:11:49 +07:00
parent b9c82fdbd6
commit 92c8846f57
5 changed files with 193 additions and 38 deletions

10
go.mod
View File

@ -5,7 +5,9 @@ go 1.22.2
require (
github.com/a-h/templ v0.2.707
github.com/google/uuid v1.6.0
github.com/gorilla/websocket v1.5.3
github.com/joho/godotenv v1.5.1
github.com/shirou/gopsutil/v3 v3.24.5
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
github.com/xlzd/gotp v0.1.0
golang.org/x/crypto v0.24.0
@ -17,6 +19,7 @@ require (
require (
filippo.io/edwards25519 v1.1.0 // indirect
github.com/go-ole/go-ole v1.2.6 // indirect
github.com/go-sql-driver/mysql v1.8.1 // indirect
github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
@ -24,7 +27,14 @@ require (
github.com/jackc/puddle/v2 v2.2.1 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.5 // indirect
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // 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
github.com/yusufpapurcu/wmi v1.2.4 // indirect
golang.org/x/sync v0.7.0 // indirect
golang.org/x/sys v0.21.0 // indirect
golang.org/x/text v0.16.0 // indirect
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
)

32
go.sum
View File

@ -5,13 +5,18 @@ github.com/a-h/templ v0.2.707/go.mod h1:5cqsugkq9IerRNucNsI4DEamdHPsoGMQy99DzydL
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
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/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y=
github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
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/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/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
@ -26,23 +31,46 @@ github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
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/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1vwZ3Je0FKVCfqOLp5zO6trqMLYs0=
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDqSt+GTGFMVlhk3ULuV0y9ZmzeVGR4mloJI3M=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
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=
github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY=
github.com/xlzd/gotp v0.1.0 h1:37blvlKCh38s+fkem+fFh7sMnceltoIEBYTVXyoa5Po=
github.com/xlzd/gotp v0.1.0/go.mod h1:ndLJ3JKzi3xLmUProq4LLxCuECL93dG9WASNLpHz8qg=
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI=
golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM=
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws=
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc h1:2gGKlE2+asNV9m7xrywl36YYNnBG5ZQ0r/BOOxqPpmk=
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=

43
public/websocket.js Normal file
View File

@ -0,0 +1,43 @@
const socket = new WebSocket('ws://localhost:27000/ws');
socket.onopen = function(event) {
console.log('WebSocket is open now.');
socket.send('Hello Server!');
};
socket.onmessage = function(event) {
try {
const data = JSON.parse(event.data);
console.log('Message from server:', data);
const cpuElement = document.getElementById('cpu_usage');
if (cpuElement) {
cpuElement.textContent = `${data.cpu_usage_percent.toFixed(2)}%`;
}
const memoryElement = document.getElementById('memory_usage');
if (memoryElement) {
memoryElement.textContent = `${data.memory_used_gb.toFixed(2)}/${data.total_memory_gb.toFixed(2)} GB`;
}
const uploadElement = document.getElementById('upload_speed');
if (uploadElement) {
uploadElement.textContent = `${data.upload_speed_mbps.toFixed(2)}Mbps`;
}
const downloadElement = document.getElementById('download_speed');
if (downloadElement) {
downloadElement.textContent = `${data.download_speed_mbps.toFixed(2)}Mbps`;
}
} catch (error) {
console.error('Error parsing message data:', error);
}
};
socket.onerror = function(event) {
console.error('WebSocket error observed:', event);
};
socket.onclose = function(event) {
console.log('WebSocket is closed now.');
};

View File

@ -1,13 +1,30 @@
package admin
import (
"github.com/fossyy/filekeeper/app"
"encoding/json"
adminIndex "github.com/fossyy/filekeeper/view/admin/index"
"github.com/gorilla/websocket"
"github.com/shirou/gopsutil/v3/cpu"
"github.com/shirou/gopsutil/v3/mem"
"github.com/shirou/gopsutil/v3/net"
"log"
"net/http"
"os"
"path/filepath"
"time"
)
var upgrader = websocket.Upgrader{
ReadBufferSize: 1024,
WriteBufferSize: 1024,
}
type SystemStats struct {
TotalMemoryGB float64 `json:"total_memory_gb"`
MemoryUsedGB float64 `json:"memory_used_gb"`
CpuUsagePercent float64 `json:"cpu_usage_percent"`
UploadSpeedMbps float64 `json:"upload_speed_mbps"`
DownloadSpeedMbps float64 `json:"download_speed_mbps"`
}
func SetupRoutes() *http.ServeMux {
handler := http.NewServeMux()
handler.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
@ -24,21 +41,59 @@ func SetupRoutes() *http.ServeMux {
adminIndex.Main().Render(r.Context(), w)
return
})
handler.HandleFunc("/public/output.css", func(w http.ResponseWriter, r *http.Request) {
openFile, err := os.OpenFile(filepath.Join("public", "output.css"), os.O_RDONLY, 0)
handler.HandleFunc("/ws", func(w http.ResponseWriter, r *http.Request) {
conn, err := upgrader.Upgrade(w, r, nil)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
app.Server.Logger.Error(err.Error())
log.Println(err)
return
}
defer openFile.Close()
stat, err := openFile.Stat()
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
app.Server.Logger.Error(err.Error())
return
for {
handlerWS(conn)
}
http.ServeContent(w, r, openFile.Name(), stat.ModTime(), openFile)
})
fileServer := http.FileServer(http.Dir("./public"))
handler.Handle("/public/", http.StripPrefix("/public", fileServer))
return handler
}
func handlerWS(conn *websocket.Conn) {
prevCounters, _ := net.IOCounters(false)
for {
vMem, _ := mem.VirtualMemory()
totalMemoryGB := float64(vMem.Total) / (1024 * 1024 * 1024)
memoryUsedGB := float64(vMem.Used) / (1024 * 1024 * 1024)
cpuPercent, _ := cpu.Percent(time.Second, false)
currentCounters, _ := net.IOCounters(false)
uploadBytes := currentCounters[0].BytesSent - prevCounters[0].BytesSent
downloadBytes := currentCounters[0].BytesRecv - prevCounters[0].BytesRecv
uploadSpeedMbps := float64(uploadBytes) * 8 / (1024 * 1024) / 2
downloadSpeedMbps := float64(downloadBytes) * 8 / (1024 * 1024) / 2
prevCounters = currentCounters
stats := SystemStats{
TotalMemoryGB: totalMemoryGB,
MemoryUsedGB: memoryUsedGB,
CpuUsagePercent: cpuPercent[0],
UploadSpeedMbps: uploadSpeedMbps,
DownloadSpeedMbps: downloadSpeedMbps,
}
statsJson, _ := json.Marshal(stats)
err := conn.WriteMessage(websocket.TextMessage, statsJson)
if err != nil {
conn.Close()
return
}
time.Sleep(2 * time.Second)
}
}

View File

@ -13,7 +13,7 @@ templ Main() {
<p class="text-sm text-muted-foreground">Real-time metrics for your server.</p>
</div>
<div class="p-6">
<div class="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3">
<div class="grid grid-cols-4 gap-4 sm:grid-cols-2 lg:grid-cols-4">
<div class="flex flex-col items-center justify-center gap-2 rounded-lg bg-background p-4">
<svg
xmlns="http://www.w3.org/2000/svg"
@ -38,7 +38,7 @@ templ Main() {
<path d="M9 2v2"></path>
<path d="M9 20v2"></path>
</svg>
<div class="text-2xl font-bold">75%</div>
<div id="cpu_usage" class="text-2xl font-bold">0%</div>
<div class="text-sm text-muted-foreground">CPU Usage</div>
</div>
<div class="flex flex-col items-center justify-center gap-2 rounded-lg bg-background p-4">
@ -64,7 +64,7 @@ templ Main() {
<path d="M2 15h20"></path>
<path d="M2 7a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v1.1a2 2 0 0 0 0 3.837V17a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2v-5.1a2 2 0 0 0 0-3.837Z"></path>
</svg>
<div class="text-2xl font-bold">8GB</div>
<div id="memory_usage" class="text-2xl font-bold">0GB</div>
<div class="text-sm text-muted-foreground">Memory Usage</div>
</div>
<div class="flex flex-col items-center justify-center gap-2 rounded-lg bg-background p-4">
@ -80,14 +80,32 @@ templ Main() {
stroke-linejoin="round"
class="h-8 w-8"
>
<rect x="16" y="16" width="6" height="6" rx="1"></rect>
<rect x="2" y="16" width="6" height="6" rx="1"></rect>
<rect x="9" y="2" width="6" height="6" rx="1"></rect>
<path d="M5 16v-3a1 1 0 0 1 1-1h12a1 1 0 0 1 1 1v3"></path>
<path d="M12 12V8"></path>
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path>
<polyline points="17 8 12 3 7 8"></polyline>
<line x1="12" x2="12" y1="3" y2="15"></line>
</svg>
<div class="text-2xl font-bold">100Mbps</div>
<div class="text-sm text-muted-foreground">Network Usage</div>
<div id="upload_speed" class="text-2xl font-bold">0Mbps</div>
<div class="text-sm text-muted-foreground">Upload Speed</div>
</div>
<div class="flex flex-col items-center justify-center gap-2 rounded-lg bg-background p-4">
<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="h-8 w-8"
>
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path>
<polyline points="7 10 12 15 17 10"></polyline>
<line x1="12" x2="12" y1="15" y2="3"></line>
</svg>
<div id="download_speed" class="text-2xl font-bold">0Mbps</div>
<div class="text-sm text-muted-foreground">Download Speed</div>
</div>
</div>
</div>
@ -425,5 +443,6 @@ templ Main() {
</div>
</main>
</div>
<script src="/public/websocket.js" />
}
}