feat(networks): Live-Traffic-Zähler in System-Interfaces-Card

GET /system/interfaces liefert jetzt rx_bytes/tx_bytes/rx_packets/
tx_packets/rx_drop/tx_drop aus /proc/net/dev. Die System-Interfaces-
Card in Netzwerk → Interfaces zeigt die Werte als Mini-Tabelle mit
10s-Refetch; Hover auf den Bytes-Werten zeigt Paketzähler + Drops.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Debian
2026-05-20 14:09:37 +02:00
parent e4b66cfcac
commit bd6e67f059
5 changed files with 130 additions and 31 deletions

View File

@@ -765,13 +765,58 @@ type addrInfo struct {
}
type interfaceInfo struct {
IfIndex int `json:"ifindex"`
IfName string `json:"ifname"`
Flags []string `json:"flags"`
MTU int `json:"mtu"`
LinkType string `json:"link_type,omitempty"`
Address string `json:"address,omitempty"`
AddrInfo []addrInfo `json:"addr_info"`
IfIndex int `json:"ifindex"`
IfName string `json:"ifname"`
Flags []string `json:"flags"`
MTU int `json:"mtu"`
LinkType string `json:"link_type,omitempty"`
Address string `json:"address,omitempty"`
AddrInfo []addrInfo `json:"addr_info"`
RxBytes int64 `json:"rx_bytes"`
TxBytes int64 `json:"tx_bytes"`
RxPackets int64 `json:"rx_packets"`
TxPackets int64 `json:"tx_packets"`
RxDrop int64 `json:"rx_drop"`
TxDrop int64 `json:"tx_drop"`
}
type netDevStat struct {
RxBytes, TxBytes int64
RxPackets, TxPackets int64
RxDrop, TxDrop int64
}
// readNetDevStats parses /proc/net/dev and returns per-interface counters.
// Missing or unreadable: returns empty map (caller gets zero-value stats).
func readNetDevStats() map[string]netDevStat {
out := map[string]netDevStat{}
data, err := os.ReadFile("/proc/net/dev")
if err != nil {
return out
}
scanner := bufio.NewScanner(strings.NewReader(string(data)))
for scanner.Scan() {
line := strings.TrimSpace(scanner.Text())
idx := strings.Index(line, ":")
if idx < 0 {
continue
}
name := strings.TrimSpace(line[:idx])
fields := strings.Fields(line[idx+1:])
if len(fields) < 12 {
continue
}
p := func(s string) int64 { n, _ := strconv.ParseInt(s, 10, 64); return n }
out[name] = netDevStat{
RxBytes: p(fields[0]),
RxPackets: p(fields[1]),
RxDrop: p(fields[3]),
TxBytes: p(fields[8]),
TxPackets: p(fields[9]),
TxDrop: p(fields[11]),
}
}
return out
}
// Interfaces enumerates the kernel-side network interfaces using
@@ -788,16 +833,24 @@ func (h *SystemHandler) Interfaces(c *gin.Context) {
response.OK(c, gin.H{"interfaces": []interfaceInfo{}})
return
}
devStats := readNetDevStats()
out := make([]interfaceInfo, 0, len(ifaces))
for _, ifc := range ifaces {
st := devStats[ifc.Name]
info := interfaceInfo{
IfIndex: ifc.Index,
IfName: ifc.Name,
MTU: ifc.MTU,
Address: ifc.HardwareAddr.String(),
LinkType: classifyLinkType(ifc),
Flags: flagsToList(ifc.Flags),
AddrInfo: []addrInfo{},
IfIndex: ifc.Index,
IfName: ifc.Name,
MTU: ifc.MTU,
Address: ifc.HardwareAddr.String(),
LinkType: classifyLinkType(ifc),
Flags: flagsToList(ifc.Flags),
AddrInfo: []addrInfo{},
RxBytes: st.RxBytes,
TxBytes: st.TxBytes,
RxPackets: st.RxPackets,
TxPackets: st.TxPackets,
RxDrop: st.RxDrop,
TxDrop: st.TxDrop,
}
addrs, err := ifc.Addrs()
if err != nil {