diff --git a/luci-app-bandix/Makefile b/luci-app-bandix/Makefile
index 4bd8186f..c7ee2848 100644
--- a/luci-app-bandix/Makefile
+++ b/luci-app-bandix/Makefile
@@ -10,8 +10,8 @@ LUCI_DEPENDS:=+luci-base +luci-lib-jsonc +curl +bandix
PKG_MAINTAINER:=timsaya
-PKG_VERSION:=0.12.5
-PKG_RELEASE:=1
+PKG_VERSION:=0.12.6
+PKG_RELEASE:=2
include $(TOPDIR)/feeds/luci/luci.mk
diff --git a/luci-app-bandix/htdocs/luci-static/resources/view/bandix/connection.js b/luci-app-bandix/htdocs/luci-static/resources/view/bandix/connection.js
index c2b6fe8d..22c33540 100644
--- a/luci-app-bandix/htdocs/luci-static/resources/view/bandix/connection.js
+++ b/luci-app-bandix/htdocs/luci-static/resources/view/bandix/connection.js
@@ -5,6 +5,18 @@
'require rpc';
'require poll';
+var BANDIX_MODAL_BG_OPENWRT2020_LIGHT = '#ffffff';
+var BANDIX_MODAL_BG_OPENWRT2020_DARK = '#2a2a2a';
+var BANDIX_MODAL_BG_MATERIAL_LIGHT = '#ffffff';
+var BANDIX_MODAL_BG_MATERIAL_DARK = '#303030';
+var BANDIX_MODAL_BG_BOOTSTRAP_LIGHT = '#ffffff';
+var BANDIX_MODAL_BG_BOOTSTRAP_DARK = '#303030';
+var BANDIX_MODAL_BG_ARGON_LIGHT = '#F4F5F7';
+var BANDIX_MODAL_BG_ARGON_DARK = '#252526';
+var BANDIX_MODAL_BG_AURORA_LIGHT = '#ffffff';
+var BANDIX_MODAL_BG_AURORA_DARK = '#0E172B';
+var BANDIX_MODAL_BG_KUCAT_LIGHT = '#ffffff';
+var BANDIX_MODAL_BG_KUCAT_DARK = '#222D3C';
function getThemeMode() {
var theme = uci.get('luci', 'main', 'mediaurlbase');
@@ -56,7 +68,7 @@ function getThemeMode() {
function getThemeType() {
// 获取 LuCI 主题设置
var mediaUrlBase = uci.get('luci', 'main', 'mediaurlbase');
-
+
if (!mediaUrlBase) {
// 如果无法获取,尝试从 DOM 中检测
var linkTags = document.querySelectorAll('link[rel="stylesheet"]');
@@ -69,56 +81,63 @@ function getThemeType() {
// 默认返回窄主题
return 'narrow';
}
-
+
var mediaUrlBaseLower = mediaUrlBase.toLowerCase();
-
+
// 宽主题关键词列表(可以根据需要扩展)
var wideThemeKeywords = ['argon', 'material', 'design', 'edge'];
-
+
// 检查是否是宽主题
for (var i = 0; i < wideThemeKeywords.length; i++) {
if (mediaUrlBaseLower.includes(wideThemeKeywords[i])) {
return 'wide';
}
}
-
+
// 默认是窄主题(Bootstrap 等)
return 'narrow';
}
-// 格式化时间戳
-function formatTimestamp(timestamp) {
- if (!timestamp) return _('Never Online');
-
- var now = Math.floor(Date.now() / 1000);
- var diff = now - timestamp;
-
- if (diff < 60) {
- return _('Just now');
- } else if (diff < 3600) {
- var minutes = Math.floor(diff / 60);
- return minutes + ' ' + _('minutes ago');
- } else if (diff < 86400) {
- var hours = Math.floor(diff / 3600);
- return hours + ' ' + _('hours ago');
- } else if (diff < 2592000) {
- var days = Math.floor(diff / 86400);
- return days + ' ' + _('days ago');
- } else if (diff < 31536000) {
- var months = Math.floor(diff / 2592000);
- return months + ' ' + _('months ago');
- } else {
- var years = Math.floor(diff / 31536000);
- return years + ' ' + _('years ago');
+function getThemeColors() {
+ var theme = uci.get('luci', 'main', 'mediaurlbase');
+ var mode = getThemeMode();
+ if (theme === '/luci-static/openwrt2020') {
+ return { modalBg: mode === 'dark' ? BANDIX_MODAL_BG_OPENWRT2020_DARK : BANDIX_MODAL_BG_OPENWRT2020_LIGHT };
}
+ if (theme === '/luci-static/material') {
+ return { modalBg: mode === 'dark' ? BANDIX_MODAL_BG_MATERIAL_DARK : BANDIX_MODAL_BG_MATERIAL_LIGHT };
+ }
+ if (theme === '/luci-static/bootstrap-light' || theme === '/luci-static/bootstrap') {
+ return { modalBg: mode === 'dark' ? BANDIX_MODAL_BG_BOOTSTRAP_DARK : BANDIX_MODAL_BG_BOOTSTRAP_LIGHT };
+ }
+ if (theme === '/luci-static/bootstrap-dark') {
+ return { modalBg: BANDIX_MODAL_BG_BOOTSTRAP_DARK };
+ }
+ if (theme === '/luci-static/argon') {
+ return { modalBg: mode === 'dark' ? BANDIX_MODAL_BG_ARGON_DARK : BANDIX_MODAL_BG_ARGON_LIGHT };
+ }
+ if (theme === '/luci-static/aurora') {
+ return { modalBg: mode === 'dark' ? BANDIX_MODAL_BG_AURORA_DARK : BANDIX_MODAL_BG_AURORA_LIGHT };
+ }
+ if (theme === '/luci-static/kucat') {
+ return { modalBg: mode === 'dark' ? BANDIX_MODAL_BG_KUCAT_DARK : BANDIX_MODAL_BG_KUCAT_LIGHT };
+ }
+ return { modalBg: mode === 'dark' ? BANDIX_MODAL_BG_OPENWRT2020_DARK : BANDIX_MODAL_BG_OPENWRT2020_LIGHT };
+}
+
+function formatSize(bytes) {
+ if (!bytes || bytes === 0) return '0 B';
+ var units = ['B', 'KB', 'MB', 'GB', 'TB', 'PB'];
+ var i = Math.floor(Math.log(bytes) / Math.log(1024));
+ return parseFloat((bytes / Math.pow(1024, i)).toFixed(2)) + ' ' + units[i];
}
// 格式化设备名称
function formatDeviceName(device) {
- if (device.hostname && device.hostname !== '') {
- return device.hostname;
+ if (device.host && device.host !== '') {
+ return device.host;
}
- return device.ip_address || device.mac_address || _('Unknown Device');
+ return device.ip4 || device.mac || _('Unknown Device');
}
// RPC调用
@@ -128,6 +147,13 @@ var callGetConnection = rpc.declare({
expect: {}
});
+var callGetConnectionFlows = rpc.declare({
+ object: 'luci.bandix',
+ method: 'getConnectionFlows',
+ params: ['ip', 'protocol', 'state'],
+ expect: {}
+});
+
return view.extend({
load: function () {
return Promise.all([
@@ -141,6 +167,7 @@ return view.extend({
render: function (data) {
var connectionEnabled = uci.get('bandix', 'connections', 'enabled') === '1';
+ var themeColors = getThemeColors();
// 创建样式
var style = E('style', {}, `
@@ -456,11 +483,12 @@ return view.extend({
white-space: nowrap;
}
- .bandix-table th:nth-child(1) { width: 30%; }
- .bandix-table th:nth-child(2) { width: 12%; }
- .bandix-table th:nth-child(3) { width: 12%; }
- .bandix-table th:nth-child(4) { width: 31%; }
- .bandix-table th:nth-child(5) { width: 15%; }
+ .bandix-table th:nth-child(1) { width: 26%; }
+ .bandix-table th:nth-child(2) { width: 10%; }
+ .bandix-table th:nth-child(3) { width: 10%; }
+ .bandix-table th:nth-child(4) { width: 28%; }
+ .bandix-table th:nth-child(5) { width: 13%; }
+ .bandix-table th:nth-child(6) { width: 13%; }
.bandix-table td {
padding: 12px 16px;
@@ -566,6 +594,195 @@ return view.extend({
text-align: center;
padding: 40px;
}
+
+ .flows-view-btn {
+ padding: 4px 10px;
+ font-size: 0.8rem;
+ }
+
+ .flows-modal-overlay {
+ position: fixed;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background-color: rgba(0,0,0,0);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ z-index: 1100;
+ opacity: 0;
+ visibility: hidden;
+ transition: opacity 0.2s ease, visibility 0.2s ease, background-color 0.2s ease;
+ }
+
+ .flows-modal-overlay.show {
+ background-color: rgba(0,0,0,0.5);
+ opacity: 1;
+ visibility: visible;
+ }
+
+ .flows-modal-overlay .flows-modal-content {
+ margin: 16px;
+ width: 95vw;
+ max-width: 1200px;
+ max-height: 85vh;
+ display: flex;
+ flex-direction: column;
+ overflow: hidden;
+ border-radius: 8px;
+ box-shadow: 0 10px 25px rgba(0,0,0,0.2);
+ box-sizing: border-box;
+ background-color: ${themeColors.modalBg};
+ }
+
+ .flows-modal-content.theme-light {
+ color: #111827;
+ }
+
+ .flows-modal-content.theme-dark {
+ color: #e5e5e5;
+ }
+
+ .flows-modal-content.theme-dark .flows-modal-header {
+ border-bottom-color: rgba(255,255,255,0.15);
+ }
+
+ .flows-modal-content.theme-dark .flows-filters {
+ border-bottom-color: rgba(255,255,255,0.15);
+ }
+
+ .flows-modal-content.theme-dark .flows-table th,
+ .flows-modal-content.theme-dark .flows-table td {
+ border-color: rgba(255,255,255,0.1);
+ }
+
+ .flows-modal-content.theme-dark .flows-table-body {
+ background-color: transparent;
+ }
+
+ .flows-modal-content .flows-modal-header {
+ padding: 12px 16px;
+ border-bottom: 1px solid rgba(0,0,0,0.1);
+ flex-shrink: 0;
+ }
+
+ .flows-modal-content .flows-modal-title {
+ font-size: 1.1rem;
+ font-weight: 600;
+ margin: 0 0 4px 0;
+ }
+
+ .flows-modal-content .flows-modal-subtitle {
+ font-size: 0.85rem;
+ opacity: 0.7;
+ }
+
+ .flows-modal-content .flows-filters {
+ padding: 12px 16px;
+ display: flex;
+ gap: 12px;
+ align-items: center;
+ flex-wrap: wrap;
+ border-bottom: 1px solid rgba(0,0,0,0.1);
+ flex-shrink: 0;
+ }
+
+ .flows-modal-content .flows-filters select {
+ padding: 6px 10px;
+ border-radius: 4px;
+ border: 1px solid rgba(0,0,0,0.2);
+ font-size: 0.875rem;
+ }
+
+ .flows-modal-content.theme-dark .flows-filters select {
+ background-color: #3a3a3a;
+ border-color: rgba(255,255,255,0.2);
+ color: #e5e5e5;
+ }
+
+ .flows-modal-content .flows-table-wrap {
+ overflow: auto;
+ flex: 1;
+ min-height: 200px;
+ }
+
+ .flows-modal-content .flows-table {
+ width: 100%;
+ table-layout: fixed;
+ border-collapse: collapse;
+ font-size: 0.85rem;
+ }
+
+ .flows-modal-content .flows-table th:nth-child(1),
+ .flows-modal-content .flows-table td:nth-child(1) { width: 5%; }
+ .flows-modal-content .flows-table th:nth-child(2),
+ .flows-modal-content .flows-table td:nth-child(2) { width: 8%; }
+ .flows-modal-content .flows-table th:nth-child(3),
+ .flows-modal-content .flows-table td:nth-child(3) { width: 14%; }
+ .flows-modal-content .flows-table th:nth-child(4),
+ .flows-modal-content .flows-table td:nth-child(4) { width: 14%; }
+ .flows-modal-content .flows-table th:nth-child(5),
+ .flows-modal-content .flows-table td:nth-child(5) { width: 10%; }
+ .flows-modal-content .flows-table th:nth-child(6),
+ .flows-modal-content .flows-table td:nth-child(6) { width: 14%; }
+ .flows-modal-content .flows-table th:nth-child(7),
+ .flows-modal-content .flows-table td:nth-child(7) { width: 14%; }
+ .flows-modal-content .flows-table th:nth-child(8),
+ .flows-modal-content .flows-table td:nth-child(8) { width: 10%; }
+ .flows-modal-content .flows-table th:nth-child(9),
+ .flows-modal-content .flows-table td:nth-child(9) { width: 11%; }
+
+ .flows-modal-content .flows-table th,
+ .flows-modal-content .flows-table td {
+ padding: 8px 12px;
+ text-align: left;
+ border-bottom: 1px solid rgba(0,0,0,0.08);
+ }
+
+ .flows-modal-content .flows-table th {
+ font-weight: 600;
+ white-space: nowrap;
+ }
+
+ .flows-modal-content .flows-addr-cell {
+ word-break: break-all;
+ font-size: 0.8rem;
+ }
+
+ .flows-modal-content .flows-protocol-tcp {
+ color: #10b981;
+ font-weight: 600;
+ }
+
+ .flows-modal-content .flows-protocol-udp {
+ color: #06b6d4;
+ font-weight: 600;
+ }
+
+ .flows-modal-content .flows-state-est {
+ color: #10b981;
+ }
+
+ .flows-modal-content .flows-state-wait {
+ color: #f59e0b;
+ }
+
+ .flows-modal-content .flows-state-close {
+ color: #6b7280;
+ }
+
+ .flows-modal-content .flows-footer {
+ padding: 12px 16px;
+ border-top: 1px solid rgba(0,0,0,0.1);
+ flex-shrink: 0;
+ display: flex;
+ justify-content: flex-end;
+ }
+
+ .flows-modal-content.theme-dark .flows-footer {
+ border-top-color: rgba(255,255,255,0.15);
+ }
`);
document.head.appendChild(style);
@@ -580,7 +797,7 @@ return view.extend({
// 检查连接监控是否启用
if (!connectionEnabled) {
- var alertDiv = E('div', {
+ var alertDiv = E('div', {
'class': 'bandix-alert' + (getThemeType() === 'wide' ? ' wide-theme' : '')
}, [
E('div', { 'style': 'display: flex; align-items: center; gap: 8px;' }, [
@@ -607,7 +824,7 @@ return view.extend({
}
// 添加提示信息
- var infoAlert = E('div', {
+ var infoAlert = E('div', {
'class': 'bandix-alert' + (getThemeType() === 'wide' ? ' wide-theme' : '')
}, [
E('div', { 'style': 'display: flex; align-items: center; gap: 8px;' }, [
@@ -660,7 +877,8 @@ return view.extend({
E('th', {}, 'TCP'),
E('th', {}, 'UDP'),
E('th', {}, _('TCP Status Details')),
- E('th', {}, _('Total Connections'))
+ E('th', {}, _('Total Connections')),
+ E('th', { 'style': 'width: 80px;' }, _('Actions'))
])
]),
E('tbody', {})
@@ -674,12 +892,12 @@ return view.extend({
function updateGlobalStats(stats) {
if (!stats) return;
- document.getElementById('total-connections').textContent = stats.total_connections || 0;
- document.getElementById('tcp-connections').textContent = stats.tcp_connections || 0;
- document.getElementById('udp-connections').textContent = stats.udp_connections || 0;
- document.getElementById('established-tcp').textContent = stats.established_tcp || 0;
- document.getElementById('time-wait-tcp').textContent = stats.time_wait_tcp || 0;
- document.getElementById('close-wait-tcp').textContent = stats.close_wait_tcp || 0;
+ document.getElementById('total-connections').textContent = stats.total || 0;
+ document.getElementById('tcp-connections').textContent = stats.tcp || 0;
+ document.getElementById('udp-connections').textContent = stats.udp || 0;
+ document.getElementById('established-tcp').textContent = stats.tcp_est || 0;
+ document.getElementById('time-wait-tcp').textContent = stats.tcp_tw || 0;
+ document.getElementById('close-wait-tcp').textContent = stats.tcp_cw || 0;
}
// 更新设备表格
@@ -701,47 +919,54 @@ return view.extend({
E('th', {}, 'TCP'),
E('th', {}, 'UDP'),
E('th', {}, _('TCP Status Details')),
- E('th', {}, _('Total Connections'))
+ E('th', {}, _('Total Connections')),
+ E('th', {}, _('Actions'))
])
]),
E('tbody', {}, devices.map(function (device) {
+ var viewBtn = E('button', {
+ 'class': 'cbi-button cbi-button-reset flows-view-btn',
+ 'data-ip': device.ip4 || '',
+ 'data-device': JSON.stringify({ ip4: device.ip4, host: device.host, mac: device.mac })
+ }, _('Details'));
return E('tr', {}, [
E('td', {}, [
E('div', { 'class': 'device-info' }, [
E('div', { 'class': 'device-status online' }),
E('div', { 'class': 'device-details' }, [
E('div', { 'class': 'device-name' }, formatDeviceName(device)),
- E('div', { 'class': 'device-ip' }, device.ip_address || '-'),
- E('div', { 'class': 'device-mac' }, device.mac_address || '-')
+ E('div', { 'class': 'device-ip' }, device.ip4 || '-'),
+ E('div', { 'class': 'device-mac' }, device.mac || '-')
])
])
]),
- E('td', { 'style': 'font-weight: 600; font-size: 1.25rem;' }, device.tcp_connections || 0),
- E('td', { 'style': 'font-weight: 600; font-size: 1.25rem;' }, device.udp_connections || 0),
+ E('td', { 'style': 'font-weight: 600; font-size: 1.25rem;' }, device.tcp || 0),
+ E('td', { 'style': 'font-weight: 600; font-size: 1.25rem;' }, device.udp || 0),
E('td', {}, [
E('div', { 'class': 'tcp-status-details' }, [
E('div', { 'class': 'tcp-status-item' }, [
E('span', { 'class': 'tcp-status-label established' }, 'EST'),
- E('span', { 'class': 'tcp-status-value' }, device.established_tcp || 0)
+ E('span', { 'class': 'tcp-status-value' }, device.tcp_est || 0)
]),
E('div', { 'class': 'tcp-status-item' }, [
E('span', { 'class': 'tcp-status-label time-wait' }, 'WAIT'),
- E('span', { 'class': 'tcp-status-value' }, device.time_wait_tcp || 0)
+ E('span', { 'class': 'tcp-status-value' }, device.tcp_tw || 0)
]),
E('div', { 'class': 'tcp-status-item' }, [
E('span', { 'class': 'tcp-status-label closed' }, 'CLOSE'),
- E('span', { 'class': 'tcp-status-value' }, device.close_wait_tcp || 0)
+ E('span', { 'class': 'tcp-status-value' }, device.tcp_cw || 0)
])
])
]),
- E('td', {}, E('strong', { 'style': 'font-size: 1.25rem;' }, device.total_connections || 0))
+ E('td', {}, E('strong', { 'style': 'font-size: 1.25rem;' }, device.total || 0)),
+ E('td', {}, viewBtn)
]);
}))
]);
// 创建卡片容器(移动端)
var cardsContainer = E('div', { 'class': 'device-list-cards' });
-
+
devices.forEach(function (device) {
var card = E('div', { 'class': 'device-card' }, [
// 卡片头部:设备信息
@@ -749,19 +974,19 @@ return view.extend({
E('div', { 'class': 'device-status online' }),
E('div', { 'class': 'device-card-name' }, [
E('div', { 'class': 'device-name' }, formatDeviceName(device)),
- E('div', { 'class': 'device-ip' }, device.ip_address || '-'),
- E('div', { 'class': 'device-mac' }, device.mac_address || '-')
+ E('div', { 'class': 'device-ip' }, device.ip4 || '-'),
+ E('div', { 'class': 'device-mac' }, device.mac || '-')
])
]),
// 统计信息:TCP 和 UDP
E('div', { 'class': 'device-card-stats' }, [
E('div', { 'class': 'device-card-stat-item' }, [
E('div', { 'class': 'device-card-stat-label' }, 'TCP'),
- E('div', { 'class': 'device-card-stat-value' }, device.tcp_connections || 0)
+ E('div', { 'class': 'device-card-stat-value' }, device.tcp || 0)
]),
E('div', { 'class': 'device-card-stat-item' }, [
E('div', { 'class': 'device-card-stat-label' }, 'UDP'),
- E('div', { 'class': 'device-card-stat-value' }, device.udp_connections || 0)
+ E('div', { 'class': 'device-card-stat-value' }, device.udp || 0)
])
]),
// TCP 状态详情
@@ -769,24 +994,31 @@ return view.extend({
E('div', { 'class': 'device-card-tcp-details-label' }, _('TCP Status Details')),
E('div', { 'class': 'device-card-tcp-status-row' }, [
E('span', { 'class': 'device-card-tcp-status-label established' }, 'EST'),
- E('span', { 'class': 'device-card-tcp-status-value' }, device.established_tcp || 0)
+ E('span', { 'class': 'device-card-tcp-status-value' }, device.tcp_est || 0)
]),
E('div', { 'class': 'device-card-tcp-status-row' }, [
E('span', { 'class': 'device-card-tcp-status-label time-wait' }, 'WAIT'),
- E('span', { 'class': 'device-card-tcp-status-value' }, device.time_wait_tcp || 0)
+ E('span', { 'class': 'device-card-tcp-status-value' }, device.tcp_tw || 0)
]),
E('div', { 'class': 'device-card-tcp-status-row' }, [
E('span', { 'class': 'device-card-tcp-status-label closed' }, 'CLOSE'),
- E('span', { 'class': 'device-card-tcp-status-value' }, device.close_wait_tcp || 0)
+ E('span', { 'class': 'device-card-tcp-status-value' }, device.tcp_cw || 0)
])
]),
// 总连接数
E('div', { 'class': 'device-card-total' }, [
E('div', { 'style': 'font-size: 0.875rem; opacity: 0.7; font-weight: 500;' }, _('Total Connections')),
- E('div', { 'style': 'font-size: 1.25rem; font-weight: 700;' }, device.total_connections || 0)
+ E('div', { 'style': 'display: flex; align-items: center; gap: 8px;' }, [
+ E('span', { 'style': 'font-size: 1.25rem; font-weight: 700;' }, device.total || 0),
+ E('button', {
+ 'class': 'cbi-button cbi-button-reset flows-view-btn',
+ 'data-ip': device.ip4 || '',
+ 'data-device': JSON.stringify({ ip4: device.ip4, host: device.host, mac: device.mac })
+ }, _('Details'))
+ ])
])
]);
-
+
cardsContainer.appendChild(card);
});
@@ -806,8 +1038,8 @@ return view.extend({
function updateConnectionData() {
return callGetConnection().then(function (result) {
if (result && result.status === 'success' && result.data) {
- updateGlobalStats(result.data.global_stats);
- updateDeviceTable(result.data.devices);
+ updateGlobalStats(result.data.g);
+ updateDeviceTable(result.data.d);
} else {
showError(_('Unable to fetch data'));
}
@@ -817,6 +1049,148 @@ return view.extend({
});
}
+ function showFlowsModal(deviceIp, deviceInfo) {
+ var currentTheme = getThemeMode();
+ var protocolSelect = E('select', { 'class': 'cbi-input-select', 'id': 'flows-protocol' }, [
+ E('option', { 'value': '' }, _('All')),
+ E('option', { 'value': 'tcp' }, 'TCP'),
+ E('option', { 'value': 'udp' }, 'UDP')
+ ]);
+ var stateSelect = E('select', { 'class': 'cbi-input-select', 'id': 'flows-state' }, [
+ E('option', { 'value': '' }, _('All')),
+ E('option', { 'value': 'ESTABLISHED' }, 'ESTABLISHED'),
+ E('option', { 'value': 'TIME_WAIT' }, 'TIME_WAIT'),
+ E('option', { 'value': 'CLOSE_WAIT' }, 'CLOSE_WAIT')
+ ]);
+ var tableBody = E('tbody', { 'class': 'flows-table-body' });
+ var tableWrap = E('div', { 'class': 'flows-table-wrap' }, [
+ E('table', { 'class': 'flows-table' }, [
+ E('thead', {}, [
+ E('tr', {}, [
+ E('th', {}, _('Protocol')),
+ E('th', {}, _('State')),
+ E('th', {}, _('Orig Src')),
+ E('th', {}, _('Orig Dst')),
+ E('th', {}, _('Send')),
+ E('th', {}, _('Repl Src')),
+ E('th', {}, _('Repl Dst')),
+ E('th', {}, _('Reply')),
+ E('th', {}, _('Flags'))
+ ])
+ ]),
+ tableBody
+ ])
+ ]);
+
+ function loadFlows() {
+ var protocol = protocolSelect.value || '';
+ var state = stateSelect.value || '';
+ tableBody.innerHTML = '';
+ tableBody.appendChild(E('tr', {}, [
+ E('td', { 'colspan': 9, 'class': 'loading-state' }, _('Loading…'))
+ ]));
+ return callGetConnectionFlows(deviceIp, protocol || null, state || null).then(function (res) {
+ tableBody.innerHTML = '';
+ if (!res || res.status !== 'success' || !res.data || res.data.length === 0) {
+ tableBody.appendChild(E('tr', {}, [
+ E('td', { 'colspan': 9, 'class': 'loading-state' }, _('No connections'))
+ ]));
+ return;
+ }
+ res.data.forEach(function (f) {
+ var stateCls = '';
+ if (f.state) {
+ if (f.state.indexOf('ESTABLISHED') >= 0) stateCls = 'flows-state-est';
+ else if (f.state.indexOf('TIME_WAIT') >= 0) stateCls = 'flows-state-wait';
+ else if (f.state.indexOf('CLOSE') >= 0) stateCls = 'flows-state-close';
+ }
+ var origSrc = f.orig ? (f.orig.src || '-') + ':' + (f.orig.sport || '-') : '-';
+ var origDst = f.orig ? (f.orig.dst || '-') + ':' + (f.orig.dport || '-') : '-';
+ var replSrc = f.repl ? (f.repl.src || '-') + ':' + (f.repl.sport || '-') : '-';
+ var replDst = f.repl ? (f.repl.dst || '-') + ':' + (f.repl.dport || '-') : '-';
+ var sendStr = (f.orig_packets || 0) + ' ' + _('pkts') + ' / ' + formatSize(f.orig_bytes || 0);
+ var replyStr = (f.repl_packets || 0) + ' ' + _('pkts') + ' / ' + formatSize(f.repl_bytes || 0);
+ var flagsStr = (f.flags && f.flags.length) ? f.flags.join(' ') : '-';
+ var protoCls = (f.protocol || '').toLowerCase() === 'tcp' ? 'flows-protocol-tcp' : 'flows-protocol-udp';
+ tableBody.appendChild(E('tr', {}, [
+ E('td', { 'class': protoCls }, (f.protocol || '-').toUpperCase()),
+ E('td', { 'class': stateCls }, f.state || '-'),
+ E('td', { 'class': 'flows-addr-cell' }, origSrc),
+ E('td', { 'class': 'flows-addr-cell' }, origDst),
+ E('td', {}, sendStr),
+ E('td', { 'class': 'flows-addr-cell' }, replSrc),
+ E('td', { 'class': 'flows-addr-cell' }, replDst),
+ E('td', {}, replyStr),
+ E('td', { 'style': 'font-size: 0.8rem;' }, flagsStr)
+ ]));
+ });
+ }).catch(function (err) {
+ tableBody.innerHTML = '';
+ tableBody.appendChild(E('tr', {}, [
+ E('td', { 'colspan': 9, 'class': 'error-state' }, _('Failed to load') + ': ' + (err.message || err))
+ ]));
+ });
+ }
+
+ function onFilterChange() {
+ loadFlows();
+ }
+
+ function hideFlowsModal() {
+ var overlay = document.getElementById('flows-modal-overlay');
+ if (overlay) overlay.classList.remove('show');
+ }
+
+ var modalContent = E('div', { 'class': 'flows-modal-content theme-' + currentTheme }, [
+ E('div', { 'class': 'flows-modal-header' }, [
+ E('div', { 'class': 'flows-modal-title' }, _('Connection Details') + ' - ' + (deviceInfo.host || deviceInfo.ip4 || deviceIp)),
+ E('div', { 'class': 'flows-modal-subtitle' }, (deviceInfo.ip4 || '') + (deviceInfo.mac ? ' · ' + deviceInfo.mac : ''))
+ ]),
+ E('div', { 'class': 'flows-filters' }, [
+ E('label', {}, _('Protocol') + ':'),
+ protocolSelect,
+ E('label', {}, _('State') + ':'),
+ stateSelect,
+ E('button', { 'class': 'cbi-button cbi-button-apply', 'click': loadFlows }, _('Refresh'))
+ ]),
+ tableWrap,
+ E('div', { 'class': 'flows-footer' }, [
+ E('button', { 'class': 'cbi-button cbi-button-reset', 'click': hideFlowsModal }, _('Close'))
+ ])
+ ]);
+
+ if (protocolSelect && protocolSelect.addEventListener) {
+ protocolSelect.addEventListener('change', onFilterChange);
+ }
+ if (stateSelect && stateSelect.addEventListener) {
+ stateSelect.addEventListener('change', onFilterChange);
+ }
+
+ var overlay = document.getElementById('flows-modal-overlay');
+ if (!overlay) {
+ overlay = E('div', { 'class': 'flows-modal-overlay', 'id': 'flows-modal-overlay' });
+ document.body.appendChild(overlay);
+ }
+ overlay.innerHTML = '';
+ overlay.appendChild(modalContent);
+ overlay.classList.add('show');
+ modalContent.addEventListener('click', function (e) { e.stopPropagation(); });
+ loadFlows();
+ }
+
+ container.addEventListener('click', function (ev) {
+ var btn = ev.target;
+ if (btn && btn.classList && btn.classList.contains('flows-view-btn')) {
+ var ip = btn.getAttribute('data-ip');
+ var deviceStr = btn.getAttribute('data-device');
+ var deviceInfo = {};
+ try {
+ if (deviceStr) deviceInfo = JSON.parse(deviceStr);
+ } catch (e) {}
+ if (ip) showFlowsModal(ip, deviceInfo);
+ }
+ });
+
// 轮询获取数据
poll.add(updateConnectionData, 1);
@@ -829,21 +1203,21 @@ return view.extend({
var mainElement = document.querySelector('.main') || document.body;
var computedStyle = window.getComputedStyle(mainElement);
var bgColor = computedStyle.backgroundColor;
-
+
// 如果父元素有背景色,应用到容器和卡片
if (bgColor && bgColor !== 'rgba(0, 0, 0, 0)' && bgColor !== 'transparent') {
var containerEl = document.querySelector('.bandix-connection-container');
if (containerEl) {
containerEl.style.backgroundColor = bgColor;
}
-
+
// 应用到表格表头
var tableHeaders = document.querySelectorAll('.bandix-table th');
- tableHeaders.forEach(function(th) {
+ tableHeaders.forEach(function (th) {
th.style.backgroundColor = bgColor;
});
}
-
+
// 检测文字颜色并应用
var textColor = computedStyle.color;
if (textColor && textColor !== 'rgba(0, 0, 0, 0)') {
@@ -857,17 +1231,17 @@ return view.extend({
console.log('Theme adaptation:', e);
}
}
-
+
// 初始应用主题颜色
setTimeout(applyThemeColors, 100);
-
+
// 监听 DOM 变化,自动应用到新创建的元素
if (typeof MutationObserver !== 'undefined') {
- var observer = new MutationObserver(function(mutations) {
+ var observer = new MutationObserver(function (mutations) {
applyThemeColors();
});
-
- setTimeout(function() {
+
+ setTimeout(function () {
var container = document.querySelector('.bandix-connection-container');
if (container) {
observer.observe(container, {
diff --git a/luci-app-bandix/htdocs/luci-static/resources/view/bandix/index.js b/luci-app-bandix/htdocs/luci-static/resources/view/bandix/index.js
index 978d802e..8f1c3d6f 100644
--- a/luci-app-bandix/htdocs/luci-static/resources/view/bandix/index.js
+++ b/luci-app-bandix/htdocs/luci-static/resources/view/bandix/index.js
@@ -850,6 +850,72 @@ return view.extend({
opacity: 0.7;
font-size: 0.875rem;
}
+
+ .device-uplink-badges {
+ display: inline-flex;
+ flex-wrap: wrap;
+ gap: 4px;
+ align-items: center;
+ margin-left: 6px;
+ vertical-align: middle;
+ }
+
+ .device-uplink-badge,
+ .device-ch-badge {
+ display: inline-block;
+ padding: 2px 6px;
+ border-radius: 4px;
+ font-size: 0.7rem;
+ font-weight: 600;
+ line-height: 1.2;
+ }
+
+ .device-uplink-badge {
+ background: rgba(107, 114, 128, 0.2);
+ color: #4b5563;
+ }
+
+ .theme-dark .device-uplink-badge {
+ background: rgba(156, 163, 175, 0.25);
+ color: #9ca3af;
+ }
+
+ .device-ch-badge.device-ch-g24 {
+ background: rgba(245, 158, 11, 0.2);
+ color: #b45309;
+ }
+
+ .theme-dark .device-ch-badge.device-ch-g24 {
+ background: rgba(251, 191, 36, 0.2);
+ color: #fbbf24;
+ }
+
+ .device-ch-badge.device-ch-g5 {
+ background: rgba(59, 130, 246, 0.2);
+ color: #2563eb;
+ }
+
+ .theme-dark .device-ch-badge.device-ch-g5 {
+ background: rgba(96, 165, 250, 0.2);
+ color: #60a5fa;
+ }
+
+ .device-ch-badge.device-ch-g6 {
+ background: rgba(139, 92, 246, 0.2);
+ color: #6d28d9;
+ }
+
+ .theme-dark .device-ch-badge.device-ch-g6 {
+ background: rgba(167, 139, 250, 0.2);
+ color: #a78bfa;
+ }
+
+ .device-uplink-badges-wrap {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 4px;
+ align-items: center;
+ }
.device-ipv6 {
opacity: 0.7;
@@ -1367,7 +1433,11 @@ return view.extend({
opacity: 0.7;
font-size: 0.875rem;
}
-
+
+ .device-summary-badges {
+ margin-top: 8px;
+ }
+
/* 加载动画 */
.loading-spinner {
display: inline-block;
@@ -1596,6 +1666,12 @@ return view.extend({
opacity: 0.7;
margin-top: 4px;
}
+
+ .device-card-ip .device-uplink-badges .device-uplink-badge,
+ .device-card-ip .device-uplink-badges .device-ch-badge {
+ padding: 1px 5px;
+ font-size: 0.65rem;
+ }
.device-card-action {
flex-shrink: 0;
@@ -3042,8 +3118,8 @@ return view.extend({
};
var daysText = days.length > 0 ? days.map(function (d) { return dayNames[d] || d; }).join(', ') : '-';
- var uploadLimit = rule.wan_tx_rate_limit || 0;
- var downloadLimit = rule.wan_rx_rate_limit || 0;
+ var uploadLimit = rule.w_tx_r_limit || 0;
+ var downloadLimit = rule.w_rx_r_limit || 0;
// 使用 isRuleActive 函数检查规则是否激活
var isActive = isRuleActive(rule);
@@ -3349,8 +3425,8 @@ return view.extend({
}
function getDeviceLabel(d) {
- var name = d && (d.hostname || d.name) ? (d.hostname || d.name) : '';
- var ip = d && d.ip ? d.ip : '';
+ var name = d && (d.host || d.name) ? (d.host || d.name) : '';
+ var ip = d && d.ip4 ? d.ip4 : '';
var mac = d && d.mac ? d.mac : '';
var left = name || ip || mac || '';
var right = mac && left !== mac ? (' ' + mac) : '';
@@ -3428,8 +3504,8 @@ return view.extend({
macs.slice().sort().forEach(function (mac) {
var d = deviceMap[mac];
- var hostname = d && d.hostname ? d.hostname : '';
- var ip = d && d.ip ? d.ip : '';
+ var hostname = d && d.host ? d.host : '';
+ var ip = d && d.ip4 ? d.ip4 : '';
var title = hostname || ip || mac;
var item = E('div', { 'class': 'whitelist-modal-item' }, [
@@ -3670,8 +3746,8 @@ return view.extend({
});
// 设置限速值
- var uploadLimit = editingRule.wan_tx_rate_limit || 0;
- var downloadLimit = editingRule.wan_rx_rate_limit || 0;
+ var uploadLimit = editingRule.w_tx_r_limit || 0;
+ var downloadLimit = editingRule.w_rx_r_limit || 0;
// 根据限速值选择合适的单位
var uploadUnit, uploadValue, downloadUnit, downloadValue;
@@ -3888,14 +3964,18 @@ return view.extend({
// 加载定时限速规则列表
loadScheduleRules();
- // 更新设备信息
- deviceSummary.innerHTML = E('div', {}, [
- E('div', { 'class': 'device-summary-name' }, device.hostname || device.ip),
- E('div', { 'class': 'device-summary-details' }, device.ip + ' (' + device.mac + ')')
- ]).innerHTML;
+ var modalContent = [
+ E('div', { 'class': 'device-summary-name' }, device.host || device.ip4),
+ E('div', { 'class': 'device-summary-details' }, device.ip4 + ' (' + device.mac + ')')
+ ];
+ var modalBadges = buildDeviceUplinkChBadges(device);
+ if (modalBadges.length) {
+ modalContent.push(E('div', { 'class': 'device-summary-badges device-uplink-badges-wrap' }, modalBadges));
+ }
+ deviceSummary.innerHTML = E('div', {}, modalContent).innerHTML;
// 设置当前hostname值
- document.getElementById('device-hostname-input').value = device.hostname || '';
+ document.getElementById('device-hostname-input').value = device.host || '';
// 显示模态框并添加动画
@@ -3984,8 +4064,8 @@ return view.extend({
var startTime = rule.time_slot && rule.time_slot.start ? rule.time_slot.start : '';
var endTime = rule.time_slot && rule.time_slot.end ? rule.time_slot.end : '';
- var uploadLimit = rule.wan_tx_rate_limit || 0;
- var downloadLimit = rule.wan_rx_rate_limit || 0;
+ var uploadLimit = rule.w_tx_r_limit || 0;
+ var downloadLimit = rule.w_rx_r_limit || 0;
var ruleItem = E('div', { 'class': 'schedule-rule-item' }, [
E('div', { 'class': 'schedule-rule-info' }, [
@@ -4050,7 +4130,7 @@ return view.extend({
var newHostname = document.getElementById('device-hostname-input').value.trim();
// 如果hostname没有变化,不需要保存
- if (newHostname === (currentDevice.hostname || '')) {
+ if (newHostname === (currentDevice.host || '')) {
return;
}
@@ -4066,7 +4146,7 @@ return view.extend({
saveButton.disabled = false;
// 更新当前设备信息
- currentDevice.hostname = newHostname;
+ currentDevice.host = newHostname;
// 刷新设备数据
updateDeviceData();
@@ -4182,8 +4262,8 @@ return view.extend({
var downloadLimits = [];
activeRules.forEach(function (rule) {
- var uploadLimit = rule.wan_tx_rate_limit || 0;
- var downloadLimit = rule.wan_rx_rate_limit || 0;
+ var uploadLimit = rule.w_tx_r_limit || 0;
+ var downloadLimit = rule.w_rx_r_limit || 0;
// 只收集非零的限制值
if (uploadLimit > 0) {
@@ -4272,8 +4352,8 @@ return view.extend({
if (!aOnline && bOnline) return 1;
// 在线状态相同时,按IP地址排序
- var aIp = a.ip || '';
- var bIp = b.ip || '';
+ var aIp = a.ip4 || '';
+ var bIp = b.ip4 || '';
// 将IP地址转换为数字进行比较
var aIpParts = aIp.split('.').map(function (part) { return parseInt(part) || 0; });
@@ -4303,7 +4383,7 @@ return view.extend({
select.innerHTML = '';
select.appendChild(E('option', { 'value': '' }, _('All Devices')));
sortedDevices.forEach(function (d) {
- var label = (d.hostname || d.ip || d.mac || '-') + (d.ip ? ' (' + d.ip + ')' : '') + (d.mac ? ' [' + d.mac + ']' : '');
+ var label = (d.host || d.ip4 || d.mac || '-') + (d.ip4 ? ' (' + d.ip4 + ')' : '') + (d.mac ? ' [' + d.mac + ']' : '');
select.appendChild(E('option', { 'value': d.mac }, label));
});
// 尽量保留之前选择
@@ -4660,11 +4740,11 @@ return view.extend({
'
' +
'
' +
'
' + _('WAN Upload') + ' (P95)
' +
- '
' + formatByterate(point.wan_tx_rate_p95 || 0, speedUnit) + '
' +
+ '
' + formatByterate(point.w_tx_p95 || 0, speedUnit) + '
' +
'
' +
'
' +
'
' + _('WAN Download') + ' (P95)
' +
- '
' + formatByterate(point.wan_rx_rate_p95 || 0, speedUnit) + '
' +
+ '
' + formatByterate(point.w_rx_p95 || 0, speedUnit) + '
' +
'
' +
'
'
);
@@ -4672,20 +4752,20 @@ return view.extend({
// 详细统计信息
lines.push('');
lines.push('' + _('Upload Statistics') + '
');
- row(_('Average'), formatByterate(point.wan_tx_rate_avg || 0, speedUnit));
- row(_('Maximum'), formatByterate(point.wan_tx_rate_max || 0, speedUnit));
- row(_('Minimum'), formatByterate(point.wan_tx_rate_min || 0, speedUnit));
- row('P90', formatByterate(point.wan_tx_rate_p90 || 0, speedUnit));
- row('P95', formatByterate(point.wan_tx_rate_p95 || 0, speedUnit));
- row('P99', formatByterate(point.wan_tx_rate_p99 || 0, speedUnit));
+ row(_('Average'), formatByterate(point.w_tx_avg || 0, speedUnit));
+ row(_('Maximum'), formatByterate(point.w_tx_max || 0, speedUnit));
+ row(_('Minimum'), formatByterate(point.w_tx_min || 0, speedUnit));
+ row('P90', formatByterate(point.w_tx_p90 || 0, speedUnit));
+ row('P95', formatByterate(point.w_tx_p95 || 0, speedUnit));
+ row('P99', formatByterate(point.w_tx_p99 || 0, speedUnit));
lines.push('' + _('Download Statistics') + '
');
- row(_('Average'), formatByterate(point.wan_rx_rate_avg || 0, speedUnit));
- row(_('Maximum'), formatByterate(point.wan_rx_rate_max || 0, speedUnit));
- row(_('Minimum'), formatByterate(point.wan_rx_rate_min || 0, speedUnit));
- row('P90', formatByterate(point.wan_rx_rate_p90 || 0, speedUnit));
- row('P95', formatByterate(point.wan_rx_rate_p95 || 0, speedUnit));
- row('P99', formatByterate(point.wan_rx_rate_p99 || 0, speedUnit));
+ row(_('Average'), formatByterate(point.w_rx_avg || 0, speedUnit));
+ row(_('Maximum'), formatByterate(point.w_rx_max || 0, speedUnit));
+ row(_('Minimum'), formatByterate(point.w_rx_min || 0, speedUnit));
+ row('P90', formatByterate(point.w_rx_p90 || 0, speedUnit));
+ row('P95', formatByterate(point.w_rx_p95 || 0, speedUnit));
+ row('P99', formatByterate(point.w_rx_p99 || 0, speedUnit));
// 累计流量(只显示 WAN)
lines.push('');
@@ -4766,7 +4846,7 @@ return view.extend({
}
// 在线状态相同时,按IP地址排序(小的在前)
- var ipCompare = compareIP(a.ip, b.ip);
+ var ipCompare = compareIP(a.ip4, b.ip4);
if (ipCompare !== 0) return ipCompare;
// IP地址也相同时,按MAC地址排序
@@ -4784,14 +4864,14 @@ return view.extend({
}
// 在线状态相同时,按LAN速度排序
- var aSpeed = (a.lan_tx_rate || 0) + (a.lan_rx_rate || 0);
- var bSpeed = (b.lan_tx_rate || 0) + (b.lan_rx_rate || 0);
+ var aSpeed = (a.l_tx_r || 0) + (a.l_rx_r || 0);
+ var bSpeed = (b.l_tx_r || 0) + (b.l_rx_r || 0);
if (aSpeed !== bSpeed) {
return ascending ? (aSpeed - bSpeed) : (bSpeed - aSpeed);
}
// 速度相同时,按IP地址排序
- return compareIP(a.ip, b.ip);
+ return compareIP(a.ip4, b.ip4);
});
break;
@@ -4805,14 +4885,14 @@ return view.extend({
}
// 在线状态相同时,按WAN速度排序
- var aSpeed = (a.wan_tx_rate || 0) + (a.wan_rx_rate || 0);
- var bSpeed = (b.wan_tx_rate || 0) + (b.wan_rx_rate || 0);
+ var aSpeed = (a.w_tx_r || 0) + (a.w_rx_r || 0);
+ var bSpeed = (b.w_tx_r || 0) + (b.w_rx_r || 0);
if (aSpeed !== bSpeed) {
return ascending ? (aSpeed - bSpeed) : (bSpeed - aSpeed);
}
// 速度相同时,按IP地址排序
- return compareIP(a.ip, b.ip);
+ return compareIP(a.ip4, b.ip4);
});
break;
@@ -4826,14 +4906,14 @@ return view.extend({
}
// 在线状态相同时,按LAN流量排序
- var aTraffic = (a.lan_tx_bytes || 0) + (a.lan_rx_bytes || 0);
- var bTraffic = (b.lan_tx_bytes || 0) + (b.lan_rx_bytes || 0);
+ var aTraffic = (a.l_tx_b || 0) + (a.l_rx_b || 0);
+ var bTraffic = (b.l_tx_b || 0) + (b.l_rx_b || 0);
if (aTraffic !== bTraffic) {
return ascending ? (aTraffic - bTraffic) : (bTraffic - aTraffic);
}
// 流量相同时,按IP地址排序
- return compareIP(a.ip, b.ip);
+ return compareIP(a.ip4, b.ip4);
});
break;
@@ -4847,14 +4927,14 @@ return view.extend({
}
// 在线状态相同时,按WAN流量排序
- var aTraffic = (a.wan_tx_bytes || 0) + (a.wan_rx_bytes || 0);
- var bTraffic = (b.wan_tx_bytes || 0) + (b.wan_rx_bytes || 0);
+ var aTraffic = (a.w_tx_b || 0) + (a.w_rx_b || 0);
+ var bTraffic = (b.w_tx_b || 0) + (b.w_rx_b || 0);
if (aTraffic !== bTraffic) {
return ascending ? (aTraffic - bTraffic) : (bTraffic - aTraffic);
}
// 流量相同时,按IP地址排序
- return compareIP(a.ip, b.ip);
+ return compareIP(a.ip4, b.ip4);
});
break;
@@ -4869,7 +4949,7 @@ return view.extend({
}
// 在线状态相同时,按IP地址排序(小的在前)
- var ipCompare = compareIP(a.ip, b.ip);
+ var ipCompare = compareIP(a.ip4, b.ip4);
if (ipCompare !== 0) return ipCompare;
// IP相同时,按MAC地址排序
@@ -4883,19 +4963,19 @@ return view.extend({
// 判断设备是否在线(基于 last_online_ts)
function isDeviceOnline(device) {
// 如果没有 last_online_ts 字段,使用原有的 online 字段
- if (typeof device.last_online_ts === 'undefined') {
+ if (typeof device.last === 'undefined') {
return device.online !== false;
}
// 如果 last_online_ts 为 0 或无效值,认为离线
- if (!device.last_online_ts || device.last_online_ts <= 0) {
+ if (!device.last || device.last <= 0) {
return false;
}
// 计算当前时间与最后在线时间的差值(毫秒)
var currentTime = Date.now();
// 如果时间戳小于1000000000000,说明是秒级时间戳,需要转换为毫秒
- var lastOnlineTime = device.last_online_ts < 1000000000000 ? device.last_online_ts * 1000 : device.last_online_ts;
+ var lastOnlineTime = device.last < 1000000000000 ? device.last * 1000 : device.last;
var timeDiff = currentTime - lastOnlineTime;
// 从UCI配置获取离线超时时间(秒),默认10分钟
@@ -4905,6 +4985,52 @@ return view.extend({
return timeDiff <= offlineThreshold;
}
+ function channelToBand(ch) {
+ var c = Number(ch);
+ if (c < 1) return null;
+ if (c <= 14) return '2.4 GHz';
+ if (c <= 165) return '5 GHz';
+ if (c <= 233) return '6 GHz';
+ return null;
+ }
+
+ function channelToBandClass(ch) {
+ var c = Number(ch);
+ if (c <= 14) return 'g24';
+ if (c <= 165) return 'g5';
+ if (c <= 233) return 'g6';
+ return '';
+ }
+
+ function buildDeviceUplinkChBadges(device) {
+ var badges = [];
+ if (device.uplink && String(device.uplink).trim() !== '') {
+ badges.push(E('span', { 'class': 'device-uplink-badge', 'title': _('Interface') }, device.uplink.trim()));
+ }
+ if (device.w_ch && Number(device.w_ch) > 0) {
+ var ch = Number(device.w_ch);
+ var band = channelToBand(ch);
+ var bandClass = channelToBandClass(ch);
+ if (band && bandClass) {
+ badges.push(E('span', {
+ 'class': 'device-ch-badge device-ch-' + bandClass,
+ 'title': _('WiFi Channel') + ' ' + ch
+ }, band));
+ }
+ }
+ return badges;
+ }
+
+ function formatDeviceUplinkCh(device) {
+ var parts = [];
+ if (device.uplink && String(device.uplink).trim() !== '') parts.push(device.uplink.trim());
+ if (device.w_ch && Number(device.w_ch) > 0) {
+ var band = channelToBand(Number(device.w_ch));
+ if (band) parts.push(band);
+ }
+ return parts.join(' · ');
+ }
+
// 格式化最后上线时间
function formatLastOnlineTime(lastOnlineTs) {
if (!lastOnlineTs || lastOnlineTs <= 0) {
@@ -5470,7 +5596,7 @@ return view.extend({
var speedUnit = uci.get('bandix', 'traffic', 'speed_unit') || 'bytes';
var stats = result;
- if (!stats || !stats.devices) {
+ if (!stats || !stats.d) {
if (trafficDiv) {
trafficDiv.innerHTML = '' + _('Unable to fetch data') + '
';
}
@@ -5479,19 +5605,19 @@ return view.extend({
// 更新设备计数
if (deviceCountDiv) {
- var onlineCount = stats.devices.filter(d => isDeviceOnline(d)).length;
- deviceCountDiv.textContent = _('Online Devices') + ': ' + onlineCount + ' / ' + stats.devices.length;
+ var onlineCount = stats.d.filter(d => isDeviceOnline(d)).length;
+ deviceCountDiv.textContent = _('Online Devices') + ': ' + onlineCount + ' / ' + stats.d.length;
}
// 计算统计数据(包含所有设备)
- var totalLanUp = stats.devices.reduce((sum, d) => sum + (d.lan_tx_bytes || 0), 0);
- var totalLanDown = stats.devices.reduce((sum, d) => sum + (d.lan_rx_bytes || 0), 0);
- var totalWanUp = stats.devices.reduce((sum, d) => sum + (d.wan_tx_bytes || 0), 0);
- var totalWanDown = stats.devices.reduce((sum, d) => sum + (d.wan_rx_bytes || 0), 0);
- var totalLanSpeedUp = stats.devices.reduce((sum, d) => sum + (d.lan_tx_rate || 0), 0);
- var totalLanSpeedDown = stats.devices.reduce((sum, d) => sum + (d.lan_rx_rate || 0), 0);
- var totalWanSpeedUp = stats.devices.reduce((sum, d) => sum + (d.wan_tx_rate || 0), 0);
- var totalWanSpeedDown = stats.devices.reduce((sum, d) => sum + (d.wan_rx_rate || 0), 0);
+ var totalLanUp = stats.d.reduce((sum, d) => sum + (d.l_tx_b || 0), 0);
+ var totalLanDown = stats.d.reduce((sum, d) => sum + (d.l_rx_b || 0), 0);
+ var totalWanUp = stats.d.reduce((sum, d) => sum + (d.w_tx_b || 0), 0);
+ var totalWanDown = stats.d.reduce((sum, d) => sum + (d.w_rx_b || 0), 0);
+ var totalLanSpeedUp = stats.d.reduce((sum, d) => sum + (d.l_tx_r || 0), 0);
+ var totalLanSpeedDown = stats.d.reduce((sum, d) => sum + (d.l_rx_r || 0), 0);
+ var totalWanSpeedUp = stats.d.reduce((sum, d) => sum + (d.w_tx_r || 0), 0);
+ var totalWanSpeedDown = stats.d.reduce((sum, d) => sum + (d.w_rx_r || 0), 0);
var totalSpeedUp = totalLanSpeedUp + totalWanSpeedUp;
var totalSpeedDown = totalLanSpeedDown + totalWanSpeedDown;
var totalUp = totalLanUp + totalWanUp;
@@ -5749,14 +5875,14 @@ return view.extend({
// 过滤:按选择设备
var selectedMac = (typeof document !== 'undefined' ? (document.getElementById('history-device-select')?.value || '') : '');
- var filteredDevices = (!selectedMac) ? stats.devices : stats.devices.filter(function (d) { return (d.mac === selectedMac); });
+ var filteredDevices = (!selectedMac) ? stats.d : stats.d.filter(function (d) { return (d.mac === selectedMac); });
// 应用排序
filteredDevices = sortDevices(filteredDevices, currentSortBy, currentSortOrder);
// 检查是否有任何设备有 IPv6 地址
var hasAnyIPv6 = filteredDevices.some(function (device) {
- var lanIPv6 = filterLanIPv6(device.ipv6_addresses);
+ var lanIPv6 = filterLanIPv6(device.ip6);
return lanIPv6.length > 0;
});
@@ -5802,20 +5928,21 @@ return view.extend({
var deviceMode = isMobileScreen ? 'simple' : (localStorage.getItem('bandix_device_mode') || 'simple');
var isDetailedMode = deviceMode === 'detailed';
- // 构建设备信息元素
+ var uplinkChBadges = buildDeviceUplinkChBadges(device);
var deviceInfoElements = [
E('div', { 'class': 'device-name' }, [
E('span', {
'class': 'device-status ' + (isOnline ? 'online' : 'offline')
}),
- device.hostname || '-'
+ device.host || '-'
]),
E('div', { 'class': 'device-ip' }, [
- device.connection_type ? E('span', {
+ device.conn ? E('span', {
'class': 'device-connection-type',
- 'title': device.connection_type === 'wifi' ? _('Wireless') : (device.connection_type === 'router' ? _('Router') : _('Wired'))
- }, getConnectionTypeIcon(device.connection_type)) : '',
- device.ip
+ 'title': device.conn === 'wifi' ? _('Wireless') : (device.conn === 'router' ? _('Router') : _('Wired'))
+ }, getConnectionTypeIcon(device.conn)) : '',
+ device.ip4,
+ uplinkChBadges.length ? E('span', { 'class': 'device-uplink-badges' }, uplinkChBadges) : ''
])
];
@@ -5823,9 +5950,9 @@ return view.extend({
if (isDetailedMode) {
// 只有当有设备有 IPv6 时才添加 IPv6 行
if (hasAnyIPv6) {
- var lanIPv6 = filterLanIPv6(device.ipv6_addresses);
+ var lanIPv6 = filterLanIPv6(device.ip6);
if (lanIPv6.length > 0) {
- var allIPv6 = device.ipv6_addresses ? device.ipv6_addresses.join(', ') : '';
+ var allIPv6 = device.ip6 ? device.ip6.join(', ') : '';
deviceInfoElements.push(E('div', {
'class': 'device-ipv6',
'title': allIPv6
@@ -5840,8 +5967,8 @@ return view.extend({
E('div', { 'class': 'device-mac' }, device.mac),
E('div', { 'class': 'device-last-online' }, [
E('span', {}, _('Last Online') + ': '),
- E('span', { 'class': 'device-last-online-value' }, formatLastOnlineTime(device.last_online_ts)),
- E('span', { 'class': 'device-last-online-exact' }, formatLastOnlineExactTime(device.last_online_ts))
+ E('span', { 'class': 'device-last-online-value' }, formatLastOnlineTime(device.last)),
+ E('span', { 'class': 'device-last-online-exact' }, formatLastOnlineExactTime(device.last))
])
);
}
@@ -5857,13 +5984,13 @@ return view.extend({
E('div', { 'class': 'traffic-info' }, [
E('div', { 'class': 'traffic-row' }, [
E('span', { 'class': 'traffic-icon upload' }, '↑'),
- E('span', { 'class': 'traffic-speed lan' }, formatByterate(device.lan_tx_rate || 0, speedUnit)),
- E('span', { 'class': 'traffic-total' }, '(' + formatSize(device.lan_tx_bytes || 0) + ')')
+ E('span', { 'class': 'traffic-speed lan' }, formatByterate(device.l_tx_r || 0, speedUnit)),
+ E('span', { 'class': 'traffic-total' }, '(' + formatSize(device.l_tx_b || 0) + ')')
]),
E('div', { 'class': 'traffic-row' }, [
E('span', { 'class': 'traffic-icon download' }, '↓'),
- E('span', { 'class': 'traffic-speed lan' }, formatByterate(device.lan_rx_rate || 0, speedUnit)),
- E('span', { 'class': 'traffic-total' }, '(' + formatSize(device.lan_rx_bytes || 0) + ')')
+ E('span', { 'class': 'traffic-speed lan' }, formatByterate(device.l_rx_r || 0, speedUnit)),
+ E('span', { 'class': 'traffic-total' }, '(' + formatSize(device.l_rx_b || 0) + ')')
])
])
]),
@@ -5873,13 +6000,13 @@ return view.extend({
E('div', { 'class': 'traffic-info' }, [
E('div', { 'class': 'traffic-row' }, [
E('span', { 'class': 'traffic-icon upload' }, '↑'),
- E('span', { 'class': 'traffic-speed wan' }, formatByterate(device.wan_tx_rate || 0, speedUnit)),
- E('span', { 'class': 'traffic-total' }, '(' + formatSize(device.wan_tx_bytes || 0) + ')')
+ E('span', { 'class': 'traffic-speed wan' }, formatByterate(device.w_tx_r || 0, speedUnit)),
+ E('span', { 'class': 'traffic-total' }, '(' + formatSize(device.w_tx_b || 0) + ')')
]),
E('div', { 'class': 'traffic-row' }, [
E('span', { 'class': 'traffic-icon download' }, '↓'),
- E('span', { 'class': 'traffic-speed wan' }, formatByterate(device.wan_rx_rate || 0, speedUnit)),
- E('span', { 'class': 'traffic-total' }, '(' + formatSize(device.wan_rx_bytes || 0) + ')')
+ E('span', { 'class': 'traffic-speed wan' }, formatByterate(device.w_rx_r || 0, speedUnit)),
+ E('span', { 'class': 'traffic-total' }, '(' + formatSize(device.w_rx_b || 0) + ')')
])
])
]),
@@ -6040,13 +6167,17 @@ return view.extend({
E('div', { 'class': 'device-card-name' }, [
E('span', { 'class': 'device-status ' + (isOnline ? 'online' : 'offline') }),
E('div', {}, [
- E('div', { 'style': 'font-weight: 600;' }, device.hostname || '-'),
+ E('div', { 'style': 'font-weight: 600;' }, device.host || '-'),
E('div', { 'class': 'device-card-ip' }, [
- device.connection_type ? E('span', {
+ device.conn ? E('span', {
'class': 'device-connection-type',
- 'title': device.connection_type === 'wifi' ? _('Wireless') : (device.connection_type === 'router' ? _('Router') : _('Wired'))
- }, getConnectionTypeIcon(device.connection_type)) : '',
- device.ip
+ 'title': device.conn === 'wifi' ? _('Wireless') : (device.conn === 'router' ? _('Router') : _('Wired'))
+ }, getConnectionTypeIcon(device.conn)) : '',
+ device.ip4,
+ (function () {
+ var badges = buildDeviceUplinkChBadges(device);
+ return badges.length ? E('span', { 'class': 'device-uplink-badges' }, badges) : '';
+ })()
])
])
]),
@@ -6092,13 +6223,13 @@ return view.extend({
E('div', { 'class': 'device-card-traffic' }, [
E('div', { 'class': 'device-card-traffic-row' }, [
E('span', { 'style': 'color: ' + BANDIX_COLOR_UPLOAD + '; font-size: 0.75rem; font-weight: bold;' }, '↑'),
- E('span', { 'style': 'font-weight: 600;' }, formatByterate(device.wan_tx_rate || 0, speedUnit)),
- E('span', { 'style': 'font-size: 0.75rem; opacity: 0.7;' }, '(' + formatSize(device.wan_tx_bytes || 0) + ')')
+ E('span', { 'style': 'font-weight: 600;' }, formatByterate(device.w_tx_r || 0, speedUnit)),
+ E('span', { 'style': 'font-size: 0.75rem; opacity: 0.7;' }, '(' + formatSize(device.w_tx_b || 0) + ')')
]),
E('div', { 'class': 'device-card-traffic-row' }, [
E('span', { 'style': 'color: ' + BANDIX_COLOR_DOWNLOAD + '; font-size: 0.75rem; font-weight: bold;' }, '↓'),
- E('span', { 'style': 'font-weight: 600;' }, formatByterate(device.wan_rx_rate || 0, speedUnit)),
- E('span', { 'style': 'font-size: 0.75rem; opacity: 0.7;' }, '(' + formatSize(device.wan_rx_bytes || 0) + ')')
+ E('span', { 'style': 'font-weight: 600;' }, formatByterate(device.w_rx_r || 0, speedUnit)),
+ E('span', { 'style': 'font-size: 0.75rem; opacity: 0.7;' }, '(' + formatSize(device.w_rx_b || 0) + ')')
])
])
])
@@ -6153,13 +6284,13 @@ return view.extend({
E('div', { 'class': 'device-card-traffic' }, [
E('div', { 'class': 'device-card-traffic-row' }, [
E('span', { 'style': 'color: ' + BANDIX_COLOR_UPLOAD + '; font-size: 0.75rem; font-weight: bold;' }, '↑'),
- E('span', { 'style': 'font-weight: 600;' }, formatByterate(device.lan_tx_rate || 0, speedUnit)),
- E('span', { 'style': 'font-size: 0.75rem; opacity: 0.7;' }, '(' + formatSize(device.lan_tx_bytes || 0) + ')')
+ E('span', { 'style': 'font-weight: 600;' }, formatByterate(device.l_tx_r || 0, speedUnit)),
+ E('span', { 'style': 'font-size: 0.75rem; opacity: 0.7;' }, '(' + formatSize(device.l_tx_b || 0) + ')')
]),
E('div', { 'class': 'device-card-traffic-row' }, [
E('span', { 'style': 'color: ' + BANDIX_COLOR_DOWNLOAD + '; font-size: 0.75rem; font-weight: bold;' }, '↓'),
- E('span', { 'style': 'font-weight: 600;' }, formatByterate(device.lan_rx_rate || 0, speedUnit)),
- E('span', { 'style': 'font-size: 0.75rem; opacity: 0.7;' }, '(' + formatSize(device.lan_rx_bytes || 0) + ')')
+ E('span', { 'style': 'font-weight: 600;' }, formatByterate(device.l_rx_r || 0, speedUnit)),
+ E('span', { 'style': 'font-size: 0.75rem; opacity: 0.7;' }, '(' + formatSize(device.l_rx_b || 0) + ')')
])
])
])
@@ -6182,7 +6313,7 @@ return view.extend({
// 更新历史趋势中的设备下拉
try {
- latestDevices = stats.devices || [];
+ latestDevices = stats.d || [];
updateDeviceOptions(latestDevices);
} catch (e) { }
});
@@ -6233,33 +6364,33 @@ return view.extend({
displayData.forEach(function (item) {
var rankingItem = E('div', {
'class': 'usage-ranking-item',
- 'style': '--progress-width: ' + (item.percentage || 0) + '%;'
+ 'style': '--progress-width: ' + (item.pct || 0) + '%;'
}, [
- E('div', { 'class': 'usage-ranking-rank' }, (item.rank || '-')),
+ E('div', { 'class': 'usage-ranking-rank' }, (item.r || '-')),
E('div', { 'class': 'usage-ranking-info' }, [
E('div', { 'class': 'usage-ranking-device' }, [
- E('div', { 'class': 'usage-ranking-name' }, item.hostname || item.ip || item.mac || '-'),
+ E('div', { 'class': 'usage-ranking-name' }, item.host || item.ip4 || item.mac || '-'),
E('div', { 'class': 'usage-ranking-meta' }, [
- E('span', {}, item.ip || '-'),
+ E('span', {}, item.ip4 || '-'),
E('span', {}, item.mac || '-'),
- E('span', { 'class': 'usage-ranking-meta-total' }, formatSize(item.total_bytes || 0))
+ E('span', { 'class': 'usage-ranking-meta-total' }, formatSize(item.t_b || 0))
])
]),
E('div', { 'class': 'usage-ranking-stats' }, [
E('div', { 'class': 'usage-ranking-traffic' }, [
E('span', { 'class': 'usage-ranking-traffic-item tx' }, [
E('span', { 'class': 'usage-ranking-traffic-arrow' }, '↑'),
- E('span', {}, formatSize(item.tx_bytes || 0))
+ E('span', {}, formatSize(item.tx_b || 0))
]),
E('span', { 'class': 'usage-ranking-traffic-item rx' }, [
E('span', { 'class': 'usage-ranking-traffic-arrow' }, '↓'),
- E('span', {}, formatSize(item.rx_bytes || 0))
+ E('span', {}, formatSize(item.rx_b || 0))
]),
E('span', { 'class': 'usage-ranking-traffic-item total' }, [
- E('span', {}, formatSize(item.total_bytes || 0))
+ E('span', {}, formatSize(item.t_b || 0))
])
]),
- E('div', { 'class': 'usage-ranking-percentage' }, (item.percentage || 0).toFixed(1) + '%')
+ E('div', { 'class': 'usage-ranking-percentage' }, (item.pct || 0).toFixed(1) + '%')
])
])
]);
@@ -6312,25 +6443,25 @@ return view.extend({
// 获取设备用量排行
callGetTrafficUsageRanking(startMs, endMs, networkType).then(function (result) {
console.log('Query result:', result);
- if (!result || !result.rankings) {
+ if (!result || !result.r) {
return;
}
- usageRankingData = result.rankings;
+ usageRankingData = result.r;
// 更新时间范围显示(包含上下行流量和总流量)
var timeRangeEl = document.getElementById('usage-ranking-timerange');
- if (timeRangeEl && result.start_ms && result.end_ms) {
- var timeRangeText = formatTimeRange(result.start_ms, result.end_ms);
+ if (timeRangeEl && result.start && result.end) {
+ var timeRangeText = formatTimeRange(result.start, result.end);
var parts = [];
- if (result.total_tx_bytes !== undefined && result.total_tx_bytes !== null) {
- parts.push('↑' + formatSize(result.total_tx_bytes));
+ if (result.t_tx_b !== undefined && result.t_tx_b !== null) {
+ parts.push('↑' + formatSize(result.t_tx_b));
}
- if (result.total_rx_bytes !== undefined && result.total_rx_bytes !== null) {
- parts.push('↓' + formatSize(result.total_rx_bytes));
+ if (result.t_rx_b !== undefined && result.t_rx_b !== null) {
+ parts.push('↓' + formatSize(result.t_rx_b));
}
- if (result.total_bytes !== undefined && result.total_bytes !== null) {
- parts.push(formatSize(result.total_bytes));
+ if (result.t_b !== undefined && result.t_b !== null) {
+ parts.push(formatSize(result.t_b));
}
if (parts.length > 0) {
timeRangeText += ' · ' + parts.join(' · ');
@@ -6344,8 +6475,8 @@ return view.extend({
} else {
// 如果还没有设备数据,先获取设备数据
callStatus().then(function (deviceResult) {
- if (deviceResult && deviceResult.devices) {
- latestDevices = deviceResult.devices;
+ if (deviceResult && deviceResult.d) {
+ latestDevices = deviceResult.d;
updateDeviceSelectForIncrements(latestDevices);
}
}).catch(function (err) {
@@ -6374,18 +6505,18 @@ return view.extend({
if (!item) return null;
var tsMs = item.ts_ms;
- if (!tsMs && item.start_ts_ms) tsMs = item.start_ts_ms;
- if (!tsMs && item.end_ts_ms) tsMs = item.end_ts_ms;
+ if (!tsMs && item.start) tsMs = item.start;
+ if (!tsMs && item.end) tsMs = item.end;
var rxBytes = (item.rx_bytes !== undefined && item.rx_bytes !== null) ? item.rx_bytes : null;
var txBytes = (item.tx_bytes !== undefined && item.tx_bytes !== null) ? item.tx_bytes : null;
var totalBytes = (item.total_bytes !== undefined && item.total_bytes !== null) ? item.total_bytes : null;
if (rxBytes === null) {
- rxBytes = (item.wan_rx_bytes_inc || 0) + (item.lan_rx_bytes_inc || 0);
+ rxBytes = (item.w_rx_b || 0) + (item.l_rx_b || 0);
}
if (txBytes === null) {
- txBytes = (item.wan_tx_bytes_inc || 0) + (item.lan_tx_bytes_inc || 0);
+ txBytes = (item.w_tx_b || 0) + (item.l_tx_b || 0);
}
if (totalBytes === null) {
totalBytes = rxBytes + txBytes;
@@ -6438,7 +6569,7 @@ return view.extend({
}
callGetTrafficUsageIncrements(startMs, endMs, selectedAggregation, selectedMac, selectedNetworkType).then(function (result) {
- if (!result || !result.increments) {
+ if (!result || !result.inc) {
var container = document.getElementById('traffic-increments-container');
if (container) {
container.innerHTML = '' + _('No data') + '
';
@@ -6448,21 +6579,21 @@ return view.extend({
return;
}
- var normalizedIncrements = normalizeTrafficIncrementsList(result.increments);
+ var normalizedIncrements = normalizeTrafficIncrementsList(result.inc);
// 更新时间范围显示(包含上下行流量和总流量)
var timeRangeEl = document.getElementById('traffic-increments-timerange');
- if (timeRangeEl && result.start_ms && result.end_ms) {
- var timeRangeText = formatTimeRange(result.start_ms, result.end_ms);
+ if (timeRangeEl && result.start && result.end) {
+ var timeRangeText = formatTimeRange(result.start, result.end);
var parts = [];
- if (result.total_tx_bytes !== undefined && result.total_tx_bytes !== null) {
- parts.push('↑' + formatSize(result.total_tx_bytes));
+ if (result.t_tx_b !== undefined && result.t_tx_b !== null) {
+ parts.push('↑' + formatSize(result.t_tx_b));
}
- if (result.total_rx_bytes !== undefined && result.total_rx_bytes !== null) {
- parts.push('↓' + formatSize(result.total_rx_bytes));
+ if (result.t_rx_b !== undefined && result.t_rx_b !== null) {
+ parts.push('↓' + formatSize(result.t_rx_b));
}
- if (result.total_bytes !== undefined && result.total_bytes !== null) {
- parts.push(formatSize(result.total_bytes));
+ if (result.t_b !== undefined && result.t_b !== null) {
+ parts.push(formatSize(result.t_b));
}
if (parts.length > 0) {
timeRangeText += ' · ' + parts.join(' · ');
@@ -6507,15 +6638,15 @@ return view.extend({
var summary = E('div', { 'class': 'traffic-increments-summary' }, [
E('div', { 'class': 'traffic-increments-summary-item' }, [
E('div', { 'class': 'traffic-increments-summary-label' }, _('Total Upload')),
- E('div', { 'class': 'traffic-increments-summary-value' }, formatSize(result.total_tx_bytes || 0))
+ E('div', { 'class': 'traffic-increments-summary-value' }, formatSize(result.t_tx_b || 0))
]),
E('div', { 'class': 'traffic-increments-summary-item' }, [
E('div', { 'class': 'traffic-increments-summary-label' }, _('Total Download')),
- E('div', { 'class': 'traffic-increments-summary-value' }, formatSize(result.total_rx_bytes || 0))
+ E('div', { 'class': 'traffic-increments-summary-value' }, formatSize(result.t_rx_b || 0))
]),
E('div', { 'class': 'traffic-increments-summary-item' }, [
E('div', { 'class': 'traffic-increments-summary-label' }, _('Total')),
- E('div', { 'class': 'traffic-increments-summary-value' }, formatSize(result.total_bytes || 0))
+ E('div', { 'class': 'traffic-increments-summary-value' }, formatSize(result.t_b || 0))
])
]);
@@ -6526,7 +6657,7 @@ return view.extend({
// 绘制图表
setTimeout(function () {
- var aggregation = result.aggregation || 'hourly';
+ var aggregation = result.agg || 'hourly';
// 重置缩放状态
if (incrementsZoomTimer) {
@@ -6639,8 +6770,8 @@ return view.extend({
if (!aOnline && bOnline) return 1;
// 在线状态相同时,按IP地址排序
- var aIp = a.ip || '';
- var bIp = b.ip || '';
+ var aIp = a.ip4 || '';
+ var bIp = b.ip4 || '';
// 将IP地址转换为数字进行比较
var aIpParts = aIp.split('.').map(function (part) { return parseInt(part) || 0; });
@@ -6662,7 +6793,7 @@ return view.extend({
// 添加设备选项
sortedDevices.forEach(function (device) {
if (device.mac) {
- var label = (device.hostname || device.ip || device.mac || '-') + (device.ip ? ' (' + device.ip + ')' : '') + (device.mac ? ' [' + device.mac + ']' : '');
+ var label = (device.host || device.ip4 || device.mac || '-') + (device.ip4 ? ' (' + device.ip4 + ')' : '') + (device.mac ? ' [' + device.mac + ']' : '');
var option = E('option', { 'value': device.mac }, label);
macSelect.appendChild(option);
}
@@ -6897,7 +7028,7 @@ return view.extend({
} else {
// 按小时聚合:显示整点小时
// 使用 start_ts_ms 来确定这个时间段代表哪个小时
- var startTs = item.start_ts_ms || item.ts_ms;
+ var startTs = item.start || item.ts_ms;
var startDate = new Date(startTs);
// 使用开始时间的整点小时
@@ -7053,7 +7184,7 @@ return view.extend({
}
var item = originalIncrements[barIndex];
- var timeStr = formatTimeRange(item.start_ts_ms || item.ts_ms, item.end_ts_ms || item.ts_ms, isDaily);
+ var timeStr = formatTimeRange(item.start || item.ts_ms, item.end || item.ts_ms, isDaily);
// Get speed unit from UCI config
var speedUnit = uci.get('bandix', 'traffic', 'speed_unit') || 'bytes';
@@ -7071,25 +7202,25 @@ return view.extend({
'' +
'
' +
'
WAN Upload
' +
- '
' + formatSize(item.wan_tx_bytes_inc || 0) + '
' +
+ '
' + formatSize(item.w_tx_b || 0) + '
' +
'
' +
'
' +
'
WAN Download
' +
- '
' + formatSize(item.wan_rx_bytes_inc || 0) + '
' +
+ '
' + formatSize(item.w_rx_b || 0) + '
' +
'
' +
'
' +
// 速度统计分组
'' +
'' + _('Upload Statistics') + '
' +
- '' + _('Average') + '' + formatByterate(item.wan_tx_rate_avg || 0, speedUnit) + '
' +
- 'P95' + formatByterate(item.wan_tx_rate_p95 || 0, speedUnit) + '
' +
- '' + _('Maximum') + '' + formatByterate(item.wan_tx_rate_max || 0, speedUnit) + '
' +
+ '' + _('Average') + '' + formatByterate(item.w_tx_avg || 0, speedUnit) + '
' +
+ 'P95' + formatByterate(item.w_tx_p95 || 0, speedUnit) + '
' +
+ '' + _('Maximum') + '' + formatByterate(item.w_tx_max || 0, speedUnit) + '
' +
'' + _('Download Statistics') + '
' +
- '' + _('Average') + '' + formatByterate(item.wan_rx_rate_avg || 0, speedUnit) + '
' +
- 'P95' + formatByterate(item.wan_rx_rate_p95 || 0, speedUnit) + '
' +
- '' + _('Maximum') + '' + formatByterate(item.wan_rx_rate_max || 0, speedUnit) + '
' +
+ '' + _('Average') + '' + formatByterate(item.w_rx_avg || 0, speedUnit) + '
' +
+ 'P95' + formatByterate(item.w_rx_p95 || 0, speedUnit) + '
' +
+ '' + _('Maximum') + '' + formatByterate(item.w_rx_max || 0, speedUnit) + '
' +
'';
} else if (networkType === 'lan') {
tooltipContent +=
@@ -7101,25 +7232,25 @@ return view.extend({
'' +
'
' +
'
LAN Upload
' +
- '
' + formatSize(item.lan_tx_bytes_inc || 0) + '
' +
+ '
' + formatSize(item.l_tx_b || 0) + '
' +
'
' +
'
' +
'
LAN Download
' +
- '
' + formatSize(item.lan_rx_bytes_inc || 0) + '
' +
+ '
' + formatSize(item.l_rx_b || 0) + '
' +
'
' +
'
' +
// 速度统计分组
'' +
'' + _('Upload Statistics') + '
' +
- '' + _('Average') + '' + formatByterate(item.lan_tx_rate_avg || 0, speedUnit) + '
' +
- 'P95' + formatByterate(item.lan_tx_rate_p95 || 0, speedUnit) + '
' +
- '' + _('Maximum') + '' + formatByterate(item.lan_tx_rate_max || 0, speedUnit) + '
' +
+ '' + _('Average') + '' + formatByterate(item.l_tx_avg || 0, speedUnit) + '
' +
+ 'P95' + formatByterate(item.l_tx_p95 || 0, speedUnit) + '
' +
+ '' + _('Maximum') + '' + formatByterate(item.l_tx_max || 0, speedUnit) + '
' +
'' + _('Download Statistics') + '
' +
- '' + _('Average') + '' + formatByterate(item.lan_rx_rate_avg || 0, speedUnit) + '
' +
- 'P95' + formatByterate(item.lan_rx_rate_p95 || 0, speedUnit) + '
' +
- '' + _('Maximum') + '' + formatByterate(item.lan_rx_rate_max || 0, speedUnit) + '
' +
+ '' + _('Average') + '' + formatByterate(item.l_rx_avg || 0, speedUnit) + '
' +
+ 'P95' + formatByterate(item.l_rx_p95 || 0, speedUnit) + '
' +
+ '' + _('Maximum') + '' + formatByterate(item.l_rx_max || 0, speedUnit) + '
' +
'';
} else {
// networkType === 'all' 或其他情况,显示所有section
@@ -7132,25 +7263,25 @@ return view.extend({
'' +
'
' +
'
WAN Upload
' +
- '
' + formatSize(item.wan_tx_bytes_inc || 0) + '
' +
+ '
' + formatSize(item.w_tx_b || 0) + '
' +
'
' +
'
' +
'
WAN Download
' +
- '
' + formatSize(item.wan_rx_bytes_inc || 0) + '
' +
+ '
' + formatSize(item.w_rx_b || 0) + '
' +
'
' +
'
' +
// 速度统计分组
'' +
'' + _('Upload Statistics') + '
' +
- '' + _('Average') + '' + formatByterate(item.wan_tx_rate_avg || 0, speedUnit) + '
' +
- 'P95' + formatByterate(item.wan_tx_rate_p95 || 0, speedUnit) + '
' +
- '' + _('Maximum') + '' + formatByterate(item.wan_tx_rate_max || 0, speedUnit) + '
' +
+ '' + _('Average') + '' + formatByterate(item.w_tx_avg || 0, speedUnit) + '
' +
+ 'P95' + formatByterate(item.w_tx_p95 || 0, speedUnit) + '
' +
+ '' + _('Maximum') + '' + formatByterate(item.w_tx_max || 0, speedUnit) + '
' +
'' + _('Download Statistics') + '
' +
- '' + _('Average') + '' + formatByterate(item.wan_rx_rate_avg || 0, speedUnit) + '
' +
- 'P95' + formatByterate(item.wan_rx_rate_p95 || 0, speedUnit) + '
' +
- '' + _('Maximum') + '' + formatByterate(item.wan_rx_rate_max || 0, speedUnit) + '
' +
+ '' + _('Average') + '' + formatByterate(item.w_rx_avg || 0, speedUnit) + '
' +
+ 'P95' + formatByterate(item.w_rx_p95 || 0, speedUnit) + '
' +
+ '' + _('Maximum') + '' + formatByterate(item.w_rx_max || 0, speedUnit) + '
' +
'' +
// LAN Traffic Section
@@ -7161,25 +7292,25 @@ return view.extend({
'' +
'
' +
'
LAN Upload
' +
- '
' + formatSize(item.lan_tx_bytes_inc || 0) + '
' +
+ '
' + formatSize(item.l_tx_b || 0) + '
' +
'
' +
'
' +
'
LAN Download
' +
- '
' + formatSize(item.lan_rx_bytes_inc || 0) + '
' +
+ '
' + formatSize(item.l_rx_b || 0) + '
' +
'
' +
'
' +
// 速度统计分组
'' +
'' + _('Upload Statistics') + '
' +
- '' + _('Average') + '' + formatByterate(item.lan_tx_rate_avg || 0, speedUnit) + '
' +
- 'P95' + formatByterate(item.lan_tx_rate_p95 || 0, speedUnit) + '
' +
- '' + _('Maximum') + '' + formatByterate(item.lan_tx_rate_max || 0, speedUnit) + '
' +
+ '' + _('Average') + '' + formatByterate(item.l_tx_avg || 0, speedUnit) + '
' +
+ 'P95' + formatByterate(item.l_tx_p95 || 0, speedUnit) + '
' +
+ '' + _('Maximum') + '' + formatByterate(item.l_tx_max || 0, speedUnit) + '
' +
'' + _('Download Statistics') + '
' +
- '' + _('Average') + '' + formatByterate(item.lan_rx_rate_avg || 0, speedUnit) + '
' +
- 'P95' + formatByterate(item.lan_rx_rate_p95 || 0, speedUnit) + '
' +
- '' + _('Maximum') + '' + formatByterate(item.lan_rx_rate_max || 0, speedUnit) + '
' +
+ '' + _('Average') + '' + formatByterate(item.l_rx_avg || 0, speedUnit) + '
' +
+ 'P95' + formatByterate(item.l_rx_p95 || 0, speedUnit) + '
' +
+ '' + _('Maximum') + '' + formatByterate(item.l_rx_max || 0, speedUnit) + '
' +
'';
}
diff --git a/luci-app-bandix/htdocs/luci-static/resources/view/bandix/settings.js b/luci-app-bandix/htdocs/luci-static/resources/view/bandix/settings.js
index 8e290dc7..7777b4c2 100644
--- a/luci-app-bandix/htdocs/luci-static/resources/view/bandix/settings.js
+++ b/luci-app-bandix/htdocs/luci-static/resources/view/bandix/settings.js
@@ -689,6 +689,24 @@ return view.extend({
o.rmempty = false;
o.depends('traffic_enable_storage', '1');
+ o = s.option(form.Flag, 'traffic_neighbor_flush_enable', _('Enable Neighbor Flush'),
+ _('Enable periodic flush of neighbor table data. Generally not recommended if the device list works normally.'));
+ o.default = '0';
+ o.rmempty = false;
+
+ o = s.option(form.ListValue, 'traffic_neighbor_flush_interval', _('Neighbor Flush Interval'),
+ _('Set the interval for flushing neighbor table data'));
+ o.value('600', _('10 minutes'));
+ o.value('900', _('15 minutes'));
+ o.value('1800', _('30 minutes'));
+ o.value('3600', _('1 hour'));
+ o.value('7200', _('2 hours'));
+ o.value('43200', _('12 hours'));
+ o.value('86400', _('24 hours'));
+ o.default = '600';
+ o.rmempty = false;
+ o.depends('traffic_neighbor_flush_enable', '1');
+
// 添加历史流量周期(秒)
o = s.option(form.ListValue, 'traffic_realtime_window', _('Realtime Traffic Period'),
_('Does not occupy storage space, stored only in memory'));
diff --git a/luci-app-bandix/po/es/bandix.po b/luci-app-bandix/po/es/bandix.po
index d8bdbef1..8ae3df77 100644
--- a/luci-app-bandix/po/es/bandix.po
+++ b/luci-app-bandix/po/es/bandix.po
@@ -1163,8 +1163,65 @@ msgstr "Excluir dispositivo de interfaz"
msgid "Exclude the router itself (interface device) from traffic statistics"
msgstr "Excluir el propio router (dispositivo de interfaz) de las estadísticas de tráfico"
+msgid "Enable Neighbor Flush"
+msgstr "Habilitar vaciado de tabla de vecinos"
+
+msgid "Enable periodic flush of neighbor table data. Generally not recommended if the device list works normally."
+msgstr "Habilitar vaciado periódico de datos de la tabla de vecinos. Generalmente no se recomienda si la lista de dispositivos funciona con normalidad."
+
+msgid "Neighbor Flush Interval"
+msgstr "Intervalo de vaciado de tabla de vecinos"
+
+msgid "Set the interval for flushing neighbor table data"
+msgstr "Establecer el intervalo de vaciado de datos de la tabla de vecinos"
+
msgid "No devices"
msgstr "Sin dispositivos"
msgid "LAN Traffic is viewed from the entire subnet perspective, so upload and download are always equal, forming a closed loop. Additionally, due to the presence of dedicated hardware switching chips, LAN traffic between 2 devices (e.g., PC - NAS) within the local network may not be monitorable."
msgstr "El tráfico LAN se ve desde la perspectiva de toda la subred, por lo que la carga y descarga siempre son iguales, formando un bucle cerrado. Además, debido a la presencia de chips de conmutación de hardware dedicados, el tráfico LAN entre 2 dispositivos (p. ej., PC - NAS) dentro de la red local puede no ser monitoreable."
+
+msgid "Protocol"
+msgstr "Protocolo"
+
+msgid "State"
+msgstr "Estado"
+
+msgid "Send"
+msgstr "Enviar"
+
+msgid "Reply"
+msgstr "Respuesta"
+
+msgid "Flags"
+msgstr "Marcas"
+
+msgid "Loading…"
+msgstr "Cargando…"
+
+msgid "No connections"
+msgstr "Sin conexiones"
+
+msgid "pkts"
+msgstr "paquetes"
+
+msgid "Failed to load"
+msgstr "Error al cargar"
+
+msgid "Connection Details"
+msgstr "Detalles de conexión"
+
+msgid "Details"
+msgstr "Detalles"
+
+msgid "Orig Src"
+msgstr "Origen orig."
+
+msgid "Orig Dst"
+msgstr "Destino orig."
+
+msgid "Repl Src"
+msgstr "Origen resp."
+
+msgid "Repl Dst"
+msgstr "Destino resp."
diff --git a/luci-app-bandix/po/fr/bandix.po b/luci-app-bandix/po/fr/bandix.po
index 508a9a2d..6db94280 100644
--- a/luci-app-bandix/po/fr/bandix.po
+++ b/luci-app-bandix/po/fr/bandix.po
@@ -1163,8 +1163,65 @@ msgstr "Exclure l'appareil d'interface"
msgid "Exclude the router itself (interface device) from traffic statistics"
msgstr "Exclure le routeur lui-même (appareil d'interface) des statistiques de trafic"
+msgid "Enable Neighbor Flush"
+msgstr "Activer la vidange de la table des voisins"
+
+msgid "Enable periodic flush of neighbor table data. Generally not recommended if the device list works normally."
+msgstr "Activer le vidage périodique des données de la table des voisins. Généralement non recommandé si la liste des appareils fonctionne normalement."
+
+msgid "Neighbor Flush Interval"
+msgstr "Intervalle de vidange de la table des voisins"
+
+msgid "Set the interval for flushing neighbor table data"
+msgstr "Définir l'intervalle de vidange des données de la table des voisins"
+
msgid "No devices"
msgstr "Aucun appareil"
msgid "LAN Traffic is viewed from the entire subnet perspective, so upload and download are always equal, forming a closed loop. Additionally, due to the presence of dedicated hardware switching chips, LAN traffic between 2 devices (e.g., PC - NAS) within the local network may not be monitorable."
-msgstr "Le trafic LAN est vu depuis la perspective de tout le sous-réseau, donc l'upload et le download sont toujours égaux, formant une boucle fermée. De plus, en raison de la présence de puces de commutation matérielles dédiées, le trafic LAN entre 2 appareils (par ex., PC - NAS) au sein du réseau local peut ne pas être surveillable."
\ No newline at end of file
+msgstr "Le trafic LAN est vu depuis la perspective de tout le sous-réseau, donc l'upload et le download sont toujours égaux, formant une boucle fermée. De plus, en raison de la présence de puces de commutation matérielles dédiées, le trafic LAN entre 2 appareils (par ex., PC - NAS) au sein du réseau local peut ne pas être surveillable."
+
+msgid "Protocol"
+msgstr "Protocole"
+
+msgid "State"
+msgstr "État"
+
+msgid "Send"
+msgstr "Envoyer"
+
+msgid "Reply"
+msgstr "Réponse"
+
+msgid "Flags"
+msgstr "Drapeaux"
+
+msgid "Loading…"
+msgstr "Chargement…"
+
+msgid "No connections"
+msgstr "Aucune connexion"
+
+msgid "pkts"
+msgstr "paquets"
+
+msgid "Failed to load"
+msgstr "Échec du chargement"
+
+msgid "Connection Details"
+msgstr "Détails de connexion"
+
+msgid "Details"
+msgstr "Détails"
+
+msgid "Orig Src"
+msgstr "Src. orig."
+
+msgid "Orig Dst"
+msgstr "Dest. orig."
+
+msgid "Repl Src"
+msgstr "Src. répl."
+
+msgid "Repl Dst"
+msgstr "Dest. répl."
\ No newline at end of file
diff --git a/luci-app-bandix/po/ja/bandix.po b/luci-app-bandix/po/ja/bandix.po
index bb021096..6988e34c 100644
--- a/luci-app-bandix/po/ja/bandix.po
+++ b/luci-app-bandix/po/ja/bandix.po
@@ -1163,8 +1163,65 @@ msgstr "インターフェースデバイスを除外"
msgid "Exclude the router itself (interface device) from traffic statistics"
msgstr "トラフィック統計からルーター自体(インターフェースデバイス)を除外する"
+msgid "Enable Neighbor Flush"
+msgstr "ネイバーテーブルフラッシュを有効にする"
+
+msgid "Enable periodic flush of neighbor table data. Generally not recommended if the device list works normally."
+msgstr "ネイバーテーブルデータの定期フラッシュを有効にします。デバイスリストが正常に動作している場合は、通常は有効にしないことをお勧めします。"
+
+msgid "Neighbor Flush Interval"
+msgstr "ネイバーテーブルフラッシュ間隔"
+
+msgid "Set the interval for flushing neighbor table data"
+msgstr "ネイバーテーブルデータのフラッシュ間隔を設定"
+
msgid "No devices"
msgstr "デバイスなし"
msgid "LAN Traffic is viewed from the entire subnet perspective, so upload and download are always equal, forming a closed loop. Additionally, due to the presence of dedicated hardware switching chips, LAN traffic between 2 devices (e.g., PC - NAS) within the local network may not be monitorable."
msgstr "LANトラフィックはサブネット全体の視点から見られるため、アップロードとダウンロードは常に等しく、閉ループを形成します。さらに、専用ハードウェアスイッチングチップの存在により、ローカルネットワーク内の2つのデバイス(例:PC - NAS)間のLANトラフィックは監視できない可能性があります。"
+
+msgid "Protocol"
+msgstr "プロトコル"
+
+msgid "State"
+msgstr "状態"
+
+msgid "Send"
+msgstr "送信"
+
+msgid "Reply"
+msgstr "応答"
+
+msgid "Flags"
+msgstr "フラグ"
+
+msgid "Loading…"
+msgstr "読み込み中…"
+
+msgid "No connections"
+msgstr "接続なし"
+
+msgid "pkts"
+msgstr "パケット"
+
+msgid "Failed to load"
+msgstr "読み込み失敗"
+
+msgid "Connection Details"
+msgstr "接続詳細"
+
+msgid "Details"
+msgstr "詳細"
+
+msgid "Orig Src"
+msgstr "送信元"
+
+msgid "Orig Dst"
+msgstr "送信先"
+
+msgid "Repl Src"
+msgstr "応答元"
+
+msgid "Repl Dst"
+msgstr "応答先"
diff --git a/luci-app-bandix/po/pl/bandix.po b/luci-app-bandix/po/pl/bandix.po
index 6ace04d7..4b05ba54 100644
--- a/luci-app-bandix/po/pl/bandix.po
+++ b/luci-app-bandix/po/pl/bandix.po
@@ -1163,8 +1163,65 @@ msgstr "Wyklucz urządzenie interfejsu"
msgid "Exclude the router itself (interface device) from traffic statistics"
msgstr "Wyklucz sam router (urządzenie interfejsu) ze statystyk ruchu"
+msgid "Enable Neighbor Flush"
+msgstr "Włącz odświeżanie tabeli sąsiadów"
+
+msgid "Enable periodic flush of neighbor table data. Generally not recommended if the device list works normally."
+msgstr "Włącz okresowe odświeżanie danych tabeli sąsiadów. Ogólnie nie zalecane, jeśli lista urządzeń działa prawidłowo."
+
+msgid "Neighbor Flush Interval"
+msgstr "Interwał odświeżania tabeli sąsiadów"
+
+msgid "Set the interval for flushing neighbor table data"
+msgstr "Ustaw interwał odświeżania danych tabeli sąsiadów"
+
msgid "No devices"
msgstr "Brak urządzeń"
msgid "LAN Traffic is viewed from the entire subnet perspective, so upload and download are always equal, forming a closed loop. Additionally, due to the presence of dedicated hardware switching chips, LAN traffic between 2 devices (e.g., PC - NAS) within the local network may not be monitorable."
msgstr "Ruch LAN jest postrzegany z perspektywy całej podsieci, więc przesyłanie i pobieranie są zawsze równe, tworząc zamkniętą pętlę. Ponadto, ze względu na obecność dedykowanych chipów przełączających sprzętowych, ruch LAN między 2 urządzeniami (np. PC - NAS) w sieci lokalnej może nie być monitorowany."
+
+msgid "Protocol"
+msgstr "Protokół"
+
+msgid "State"
+msgstr "Stan"
+
+msgid "Send"
+msgstr "Wyślij"
+
+msgid "Reply"
+msgstr "Odpowiedź"
+
+msgid "Flags"
+msgstr "Flagi"
+
+msgid "Loading…"
+msgstr "Ładowanie…"
+
+msgid "No connections"
+msgstr "Brak połączeń"
+
+msgid "pkts"
+msgstr "pakiety"
+
+msgid "Failed to load"
+msgstr "Błąd ładowania"
+
+msgid "Connection Details"
+msgstr "Szczegóły połączenia"
+
+msgid "Details"
+msgstr "Szczegóły"
+
+msgid "Orig Src"
+msgstr "Źródło orig."
+
+msgid "Orig Dst"
+msgstr "Cel orig."
+
+msgid "Repl Src"
+msgstr "Źródło odp."
+
+msgid "Repl Dst"
+msgstr "Cel odp."
diff --git a/luci-app-bandix/po/ru/bandix.po b/luci-app-bandix/po/ru/bandix.po
index dad07249..d8897d0b 100644
--- a/luci-app-bandix/po/ru/bandix.po
+++ b/luci-app-bandix/po/ru/bandix.po
@@ -1163,8 +1163,65 @@ msgstr "Исключить устройство интерфейса"
msgid "Exclude the router itself (interface device) from traffic statistics"
msgstr "Исключить сам роутер (устройство интерфейса) из статистики трафика"
+msgid "Enable Neighbor Flush"
+msgstr "Включить сброс таблицы соседей"
+
+msgid "Enable periodic flush of neighbor table data. Generally not recommended if the device list works normally."
+msgstr "Включить периодический сброс данных таблицы соседей. Обычно не рекомендуется, если список устройств работает нормально."
+
+msgid "Neighbor Flush Interval"
+msgstr "Интервал сброса таблицы соседей"
+
+msgid "Set the interval for flushing neighbor table data"
+msgstr "Установить интервал сброса данных таблицы соседей"
+
msgid "No devices"
msgstr "Нет устройств"
msgid "LAN Traffic is viewed from the entire subnet perspective, so upload and download are always equal, forming a closed loop. Additionally, due to the presence of dedicated hardware switching chips, LAN traffic between 2 devices (e.g., PC - NAS) within the local network may not be monitorable."
-msgstr "Трафик LAN рассматривается с точки зрения всей подсети, поэтому загрузка и выгрузка всегда равны, образуя замкнутый цикл. Кроме того, из-за наличия специализированных аппаратных коммутационных чипов, трафик LAN между 2 устройствами (например, ПК - NAS) в локальной сети может быть не подлежащим мониторингу."
\ No newline at end of file
+msgstr "Трафик LAN рассматривается с точки зрения всей подсети, поэтому загрузка и выгрузка всегда равны, образуя замкнутый цикл. Кроме того, из-за наличия специализированных аппаратных коммутационных чипов, трафик LAN между 2 устройствами (например, ПК - NAS) в локальной сети может быть не подлежащим мониторингу."
+
+msgid "Protocol"
+msgstr "Протокол"
+
+msgid "State"
+msgstr "Состояние"
+
+msgid "Send"
+msgstr "Отправка"
+
+msgid "Reply"
+msgstr "Ответ"
+
+msgid "Flags"
+msgstr "Флаги"
+
+msgid "Loading…"
+msgstr "Загрузка…"
+
+msgid "No connections"
+msgstr "Нет подключений"
+
+msgid "pkts"
+msgstr "пакеты"
+
+msgid "Failed to load"
+msgstr "Ошибка загрузки"
+
+msgid "Connection Details"
+msgstr "Подробности соединения"
+
+msgid "Details"
+msgstr "Подробнее"
+
+msgid "Orig Src"
+msgstr "Ист. отпр."
+
+msgid "Orig Dst"
+msgstr "Назнач. отпр."
+
+msgid "Repl Src"
+msgstr "Ист. ответа"
+
+msgid "Repl Dst"
+msgstr "Назнач. ответа"
\ No newline at end of file
diff --git a/luci-app-bandix/po/zh_Hans/bandix.po b/luci-app-bandix/po/zh_Hans/bandix.po
index 52698a34..ab064ace 100644
--- a/luci-app-bandix/po/zh_Hans/bandix.po
+++ b/luci-app-bandix/po/zh_Hans/bandix.po
@@ -1190,8 +1190,65 @@ msgstr "排除接口设备"
msgid "Exclude the router itself (interface device) from traffic statistics"
msgstr "从流量统计中排除路由器本身(接口设备)"
+msgid "Enable Neighbor Flush"
+msgstr "启用邻居表刷新"
+
+msgid "Enable periodic flush of neighbor table data. Generally not recommended if the device list works normally."
+msgstr "启用定期刷新邻居表数据。若设备列表正常,通常不建议开启。"
+
+msgid "Neighbor Flush Interval"
+msgstr "邻居表刷新间隔"
+
+msgid "Set the interval for flushing neighbor table data"
+msgstr "设置邻居表数据刷新的时间间隔"
+
msgid "No devices"
msgstr "无设备"
msgid "LAN Traffic is viewed from the entire subnet perspective, so upload and download are always equal, forming a closed loop. Additionally, due to the presence of dedicated hardware switching chips, LAN traffic between 2 devices (e.g., PC - NAS) within the local network may not be monitorable."
-msgstr "LAN 流量是从整个子网视角来看,所以上传和下载总是相等,是一个闭环。另外,由于专用硬件交换芯片的存在,局域网中 2 终端(比如 PC - NAS)之间的 LAN 流量可能无法监控。"
\ No newline at end of file
+msgstr "LAN 流量是从整个子网视角来看,所以上传和下载总是相等,是一个闭环。另外,由于专用硬件交换芯片的存在,局域网中 2 终端(比如 PC - NAS)之间的 LAN 流量可能无法监控。"
+
+msgid "Protocol"
+msgstr "协议"
+
+msgid "State"
+msgstr "状态"
+
+msgid "Send"
+msgstr "发送"
+
+msgid "Reply"
+msgstr "回复"
+
+msgid "Flags"
+msgstr "标记"
+
+msgid "Loading…"
+msgstr "加载中…"
+
+msgid "No connections"
+msgstr "暂无连接"
+
+msgid "pkts"
+msgstr "包"
+
+msgid "Failed to load"
+msgstr "加载失败"
+
+msgid "Connection Details"
+msgstr "连接详情"
+
+msgid "Details"
+msgstr "详情"
+
+msgid "Orig Src"
+msgstr "发起源"
+
+msgid "Orig Dst"
+msgstr "发起目标"
+
+msgid "Repl Src"
+msgstr "回复源"
+
+msgid "Repl Dst"
+msgstr "回复目标"
\ No newline at end of file
diff --git a/luci-app-bandix/po/zh_Hant/bandix.po b/luci-app-bandix/po/zh_Hant/bandix.po
index 2650b03b..e342720b 100644
--- a/luci-app-bandix/po/zh_Hant/bandix.po
+++ b/luci-app-bandix/po/zh_Hant/bandix.po
@@ -1163,8 +1163,65 @@ msgstr "排除介面設備"
msgid "Exclude the router itself (interface device) from traffic statistics"
msgstr "從流量統計中排除路由器本身(介面設備)"
+msgid "Enable Neighbor Flush"
+msgstr "啟用鄰居表刷新"
+
+msgid "Enable periodic flush of neighbor table data. Generally not recommended if the device list works normally."
+msgstr "啟用定期刷新鄰居表數據。若設備列表正常,通常不建議開啟。"
+
+msgid "Neighbor Flush Interval"
+msgstr "鄰居表刷新間隔"
+
+msgid "Set the interval for flushing neighbor table data"
+msgstr "設置鄰居表數據刷新的時間間隔"
+
msgid "No devices"
msgstr "無設備"
msgid "LAN Traffic is viewed from the entire subnet perspective, so upload and download are always equal, forming a closed loop. Additionally, due to the presence of dedicated hardware switching chips, LAN traffic between 2 devices (e.g., PC - NAS) within the local network may not be monitorable."
-msgstr "LAN 流量是從整個子網視角來看,所以上傳和下載總是相等,是一個閉環。另外,由於專用硬體交換晶片的存在,區域網路中 2 終端(比如 PC - NAS)之間的 LAN 流量可能無法監控。"
\ No newline at end of file
+msgstr "LAN 流量是從整個子網視角來看,所以上傳和下載總是相等,是一個閉環。另外,由於專用硬體交換晶片的存在,區域網路中 2 終端(比如 PC - NAS)之間的 LAN 流量可能無法監控。"
+
+msgid "Protocol"
+msgstr "協定"
+
+msgid "State"
+msgstr "狀態"
+
+msgid "Send"
+msgstr "發送"
+
+msgid "Reply"
+msgstr "回覆"
+
+msgid "Flags"
+msgstr "標記"
+
+msgid "Loading…"
+msgstr "載入中…"
+
+msgid "No connections"
+msgstr "暫無連線"
+
+msgid "pkts"
+msgstr "封包"
+
+msgid "Failed to load"
+msgstr "載入失敗"
+
+msgid "Connection Details"
+msgstr "連線詳情"
+
+msgid "Details"
+msgstr "詳情"
+
+msgid "Orig Src"
+msgstr "發起源"
+
+msgid "Orig Dst"
+msgstr "發起目標"
+
+msgid "Repl Src"
+msgstr "回覆源"
+
+msgid "Repl Dst"
+msgstr "回覆目標"
\ No newline at end of file
diff --git a/luci-app-bandix/root/usr/libexec/rpcd/luci.bandix b/luci-app-bandix/root/usr/libexec/rpcd/luci.bandix
index fec4cc4a..4e21e368 100755
--- a/luci-app-bandix/root/usr/libexec/rpcd/luci.bandix
+++ b/luci-app-bandix/root/usr/libexec/rpcd/luci.bandix
@@ -10,6 +10,7 @@ readonly BANDIX_DEVICES_API="$BANDIX_API_BASE/api/traffic/devices"
readonly BANDIX_LIMITS_API="$BANDIX_API_BASE/api/traffic/limits"
readonly BANDIX_METRICS_API="$BANDIX_API_BASE/api/traffic/metrics"
readonly BANDIX_CONNECTION_API="$BANDIX_API_BASE/api/connection/devices"
+readonly BANDIX_CONNECTION_FLOWS_API="$BANDIX_API_BASE/api/connection/flows"
readonly BANDIX_DNS_QUERIES_API="$BANDIX_API_BASE/api/dns/queries"
readonly BANDIX_DNS_STATS_API="$BANDIX_API_BASE/api/dns/stats"
readonly BANDIX_SCHEDULE_LIMITS_API="$BANDIX_API_BASE/api/traffic/limits/schedule"
@@ -152,6 +153,26 @@ delete_device() {
}
# 获取设备状态
+# GET /api/traffic/devices -> $.data
+# {
+# d: [{
+# mac, # MAC 地址
+# ip4, # IPv4 地址
+# ip6, # IPv6 地址列表
+# host, # 主机名
+# conn, # 接入类型 (wifi/wired/router)
+# uplink, # 接入接口名 (eth1/lan2/phy0-ap0),无则空串
+# w_ch, # WiFi 频道号 (1/36/149),非 WiFi 为 0
+# last, # 最后在线时间戳(ms)
+# t_rx_b, t_tx_b, # 总下/上行字节
+# t_rx_r, t_tx_r, # 总下/上行速率
+# w_rx_b, w_tx_b, # WAN 下/上行字节
+# w_rx_r, w_tx_r, # WAN 下/上行速率
+# w_rx_l, w_tx_l, # WAN 下/上行限速
+# l_rx_b, l_tx_b, # LAN 下/上行字节
+# l_rx_r, l_tx_r # LAN 下/上行速率
+# }]
+# }
get_device_status() {
local url="$BANDIX_DEVICES_API"
local start_ms="$1"
@@ -171,7 +192,7 @@ get_device_status() {
# 检查API调用是否成功
if [ $? -ne 0 ] || [ -z "$api_result" ]; then
- echo '{"devices":[]}'
+ echo '{"d":[]}'
return
fi
@@ -180,7 +201,7 @@ get_device_status() {
if [ -n "$data_part" ]; then
echo "$data_part"
else
- echo '{"devices":[]}'
+ echo '{"d":[]}'
fi
}
@@ -275,6 +296,25 @@ set_device_hostname() {
}
# 获取连接监控数据
+# GET /api/connection/devices -> raw response (no $.data extraction)
+# {
+# status, data: {
+# cnt, # 设备数量
+# last, # 最后更新时间戳(ms)
+# g: { # 全局连接统计
+# total, # 总连接数
+# tcp, udp, # TCP/UDP 连接数
+# tcp_est, tcp_tw, tcp_cw, # ESTABLISHED/TIME_WAIT/CLOSE_WAIT
+# last # 最后更新时间戳(ms)
+# },
+# d: [{ # 各设备连接统计
+# mac, ip4, host, # 设备标识
+# total, tcp, udp, # 总/TCP/UDP 连接数
+# tcp_est, tcp_tw, tcp_cw, # ESTABLISHED/TIME_WAIT/CLOSE_WAIT
+# last # 最后更新时间戳(ms)
+# }]
+# }
+# }
get_connection_devices() {
# 检查连接监控是否启用
local connection_enabled=$(uci get bandix.connections.enabled 2>/dev/null)
@@ -296,6 +336,34 @@ get_connection_devices() {
echo "$response"
}
+get_connection_flows() {
+ local connection_enabled=$(uci get bandix.connections.enabled 2>/dev/null)
+ if [ "$connection_enabled" != "1" ]; then
+ make_error "Connection monitoring is not enabled"
+ return
+ fi
+
+ local ip="$1"
+ local protocol="$2"
+ local state="$3"
+
+ local query_params=""
+ [ -n "$ip" ] && query_params="${query_params}ip=$(printf '%s' "$ip" | sed 's/ /%20/g')&"
+ [ -n "$protocol" ] && query_params="${query_params}protocol=$(printf '%s' "$protocol" | sed 's/ /%20/g')&"
+ [ -n "$state" ] && query_params="${query_params}state=$(printf '%s' "$state" | sed 's/ /%20/g')&"
+ query_params=$(echo "$query_params" | sed 's/&$//')
+
+ local url="$BANDIX_CONNECTION_FLOWS_API"
+ [ -n "$query_params" ] && url="${url}?${query_params}"
+
+ local response=$(curl -s --connect-timeout 2 --max-time 5 -X GET "$url" 2>/dev/null)
+ if [ $? -ne 0 ] || [ -z "$response" ]; then
+ make_error "Failed to connect to Bandix service"
+ return
+ fi
+ echo "$response"
+}
+
# 获取 DNS 查询记录
get_dns_queries() {
# 检查 DNS 监控是否启用
@@ -365,6 +433,19 @@ get_dns_stats() {
}
# 获取设备用量排行
+# GET /api/traffic/usage/ranking -> $.data
+# {
+# start, end, # 统计时间范围(ms)
+# net, # 网络类型 (wan/lan/all)
+# cnt, # 设备数量
+# t_b, t_rx_b, t_tx_b, # 总/下/上行字节
+# r: [{
+# mac, ip4, host, # 设备标识
+# r, # 排名
+# pct, # 占比(%)
+# t_b, rx_b, tx_b # 总/下/上行字节
+# }]
+# }
get_traffic_usage_ranking() {
local start_ms="$1"
local end_ms="$2"
@@ -406,11 +487,28 @@ get_traffic_usage_ranking() {
echo "$data_part"
else
# 如果提取失败,返回空结果
- echo '{"start_ms":0,"end_ms":0,"total_rx_bytes":0,"total_tx_bytes":0,"total_bytes":0,"device_count":0,"rankings":[]}'
+ echo '{"start":0,"end":0,"t_rx_b":0,"t_tx_b":0,"t_b":0,"cnt":0,"r":[]}'
fi
}
# 获取时间序列增量数据
+# GET /api/traffic/usage/increments -> $.data
+# {
+# start, end, # 统计时间范围(ms)
+# agg, # 聚合粒度 (1m/5m/1h/1d)
+# net, # 网络类型 (wan/lan/all)
+# mac, # 指定设备 MAC(空=全部)
+# t_b, t_rx_b, t_tx_b, # 总/下/上行字节
+# inc: [{
+# start, end, # 分段时间范围(ms)
+# w_rx_avg/max/min/p90/p95/p99, # WAN 下行速率统计
+# w_tx_avg/max/min/p90/p95/p99, # WAN 上行速率统计
+# l_rx_avg/max/min/p90/p95/p99, # LAN 下行速率统计
+# l_tx_avg/max/min/p90/p95/p99, # LAN 上行速率统计
+# w_rx_b, w_tx_b, # WAN 下/上行字节增量
+# l_rx_b, l_tx_b # LAN 下/上行字节增量
+# }]
+# }
get_traffic_usage_increments() {
local start_ms="$1"
local end_ms="$2"
@@ -462,7 +560,7 @@ get_traffic_usage_increments() {
echo "$data_part"
else
# 如果提取失败,返回空结果
- echo '{"start_ms":0,"end_ms":0,"aggregation":"hourly","mac":"all","increments":[],"total_rx_bytes":0,"total_tx_bytes":0,"total_bytes":0}'
+ echo '{"start":0,"end":0,"agg":"hourly","mac":"all","inc":[],"t_rx_b":0,"t_tx_b":0,"t_b":0}'
fi
}
@@ -1145,6 +1243,12 @@ case "$1" in
json_add_object "getConnection"
json_close_object
+
+ json_add_object "getConnectionFlows"
+ json_add_string "ip"
+ json_add_string "protocol"
+ json_add_string "state"
+ json_close_object
json_add_object "getDnsQueries"
json_add_string "domain"
@@ -1336,6 +1440,28 @@ case "$1" in
# logger "luci.bandix: getConnection called"
get_connection_devices
;;
+ getConnectionFlows)
+ ip=""
+ protocol=""
+ state=""
+ input=""
+ if read -t 1 -r input; then
+ :
+ fi
+ if [ -n "$input" ]; then
+ ip="$(echo "$input" | jsonfilter -e '$[0]' 2>/dev/null)"
+ [ -z "$ip" ] && ip="$(echo "$input" | jsonfilter -e '$.ip' 2>/dev/null)"
+ protocol="$(echo "$input" | jsonfilter -e '$[1]' 2>/dev/null)"
+ [ -z "$protocol" ] && protocol="$(echo "$input" | jsonfilter -e '$.protocol' 2>/dev/null)"
+ state="$(echo "$input" | jsonfilter -e '$[2]' 2>/dev/null)"
+ [ -z "$state" ] && state="$(echo "$input" | jsonfilter -e '$.state' 2>/dev/null)"
+ else
+ [ -n "$3" ] && ip="$3"
+ [ -n "$4" ] && protocol="$4"
+ [ -n "$5" ] && state="$5"
+ fi
+ get_connection_flows "$ip" "$protocol" "$state"
+ ;;
getDnsQueries)
# logger "luci.bandix: getDnsQueries called"
# 从 stdin 读取 JSON 参数
diff --git a/luci-app-bandix/root/usr/share/rpcd/acl.d/luci-app-bandix.json b/luci-app-bandix/root/usr/share/rpcd/acl.d/luci-app-bandix.json
index 14d76272..452ec7dd 100644
--- a/luci-app-bandix/root/usr/share/rpcd/acl.d/luci-app-bandix.json
+++ b/luci-app-bandix/root/usr/share/rpcd/acl.d/luci-app-bandix.json
@@ -10,6 +10,7 @@
"getMetricsWeek",
"getMetricsMonth",
"getConnection",
+ "getConnectionFlows",
"setHostname",
"deleteDevice",
"getDnsQueries",
@@ -41,6 +42,7 @@
"getMetricsWeek",
"getMetricsMonth",
"getConnection",
+ "getConnectionFlows",
"setHostname",
"deleteDevice",
"getDnsQueries",
diff --git a/openwrt-bandix/Makefile b/openwrt-bandix/Makefile
index 87f76914..0c2b9f10 100644
--- a/openwrt-bandix/Makefile
+++ b/openwrt-bandix/Makefile
@@ -1,8 +1,8 @@
include $(TOPDIR)/rules.mk
PKG_NAME:=bandix
-PKG_VERSION:=0.12.6
-PKG_RELEASE:=1
+PKG_VERSION:=0.12.7
+PKG_RELEASE:=2
PKG_LICENSE:=Apache-2.0
PKG_MAINTAINER:=timsaya
@@ -13,7 +13,7 @@ include $(INCLUDE_DIR)/package.mk
include $(TOPDIR)/feeds/packages/lang/rust/rust-values.mk
# 二进制文件的文件名和URL
-RUST_BANDIX_VERSION:=0.12.6
+RUST_BANDIX_VERSION:=0.12.7
RUST_BINARY_FILENAME:=bandix-$(RUST_BANDIX_VERSION)-$(RUSTC_TARGET_ARCH).tar.gz
@@ -69,7 +69,7 @@ define Package/$(PKG_NAME)
SECTION:=net
CATEGORY:=Network
TITLE:=Bandix - Network traffic monitoring tool
- DEPENDS:=+zoneinfo-core +conntrack
+ DEPENDS:=+zoneinfo-all +conntrack +iw
endef
define Package/$(PKG_NAME)/description
diff --git a/openwrt-bandix/files/bandix.config b/openwrt-bandix/files/bandix.config
index 7ff07215..569bbab1 100644
--- a/openwrt-bandix/files/bandix.config
+++ b/openwrt-bandix/files/bandix.config
@@ -22,6 +22,8 @@ config bandix 'traffic'
option traffic_event_url ''
option traffic_additional_subnets ''
option traffic_exclude_iface_device '0'
+ option traffic_neighbor_flush_enable '0'
+ option traffic_neighbor_flush_interval '600'
config bandix 'connections'
diff --git a/openwrt-bandix/files/bandix.init b/openwrt-bandix/files/bandix.init
index a1359ddb..72ae4771 100644
--- a/openwrt-bandix/files/bandix.init
+++ b/openwrt-bandix/files/bandix.init
@@ -26,6 +26,8 @@ start_service() {
local traffic_event_url
local traffic_additional_subnets
local traffic_exclude_iface_device
+ local traffic_neighbor_flush_enable
+ local traffic_neighbor_flush_interval
local connections_enabled
local dns_enabled
local dns_max_records
@@ -46,6 +48,8 @@ start_service() {
config_get traffic_event_url 'traffic' 'traffic_event_url'
config_get traffic_additional_subnets 'traffic' 'traffic_additional_subnets'
config_get_bool traffic_exclude_iface_device 'traffic' 'traffic_exclude_iface_device' '0'
+ config_get_bool traffic_neighbor_flush_enable 'traffic' 'traffic_neighbor_flush_enable' '0'
+ config_get traffic_neighbor_flush_interval 'traffic' 'traffic_neighbor_flush_interval' '600'
config_get_bool connections_enabled 'connections' 'enabled'
config_get_bool dns_enabled 'dns' 'enabled'
config_get dns_max_records 'dns' 'dns_max_records'
@@ -89,6 +93,12 @@ start_service() {
if [ "$traffic_exclude_iface_device" -eq 1 ]; then
args="$args --traffic-exclude-iface-device"
fi
+ if [ "$traffic_neighbor_flush_enable" -eq 1 ]; then
+ args="$args --traffic-neighbor-flush-enable"
+ fi
+ if [ -n "$traffic_neighbor_flush_interval" ]; then
+ args="$args --traffic-neighbor-flush-interval $traffic_neighbor_flush_interval"
+ fi
# 添加连接统计监控参数
if [ "$connections_enabled" -eq 1 ]; then