feat(dashboard): richer System History & Xray Metrics charts

- Collect disk read/write and network packet-rate metrics on the host sampler
- Sparkline: optional 2nd/3rd overlaid series with a colored legend
- System History: merge Bandwidth (up/down), Disk I/O (read/write) and Load (1m/5m/15m) into single multi-line tabs
- Add a descriptive per-chart title and mobile-only tab icons to both modals
- Localize every chart title and tab label across all 13 languages
This commit is contained in:
MHSanaei
2026-06-03 11:25:45 +02:00
parent a4dae566ce
commit 4b11c54206
21 changed files with 591 additions and 36 deletions
+55 -5
View File
@@ -67,6 +67,14 @@ type Status struct {
Current uint64 `json:"current"`
Total uint64 `json:"total"`
} `json:"disk"`
DiskIO struct {
Read uint64 `json:"read"`
Write uint64 `json:"write"`
} `json:"diskIO"`
DiskTraffic struct {
Read uint64 `json:"read"`
Write uint64 `json:"write"`
} `json:"diskTraffic"`
Xray struct {
State ProcessState `json:"state"`
ErrorMsg string `json:"errorMsg"`
@@ -78,12 +86,16 @@ type Status struct {
TcpCount int `json:"tcpCount"`
UdpCount int `json:"udpCount"`
NetIO struct {
Up uint64 `json:"up"`
Down uint64 `json:"down"`
Up uint64 `json:"up"`
Down uint64 `json:"down"`
PktUp uint64 `json:"pktUp"`
PktDown uint64 `json:"pktDown"`
} `json:"netIO"`
NetTraffic struct {
Sent uint64 `json:"sent"`
Recv uint64 `json:"recv"`
Sent uint64 `json:"sent"`
Recv uint64 `json:"recv"`
PktSent uint64 `json:"pktSent"`
PktRecv uint64 `json:"pktRecv"`
} `json:"netTraffic"`
PublicIP struct {
IPv4 string `json:"ipv4"`
@@ -383,6 +395,30 @@ func (s *ServerService) GetStatus(lastStatus *Status) *Status {
status.Disk.Total = diskInfo.Total
}
diskIOStats, err := disk.IOCounters()
if err != nil {
logger.Warning("get disk io counters failed:", err)
} else {
var totalRead, totalWrite uint64
for _, counter := range diskIOStats {
totalRead += counter.ReadBytes
totalWrite += counter.WriteBytes
}
status.DiskTraffic.Read = totalRead
status.DiskTraffic.Write = totalWrite
if lastStatus != nil {
duration := now.Sub(lastStatus.T)
seconds := float64(duration) / float64(time.Second)
if seconds > 0 && status.DiskTraffic.Read >= lastStatus.DiskTraffic.Read {
status.DiskIO.Read = uint64(float64(status.DiskTraffic.Read-lastStatus.DiskTraffic.Read) / seconds)
}
if seconds > 0 && status.DiskTraffic.Write >= lastStatus.DiskTraffic.Write {
status.DiskIO.Write = uint64(float64(status.DiskTraffic.Write-lastStatus.DiskTraffic.Write) / seconds)
}
}
}
// Load averages
avgState, err := load.Avg()
if err != nil {
@@ -396,7 +432,7 @@ func (s *ServerService) GetStatus(lastStatus *Status) *Status {
if err != nil {
logger.Warning("get io counters failed:", err)
} else {
var totalSent, totalRecv uint64
var totalSent, totalRecv, totalPktSent, totalPktRecv uint64
for _, iface := range ioStats {
name := strings.ToLower(iface.Name)
if isVirtualInterface(name) {
@@ -404,9 +440,13 @@ func (s *ServerService) GetStatus(lastStatus *Status) *Status {
}
totalSent += iface.BytesSent
totalRecv += iface.BytesRecv
totalPktSent += iface.PacketsSent
totalPktRecv += iface.PacketsRecv
}
status.NetTraffic.Sent = totalSent
status.NetTraffic.Recv = totalRecv
status.NetTraffic.PktSent = totalPktSent
status.NetTraffic.PktRecv = totalPktRecv
if lastStatus != nil {
duration := now.Sub(lastStatus.T)
@@ -415,6 +455,12 @@ func (s *ServerService) GetStatus(lastStatus *Status) *Status {
down := uint64(float64(status.NetTraffic.Recv-lastStatus.NetTraffic.Recv) / seconds)
status.NetIO.Up = up
status.NetIO.Down = down
if seconds > 0 && status.NetTraffic.PktSent >= lastStatus.NetTraffic.PktSent {
status.NetIO.PktUp = uint64(float64(status.NetTraffic.PktSent-lastStatus.NetTraffic.PktSent) / seconds)
}
if seconds > 0 && status.NetTraffic.PktRecv >= lastStatus.NetTraffic.PktRecv {
status.NetIO.PktDown = uint64(float64(status.NetTraffic.PktRecv-lastStatus.NetTraffic.PktRecv) / seconds)
}
}
}
@@ -521,6 +567,10 @@ func (s *ServerService) AppendStatusSample(t time.Time, status *Status) {
}
systemMetrics.append("netUp", t, float64(status.NetIO.Up))
systemMetrics.append("netDown", t, float64(status.NetIO.Down))
systemMetrics.append("diskRead", t, float64(status.DiskIO.Read))
systemMetrics.append("diskWrite", t, float64(status.DiskIO.Write))
systemMetrics.append("pktUp", t, float64(status.NetIO.PktUp))
systemMetrics.append("pktDown", t, float64(status.NetIO.PktDown))
online := 0
if p != nil && p.IsRunning() {
online = len(p.GetOnlineClients())