feat(netmgrd): 播放器流量统计细化 — A.实时速率去lo(uplink_totals) C1.netlink sock_diag TCP流公网/内网字节(pub_bytes)=status+panel展示+诚实标注;UDP暂不计(tcp_only),netmgrd宿主70/70pass

This commit is contained in:
AskaEth committed 2026-09-03 18:00:39 +08:00
1 parent dce12c63a2
commit 26a140e00f
2 files changed
+325 -26

No files matched your search

+43 -1
View File
@@ -747,6 +747,12 @@
} else {
rows += homeKv('DNS', '<span class="home-chip mut">—</span>');
}
const pb = (nd && nd.pub_bytes) || null;
if (pb && pb.total) {
rows += homeKv('公网流量', '↓ ' + fmtBytes(pb.total.rx || 0) + ' · ↑ ' + fmtBytes(pb.total.tx || 0) + '<span class="muted">(TCP)</span>');
} else {
rows += homeKv('公网流量', '<span class="home-chip mut">—</span>');
}
return homeCard('WiFi / DNS', '<div class="home-card-grid">' + rows + '</div>');
}
function healthCard() {
@@ -1269,6 +1275,7 @@
meta += (ifd.rssi != null) ? '信号 ' + esc(ifd.rssi) + ' dBm' : '信号 —';
if (ifd.dns && ifd.dns.length) meta += ' · DNS ' + esc(ifd.dns.join(' / '));
meta += '</div>';
h += '<p class="muted" style="margin-bottom:8px">RX / TX 为接口累计字节(含局域网/面板轮询/DNS 等内网往返),非手机热点公网消耗。</p>';
h += meta;
h += '<table class="table"><thead><tr><th>接口</th><th>类型</th><th>状态</th><th>IP 地址</th><th>网关</th><th>RX / TX</th></tr></thead><tbody>';
if (!ifs.length) h += '<tr><td colspan="6" class="empty">无接口数据</td></tr>';
@@ -1289,11 +1296,46 @@
h += '</div>';
return h;
}
/* 公网流量卡(netmgrd pub_bytes:netlink sock_diag 按 TCP 连接累积,远端公网/内网分流)。
* 口径诚实:仅 TCP;UDP/DNS/mDNS/ARP 不计;内网本链路(面板轮询等)归 lan。 */
function pubBytesCard(pb) {
if (!pb) return '';
const t = pb.total || {};
let h = '<div class="card"><h3 class="card-title">公网流量</h3>';
h += '<p class="muted">按 TCP 连接计量(远端公网才计入);UDP/DNS/mDNS/ARP 不计。接口累计含内网往返,非公网消耗。</p>';
h += '<div class="home-sub" style="margin-bottom:8px">公网累计:↓ ' + fmtBytes(t.rx || 0) + ' · ↑ ' + fmtBytes(t.tx || 0) + '</div>';
const ents = pb.by_entity || [];
if (ents.length) {
h += '<table class="table"><thead><tr><th>实体</th><th>公网 ↓ / ↑</th><th>内网 ↓ / ↑</th></tr></thead><tbody>';
ents.forEach(e => {
h += '<tr><td class="net-entity">' + netBadge(e.entity) + '</td>' +
'<td class="muted">' + fmtBytes((e.pub && e.pub[0]) || 0) + ' / ' + fmtBytes((e.pub && e.pub[1]) || 0) + '</td>' +
'<td class="muted">' + fmtBytes((e.lan && e.lan[0]) || 0) + ' / ' + fmtBytes((e.lan && e.lan[1]) || 0) + '</td></tr>';
});
h += '</tbody></table>';
}
const flows = pb.by_flow || [];
if (flows.length) {
h += '<div class="home-sub" style="margin-top:10px">公网流 Top</div>';
h += '<table class="table"><thead><tr><th>远端</th><th>实体</th><th>↓ / ↑</th></tr></thead><tbody>';
flows.forEach(f => {
h += '<tr><td class="mono">' + esc(f.ip || '') + ':' + esc('' + f.port) +
(f.domain ? ' <span class="muted">' + esc(f.domain) + '</span>' : '') + '</td>' +
'<td class="net-entity">' + netBadge(f.entity) + '</td>' +
'<td class="muted">' + fmtBytes(f.rx || 0) + ' / ' + fmtBytes(f.tx || 0) + '</td></tr>';
});
h += '</tbody></table>';
}
h += '</div>';
return h;
}
/* WiFi / 蓝牙摘要(/api/net/status:E0 概览,含实时速率)。接口细分已迁 netIfaceCards。 */
function netStatusCards(d) {
let h = '';
if (d && d.rate && (d.rate.rx_bps != null || d.rate.tx_bps != null))
h += '<div class="card"><p class="muted">实时速率:↓ ' + fmtBytes(d.rate.rx_bps) + '/s · ↑ ' + fmtBytes(d.rate.tx_bps) + '/s</p></div>';
h += '<div class="card"><p class="muted">实时速率(上行接口,不含回环):↓ ' + fmtBytes(d.rate.rx_bps) + '/s · ↑ ' + fmtBytes(d.rate.tx_bps) + '/s</p></div>';
if (d && d.pub_bytes) h += pubBytesCard(d.pub_bytes);
// WiFi 摘要
const w = (d && d.wifi) || {};
h += '<div class="card"><h3 class="card-title">WiFi</h3><table class="table"><tbody>';