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+"]") {