From 6541db9764a546c6f8bedb8ae421a35029ed5adc Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 9 Mar 2026 20:23:16 +0800 Subject: [PATCH] =?UTF-8?q?=F0=9F=9A=80=20Sync=202026-03-09=2020:23:16?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- luci-app-bandix/Makefile | 4 +- .../resources/view/bandix/connection.js | 526 +++++++++++++++--- .../resources/view/bandix/index.js | 483 ++++++++++------ .../resources/view/bandix/settings.js | 18 + luci-app-bandix/po/es/bandix.po | 57 ++ luci-app-bandix/po/fr/bandix.po | 59 +- luci-app-bandix/po/ja/bandix.po | 57 ++ luci-app-bandix/po/pl/bandix.po | 57 ++ luci-app-bandix/po/ru/bandix.po | 59 +- luci-app-bandix/po/zh_Hans/bandix.po | 59 +- luci-app-bandix/po/zh_Hant/bandix.po | 59 +- .../root/usr/libexec/rpcd/luci.bandix | 134 ++++- .../usr/share/rpcd/acl.d/luci-app-bandix.json | 2 + openwrt-bandix/Makefile | 8 +- openwrt-bandix/files/bandix.config | 2 + openwrt-bandix/files/bandix.init | 10 + 16 files changed, 1328 insertions(+), 266 deletions(-) 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