Spaces:
Sleeping
Sleeping
Commit ·
de65817
0
Parent(s):
feat: enrich features and configure for huggingface
Browse files- Dockerfile +33 -0
- README.md +45 -0
- go.mod +20 -0
- go.sum +49 -0
- main.go +165 -0
- static/app.js +101 -0
- static/index.html +162 -0
Dockerfile
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# 构建阶段
|
| 2 |
+
FROM golang:1.21-alpine AS builder
|
| 3 |
+
|
| 4 |
+
WORKDIR /app
|
| 5 |
+
|
| 6 |
+
# 安装必要的构建依赖
|
| 7 |
+
RUN apk add --no-cache gcc musl-dev
|
| 8 |
+
|
| 9 |
+
# 复制依赖文件并下载
|
| 10 |
+
COPY go.mod go.sum ./
|
| 11 |
+
RUN go mod download
|
| 12 |
+
|
| 13 |
+
# 复制源代码并构建
|
| 14 |
+
COPY . .
|
| 15 |
+
RUN go build -o main .
|
| 16 |
+
|
| 17 |
+
# 运行阶段
|
| 18 |
+
FROM alpine:latest
|
| 19 |
+
|
| 20 |
+
WORKDIR /app
|
| 21 |
+
|
| 22 |
+
# 复制构建产物和静态资源
|
| 23 |
+
COPY --from=builder /app/main .
|
| 24 |
+
COPY --from=builder /app/static ./static
|
| 25 |
+
|
| 26 |
+
# 暴露端口 (Hugging Face Spaces 默认 7860)
|
| 27 |
+
EXPOSE 7860
|
| 28 |
+
|
| 29 |
+
# 设置环境变量
|
| 30 |
+
ENV PORT=7860
|
| 31 |
+
|
| 32 |
+
# 启动应用
|
| 33 |
+
CMD ["./main"]
|
README.md
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
title: Go 系统实时监控仪表板
|
| 3 |
+
emoji: 📊
|
| 4 |
+
colorFrom: blue
|
| 5 |
+
colorTo: green
|
| 6 |
+
sdk: docker
|
| 7 |
+
app_port: 7860
|
| 8 |
+
pinned: false
|
| 9 |
+
short_description: 基于 Go 语言的实时系统性能监控仪表板,支持主机信息、Top 10 进程监控及移动端适配。
|
| 10 |
+
---
|
| 11 |
+
|
| 12 |
+
# Go 系统实时监控仪表板 (Go-Sys-Dash)
|
| 13 |
+
|
| 14 |
+
本项目是一个基于 Go 语言开发的轻量级系统性能监控工具。它通过 WebSocket 实时推送服务器的 CPU、内存、磁盘及进程信息,并提供一个现代化的、响应式的 Web 界面。
|
| 15 |
+
|
| 16 |
+
## 核心特性
|
| 17 |
+
|
| 18 |
+
- **实时数据推送**:使用 WebSocket 技术,每 2 秒自动刷新系统指标。
|
| 19 |
+
- **主机详情展示**:新增主机名、操作系统、平台及运行时间显示。
|
| 20 |
+
- **直观的可视化**:采用 Chart.js 绘制环形图,清晰展示资源占用情况。
|
| 21 |
+
- **增强型进程管理**:实时列出当前系统中 CPU 占用最高的前 10 个进程。
|
| 22 |
+
- **移动端适配**:响应式布局设计,支持在手机和平板浏览器上流畅查看。
|
| 23 |
+
- **汉化支持**:界面全中文显示,代码包含详尽的中文注释。
|
| 24 |
+
- **一键部署**:支持 Docker 容器化部署,适配主流云平台和 Hugging Face Spaces。
|
| 25 |
+
|
| 26 |
+
## 技术栈
|
| 27 |
+
|
| 28 |
+
- **后端**: Go 1.21+, `gopsutil` (系统信息采集), `gorilla/websocket` (通信)
|
| 29 |
+
- **前端**: HTML5, CSS3, JavaScript (ES6+), Chart.js
|
| 30 |
+
- **部署**: Docker, Alpine Linux (轻量化运行环境)
|
| 31 |
+
|
| 32 |
+
## 本地运行
|
| 33 |
+
|
| 34 |
+
1. 确保已安装 Go 环境。
|
| 35 |
+
2. 克隆项目并进入目录:
|
| 36 |
+
```bash
|
| 37 |
+
go mod tidy
|
| 38 |
+
go run main.go
|
| 39 |
+
```
|
| 40 |
+
3. 访问 `http://localhost:7860`。
|
| 41 |
+
|
| 42 |
+
## Hugging Face 部署说明
|
| 43 |
+
|
| 44 |
+
本项目已针对 Hugging Face Spaces 进行优化,通过 `Dockerfile` 进行构建和部署。系统会自动识别根目录下的 `Dockerfile` 并完成环境搭建。
|
| 45 |
+
默认端口已设为 `7860`,适配 Hugging Face 运行环境。
|
go.mod
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
module go-sys-dash
|
| 2 |
+
|
| 3 |
+
go 1.21
|
| 4 |
+
|
| 5 |
+
require (
|
| 6 |
+
github.com/gorilla/websocket v1.5.1
|
| 7 |
+
github.com/shirou/gopsutil/v3 v3.23.12
|
| 8 |
+
)
|
| 9 |
+
|
| 10 |
+
require (
|
| 11 |
+
github.com/go-ole/go-ole v1.2.6 // indirect
|
| 12 |
+
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
|
| 13 |
+
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
|
| 14 |
+
github.com/shoenig/go-m1cpu v0.1.6 // indirect
|
| 15 |
+
github.com/tklauser/go-sysconf v0.3.12 // indirect
|
| 16 |
+
github.com/tklauser/numcpus v0.6.1 // indirect
|
| 17 |
+
github.com/yusufpapurcu/wmi v1.2.3 // indirect
|
| 18 |
+
golang.org/x/net v0.17.0 // indirect
|
| 19 |
+
golang.org/x/sys v0.15.0 // indirect
|
| 20 |
+
)
|
go.sum
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
| 2 |
+
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
| 3 |
+
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
| 4 |
+
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
|
| 5 |
+
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
|
| 6 |
+
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
| 7 |
+
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
| 8 |
+
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
| 9 |
+
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
| 10 |
+
github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY=
|
| 11 |
+
github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY=
|
| 12 |
+
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4=
|
| 13 |
+
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
|
| 14 |
+
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
| 15 |
+
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
| 16 |
+
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw=
|
| 17 |
+
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
|
| 18 |
+
github.com/shirou/gopsutil/v3 v3.23.12 h1:z90NtUkp3bMtmICZKpC4+WaknU1eXtp5vtbQ11DgpE4=
|
| 19 |
+
github.com/shirou/gopsutil/v3 v3.23.12/go.mod h1:1FrWgea594Jp7qmjHUUPlJDTPgcsb9mGnXDxavtikzM=
|
| 20 |
+
github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM=
|
| 21 |
+
github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ=
|
| 22 |
+
github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU=
|
| 23 |
+
github.com/shoenig/test v0.6.4/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k=
|
| 24 |
+
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
| 25 |
+
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
| 26 |
+
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
| 27 |
+
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
| 28 |
+
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
| 29 |
+
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
| 30 |
+
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
| 31 |
+
github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU=
|
| 32 |
+
github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI=
|
| 33 |
+
github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk=
|
| 34 |
+
github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY=
|
| 35 |
+
github.com/yusufpapurcu/wmi v1.2.3 h1:E1ctvB7uKFMOJw3fdOW32DwGE9I7t++CRUEMKvFoFiw=
|
| 36 |
+
github.com/yusufpapurcu/wmi v1.2.3/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
| 37 |
+
golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
|
| 38 |
+
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
|
| 39 |
+
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
| 40 |
+
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
| 41 |
+
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
| 42 |
+
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
| 43 |
+
golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
|
| 44 |
+
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
| 45 |
+
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
| 46 |
+
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
| 47 |
+
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
| 48 |
+
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
| 49 |
+
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
main.go
ADDED
|
@@ -0,0 +1,165 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
package main
|
| 2 |
+
|
| 3 |
+
import (
|
| 4 |
+
"encoding/json"
|
| 5 |
+
"fmt"
|
| 6 |
+
"log"
|
| 7 |
+
"net/http"
|
| 8 |
+
"os"
|
| 9 |
+
"sort"
|
| 10 |
+
"time"
|
| 11 |
+
|
| 12 |
+
"github.com/gorilla/websocket"
|
| 13 |
+
"github.com/shirou/gopsutil/v3/cpu"
|
| 14 |
+
"github.com/shirou/gopsutil/v3/disk"
|
| 15 |
+
"github.com/shirou/gopsutil/v3/host"
|
| 16 |
+
"github.com/shirou/gopsutil/v3/mem"
|
| 17 |
+
"github.com/shirou/gopsutil/v3/process"
|
| 18 |
+
)
|
| 19 |
+
|
| 20 |
+
// 系统状态结构体
|
| 21 |
+
type SystemStatus struct {
|
| 22 |
+
CPUUsage float64 `json:"cpuUsage"`
|
| 23 |
+
MemoryUsage float64 `json:"memoryUsage"`
|
| 24 |
+
DiskUsage float64 `json:"diskUsage"`
|
| 25 |
+
Processes []Process `json:"processes"`
|
| 26 |
+
Timestamp string `json:"timestamp"`
|
| 27 |
+
HostInfo HostSummary `json:"hostInfo"`
|
| 28 |
+
}
|
| 29 |
+
|
| 30 |
+
// 进程信息结构体
|
| 31 |
+
type Process struct {
|
| 32 |
+
PID int32 `json:"pid"`
|
| 33 |
+
Name string `json:"name"`
|
| 34 |
+
CPU float64 `json:"cpu"`
|
| 35 |
+
Memory float32 `json:"memory"`
|
| 36 |
+
}
|
| 37 |
+
|
| 38 |
+
// 主机摘要信息
|
| 39 |
+
type HostSummary struct {
|
| 40 |
+
Hostname string `json:"hostname"`
|
| 41 |
+
OS string `json:"os"`
|
| 42 |
+
Platform string `json:"platform"`
|
| 43 |
+
Uptime uint64 `json:"uptime"`
|
| 44 |
+
}
|
| 45 |
+
|
| 46 |
+
var upgrader = websocket.Upgrader{
|
| 47 |
+
CheckOrigin: func(r *http.Request) bool {
|
| 48 |
+
return true // 允许所有来源,方便演示
|
| 49 |
+
},
|
| 50 |
+
}
|
| 51 |
+
|
| 52 |
+
// 获取当前系统状态
|
| 53 |
+
func getSystemStatus() SystemStatus {
|
| 54 |
+
// CPU 使用率
|
| 55 |
+
cpuPercent, _ := cpu.Percent(time.Second, false)
|
| 56 |
+
var cpuVal float64
|
| 57 |
+
if len(cpuPercent) > 0 {
|
| 58 |
+
cpuVal = cpuPercent[0]
|
| 59 |
+
}
|
| 60 |
+
|
| 61 |
+
// 内存使用率
|
| 62 |
+
vMem, _ := mem.VirtualMemory()
|
| 63 |
+
memVal := vMem.UsedPercent
|
| 64 |
+
|
| 65 |
+
// 磁盘使用率 (根目录)
|
| 66 |
+
dUsage, _ := disk.Usage("/")
|
| 67 |
+
diskVal := dUsage.UsedPercent
|
| 68 |
+
|
| 69 |
+
// 获取主机信息
|
| 70 |
+
hInfo, _ := host.Info()
|
| 71 |
+
hostSummary := HostSummary{
|
| 72 |
+
Hostname: hInfo.Hostname,
|
| 73 |
+
OS: hInfo.OS,
|
| 74 |
+
Platform: hInfo.Platform,
|
| 75 |
+
Uptime: hInfo.Uptime,
|
| 76 |
+
}
|
| 77 |
+
|
| 78 |
+
// 获取所有进程并按 CPU 排序
|
| 79 |
+
allProcs, _ := process.Processes()
|
| 80 |
+
var procList []Process
|
| 81 |
+
for _, p := range allProcs {
|
| 82 |
+
name, _ := p.Name()
|
| 83 |
+
cpuP, _ := p.CPUPercent()
|
| 84 |
+
memP, _ := p.MemoryPercent()
|
| 85 |
+
if cpuP > 0 || memP > 0 {
|
| 86 |
+
procList = append(procList, Process{
|
| 87 |
+
PID: p.Pid,
|
| 88 |
+
Name: name,
|
| 89 |
+
CPU: cpuP,
|
| 90 |
+
Memory: memP,
|
| 91 |
+
})
|
| 92 |
+
}
|
| 93 |
+
}
|
| 94 |
+
|
| 95 |
+
// 按 CPU 使用率降序排序
|
| 96 |
+
sort.Slice(procList, func(i, j int) bool {
|
| 97 |
+
return procList[i].CPU > procList[j].CPU
|
| 98 |
+
})
|
| 99 |
+
|
| 100 |
+
return SystemStatus{
|
| 101 |
+
CPUUsage: cpuVal,
|
| 102 |
+
MemoryUsage: memVal,
|
| 103 |
+
DiskUsage: diskVal,
|
| 104 |
+
Processes: procList[:min(10, len(procList))], // 取前10个
|
| 105 |
+
Timestamp: time.Now().Format("15:04:05"),
|
| 106 |
+
HostInfo: hostSummary,
|
| 107 |
+
}
|
| 108 |
+
}
|
| 109 |
+
|
| 110 |
+
func min(a, b int) int {
|
| 111 |
+
if a < b {
|
| 112 |
+
return a
|
| 113 |
+
}
|
| 114 |
+
return b
|
| 115 |
+
}
|
| 116 |
+
|
| 117 |
+
// 处理 WebSocket 连接
|
| 118 |
+
func handleWebSocket(w http.ResponseWriter, r *http.Request) {
|
| 119 |
+
conn, err := upgrader.Upgrade(w, r, nil)
|
| 120 |
+
if err != nil {
|
| 121 |
+
log.Println("升级 WebSocket 失败:", err)
|
| 122 |
+
return
|
| 123 |
+
}
|
| 124 |
+
defer conn.Close()
|
| 125 |
+
|
| 126 |
+
log.Println("新的客户端已连接")
|
| 127 |
+
|
| 128 |
+
// 定期发送系统数据
|
| 129 |
+
for {
|
| 130 |
+
status := getSystemStatus()
|
| 131 |
+
data, err := json.Marshal(status)
|
| 132 |
+
if err != nil {
|
| 133 |
+
log.Println("序列化数据失败:", err)
|
| 134 |
+
break
|
| 135 |
+
}
|
| 136 |
+
|
| 137 |
+
if err := conn.WriteMessage(websocket.TextMessage, data); err != nil {
|
| 138 |
+
log.Println("发送消息失败:", err)
|
| 139 |
+
break
|
| 140 |
+
}
|
| 141 |
+
|
| 142 |
+
time.Sleep(2 * time.Second) // 每2秒更新一次
|
| 143 |
+
}
|
| 144 |
+
}
|
| 145 |
+
|
| 146 |
+
func main() {
|
| 147 |
+
// 静态文件服务
|
| 148 |
+
fs := http.FileServer(http.Dir("./static"))
|
| 149 |
+
http.Handle("/", fs)
|
| 150 |
+
|
| 151 |
+
// WebSocket 路由
|
| 152 |
+
http.HandleFunc("/ws", handleWebSocket)
|
| 153 |
+
|
| 154 |
+
port := os.Getenv("PORT")
|
| 155 |
+
if port == "" {
|
| 156 |
+
port = "7860" // 默认端口 7860 (Hugging Face Spaces)
|
| 157 |
+
}
|
| 158 |
+
|
| 159 |
+
fmt.Printf("服务器启动在 http://0.0.0.0:%s\n", port)
|
| 160 |
+
fmt.Println("正在监控系统性能...")
|
| 161 |
+
|
| 162 |
+
if err := http.ListenAndServe(":"+port, nil); err != nil {
|
| 163 |
+
log.Fatal("服务器启动失败:", err)
|
| 164 |
+
}
|
| 165 |
+
}
|
static/app.js
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// 初始化图表配置
|
| 2 |
+
function createChart(id, label, color) {
|
| 3 |
+
const ctx = document.getElementById(id).getContext('2d');
|
| 4 |
+
return new Chart(ctx, {
|
| 5 |
+
type: 'doughnut',
|
| 6 |
+
data: {
|
| 7 |
+
labels: [label, '可用'],
|
| 8 |
+
datasets: [{
|
| 9 |
+
data: [0, 100],
|
| 10 |
+
backgroundColor: [color, '#eee'],
|
| 11 |
+
borderWidth: 0
|
| 12 |
+
}]
|
| 13 |
+
},
|
| 14 |
+
options: {
|
| 15 |
+
cutout: '70%',
|
| 16 |
+
responsive: true,
|
| 17 |
+
plugins: {
|
| 18 |
+
legend: { display: false }
|
| 19 |
+
}
|
| 20 |
+
}
|
| 21 |
+
});
|
| 22 |
+
}
|
| 23 |
+
|
| 24 |
+
const cpuChart = createChart('cpuChart', 'CPU', '#3498db');
|
| 25 |
+
const memChart = createChart('memChart', '内存', '#2ecc71');
|
| 26 |
+
const diskChart = createChart('diskChart', '磁盘', '#e67e22');
|
| 27 |
+
|
| 28 |
+
function formatUptime(seconds) {
|
| 29 |
+
const days = Math.floor(seconds / 86400);
|
| 30 |
+
const hours = Math.floor((seconds % 86400) / 3600);
|
| 31 |
+
const minutes = Math.floor((seconds % 3600) / 60);
|
| 32 |
+
let res = "";
|
| 33 |
+
if (days > 0) res += days + "天 ";
|
| 34 |
+
if (hours > 0) res += hours + "时 ";
|
| 35 |
+
res += minutes + "分";
|
| 36 |
+
return res;
|
| 37 |
+
}
|
| 38 |
+
|
| 39 |
+
// 连接 WebSocket
|
| 40 |
+
function connect() {
|
| 41 |
+
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
|
| 42 |
+
const ws = new WebSocket(`${protocol}//${window.location.host}/ws`);
|
| 43 |
+
|
| 44 |
+
ws.onmessage = (event) => {
|
| 45 |
+
const data = JSON.parse(event.data);
|
| 46 |
+
|
| 47 |
+
// 更新文本显示
|
| 48 |
+
document.getElementById('cpu-val').innerText = `${data.cpuUsage.toFixed(1)}%`;
|
| 49 |
+
document.getElementById('mem-val').innerText = `${data.memoryUsage.toFixed(1)}%`;
|
| 50 |
+
document.getElementById('disk-val').innerText = `${data.diskUsage.toFixed(1)}%`;
|
| 51 |
+
document.getElementById('time-display').innerText = `最后更新: ${data.timestamp}`;
|
| 52 |
+
|
| 53 |
+
// 更新主机信息
|
| 54 |
+
if (data.hostInfo) {
|
| 55 |
+
document.getElementById('hostname').innerText = data.hostInfo.hostname;
|
| 56 |
+
document.getElementById('os-info').innerText = data.hostInfo.os;
|
| 57 |
+
document.getElementById('platform').innerText = data.hostInfo.platform;
|
| 58 |
+
document.getElementById('uptime').innerText = formatUptime(data.hostInfo.uptime);
|
| 59 |
+
}
|
| 60 |
+
|
| 61 |
+
// 更新图表
|
| 62 |
+
cpuChart.data.datasets[0].data = [data.cpuUsage, 100 - data.cpuUsage];
|
| 63 |
+
cpuChart.update();
|
| 64 |
+
|
| 65 |
+
memChart.data.datasets[0].data = [data.memoryUsage, 100 - data.memoryUsage];
|
| 66 |
+
memChart.update();
|
| 67 |
+
|
| 68 |
+
diskChart.data.datasets[0].data = [data.diskUsage, 100 - data.diskUsage];
|
| 69 |
+
diskChart.update();
|
| 70 |
+
|
| 71 |
+
// 更新进程列表
|
| 72 |
+
const processList = document.getElementById('process-list');
|
| 73 |
+
processList.innerHTML = '';
|
| 74 |
+
data.processes.forEach(p => {
|
| 75 |
+
const row = `<tr>
|
| 76 |
+
<td>${p.pid}</td>
|
| 77 |
+
<td>${p.name}</td>
|
| 78 |
+
<td>${p.cpu.toFixed(1)}</td>
|
| 79 |
+
<td>${p.memory.toFixed(1)}</td>
|
| 80 |
+
</tr>`;
|
| 81 |
+
processList.innerHTML += row;
|
| 82 |
+
});
|
| 83 |
+
};
|
| 84 |
+
|
| 85 |
+
ws.onopen = () => {
|
| 86 |
+
console.log('WebSocket 连接已建立');
|
| 87 |
+
document.querySelector('.status-dot').style.backgroundColor = '#2ecc71';
|
| 88 |
+
};
|
| 89 |
+
|
| 90 |
+
ws.onclose = () => {
|
| 91 |
+
console.log('WebSocket 连接已关闭,尝试重连...');
|
| 92 |
+
document.querySelector('.status-dot').style.backgroundColor = '#e74c3c';
|
| 93 |
+
setTimeout(connect, 3000); // 3秒后重连
|
| 94 |
+
};
|
| 95 |
+
|
| 96 |
+
ws.onerror = (err) => {
|
| 97 |
+
console.error('WebSocket 错误:', err);
|
| 98 |
+
};
|
| 99 |
+
}
|
| 100 |
+
|
| 101 |
+
connect();
|
static/index.html
ADDED
|
@@ -0,0 +1,162 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!DOCTYPE html>
|
| 2 |
+
<html lang="zh-CN">
|
| 3 |
+
<head>
|
| 4 |
+
<meta charset="UTF-8">
|
| 5 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 6 |
+
<title>Go 系统监控仪表板</title>
|
| 7 |
+
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
| 8 |
+
<style>
|
| 9 |
+
:root {
|
| 10 |
+
--primary-color: #3498db;
|
| 11 |
+
--bg-color: #f4f7f6;
|
| 12 |
+
--card-bg: #ffffff;
|
| 13 |
+
--text-color: #2c3e50;
|
| 14 |
+
}
|
| 15 |
+
|
| 16 |
+
body {
|
| 17 |
+
font-family: 'PingFang SC', 'Microsoft YaHei', sans-serif;
|
| 18 |
+
background-color: var(--bg-color);
|
| 19 |
+
color: var(--text-color);
|
| 20 |
+
margin: 0;
|
| 21 |
+
padding: 20px;
|
| 22 |
+
}
|
| 23 |
+
|
| 24 |
+
.container {
|
| 25 |
+
max-width: 1200px;
|
| 26 |
+
margin: 0 auto;
|
| 27 |
+
}
|
| 28 |
+
|
| 29 |
+
header {
|
| 30 |
+
text-align: center;
|
| 31 |
+
margin-bottom: 30px;
|
| 32 |
+
}
|
| 33 |
+
|
| 34 |
+
.dashboard-grid {
|
| 35 |
+
display: grid;
|
| 36 |
+
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
| 37 |
+
gap: 20px;
|
| 38 |
+
margin-bottom: 30px;
|
| 39 |
+
}
|
| 40 |
+
|
| 41 |
+
.info-bar {
|
| 42 |
+
background-color: var(--card-bg);
|
| 43 |
+
border-radius: 12px;
|
| 44 |
+
padding: 15px 20px;
|
| 45 |
+
margin-bottom: 20px;
|
| 46 |
+
display: flex;
|
| 47 |
+
justify-content: space-around;
|
| 48 |
+
flex-wrap: wrap;
|
| 49 |
+
box-shadow: 0 4px 6px rgba(0,0,0,0.1);
|
| 50 |
+
font-size: 0.9em;
|
| 51 |
+
}
|
| 52 |
+
|
| 53 |
+
.info-item {
|
| 54 |
+
margin: 5px 15px;
|
| 55 |
+
}
|
| 56 |
+
|
| 57 |
+
.info-item strong {
|
| 58 |
+
color: var(--primary-color);
|
| 59 |
+
}
|
| 60 |
+
|
| 61 |
+
.card {
|
| 62 |
+
background-color: var(--card-bg);
|
| 63 |
+
border-radius: 12px;
|
| 64 |
+
padding: 20px;
|
| 65 |
+
box-shadow: 0 4px 6px rgba(0,0,0,0.1);
|
| 66 |
+
text-align: center;
|
| 67 |
+
}
|
| 68 |
+
|
| 69 |
+
.card h3 {
|
| 70 |
+
margin-top: 0;
|
| 71 |
+
color: var(--primary-color);
|
| 72 |
+
}
|
| 73 |
+
|
| 74 |
+
.process-table {
|
| 75 |
+
width: 100%;
|
| 76 |
+
border-collapse: collapse;
|
| 77 |
+
background-color: var(--card-bg);
|
| 78 |
+
border-radius: 12px;
|
| 79 |
+
overflow: hidden;
|
| 80 |
+
box-shadow: 0 4px 6px rgba(0,0,0,0.1);
|
| 81 |
+
}
|
| 82 |
+
|
| 83 |
+
.process-table th, .process-table td {
|
| 84 |
+
padding: 12px 15px;
|
| 85 |
+
text-align: left;
|
| 86 |
+
border-bottom: 1px solid #eee;
|
| 87 |
+
}
|
| 88 |
+
|
| 89 |
+
.process-table th {
|
| 90 |
+
background-color: var(--primary-color);
|
| 91 |
+
color: white;
|
| 92 |
+
}
|
| 93 |
+
|
| 94 |
+
.status-dot {
|
| 95 |
+
height: 10px;
|
| 96 |
+
width: 10px;
|
| 97 |
+
background-color: #2ecc71;
|
| 98 |
+
border-radius: 50%;
|
| 99 |
+
display: inline-block;
|
| 100 |
+
margin-right: 5px;
|
| 101 |
+
}
|
| 102 |
+
|
| 103 |
+
@media (max-width: 600px) {
|
| 104 |
+
body { padding: 10px; }
|
| 105 |
+
.dashboard-grid { grid-template-columns: 1fr; }
|
| 106 |
+
.process-table { font-size: 14px; }
|
| 107 |
+
}
|
| 108 |
+
</style>
|
| 109 |
+
</head>
|
| 110 |
+
<body>
|
| 111 |
+
<div class="container">
|
| 112 |
+
<header>
|
| 113 |
+
<h1><span class="status-dot"></span>系统实时监控</h1>
|
| 114 |
+
<p id="time-display">正在获取时间...</p>
|
| 115 |
+
</header>
|
| 116 |
+
|
| 117 |
+
<div class="info-bar" id="host-info">
|
| 118 |
+
<div class="info-item"><strong>主机名:</strong> <span id="hostname">-</span></div>
|
| 119 |
+
<div class="info-item"><strong>系统:</strong> <span id="os-info">-</span></div>
|
| 120 |
+
<div class="info-item"><strong>平台:</strong> <span id="platform">-</span></div>
|
| 121 |
+
<div class="info-item"><strong>运行时间:</strong> <span id="uptime">-</span></div>
|
| 122 |
+
</div>
|
| 123 |
+
|
| 124 |
+
<div class="dashboard-grid">
|
| 125 |
+
<div class="card">
|
| 126 |
+
<h3>CPU 使用率</h3>
|
| 127 |
+
<canvas id="cpuChart"></canvas>
|
| 128 |
+
<p id="cpu-val">0%</p>
|
| 129 |
+
</div>
|
| 130 |
+
<div class="card">
|
| 131 |
+
<h3>内存 使用率</h3>
|
| 132 |
+
<canvas id="memChart"></canvas>
|
| 133 |
+
<p id="mem-val">0%</p>
|
| 134 |
+
</div>
|
| 135 |
+
<div class="card">
|
| 136 |
+
<h3>磁盘 使用率</h3>
|
| 137 |
+
<canvas id="diskChart"></canvas>
|
| 138 |
+
<p id="disk-val">0%</p>
|
| 139 |
+
</div>
|
| 140 |
+
</div>
|
| 141 |
+
|
| 142 |
+
<div class="card">
|
| 143 |
+
<h3>活跃进程 (Top 10)</h3>
|
| 144 |
+
<table class="process-table">
|
| 145 |
+
<thead>
|
| 146 |
+
<tr>
|
| 147 |
+
<th>PID</th>
|
| 148 |
+
<th>名称</th>
|
| 149 |
+
<th>CPU %</th>
|
| 150 |
+
<th>内存 %</th>
|
| 151 |
+
</tr>
|
| 152 |
+
</thead>
|
| 153 |
+
<tbody id="process-list">
|
| 154 |
+
<!-- 动态加载 -->
|
| 155 |
+
</tbody>
|
| 156 |
+
</table>
|
| 157 |
+
</div>
|
| 158 |
+
</div>
|
| 159 |
+
|
| 160 |
+
<script src="app.js"></script>
|
| 161 |
+
</body>
|
| 162 |
+
</html>
|