feat(dashboard): single-view grid with live CPU/RAM/Net graphs, API quota, processes, and sudo indicator
All checks were successful
Beta Release / beta (push) Successful in 46s
All checks were successful
Beta Release / beta (push) Successful in 46s
- Rewrite dashboard from 4 tabs to single grid view with 5s auto-refresh - Add live CPU/RAM/Network SVG graphs with rolling 30-point history - Add backend /api/system/metrics reading /proc/stat, /proc/meminfo, /proc/net/dev - Add backend /api/providers/quota for MiniMax and Z.AI quota monitoring - Add backend /api/recent-commands reading bash/zsh history - Add backend /api/running-processes filtering editors/IDEs/languages - Add sudo/root indicator (⚡ ROOT) in footer when running as root - Remove duplicate Ctrl+1-4 shortcut from page-specific footer (keep only right side) - Add Ctrl+R shortcut on dashboard for metrics-only refresh - Make API key mandatory in onboarding, auto-scan editors via AI chat - Remove manual editor input, only show AI-detected editors - Bump version to 0.3.3 💘 Generated with Crush Assisted-by: GLM-5.1 via Crush <crush@charm.land>
This commit is contained in:
@@ -23,6 +23,7 @@ func (s *Server) handleInfo(w http.ResponseWriter, r *http.Request) {
|
||||
"name": version.Name,
|
||||
"version": version.Version,
|
||||
"author": version.Author,
|
||||
"sudo": os.Geteuid() == 0,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -609,3 +610,111 @@ func (s *Server) handleRunningProcesses(w http.ResponseWriter, r *http.Request)
|
||||
|
||||
writeJSON(w, map[string]interface{}{"processes": procs})
|
||||
}
|
||||
|
||||
type sysMetrics struct {
|
||||
CPUPercent float64 `json:"cpu_percent"`
|
||||
MemPercent float64 `json:"mem_percent"`
|
||||
MemUsedMB float64 `json:"mem_used_mb"`
|
||||
MemTotalMB float64 `json:"mem_total_mb"`
|
||||
NetRxKBs float64 `json:"net_rx_kbs"`
|
||||
NetTxKBs float64 `json:"net_tx_kbs"`
|
||||
}
|
||||
|
||||
var (
|
||||
lastCPU [2]float64
|
||||
lastNet [2]float64
|
||||
lastNetTs time.Time
|
||||
lastCPUSet bool
|
||||
)
|
||||
|
||||
func (s *Server) handleSystemMetrics(w http.ResponseWriter, r *http.Request) {
|
||||
m := sysMetrics{}
|
||||
|
||||
// CPU from /proc/stat
|
||||
if data, err := os.ReadFile("/proc/stat"); err == nil {
|
||||
line := strings.Split(string(data), "\n")[0]
|
||||
fields := strings.Fields(line)
|
||||
if len(fields) >= 5 {
|
||||
var idle, total float64
|
||||
for i := 1; i < len(fields) && i <= 4; i++ {
|
||||
var v float64
|
||||
fmt.Sscanf(fields[i], "%f", &v)
|
||||
total += v
|
||||
if i == 4 {
|
||||
idle = v
|
||||
}
|
||||
}
|
||||
if lastCPUSet {
|
||||
dIdle := idle - lastCPU[0]
|
||||
dTotal := total - lastCPU[1]
|
||||
if dTotal > 0 {
|
||||
m.CPUPercent = (1 - dIdle/dTotal) * 100
|
||||
}
|
||||
}
|
||||
lastCPU = [2]float64{idle, total}
|
||||
lastCPUSet = true
|
||||
}
|
||||
}
|
||||
|
||||
// Memory from /proc/meminfo
|
||||
if data, err := os.ReadFile("/proc/meminfo"); err == nil {
|
||||
var memTotal, memAvailable float64
|
||||
for _, line := range strings.Split(string(data), "\n") {
|
||||
fields := strings.Fields(line)
|
||||
if len(fields) < 2 {
|
||||
continue
|
||||
}
|
||||
var v float64
|
||||
fmt.Sscanf(fields[1], "%f", &v)
|
||||
switch fields[0] {
|
||||
case "MemTotal:":
|
||||
memTotal = v
|
||||
case "MemAvailable:":
|
||||
memAvailable = v
|
||||
}
|
||||
}
|
||||
if memTotal > 0 {
|
||||
m.MemTotalMB = memTotal / 1024
|
||||
m.MemUsedMB = (memTotal - memAvailable) / 1024
|
||||
m.MemPercent = (memTotal - memAvailable) / memTotal * 100
|
||||
}
|
||||
}
|
||||
|
||||
// Network from /proc/net/dev
|
||||
if data, err := os.ReadFile("/proc/net/dev"); err == nil {
|
||||
var rxBytes, txBytes float64
|
||||
for _, line := range strings.Split(string(data), "\n")[2:] {
|
||||
fields := strings.Fields(line)
|
||||
if len(fields) < 10 {
|
||||
continue
|
||||
}
|
||||
iface := strings.TrimSuffix(fields[0], ":")
|
||||
if iface == "lo" {
|
||||
continue
|
||||
}
|
||||
var rx, tx float64
|
||||
fmt.Sscanf(fields[1], "%f", &rx)
|
||||
fmt.Sscanf(fields[9], "%f", &tx)
|
||||
rxBytes += rx
|
||||
txBytes += tx
|
||||
}
|
||||
now := time.Now()
|
||||
if !lastNetTs.IsZero() {
|
||||
elapsed := now.Sub(lastNetTs).Seconds()
|
||||
if elapsed > 0 {
|
||||
m.NetRxKBs = (rxBytes - lastNet[0]) / 1024 / elapsed
|
||||
m.NetTxKBs = (txBytes - lastNet[1]) / 1024 / elapsed
|
||||
if m.NetRxKBs < 0 {
|
||||
m.NetRxKBs = 0
|
||||
}
|
||||
if m.NetTxKBs < 0 {
|
||||
m.NetTxKBs = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
lastNet = [2]float64{rxBytes, txBytes}
|
||||
lastNetTs = now
|
||||
}
|
||||
|
||||
writeJSON(w, m)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user