diff --git a/web/html/component/aClientTable.html b/web/html/component/aClientTable.html index d079d237..1c24e94d 100644 --- a/web/html/component/aClientTable.html +++ b/web/html/component/aClientTable.html @@ -96,7 +96,7 @@
-
[[ SizeFormatter.sizeFormat(getSumStats(record, client.email)) ]]
+
[[ SizeFormatter.sizeFormat(getSumStats(record, client.email)) ]]
diff --git a/web/html/inbounds.html b/web/html/inbounds.html index 2c81e0da..c84dc551 100644 --- a/web/html/inbounds.html +++ b/web/html/inbounds.html @@ -182,6 +182,7 @@ {{ i18n "none" }} {{ i18n "subscription.active" @@ -2241,6 +2242,31 @@ #content-layout>.ant-layout-content>.ant-spin-nested-loading>div>.ant-spin { left: 50vw !important; } + + /* Keep filter choices in a single horizontal line on phones. */ + .inbounds-page .mobile-filter-group { + display: flex; + flex-wrap: nowrap; + max-width: 100%; + overflow-x: auto; + overflow-y: hidden; + padding-bottom: 2px; + -webkit-overflow-scrolling: touch; + scrollbar-width: thin; + } + .inbounds-page .mobile-filter-group .ant-radio-button-wrapper { + flex: 0 0 auto; + white-space: nowrap; + } + + /* Prevent mobile row content from splitting across multiple lines. */ + .inbounds-page .ant-table-tbody > tr > td { + white-space: nowrap; + } + .inbounds-page .ant-table-tbody > tr > td:nth-child(3) { + overflow: hidden; + text-overflow: ellipsis; + } } /* Protocol cell — wrap tags into a flex grid with consistent gap so diff --git a/web/service/server.go b/web/service/server.go index 0d531e12..3bb93028 100644 --- a/web/service/server.go +++ b/web/service/server.go @@ -315,13 +315,21 @@ func (s *ServerService) GetStatus(lastStatus *Status) *Status { } // Network stats - ioStats, err := net.IOCounters(false) + ioStats, err := net.IOCounters(true) if err != nil { logger.Warning("get io counters failed:", err) - } else if len(ioStats) > 0 { - ioStat := ioStats[0] - status.NetTraffic.Sent = ioStat.BytesSent - status.NetTraffic.Recv = ioStat.BytesRecv + } else { + var totalSent, totalRecv uint64 + for _, iface := range ioStats { + name := strings.ToLower(iface.Name) + if isVirtualInterface(name) { + continue + } + totalSent += iface.BytesSent + totalRecv += iface.BytesRecv + } + status.NetTraffic.Sent = totalSent + status.NetTraffic.Recv = totalRecv if lastStatus != nil { duration := now.Sub(lastStatus.T) @@ -331,8 +339,6 @@ func (s *ServerService) GetStatus(lastStatus *Status) *Status { status.NetIO.Up = up status.NetIO.Down = down } - } else { - logger.Warning("can not find io counters") } // TCP/UDP connections @@ -860,6 +866,34 @@ func (s *ServerService) GetXrayLogs( return entries } +// isVirtualInterface returns true for loopback and virtual/tunnel interfaces +// that should be excluded from network traffic statistics. +func isVirtualInterface(name string) bool { + // Exact matches + if name == "lo" || name == "lo0" { + return true + } + // Prefix matches for virtual/tunnel interfaces + virtualPrefixes := []string{ + "loopback", + "docker", + "br-", + "veth", + "virbr", + "tun", + "tap", + "wg", + "tailscale", + "zt", + } + for _, prefix := range virtualPrefixes { + if strings.HasPrefix(name, prefix) { + return true + } + } + return false +} + func logEntryContains(line string, suffixes []string) bool { for _, sfx := range suffixes { if strings.Contains(line, sfx+"]") {