🐶 Sync 2025-11-02 14:26:26
This commit is contained in:
18
luci-app-bandix/Makefile
Normal file
18
luci-app-bandix/Makefile
Normal file
@@ -0,0 +1,18 @@
|
||||
# See /LICENSE for more information.
|
||||
# This is free software, licensed under the GNU General Public License v2.
|
||||
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_LICENSE:=Apache-2.0
|
||||
|
||||
LUCI_TITLE:=LuCI Bandix app for network traffic monitoring
|
||||
LUCI_DEPENDS:=+luci-base +luci-lib-jsonc +curl +bandix
|
||||
|
||||
PKG_MAINTAINER:=timsaya
|
||||
|
||||
PKG_VERSION:=0.6.2
|
||||
PKG_RELEASE:=1
|
||||
|
||||
include $(TOPDIR)/feeds/luci/luci.mk
|
||||
|
||||
# call BuildPackage - OpenWrt buildroot signature
|
||||
@@ -0,0 +1,862 @@
|
||||
'use strict';
|
||||
'require view';
|
||||
'require ui';
|
||||
'require uci';
|
||||
'require rpc';
|
||||
'require poll';
|
||||
|
||||
const translations = {
|
||||
'zh-cn': {
|
||||
'Bandix 连接监控': 'Bandix 连接监控',
|
||||
'正在加载数据...': '正在加载数据...',
|
||||
'无法获取数据': '无法获取数据',
|
||||
'连接监控': '连接监控',
|
||||
'设备连接统计': '设备连接统计',
|
||||
'全局连接统计': '全局连接统计',
|
||||
'设备': '设备',
|
||||
'IP地址': 'IP地址',
|
||||
'MAC地址': 'MAC地址',
|
||||
'活跃TCP': '活跃TCP',
|
||||
'活跃UDP': '活跃UDP',
|
||||
'已关闭TCP': '已关闭TCP',
|
||||
'总连接数': '总连接数',
|
||||
'最后更新': '最后更新',
|
||||
'总连接数统计': '总连接数统计',
|
||||
'TCP连接数': 'TCP连接数',
|
||||
'UDP连接数': 'UDP连接数',
|
||||
'已建立TCP': '已建立TCP',
|
||||
'TIME_WAIT TCP': 'TIME_WAIT TCP',
|
||||
'CLOSE_WAIT TCP': 'CLOSE_WAIT TCP',
|
||||
'设备总数': '设备总数',
|
||||
'连接监控未启用': '连接监控未启用',
|
||||
'请在设置中启用连接监控功能': '请在设置中启用连接监控功能',
|
||||
'前往设置': '前往设置',
|
||||
'无数据': '无数据',
|
||||
'未知设备': '未知设备',
|
||||
'在线设备': '在线设备',
|
||||
'从未上线': '从未上线',
|
||||
'刚刚': '刚刚',
|
||||
'分钟前': '分钟前',
|
||||
'小时前': '小时前',
|
||||
'天前': '天前',
|
||||
'个月前': '个月前',
|
||||
'年前': '年前',
|
||||
'最后上线': '最后上线',
|
||||
'列表只显示局域网设备连接,数据可能和总连接数不一致。': '列表只显示局域网设备连接,数据可能和总连接数不一致。',
|
||||
'TCP 状态详情': 'TCP 状态详情'
|
||||
},
|
||||
'zh-tw': {
|
||||
'Bandix 连接监控': 'Bandix 連接監控',
|
||||
'正在加载数据...': '正在載入資料...',
|
||||
'无法获取数据': '無法獲取資料',
|
||||
'连接监控': '連接監控',
|
||||
'设备连接统计': '設備連接統計',
|
||||
'全局连接统计': '全局連接統計',
|
||||
'设备': '設備',
|
||||
'IP地址': 'IP地址',
|
||||
'MAC地址': 'MAC地址',
|
||||
'活跃TCP': '活躍TCP',
|
||||
'活跃UDP': '活躍UDP',
|
||||
'已关闭TCP': '已關閉TCP',
|
||||
'总连接数': '總連接數',
|
||||
'最后更新': '最後更新',
|
||||
'总连接数统计': '總連接數統計',
|
||||
'TCP连接数': 'TCP連接數',
|
||||
'UDP连接数': 'UDP連接數',
|
||||
'已建立TCP': '已建立TCP',
|
||||
'TIME_WAIT TCP': 'TIME_WAIT TCP',
|
||||
'CLOSE_WAIT TCP': 'CLOSE_WAIT TCP',
|
||||
'设备总数': '設備總數',
|
||||
'连接监控未启用': '連接監控未啟用',
|
||||
'请在设置中启用连接监控功能': '請在設置中啟用連接監控功能',
|
||||
'前往设置': '前往設置',
|
||||
'无数据': '無數據',
|
||||
'未知设备': '未知設備',
|
||||
'在线设备': '在線設備',
|
||||
'从未上线': '從未上線',
|
||||
'刚刚': '剛剛',
|
||||
'分钟前': '分鐘前',
|
||||
'小时前': '小時前',
|
||||
'天前': '天前',
|
||||
'个月前': '個月前',
|
||||
'年前': '年前',
|
||||
'最后上线': '最後上線',
|
||||
'列表只显示局域网设备连接,数据可能和总连接数不一致。': '列表只顯示局域網設備連接,數據可能和總連接數不一致。',
|
||||
'TCP 状态详情': 'TCP 狀態詳情'
|
||||
},
|
||||
'en': {
|
||||
'Bandix 连接监控': 'Bandix Connection Monitor',
|
||||
'正在加载数据...': 'Loading data...',
|
||||
'无法获取数据': 'Unable to fetch data',
|
||||
'连接监控': 'Connection Monitor',
|
||||
'设备连接统计': 'Device Connection Statistics',
|
||||
'全局连接统计': 'Global Connection Statistics',
|
||||
'设备': 'Device',
|
||||
'IP地址': 'IP Address',
|
||||
'MAC地址': 'MAC Address',
|
||||
'活跃TCP': 'Active TCP',
|
||||
'活跃UDP': 'Active UDP',
|
||||
'已关闭TCP': 'Closed TCP',
|
||||
'总连接数': 'Total Connections',
|
||||
'最后更新': 'Last Updated',
|
||||
'总连接数统计': 'Total Connections',
|
||||
'TCP连接数': 'TCP Connections',
|
||||
'UDP连接数': 'UDP Connections',
|
||||
'已建立TCP': 'Established TCP',
|
||||
'TIME_WAIT TCP': 'TIME_WAIT TCP',
|
||||
'CLOSE_WAIT TCP': 'CLOSE_WAIT TCP',
|
||||
'设备总数': 'Total Devices',
|
||||
'连接监控未启用': 'Connection Monitor Disabled',
|
||||
'请在设置中启用连接监控功能': 'Please enable connection monitoring in settings',
|
||||
'前往设置': 'Go to Settings',
|
||||
'无数据': 'No Data',
|
||||
'未知设备': 'Unknown Device',
|
||||
'在线设备': 'Online Devices',
|
||||
'从未上线': 'Never Online',
|
||||
'刚刚': 'Just now',
|
||||
'分钟前': 'minutes ago',
|
||||
'小时前': 'hours ago',
|
||||
'天前': 'days ago',
|
||||
'个月前': 'months ago',
|
||||
'年前': 'years ago',
|
||||
'最后上线': 'Last seen',
|
||||
'列表只显示局域网设备连接,数据可能和总连接数不一致。': 'List only shows LAN device connections, data may differ from total connections.',
|
||||
'TCP 状态详情': 'TCP Status Details'
|
||||
},
|
||||
'fr': {
|
||||
'Bandix 连接监控': 'Surveillance de Connexion Bandix',
|
||||
'正在加载数据...': 'Chargement des données...',
|
||||
'无法获取数据': 'Impossible de récupérer les données',
|
||||
'连接监控': 'Surveillance des Connexions',
|
||||
'设备连接统计': 'Statistiques de Connexion des Appareils',
|
||||
'全局连接统计': 'Statistiques de Connexion Globales',
|
||||
'设备': 'Appareil',
|
||||
'IP地址': 'Adresse IP',
|
||||
'MAC地址': 'Adresse MAC',
|
||||
'活跃TCP': 'TCP Actif',
|
||||
'活跃UDP': 'UDP Actif',
|
||||
'已关闭TCP': 'TCP Fermé',
|
||||
'总连接数': 'Total des Connexions',
|
||||
'最后更新': 'Dernière Mise à Jour',
|
||||
'总连接数统计': 'Total des Connexions',
|
||||
'TCP连接数': 'Connexions TCP',
|
||||
'UDP连接数': 'Connexions UDP',
|
||||
'已建立TCP': 'TCP Établi',
|
||||
'TIME_WAIT TCP': 'TCP TIME_WAIT',
|
||||
'CLOSE_WAIT TCP': 'TCP CLOSE_WAIT',
|
||||
'设备总数': 'Total des Appareils',
|
||||
'连接监控未启用': 'Surveillance des Connexions Désactivée',
|
||||
'请在设置中启用连接监控功能': 'Veuillez activer la surveillance des connexions dans les paramètres',
|
||||
'前往设置': 'Aller aux Paramètres',
|
||||
'无数据': 'Aucune Donnée',
|
||||
'未知设备': 'Appareil Inconnu',
|
||||
'在线设备': 'Appareils En Ligne',
|
||||
'从未上线': 'Jamais En Ligne',
|
||||
'刚刚': 'À l\'instant',
|
||||
'分钟前': 'il y a minutes',
|
||||
'小时前': 'il y a heures',
|
||||
'天前': 'il y a jours',
|
||||
'个月前': 'il y a mois',
|
||||
'年前': 'il y a années',
|
||||
'最后上线': 'Dernier Vue',
|
||||
'列表只显示局域网设备连接,数据可能和总连接数不一致。': 'La liste ne montre que les connexions des appareils LAN, les données peuvent différer du total des connexions.',
|
||||
'TCP 状态详情': 'Détails du Statut TCP'
|
||||
},
|
||||
'ja': {
|
||||
'Bandix 连接监控': 'Bandix 接続監視',
|
||||
'正在加载数据...': 'データを読み込み中...',
|
||||
'无法获取数据': 'データを取得できません',
|
||||
'连接监控': '接続監視',
|
||||
'设备连接统计': 'デバイス接続統計',
|
||||
'全局连接统计': 'グローバル接続統計',
|
||||
'设备': 'デバイス',
|
||||
'IP地址': 'IPアドレス',
|
||||
'MAC地址': 'MACアドレス',
|
||||
'活跃TCP': 'アクティブTCP',
|
||||
'活跃UDP': 'アクティブUDP',
|
||||
'已关闭TCP': 'クローズドTCP',
|
||||
'总连接数': '総接続数',
|
||||
'最后更新': '最終更新',
|
||||
'总连接数统计': '総接続数',
|
||||
'TCP连接数': 'TCP接続数',
|
||||
'UDP连接数': 'UDP接続数',
|
||||
'已建立TCP': 'TCP確立済',
|
||||
'TIME_WAIT TCP': 'TCP TIME_WAIT',
|
||||
'CLOSE_WAIT TCP': 'TCP CLOSE_WAIT',
|
||||
'设备总数': '総デバイス数',
|
||||
'连接监控未启用': '接続監視が無効',
|
||||
'请在设置中启用连接监控功能': '設定で接続監視機能を有効にしてください',
|
||||
'前往设置': '設定に移動',
|
||||
'无数据': 'データなし',
|
||||
'未知设备': '不明なデバイス',
|
||||
'在线设备': 'オンラインデバイス',
|
||||
'从未上线': '未接続',
|
||||
'刚刚': 'たった今',
|
||||
'分钟前': '分前',
|
||||
'小时前': '時間前',
|
||||
'天前': '日前',
|
||||
'个月前': 'ヶ月前',
|
||||
'年前': '年前',
|
||||
'最后上线': '最後の接続',
|
||||
'列表只显示局域网设备连接,数据可能和总连接数不一致。': 'リストはLANデバイスの接続のみを表示し、データは総接続数と異なる場合があります。',
|
||||
'TCP 状态详情': 'TCP状態詳細'
|
||||
},
|
||||
'ru': {
|
||||
'Bandix 连接监控': 'Мониторинг Соединений Bandix',
|
||||
'正在加载数据...': 'Загрузка данных...',
|
||||
'无法获取数据': 'Не удалось получить данные',
|
||||
'连接监控': 'Мониторинг Соединений',
|
||||
'设备连接统计': 'Статистика Соединений Устройств',
|
||||
'全局连接统计': 'Глобальная Статистика Соединений',
|
||||
'设备': 'Устройство',
|
||||
'IP地址': 'IP-адрес',
|
||||
'MAC地址': 'MAC-адрес',
|
||||
'活跃TCP': 'Активные TCP',
|
||||
'活跃UDP': 'Активные UDP',
|
||||
'已关闭TCP': 'Закрытые TCP',
|
||||
'总连接数': 'Всего Соединений',
|
||||
'最后更新': 'Последнее Обновление',
|
||||
'总连接数统计': 'Всего Соединений',
|
||||
'TCP连接数': 'TCP Соединения',
|
||||
'UDP连接数': 'UDP Соединения',
|
||||
'已建立TCP': 'Установленные TCP',
|
||||
'TIME_WAIT TCP': 'TCP TIME_WAIT',
|
||||
'CLOSE_WAIT TCP': 'TCP CLOSE_WAIT',
|
||||
'设备总数': 'Всего Устройств',
|
||||
'连接监控未启用': 'Мониторинг Соединений Отключен',
|
||||
'请在设置中启用连接监控功能': 'Пожалуйста, включите мониторинг соединений в настройках',
|
||||
'前往设置': 'Перейти к Настройкам',
|
||||
'无数据': 'Нет Данных',
|
||||
'未知设备': 'Неизвестное Устройство',
|
||||
'在线设备': 'Устройства Онлайн',
|
||||
'从未上线': 'Никогда Не Было Онлайн',
|
||||
'刚刚': 'Только что',
|
||||
'分钟前': 'минут назад',
|
||||
'小时前': 'часов назад',
|
||||
'天前': 'дней назад',
|
||||
'个月前': 'месяцев назад',
|
||||
'年前': 'лет назад',
|
||||
'最后上线': 'Последний раз видели',
|
||||
'列表只显示局域网设备连接,数据可能和总连接数不一致。': 'Список показывает только соединения LAN-устройств, данные могут отличаться от общего количества соединений.',
|
||||
'TCP 状态详情': 'Детали Статуса TCP'
|
||||
}
|
||||
};
|
||||
|
||||
function getTranslation(key, language) {
|
||||
return translations[language]?.[key] || key;
|
||||
}
|
||||
|
||||
// 获取系统语言
|
||||
function getSystemLanguage() {
|
||||
var luciLang = uci.get('luci', 'main', 'lang');
|
||||
if (luciLang && translations[luciLang]) {
|
||||
return luciLang;
|
||||
}
|
||||
var systemLang = document.documentElement.lang || 'en';
|
||||
if (translations[systemLang]) {
|
||||
return systemLang;
|
||||
}
|
||||
return 'en';
|
||||
}
|
||||
|
||||
// 检查是否为暗黑模式
|
||||
function isDarkMode() {
|
||||
// 首先检查用户设置的主题
|
||||
var userTheme = uci.get('bandix', 'general', 'theme');
|
||||
if (userTheme) {
|
||||
if (userTheme === 'dark') {
|
||||
return true;
|
||||
} else if (userTheme === 'light') {
|
||||
return false;
|
||||
}
|
||||
// 如果是 'auto',继续检查系统主题
|
||||
}
|
||||
|
||||
// 获取 LuCI 主题设置
|
||||
var mediaUrlBase = uci.get('luci', 'main', 'mediaurlbase');
|
||||
if (mediaUrlBase && mediaUrlBase.toLowerCase().includes('dark')) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 如果是 argon 主题,检查 argon 配置
|
||||
if (mediaUrlBase && mediaUrlBase.toLowerCase().includes('argon')) {
|
||||
var argonMode = uci.get('argon', '@global[0]', 'mode');
|
||||
if (argonMode) {
|
||||
if (argonMode.toLowerCase() === 'dark') {
|
||||
return true;
|
||||
} else if (argonMode.toLowerCase() === 'light') {
|
||||
return false;
|
||||
}
|
||||
// 如果是 'normal' 或 'auto',使用浏览器检测系统颜色偏好
|
||||
if (argonMode.toLowerCase() === 'normal' || argonMode.toLowerCase() === 'auto') {
|
||||
if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 默认情况下也使用浏览器检测系统颜色偏好
|
||||
if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// 格式化时间戳
|
||||
function formatTimestamp(timestamp) {
|
||||
if (!timestamp) return getTranslation('从未上线', getSystemLanguage());
|
||||
|
||||
var now = Math.floor(Date.now() / 1000);
|
||||
var diff = now - timestamp;
|
||||
var language = getSystemLanguage();
|
||||
|
||||
if (diff < 60) {
|
||||
return getTranslation('刚刚', language);
|
||||
} else if (diff < 3600) {
|
||||
var minutes = Math.floor(diff / 60);
|
||||
return minutes + ' ' + getTranslation('分钟前', language);
|
||||
} else if (diff < 86400) {
|
||||
var hours = Math.floor(diff / 3600);
|
||||
return hours + ' ' + getTranslation('小时前', language);
|
||||
} else if (diff < 2592000) {
|
||||
var days = Math.floor(diff / 86400);
|
||||
return days + ' ' + getTranslation('天前', language);
|
||||
} else if (diff < 31536000) {
|
||||
var months = Math.floor(diff / 2592000);
|
||||
return months + ' ' + getTranslation('个月前', language);
|
||||
} else {
|
||||
var years = Math.floor(diff / 31536000);
|
||||
return years + ' ' + getTranslation('年前', language);
|
||||
}
|
||||
}
|
||||
|
||||
// 格式化设备名称
|
||||
function formatDeviceName(device) {
|
||||
if (device.hostname && device.hostname !== '') {
|
||||
return device.hostname;
|
||||
}
|
||||
return device.ip_address || device.mac_address || getTranslation('未知设备', getSystemLanguage());
|
||||
}
|
||||
|
||||
// RPC调用
|
||||
var callGetConnection = rpc.declare({
|
||||
object: 'luci.bandix',
|
||||
method: 'getConnection',
|
||||
expect: {}
|
||||
});
|
||||
|
||||
return view.extend({
|
||||
load: function () {
|
||||
return Promise.all([
|
||||
uci.load('bandix'),
|
||||
uci.load('luci'),
|
||||
uci.load('argon').catch(function () {
|
||||
return null;
|
||||
})
|
||||
]);
|
||||
},
|
||||
|
||||
render: function (data) {
|
||||
var language = uci.get('bandix', 'general', 'language');
|
||||
if (!language || language === 'auto') {
|
||||
language = getSystemLanguage();
|
||||
}
|
||||
var darkMode = isDarkMode();
|
||||
var connectionEnabled = uci.get('bandix', 'connections', 'enabled') === '1';
|
||||
|
||||
// 创建样式
|
||||
var style = E('style', {}, `
|
||||
.bandix-connection-container {
|
||||
margin: 0;
|
||||
padding: 24px;
|
||||
background-color: ${darkMode ? '#1E1E1E' : '#f8fafc'};
|
||||
min-height: calc(100vh - 100px);
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
||||
color: ${darkMode ? '#e2e8f0' : '#1f2937'};
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.bandix-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.bandix-title {
|
||||
font-size: 1.5rem;
|
||||
font-weight: 700;
|
||||
color: ${darkMode ? '#f1f5f9' : '#1f2937'};
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.bandix-badge {
|
||||
background-color: ${darkMode ? '#333333' : '#f3f4f6'};
|
||||
border: 1px solid ${darkMode ? '#252526' : '#d1d5db'};
|
||||
border-radius: 6px;
|
||||
padding: 4px 12px;
|
||||
font-size: 0.875rem;
|
||||
color: ${darkMode ? '#e2e8f0' : '#374151'};
|
||||
}
|
||||
|
||||
.bandix-alert {
|
||||
background-color: ${darkMode ? '#451a03' : '#fef3c7'};
|
||||
border: 1px solid ${darkMode ? '#92400e' : '#f59e0b'};
|
||||
border-radius: 8px;
|
||||
padding: 8px;
|
||||
margin-bottom: 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
color: ${darkMode ? '#fbbf24' : '#92400e'};
|
||||
}
|
||||
|
||||
.bandix-alert-icon {
|
||||
color: ${darkMode ? '#fbbf24' : '#f59e0b'};
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.bandix-card {
|
||||
background-color: ${darkMode ? '#252526' : 'white'};
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 1px 3px 0 rgba(0, 0, 0, ${darkMode ? '0.3' : '0.1'});
|
||||
margin-bottom: 24px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.bandix-card-header {
|
||||
padding: 20px 24px 16px;
|
||||
border-bottom: 1px solid ${darkMode ? '#252526' : '#e5e7eb'};
|
||||
background-color: ${darkMode ? '#333333' : '#fafafa'};
|
||||
}
|
||||
|
||||
.bandix-card-title {
|
||||
font-size: 1.125rem;
|
||||
font-weight: 600;
|
||||
color: ${darkMode ? '#f1f5f9' : '#1f2937'};
|
||||
margin: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.bandix-card-body {
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
.stats-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 20px;
|
||||
margin-bottom: 32px;
|
||||
}
|
||||
|
||||
.stats-card {
|
||||
background-color: ${darkMode ? '#252526' : 'white'};
|
||||
border-radius: 12px;
|
||||
padding: 20px;
|
||||
box-shadow: 0 1px 3px 0 rgba(0, 0, 0, ${darkMode ? '0.3' : '0.1'});
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.stats-card-title {
|
||||
font-size: 0.875rem;
|
||||
font-weight: 500;
|
||||
color: ${darkMode ? '#9ca3af' : '#6b7280'};
|
||||
margin: 0 0 12px 0;
|
||||
}
|
||||
|
||||
.stats-card-main-value {
|
||||
font-size: 2.25rem;
|
||||
font-weight: 700;
|
||||
color: ${darkMode ? '#f1f5f9' : '#1f2937'};
|
||||
margin: 0 0 8px 0;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.stats-card-details {
|
||||
margin-top: 12px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.stats-detail-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
.stats-detail-label {
|
||||
color: ${darkMode ? '#9ca3af' : '#6b7280'};
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.stats-detail-value {
|
||||
font-weight: 600;
|
||||
color: ${darkMode ? '#e2e8f0' : '#374151'};
|
||||
}
|
||||
|
||||
.bandix-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
background-color: transparent;
|
||||
table-layout: fixed;
|
||||
}
|
||||
|
||||
.bandix-table th {
|
||||
background-color: ${darkMode ? '#333333' : '#f9fafb'};
|
||||
padding: 16px 20px;
|
||||
text-align: left;
|
||||
font-weight: 600;
|
||||
color: ${darkMode ? '#e2e8f0' : '#374151'};
|
||||
border: none;
|
||||
font-size: 0.875rem;
|
||||
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 td {
|
||||
padding: 16px 20px;
|
||||
border-bottom: 1px solid ${darkMode ? '#252526' : '#f1f5f9'};
|
||||
vertical-align: middle;
|
||||
word-break: break-word;
|
||||
overflow-wrap: break-word;
|
||||
color: ${darkMode ? '#cbd5e1' : 'inherit'};
|
||||
}
|
||||
|
||||
|
||||
.device-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.device-status {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 50%;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.device-status.online {
|
||||
background-color: #10b981;
|
||||
}
|
||||
|
||||
.device-status.offline {
|
||||
background-color: #9ca3af;
|
||||
}
|
||||
|
||||
.device-details {
|
||||
min-width: 0;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.device-name {
|
||||
font-weight: 600;
|
||||
color: ${darkMode ? '#f1f5f9' : '#1f2937'};
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.device-ip {
|
||||
color: ${darkMode ? '#94a3b8' : '#6b7280'};
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
.device-mac {
|
||||
color: ${darkMode ? '#64748b' : '#9ca3af'};
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
|
||||
|
||||
.tcp-status-details {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
.tcp-status-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.tcp-status-label {
|
||||
display: inline-block;
|
||||
padding: 2px 6px;
|
||||
border-radius: 4px;
|
||||
font-size: 0.75rem;
|
||||
font-weight: 600;
|
||||
color: white;
|
||||
min-width: 50px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.tcp-status-label.established {
|
||||
background-color: #10b981;
|
||||
}
|
||||
|
||||
.tcp-status-label.time-wait {
|
||||
background-color: #f59e0b;
|
||||
}
|
||||
|
||||
.tcp-status-label.closed {
|
||||
background-color: #6b7280;
|
||||
}
|
||||
|
||||
.tcp-status-value {
|
||||
font-weight: 600;
|
||||
color: ${darkMode ? '#e2e8f0' : '#374151'};
|
||||
}
|
||||
|
||||
.loading-state {
|
||||
text-align: center;
|
||||
padding: 40px;
|
||||
color: ${darkMode ? '#94a3b8' : '#6b7280'};
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.error-state {
|
||||
text-align: center;
|
||||
padding: 40px;
|
||||
color: ${darkMode ? '#f87171' : '#ef4444'};
|
||||
}
|
||||
|
||||
.btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 8px 16px;
|
||||
border-radius: 6px;
|
||||
font-size: 0.875rem;
|
||||
font-weight: 500;
|
||||
text-decoration: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background-color: #3b82f6;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-primary:hover {
|
||||
background-color: #2563eb;
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 4px 12px rgba(59, 130, 246, 0.3);
|
||||
}
|
||||
`);
|
||||
document.head.appendChild(style);
|
||||
|
||||
var container = E('div', { 'class': 'bandix-connection-container' });
|
||||
|
||||
// 页面标题
|
||||
var header = E('div', { 'class': 'bandix-header' }, [
|
||||
E('h1', { 'class': 'bandix-title' }, getTranslation('Bandix 连接监控', language))
|
||||
]);
|
||||
container.appendChild(header);
|
||||
|
||||
// 检查连接监控是否启用
|
||||
if (!connectionEnabled) {
|
||||
var alertDiv = E('div', { 'class': 'bandix-alert' }, [
|
||||
E('span', { 'class': 'bandix-alert-icon' }, '⚠'),
|
||||
E('div', {}, [
|
||||
E('strong', {}, getTranslation('连接监控未启用', language)),
|
||||
E('p', { 'style': 'margin: 4px 0 0 0;' },
|
||||
getTranslation('请在设置中启用连接监控功能', language))
|
||||
])
|
||||
]);
|
||||
container.appendChild(alertDiv);
|
||||
|
||||
var settingsCard = E('div', { 'class': 'bandix-card' }, [
|
||||
E('div', { 'class': 'bandix-card-body', 'style': 'text-align: center;' }, [
|
||||
E('a', {
|
||||
'href': '/cgi-bin/luci/admin/network/bandix/settings',
|
||||
'class': 'btn btn-primary'
|
||||
}, getTranslation('前往设置', language))
|
||||
])
|
||||
]);
|
||||
container.appendChild(settingsCard);
|
||||
return container;
|
||||
}
|
||||
|
||||
// 添加提示信息
|
||||
var infoAlert = E('div', { 'class': 'bandix-alert' }, [
|
||||
E('span', { 'class': 'bandix-alert-icon' }, '⚠️'),
|
||||
E('span', {}, getTranslation('列表只显示局域网设备连接,数据可能和总连接数不一致。', language))
|
||||
]);
|
||||
container.appendChild(infoAlert);
|
||||
|
||||
// 全局统计卡片
|
||||
var statsGrid = E('div', { 'class': 'stats-grid' }, [
|
||||
E('div', { 'class': 'stats-card' }, [
|
||||
E('div', { 'class': 'stats-card-title' }, getTranslation('总连接数统计', language)),
|
||||
E('div', { 'class': 'stats-card-main-value', 'id': 'total-connections' }, '-')
|
||||
]),
|
||||
E('div', { 'class': 'stats-card' }, [
|
||||
E('div', { 'class': 'stats-card-title' }, getTranslation('TCP连接数', language)),
|
||||
E('div', { 'class': 'stats-card-main-value', 'id': 'tcp-connections' }, '-'),
|
||||
E('div', { 'class': 'stats-card-details' }, [
|
||||
E('div', { 'class': 'stats-detail-row' }, [
|
||||
E('span', { 'class': 'stats-detail-label' }, 'ESTABLISHED'),
|
||||
E('span', { 'class': 'stats-detail-value', 'id': 'established-tcp' }, '-')
|
||||
]),
|
||||
E('div', { 'class': 'stats-detail-row' }, [
|
||||
E('span', { 'class': 'stats-detail-label' }, 'TIME_WAIT'),
|
||||
E('span', { 'class': 'stats-detail-value', 'id': 'time-wait-tcp' }, '-')
|
||||
]),
|
||||
E('div', { 'class': 'stats-detail-row' }, [
|
||||
E('span', { 'class': 'stats-detail-label' }, 'CLOSE_WAIT'),
|
||||
E('span', { 'class': 'stats-detail-value', 'id': 'close-wait-tcp' }, '-')
|
||||
])
|
||||
])
|
||||
]),
|
||||
E('div', { 'class': 'stats-card' }, [
|
||||
E('div', { 'class': 'stats-card-title' }, getTranslation('UDP连接数', language)),
|
||||
E('div', { 'class': 'stats-card-main-value', 'id': 'udp-connections' }, '-')
|
||||
])
|
||||
]);
|
||||
container.appendChild(statsGrid);
|
||||
|
||||
// 设备连接统计表格
|
||||
var deviceCard = E('div', { 'class': 'bandix-card' }, [
|
||||
E('div', { 'class': 'bandix-card-body' }, [
|
||||
E('div', { 'id': 'device-table-container' }, [
|
||||
E('table', { 'class': 'bandix-table' }, [
|
||||
E('thead', {}, [
|
||||
E('tr', {}, [
|
||||
E('th', {}, getTranslation('设备', language)),
|
||||
E('th', {}, 'TCP'),
|
||||
E('th', {}, 'UDP'),
|
||||
E('th', {}, getTranslation('TCP 状态详情', language)),
|
||||
E('th', {}, getTranslation('总连接数', language))
|
||||
])
|
||||
]),
|
||||
E('tbody', {})
|
||||
])
|
||||
])
|
||||
])
|
||||
]);
|
||||
container.appendChild(deviceCard);
|
||||
|
||||
// 更新全局统计
|
||||
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;
|
||||
}
|
||||
|
||||
// 更新设备表格
|
||||
function updateDeviceTable(devices) {
|
||||
var container = document.getElementById('device-table-container');
|
||||
|
||||
if (!devices || devices.length === 0) {
|
||||
container.innerHTML = '';
|
||||
container.appendChild(E('div', { 'class': 'loading-state' },
|
||||
getTranslation('无数据', language)));
|
||||
return;
|
||||
}
|
||||
|
||||
var table = E('table', { 'class': 'bandix-table' }, [
|
||||
E('thead', {}, [
|
||||
E('tr', {}, [
|
||||
E('th', {}, getTranslation('设备', language)),
|
||||
E('th', {}, 'TCP'),
|
||||
E('th', {}, 'UDP'),
|
||||
E('th', {}, getTranslation('TCP 状态详情', language)),
|
||||
E('th', {}, getTranslation('总连接数', language))
|
||||
])
|
||||
]),
|
||||
E('tbody', {}, devices.map(function (device) {
|
||||
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('td', { 'style': 'font-weight: 600;' }, device.tcp_connections || 0),
|
||||
E('td', { 'style': 'font-weight: 600;' }, device.udp_connections || 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('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('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('td', {}, E('strong', {}, device.total_connections || 0))
|
||||
]);
|
||||
}))
|
||||
]);
|
||||
|
||||
container.innerHTML = '';
|
||||
container.appendChild(table);
|
||||
}
|
||||
|
||||
// 显示错误信息
|
||||
function showError(message) {
|
||||
var container = document.getElementById('device-table-container');
|
||||
container.innerHTML = '';
|
||||
container.appendChild(E('div', { 'class': 'error-state' }, message));
|
||||
}
|
||||
|
||||
// 定义更新连接数据的函数
|
||||
function updateConnectionData() {
|
||||
return callGetConnection().then(function (result) {
|
||||
if (result && result.status === 'success' && result.data) {
|
||||
updateGlobalStats(result.data.global_stats);
|
||||
updateDeviceTable(result.data.devices);
|
||||
} else {
|
||||
showError(getTranslation('无法获取数据', language));
|
||||
}
|
||||
}).catch(function (error) {
|
||||
console.error('Failed to load connection data:', error);
|
||||
showError(getTranslation('无法获取数据', language));
|
||||
});
|
||||
}
|
||||
|
||||
// 轮询获取数据
|
||||
poll.add(updateConnectionData, 1);
|
||||
|
||||
// 立即执行一次,不等待轮询
|
||||
updateConnectionData();
|
||||
|
||||
return container;
|
||||
}
|
||||
});
|
||||
3151
luci-app-bandix/htdocs/luci-static/resources/view/bandix/index.js
Normal file
3151
luci-app-bandix/htdocs/luci-static/resources/view/bandix/index.js
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,681 @@
|
||||
'use strict';
|
||||
'require view';
|
||||
'require form';
|
||||
'require ui';
|
||||
'require uci';
|
||||
'require fs';
|
||||
|
||||
const translations = {
|
||||
'zh-cn': {
|
||||
'基本设置': '基本设置',
|
||||
'流量监控设置': '流量监控设置',
|
||||
'连接监控设置': '连接监控设置',
|
||||
'Bandix 基本配置': 'Bandix 基本配置',
|
||||
'配置 Bandix 服务的基本参数': '配置 Bandix 服务的基本参数',
|
||||
'Bandix 流量监控配置': 'Bandix 流量监控配置',
|
||||
'配置流量监控相关参数': '配置流量监控相关参数',
|
||||
'Bandix 连接监控配置': 'Bandix 连接监控配置',
|
||||
'配置连接监控相关参数': '配置连接监控相关参数',
|
||||
'启用': '启用',
|
||||
'启用 Bandix 流量监控服务': '启用 Bandix 流量监控服务',
|
||||
'启用流量监控': '启用流量监控',
|
||||
'启用连接监控': '启用连接监控',
|
||||
'界面语言': '界面语言',
|
||||
'选择 Bandix 流量监控的显示语言': '选择 Bandix 流量监控的显示语言',
|
||||
'简体中文': '简体中文',
|
||||
'繁体中文': '繁体中文',
|
||||
'端口': '端口',
|
||||
'Bandix 服务监听的端口': 'Bandix 服务监听的端口',
|
||||
'监控网卡': '监控网卡',
|
||||
'选择要监控的网络接口': '选择要监控的LAN网络接口',
|
||||
'网速单位': '网速单位',
|
||||
'选择网速显示的单位格式': '选择网速显示的单位格式',
|
||||
'字节单位 (B/s, KB/s, MB/s)': '字节单位 (B/s, KB/s, MB/s)',
|
||||
'比特单位 (bps, Kbps, Mbps)': '比特单位 (bps, Kbps, Mbps)',
|
||||
'界面主题': '界面主题',
|
||||
'选择 Bandix 流量监控的显示主题': '选择 Bandix 流量监控的显示主题',
|
||||
'跟随系统': '跟随系统',
|
||||
'明亮模式': '明亮模式',
|
||||
'暗黑模式': '暗黑模式',
|
||||
'意见反馈': '意见反馈',
|
||||
'离线超时时间': '离线超时时间',
|
||||
'设置设备离线判断的超时时间(秒)': '设置设备离线判断的超时时间(秒)。超过此时间未活动的设备将被标记为离线',
|
||||
'历史流量周期': '历史流量周期',
|
||||
'设置历史流量数据周期': '10分钟下,1个设备固定占用约60KB,修改会清空已有数据。\n由于ubus单次通信限制,无法设置更大的值。',
|
||||
'数据 flush 间隔': '数据 flush 间隔',
|
||||
'设置数据 flush 间隔': '设置数据写入磁盘的时间间隔',
|
||||
'1 分钟': '1 分钟',
|
||||
'5 分钟': '5 分钟',
|
||||
'10 分钟': '10 分钟',
|
||||
'15 分钟': '15 分钟',
|
||||
'20 分钟': '20 分钟',
|
||||
'25 分钟': '25 分钟',
|
||||
'30 分钟': '30 分钟',
|
||||
'1 小时': '1 小时',
|
||||
'2 小时': '2 小时',
|
||||
'数据目录': '数据目录',
|
||||
'Bandix 数据存储目录': 'Bandix 数据存储目录',
|
||||
'启用 Bandix 连接监控功能': '启用 Bandix 连接监控功能',
|
||||
'持久化历史数据': '持久化历史数据',
|
||||
'启用数据持久化功能': '启用数据持久化功能,只有启用此选项后才会持久化到磁盘'
|
||||
},
|
||||
'zh-tw': {
|
||||
'基本设置': '基本設置',
|
||||
'流量监控设置': '流量監控設置',
|
||||
'连接监控设置': '連接監控設置',
|
||||
'Bandix 基本配置': 'Bandix 基本配置',
|
||||
'配置 Bandix 服务的基本参数': '配置 Bandix 服務的基本參數',
|
||||
'Bandix 流量监控配置': 'Bandix 流量監控配置',
|
||||
'配置流量监控相关参数': '配置流量監控相關參數',
|
||||
'Bandix 连接监控配置': 'Bandix 連接監控配置',
|
||||
'配置连接监控相关参数': '配置連接監控相關參數',
|
||||
'启用': '啟用',
|
||||
'启用 Bandix 流量监控服务': '啟用 Bandix 流量監控服務',
|
||||
'启用流量监控': '啟用流量監控',
|
||||
'启用连接监控': '啟用連接監控',
|
||||
'界面语言': '界面語言',
|
||||
'选择 Bandix 流量监控的显示语言': '選擇 Bandix 流量監控的顯示語言',
|
||||
'简体中文': '簡體中文',
|
||||
'繁体中文': '繁體中文',
|
||||
'端口': '端口',
|
||||
'Bandix 服务监听的端口': 'Bandix 服務監聽的端口',
|
||||
'监控网卡': '監控網卡',
|
||||
'选择要监控的网络接口': '選擇要監控的LAN網絡接口',
|
||||
'网速单位': '網速單位',
|
||||
'选择网速显示的单位格式': '選擇網速顯示的單位格式',
|
||||
'字节单位 (B/s, KB/s, MB/s)': '字節單位 (B/s, KB/s, MB/s)',
|
||||
'比特单位 (bps, Kbps, Mbps)': '比特單位 (bps, Kbps, Mbps)',
|
||||
'界面主题': '介面主題',
|
||||
'选择 Bandix 流量监控的显示主题': '選擇 Bandix 流量監控的顯示主題',
|
||||
'跟随系统': '跟隨系統',
|
||||
'明亮模式': '明亮模式',
|
||||
'暗黑模式': '暗黑模式',
|
||||
'意见反馈': '意見反饋',
|
||||
'离线超时时间': '離線超時時間',
|
||||
'设置设备离线判断的超时时间(秒)': '設定設備離線判斷的超時時間(秒)。超過此時間未活動的設備將被標記為離線',
|
||||
'历史流量周期': '歷史流量週期',
|
||||
'设置历史流量数据周期': '10分鐘下,1個設備固定占用約60KB,修改會清空已有資料。\n由於ubus單次通信限制,無法設置更大的值。',
|
||||
'数据 flush 间隔': '資料flush間隔',
|
||||
'设置数据 flush 间隔': '設定資料寫入磁碟的時間間隔',
|
||||
'1 分钟': '1 分鐘',
|
||||
'5 分钟': '5 分鐘',
|
||||
'10 分钟': '10 分鐘',
|
||||
'15 分钟': '15 分鐘',
|
||||
'20 分钟': '20 分鐘',
|
||||
'25 分钟': '25 分鐘',
|
||||
'30 分钟': '30 分鐘',
|
||||
'1 小时': '1 小時',
|
||||
'2 小时': '2 小時',
|
||||
'数据目录': '數據目錄',
|
||||
'Bandix 数据存储目录': 'Bandix 數據存儲目錄',
|
||||
'启用 Bandix 连接监控功能': '啟用 Bandix 連接監控功能',
|
||||
'持久化历史数据': '持久化歷史數據',
|
||||
'启用数据持久化功能': '啟用數據持久化功能,只有啟用此選項後才會持久化到磁碟'
|
||||
},
|
||||
'en': {
|
||||
'基本设置': 'Basic Settings',
|
||||
'流量监控设置': 'Traffic Monitor Settings',
|
||||
'连接监控设置': 'Connection Monitor Settings',
|
||||
'Bandix 基本配置': 'Bandix Basic Configuration',
|
||||
'配置 Bandix 服务的基本参数': 'Configure basic parameters for Bandix service',
|
||||
'Bandix 流量监控配置': 'Bandix Traffic Monitor Configuration',
|
||||
'配置流量监控相关参数': 'Configure traffic monitoring related parameters',
|
||||
'Bandix 连接监控配置': 'Bandix Connection Monitor Configuration',
|
||||
'配置连接监控相关参数': 'Configure connection monitoring related parameters',
|
||||
'启用': 'Enable',
|
||||
'启用 Bandix 流量监控服务': 'Enable Bandix Traffic Monitor Service',
|
||||
'启用流量监控': 'Enable Traffic Monitoring',
|
||||
'启用连接监控': 'Enable Connection Monitoring',
|
||||
'界面语言': 'Interface Language',
|
||||
'选择 Bandix 流量监控的显示语言': 'Select the display language for Bandix Traffic Monitor',
|
||||
'简体中文': 'Simplified Chinese',
|
||||
'繁体中文': 'Traditional Chinese',
|
||||
'端口': 'Port',
|
||||
'Bandix 服务监听的端口': 'Port for Bandix service to listen on',
|
||||
'监控网卡': 'Monitor Interface',
|
||||
'选择要监控的网络接口': 'Select the LAN network interface to monitor',
|
||||
'网速单位': 'Speed Units',
|
||||
'选择网速显示的单位格式': 'Select the speed display unit format',
|
||||
'字节单位 (B/s, KB/s, MB/s)': 'Bytes Units (B/s, KB/s, MB/s)',
|
||||
'比特单位 (bps, Kbps, Mbps)': 'Bits Units (bps, Kbps, Mbps)',
|
||||
'界面主题': 'Interface Theme',
|
||||
'选择 Bandix 流量监控的显示主题': 'Select the display theme for Bandix Traffic Monitor',
|
||||
'跟随系统': 'Follow System',
|
||||
'明亮模式': 'Light Mode',
|
||||
'暗黑模式': 'Dark Mode',
|
||||
'意见反馈': 'Feedback',
|
||||
'离线超时时间': 'Offline Timeout',
|
||||
'设置设备离线判断的超时时间(秒)': 'Set the timeout for device offline detection (seconds). Devices inactive for longer than this time will be marked as offline',
|
||||
'历史流量周期': 'Traffic History Period',
|
||||
'设置历史流量数据周期': 'With 10-minute interval, 1 device uses a fixed size of about 60 KB, changing will clear existing data.\nDue to ubus single communication limit, larger values cannot be set.',
|
||||
'数据 flush 间隔': 'Data Flush Interval',
|
||||
'设置数据 flush 间隔': 'Set the interval for flushing data to disk',
|
||||
'1 分钟': '1 minute',
|
||||
'5 分钟': '5 minutes',
|
||||
'10 分钟': '10 minutes',
|
||||
'15 分钟': '15 minutes',
|
||||
'20 分钟': '20 minutes',
|
||||
'25 分钟': '25 minutes',
|
||||
'30 分钟': '30 minutes',
|
||||
'1 小时': '1 hour',
|
||||
'2 小时': '2 hours',
|
||||
'数据目录': 'Data Directory',
|
||||
'Bandix 数据存储目录': 'Bandix data storage directory',
|
||||
'启用 Bandix 连接监控功能': 'Enable Bandix connection monitoring',
|
||||
'持久化历史数据': 'Persist History Data',
|
||||
'启用数据持久化功能': 'Enable data persistence functionality, data will only be persisted to disk when this option is enabled'
|
||||
},
|
||||
'fr': {
|
||||
'基本设置': 'Paramètres de Base',
|
||||
'流量监控设置': 'Paramètres de Surveillance du Trafic',
|
||||
'连接监控设置': 'Paramètres de Surveillance des Connexions',
|
||||
'Bandix 基本配置': 'Configuration de Base Bandix',
|
||||
'配置 Bandix 服务的基本参数': 'Configurer les paramètres de base du service Bandix',
|
||||
'Bandix 流量监控配置': 'Configuration de Surveillance du Trafic Bandix',
|
||||
'配置流量监控相关参数': 'Configurer les paramètres liés à la surveillance du trafic',
|
||||
'Bandix 连接监控配置': 'Configuration de Surveillance des Connexions Bandix',
|
||||
'配置连接监控相关参数': 'Configurer les paramètres liés à la surveillance des connexions',
|
||||
'启用': 'Activer',
|
||||
'启用 Bandix 流量监控服务': 'Activer le Service de Surveillance du Trafic Bandix',
|
||||
'启用流量监控': 'Activer la Surveillance du Trafic',
|
||||
'启用连接监控': 'Activer la Surveillance des Connexions',
|
||||
'界面语言': 'Langue de l\'Interface',
|
||||
'选择 Bandix 流量监控的显示语言': 'Sélectionner la langue d\'affichage pour le Moniteur de Trafic Bandix',
|
||||
'简体中文': 'Chinois Simplifié',
|
||||
'繁体中文': 'Chinois Traditionnel',
|
||||
'端口': 'Port',
|
||||
'Bandix 服务监听的端口': 'Port d\'écoute du service Bandix',
|
||||
'监控网卡': 'Interface de Surveillance',
|
||||
'选择要监控的网络接口': 'Sélectionner l\'interface réseau LAN à surveiller',
|
||||
'网速单位': 'Unités de Vitesse',
|
||||
'选择网速显示的单位格式': 'Sélectionner le format d\'unité d\'affichage de la vitesse',
|
||||
'字节单位 (B/s, KB/s, MB/s)': 'Unités d\'Octets (B/s, KB/s, MB/s)',
|
||||
'比特单位 (bps, Kbps, Mbps)': 'Unités de Bits (bps, Kbps, Mbps)',
|
||||
'界面主题': 'Thème de l\'Interface',
|
||||
'选择 Bandix 流量监控的显示主题': 'Sélectionner le thème d\'affichage pour le Moniteur de Trafic Bandix',
|
||||
'跟随系统': 'Suivre le Système',
|
||||
'明亮模式': 'Mode Clair',
|
||||
'暗黑模式': 'Mode Sombre',
|
||||
'意见反馈': 'Commentaires',
|
||||
'离线超时时间': 'Délai d\'expiration hors ligne',
|
||||
'设置设备离线判断的超时时间(秒)': 'Définir le délai d\'expiration pour la détection hors ligne des appareils (secondes). Les appareils inactifs plus longtemps que cette durée seront marqués comme hors ligne',
|
||||
'历史流量周期': 'Période d\'Historique du Trafic',
|
||||
'设置历史流量数据周期': "Avec un intervalle de 10 minutes, 1 appareil occupe environ 60 Ko (taille fixe), la modification effacera les données existantes.\nEn raison de la limite de communication unique d'ubus, des valeurs plus importantes ne peuvent pas être définies.",
|
||||
'数据 flush 间隔': 'Intervalle de flush',
|
||||
'设置数据 flush 间隔': 'Définir l\'intervalle pour effectuer le flush des données sur le disque',
|
||||
'1 分钟': '1 minute',
|
||||
'5 分钟': '5 minutes',
|
||||
'10 分钟': '10 minutes',
|
||||
'15 分钟': '15 minutes',
|
||||
'20 分钟': '20 minutes',
|
||||
'25 分钟': '25 minutes',
|
||||
'30 分钟': '30 minutes',
|
||||
'1 小时': '1 heure',
|
||||
'2 小时': '2 heures',
|
||||
'数据目录': 'Répertoire de Données',
|
||||
'Bandix 数据存储目录': 'Répertoire de stockage de données Bandix',
|
||||
'启用 Bandix 连接监控功能': 'Activer la surveillance des connexions Bandix',
|
||||
'持久化历史数据': 'Persister les Données Historiques',
|
||||
'启用数据持久化功能': 'Activer la fonctionnalité de persistance des données, les données ne seront persistées sur le disque que lorsque cette option est activée'
|
||||
},
|
||||
'ja': {
|
||||
'基本设置': '基本設定',
|
||||
'流量监控设置': 'トラフィック監視設定',
|
||||
'连接监控设置': '接続監視設定',
|
||||
'Bandix 基本配置': 'Bandix 基本設定',
|
||||
'配置 Bandix 服务的基本参数': 'Bandix サービスの基本パラメータを設定',
|
||||
'Bandix 流量监控配置': 'Bandix トラフィック監視設定',
|
||||
'配置流量监控相关参数': 'トラフィック監視関連パラメータを設定',
|
||||
'Bandix 连接监控配置': 'Bandix 接続監視設定',
|
||||
'配置连接监控相关参数': '接続監視関連パラメータを設定',
|
||||
'启用': '有効',
|
||||
'启用 Bandix 流量监控服务': 'Bandix トラフィックモニターサービスを有効にする',
|
||||
'启用流量监控': 'トラフィック監視を有効にする',
|
||||
'启用连接监控': '接続監視を有効にする',
|
||||
'界面语言': 'インターフェース言語',
|
||||
'选择 Bandix 流量监控的显示语言': 'Bandix トラフィックモニターの表示言語を選択',
|
||||
'简体中文': '簡体字中国語',
|
||||
'繁体中文': '繁体字中国語',
|
||||
'端口': 'ポート',
|
||||
'Bandix 服务监听的端口': 'Bandix サービスのリッスンポート',
|
||||
'监控网卡': '監視インターフェース',
|
||||
'选择要监控的网络接口': '監視するLANネットワークインターフェースを選択',
|
||||
'网速单位': '速度単位',
|
||||
'选择网速显示的单位格式': '速度表示の単位形式を選択',
|
||||
'字节单位 (B/s, KB/s, MB/s)': 'バイト単位 (B/s, KB/s, MB/s)',
|
||||
'比特单位 (bps, Kbps, Mbps)': 'ビット単位 (bps, Kbps, Mbps)',
|
||||
'界面主题': 'インターフェーステーマ',
|
||||
'选择 Bandix 流量监控的显示主题': 'Bandix トラフィックモニターの表示テーマを選択',
|
||||
'跟随系统': 'システムに従う',
|
||||
'明亮模式': 'ライトモード',
|
||||
'暗黑模式': 'ダークモード',
|
||||
'意见反馈': 'フィードバック',
|
||||
'离线超时时间': 'オフラインタイムアウト',
|
||||
'设置设备离线判断的超时时间(秒)': 'デバイスのオフライン検出のタイムアウト時間(秒)を設定。この時間を超えて非アクティブなデバイスはオフラインとしてマークされます',
|
||||
'历史流量周期': 'トラフィック履歴期間',
|
||||
'设置历史流量数据周期': '10分間隔では、1台のデバイスで固定サイズとして約60KBを使用し、変更すると既存データがクリアされます。\nubusの単一通信制限により、より大きな値を設定することはできません。',
|
||||
'数据 flush 间隔': 'データflush間隔',
|
||||
'设置数据 flush 间隔': 'データをディスクにflushする間隔を設定',
|
||||
'1 分钟': '1 分',
|
||||
'5 分钟': '5 分',
|
||||
'10 分钟': '10 分',
|
||||
'15 分钟': '15 分',
|
||||
'20 分钟': '20 分',
|
||||
'25 分钟': '25 分',
|
||||
'30 分钟': '30 分',
|
||||
'1 小时': '1 時間',
|
||||
'2 小时': '2 時間',
|
||||
'数据目录': 'データディレクトリ',
|
||||
'Bandix 数据存储目录': 'Bandix データ保存ディレクトリ',
|
||||
'启用 Bandix 连接监控功能': 'Bandix 接続監視機能を有効にする',
|
||||
'持久化历史数据': '履歴データの永続化',
|
||||
'启用数据持久化功能': 'データ永続化機能を有効にする。このオプションが有効な場合のみ、データがディスクに永続化されます'
|
||||
},
|
||||
'ru': {
|
||||
'基本设置': 'Основные Настройки',
|
||||
'流量监控设置': 'Настройки Мониторинга Трафика',
|
||||
'连接监控设置': 'Настройки Мониторинга Соединений',
|
||||
'Bandix 基本配置': 'Базовая Конфигурация Bandix',
|
||||
'配置 Bandix 服务的基本参数': 'Настроить основные параметры службы Bandix',
|
||||
'Bandix 流量监控配置': 'Конфигурация Мониторинга Трафика Bandix',
|
||||
'配置流量监控相关参数': 'Настроить параметры, связанные с мониторингом трафика',
|
||||
'Bandix 连接监控配置': 'Конфигурация Мониторинга Соединений Bandix',
|
||||
'配置连接监控相关参数': 'Настроить параметры, связанные с мониторингом соединений',
|
||||
'启用': 'Включить',
|
||||
'启用 Bandix 流量监控服务': 'Включить Службу Мониторинга Трафика Bandix',
|
||||
'启用流量监控': 'Включить Мониторинг Трафика',
|
||||
'启用连接监控': 'Включить Мониторинг Соединений',
|
||||
'界面语言': 'Язык Интерфейса',
|
||||
'选择 Bandix 流量监控的显示语言': 'Выберите язык отображения для Монитора Трафика Bandix',
|
||||
'简体中文': 'Упрощенный Китайский',
|
||||
'繁体中文': 'Традиционный Китайский',
|
||||
'端口': 'Порт',
|
||||
'Bandix 服务监听的端口': 'Порт прослушивания службы Bandix',
|
||||
'监控网卡': 'Интерфейс Мониторинга',
|
||||
'选择要监控的网络接口': 'Выберите сетевой интерфейс LAN для мониторинга',
|
||||
'网速单位': 'Единицы Скорости',
|
||||
'选择网速显示的单位格式': 'Выберите формат единиц отображения скорости',
|
||||
'字节单位 (B/s, KB/s, MB/s)': 'Единицы Байтов (B/s, KB/s, MB/s)',
|
||||
'比特单位 (bps, Kbps, Mbps)': 'Единицы Битов (bps, Kbps, Mbps)',
|
||||
'界面主题': 'Тема Интерфейса',
|
||||
'选择 Bandix 流量监控的显示主题': 'Выберите тему отображения для Монитора Трафика Bandix',
|
||||
'跟随系统': 'Следовать Системе',
|
||||
'明亮模式': 'Светлый Режим',
|
||||
'暗黑模式': 'Темный Режим',
|
||||
'意见反馈': 'Обратная связь',
|
||||
'离线超时时间': 'Таймаут отключения',
|
||||
'设置设备离线判断的超时时间(秒)': 'Установить таймаут для обнаружения отключения устройств (секунды). Устройства, неактивные дольше этого времени, будут помечены как отключенные',
|
||||
'历史流量周期': 'Период Истории Трафика',
|
||||
'设置历史流量数据周期': 'При интервале 10 минут одно устройство занимает около 60 КБ (фиксированный размер), изменение очистит существующие данные.\nИз-за ограничения единичной связи ubus нельзя установить большие значения.',
|
||||
'数据 flush 间隔': 'Интервал flush данных',
|
||||
'设置数据 flush 间隔': 'Установить интервал flush данных на диск',
|
||||
'1 分钟': '1 минута',
|
||||
'5 分钟': '5 минут',
|
||||
'10 分钟': '10 минут',
|
||||
'15 分钟': '15 минут',
|
||||
'20 分钟': '20 минут',
|
||||
'25 分钟': '25 минут',
|
||||
'30 分钟': '30 минут',
|
||||
'1 小时': '1 час',
|
||||
'2 小时': '2 часа',
|
||||
'数据目录': 'Каталог Данных',
|
||||
'Bandix 数据存储目录': 'Каталог хранения данных Bandix',
|
||||
'启用 Bandix 连接监控功能': 'Включить мониторинг соединений Bandix',
|
||||
'持久化历史数据': 'Персистентность Исторических Данных',
|
||||
'启用数据持久化功能': 'Включить функциональность персистентности данных, данные будут сохраняться на диск только при включении этой опции'
|
||||
}
|
||||
};
|
||||
|
||||
function getTranslation(key, language) {
|
||||
return translations[language]?.[key] || key;
|
||||
}
|
||||
|
||||
// 获取系统语言并返回支持的语言代码
|
||||
function getSystemLanguage() {
|
||||
// 尝试获取 LuCI 的语言设置
|
||||
var luciLang = uci.get('luci', 'main', 'lang');
|
||||
|
||||
if (luciLang && translations[luciLang]) {
|
||||
return luciLang;
|
||||
}
|
||||
|
||||
// 如果没有 LuCI 语言设置,尝试获取浏览器语言作为回退
|
||||
var systemLang = document.documentElement.lang || 'en';
|
||||
|
||||
// 检查是否支持该语言
|
||||
if (translations[systemLang]) {
|
||||
return systemLang;
|
||||
}
|
||||
|
||||
// 如果不支持,返回英语
|
||||
return 'en';
|
||||
}
|
||||
|
||||
function isDarkMode() {
|
||||
// 首先检查用户设置的主题
|
||||
var userTheme = uci.get('bandix', 'general', 'theme');
|
||||
if (userTheme) {
|
||||
if (userTheme === 'dark') {
|
||||
return true;
|
||||
} else if (userTheme === 'light') {
|
||||
return false;
|
||||
}
|
||||
// 如果是 'auto',继续检查系统主题
|
||||
}
|
||||
|
||||
// 获取 LuCI 主题设置
|
||||
var mediaUrlBase = uci.get('luci', 'main', 'mediaurlbase');
|
||||
if (mediaUrlBase && mediaUrlBase.toLowerCase().includes('dark')) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 如果是 argon 主题,检查 argon 配置
|
||||
if (mediaUrlBase && mediaUrlBase.toLowerCase().includes('argon')) {
|
||||
var argonMode = uci.get('argon', '@global[0]', 'mode');
|
||||
if (argonMode && argonMode.toLowerCase().includes('dark')) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return view.extend({
|
||||
load: function () {
|
||||
return Promise.all([
|
||||
uci.load('bandix'),
|
||||
uci.load('network'),
|
||||
uci.load('luci'),
|
||||
uci.load('argon').catch(function() {
|
||||
// argon 配置可能不存在,忽略错误
|
||||
return null;
|
||||
})
|
||||
]);
|
||||
},
|
||||
|
||||
render: function (data) {
|
||||
var m, s, o;
|
||||
var networkConfig = uci.sections('network', 'device');
|
||||
var physicalInterfaces = [];
|
||||
|
||||
// 确保UCI section存在,否则表单不会显示
|
||||
if (!uci.get('bandix', 'general')) {
|
||||
uci.add('bandix', 'general', 'general');
|
||||
}
|
||||
if (!uci.get('bandix', 'traffic')) {
|
||||
uci.add('bandix', 'traffic', 'traffic');
|
||||
}
|
||||
if (!uci.get('bandix', 'connections')) {
|
||||
uci.add('bandix', 'connections', 'connections');
|
||||
}
|
||||
|
||||
var language = uci.get('bandix', 'general', 'language');
|
||||
if (!language || language === 'auto') {
|
||||
language = getSystemLanguage();
|
||||
}
|
||||
var darkMode = isDarkMode();
|
||||
|
||||
// 添加暗黑模式样式支持
|
||||
if (darkMode) {
|
||||
var style = E('style', {}, `
|
||||
body, .main {
|
||||
background-color: #0f172a !important;
|
||||
color: #e2e8f0 !important;
|
||||
}
|
||||
|
||||
.cbi-section {
|
||||
background-color: #1E1E1E !important;
|
||||
|
||||
border-radius: 8px !important;
|
||||
}
|
||||
|
||||
.cbi-section h3 {
|
||||
color: #f1f5f9 !important;
|
||||
background-color: #333333 !important;
|
||||
border-bottom: 1px solid #1E1E1E !important;
|
||||
}
|
||||
|
||||
.cbi-section-descr {
|
||||
color: #94a3b8 !important;
|
||||
}
|
||||
|
||||
.cbi-value {
|
||||
border-bottom: 1px solid #1E1E1E !important;
|
||||
}
|
||||
|
||||
.cbi-value-title {
|
||||
color: #e2e8f0 !important;
|
||||
}
|
||||
|
||||
.cbi-value-description {
|
||||
color: #94a3b8 !important;
|
||||
}
|
||||
|
||||
input[type="text"], input[type="number"], select, textarea {
|
||||
background-color: #333333 !important;
|
||||
|
||||
color: #e2e8f0 !important;
|
||||
}
|
||||
|
||||
input[type="text"]:focus, input[type="number"]:focus, select:focus, textarea:focus {
|
||||
border-color: #3b82f6 !important;
|
||||
box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.2) !important;
|
||||
}
|
||||
|
||||
input[type="checkbox"] {
|
||||
accent-color: #3b82f6 !important;
|
||||
}
|
||||
|
||||
.cbi-button, .btn {
|
||||
background-color: #333333 !important;
|
||||
|
||||
color: #e2e8f0 !important;
|
||||
}
|
||||
|
||||
.cbi-button:hover, .btn:hover {
|
||||
background-color: #1E1E1E !important;
|
||||
}
|
||||
|
||||
.cbi-button-save {
|
||||
background-color: #3b82f6 !important;
|
||||
border-color: #3b82f6 !important;
|
||||
color: white !important;
|
||||
}
|
||||
|
||||
.cbi-button-save:hover {
|
||||
background-color: #2563eb !important;
|
||||
}
|
||||
|
||||
.cbi-section-error {
|
||||
background-color: #7f1d1d !important;
|
||||
border-color: #dc2626 !important;
|
||||
color: #fca5a5 !important;
|
||||
}
|
||||
|
||||
/* 表格样式 */
|
||||
.table {
|
||||
background-color: #1E1E1E !important;
|
||||
|
||||
}
|
||||
|
||||
.table th {
|
||||
background-color: #333333 !important;
|
||||
color: #e2e8f0 !important;
|
||||
border-bottom: 1px solid #1E1E1E !important;
|
||||
}
|
||||
|
||||
.table td {
|
||||
color: #cbd5e1 !important;
|
||||
border-bottom: 1px solid #1E1E1E !important;
|
||||
}
|
||||
|
||||
.table tr:hover {
|
||||
background-color: #1E1E1E !important;
|
||||
}
|
||||
`);
|
||||
document.head.appendChild(style);
|
||||
}
|
||||
|
||||
// 从network配置中提取物理接口名称
|
||||
if (networkConfig && networkConfig.length > 0) {
|
||||
networkConfig.forEach(function (device) {
|
||||
if (device.name) {
|
||||
physicalInterfaces.push(device.name);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 添加网络接口配置中的物理接口
|
||||
var interfaces = uci.sections('network', 'interface');
|
||||
if (interfaces && interfaces.length > 0) {
|
||||
interfaces.forEach(function (iface) {
|
||||
if (iface.device && physicalInterfaces.indexOf(iface.device) === -1) {
|
||||
physicalInterfaces.push(iface.device);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 确保至少有一些默认值
|
||||
if (physicalInterfaces.length === 0) {
|
||||
physicalInterfaces = [];
|
||||
}
|
||||
|
||||
// 创建表单
|
||||
m = new form.Map('bandix');
|
||||
|
||||
// 1. 基本设置部分 (general)
|
||||
s = m.section(form.NamedSection, 'general', 'general', getTranslation('基本设置', language));
|
||||
s.description = getTranslation('配置 Bandix 服务的基本参数', language);
|
||||
s.addremove = false;
|
||||
|
||||
// 添加端口设置选项
|
||||
o = s.option(form.Value, 'port', getTranslation('端口', language),
|
||||
getTranslation('Bandix 服务监听的端口', language));
|
||||
o.default = '8686';
|
||||
o.datatype = 'port';
|
||||
o.placeholder = '8686';
|
||||
o.rmempty = false;
|
||||
|
||||
// 添加网卡选择下拉菜单
|
||||
o = s.option(form.ListValue, 'iface', getTranslation('监控网卡', language),
|
||||
getTranslation('选择要监控的网络接口', language));
|
||||
o.rmempty = false;
|
||||
|
||||
// 添加从配置获取的物理接口
|
||||
physicalInterfaces.forEach(function (iface) {
|
||||
o.value(iface, iface);
|
||||
});
|
||||
|
||||
// 添加语言选择选项
|
||||
o = s.option(form.ListValue, 'language', getTranslation('界面语言', language),
|
||||
getTranslation('选择 Bandix 流量监控的显示语言', language));
|
||||
o.value('auto', getTranslation('跟随系统', language));
|
||||
o.value('zh-cn', getTranslation('简体中文', language));
|
||||
o.value('zh-tw', getTranslation('繁体中文', language));
|
||||
o.value('en', 'English');
|
||||
o.value('fr', 'Français');
|
||||
o.value('ja', '日本語');
|
||||
o.value('ru', 'Русский');
|
||||
o.default = 'auto';
|
||||
o.rmempty = false;
|
||||
|
||||
// 添加主题选择选项
|
||||
o = s.option(form.ListValue, 'theme', getTranslation('界面主题', language),
|
||||
getTranslation('选择 Bandix 流量监控的显示主题', language));
|
||||
o.value('auto', getTranslation('跟随系统', language));
|
||||
o.value('light', getTranslation('明亮模式', language));
|
||||
o.value('dark', getTranslation('暗黑模式', language));
|
||||
o.default = 'auto';
|
||||
o.rmempty = false;
|
||||
|
||||
// 添加数据目录设置(只读)
|
||||
o = s.option(form.DummyValue, 'data_dir', getTranslation('数据目录', language));
|
||||
o.default = '/usr/share/bandix';
|
||||
o.cfgvalue = function(section_id) {
|
||||
return uci.get('bandix', section_id, 'data_dir') || '/usr/share/bandix';
|
||||
};
|
||||
|
||||
// 添加意见反馈信息
|
||||
o = s.option(form.DummyValue, 'feedback_info', getTranslation('意见反馈', language));
|
||||
o.href = 'https://github.com/timsaya';
|
||||
o.cfgvalue = function() {
|
||||
return 'https://github.com/timsaya';
|
||||
};
|
||||
|
||||
// 2. 流量监控设置部分 (traffic)
|
||||
s = m.section(form.NamedSection, 'traffic', 'traffic', getTranslation('流量监控设置', language));
|
||||
s.description = getTranslation('配置流量监控相关参数', language);
|
||||
s.addremove = false;
|
||||
|
||||
o = s.option(form.Flag, 'enabled', getTranslation('启用流量监控', language),
|
||||
getTranslation('启用 Bandix 流量监控服务', language));
|
||||
o.default = '0';
|
||||
o.rmempty = false;
|
||||
|
||||
// 添加网速单位选择选项
|
||||
o = s.option(form.ListValue, 'speed_unit', getTranslation('网速单位', language),
|
||||
getTranslation('选择网速显示的单位格式', language));
|
||||
o.value('bytes', getTranslation('字节单位 (B/s, KB/s, MB/s)', language));
|
||||
o.value('bits', getTranslation('比特单位 (bps, Kbps, Mbps)', language));
|
||||
o.default = 'bytes';
|
||||
o.rmempty = false;
|
||||
|
||||
// 添加离线超时时间(秒)
|
||||
o = s.option(form.Value, 'offline_timeout', getTranslation('离线超时时间', language),
|
||||
getTranslation('设置设备离线判断的超时时间(秒)', language));
|
||||
o.datatype = 'uinteger';
|
||||
o.placeholder = '600';
|
||||
o.default = '600';
|
||||
o.rmempty = true;
|
||||
|
||||
// 添加持久化历史数据选项
|
||||
o = s.option(form.Flag, 'traffic_persist_history', getTranslation('持久化历史数据', language),
|
||||
getTranslation('启用数据持久化功能', language));
|
||||
o.default = '0';
|
||||
o.rmempty = false;
|
||||
|
||||
// 添加数据 flush 间隔(秒)
|
||||
o = s.option(form.ListValue, 'traffic_flush_interval_seconds', getTranslation('数据 flush 间隔', language),
|
||||
getTranslation('设置数据 flush 间隔', language));
|
||||
o.value('60', getTranslation('1 分钟', language));
|
||||
o.value('300', getTranslation('5 分钟', language));
|
||||
o.value('600', getTranslation('10 分钟', language));
|
||||
o.value('900', getTranslation('15 分钟', language));
|
||||
o.value('1200', getTranslation('20 分钟', language));
|
||||
o.value('1500', getTranslation('25 分钟', language));
|
||||
o.value('1800', getTranslation('30 分钟', language));
|
||||
o.value('3600', getTranslation('1 小时', language));
|
||||
o.value('7200', getTranslation('2 小时', language));
|
||||
o.default = '600';
|
||||
o.rmempty = false;
|
||||
o.depends('traffic_persist_history', '1');
|
||||
|
||||
|
||||
// 添加历史流量周期(秒)
|
||||
o = s.option(form.ListValue, 'traffic_retention_seconds', getTranslation('历史流量周期', language),
|
||||
getTranslation('设置历史流量数据周期', language));
|
||||
o.value('60', getTranslation('1 分钟', language));
|
||||
o.value('300', getTranslation('5 分钟', language));
|
||||
o.value('600', getTranslation('10 分钟', language));
|
||||
o.value('900', getTranslation('15 分钟', language));
|
||||
o.value('1200', getTranslation('20 分钟', language));
|
||||
o.value('1500', getTranslation('25 分钟', language));
|
||||
o.value('1800', getTranslation('30 分钟', language));
|
||||
o.default = '600';
|
||||
o.rmempty = false;
|
||||
|
||||
|
||||
|
||||
// 3. 连接监控设置部分 (connections)
|
||||
s = m.section(form.NamedSection, 'connections', 'connections', getTranslation('连接监控设置', language));
|
||||
s.description = getTranslation('配置连接监控相关参数', language);
|
||||
s.addremove = false;
|
||||
|
||||
o = s.option(form.Flag, 'enabled', getTranslation('启用连接监控', language),
|
||||
getTranslation('启用 Bandix 连接监控功能', language));
|
||||
o.default = '0';
|
||||
o.rmempty = false;
|
||||
|
||||
return m.render();
|
||||
}
|
||||
});
|
||||
3
luci-app-bandix/po/zh-cn/bandix.po
Normal file
3
luci-app-bandix/po/zh-cn/bandix.po
Normal file
@@ -0,0 +1,3 @@
|
||||
msgid "Bandix"
|
||||
msgstr "Bandix 流量监控"
|
||||
|
||||
3
luci-app-bandix/po/zh_Hans/bandix.po
Normal file
3
luci-app-bandix/po/zh_Hans/bandix.po
Normal file
@@ -0,0 +1,3 @@
|
||||
msgid "Bandix"
|
||||
msgstr "Bandix 流量监控"
|
||||
|
||||
356
luci-app-bandix/root/usr/libexec/rpcd/luci.bandix
Executable file
356
luci-app-bandix/root/usr/libexec/rpcd/luci.bandix
Executable file
@@ -0,0 +1,356 @@
|
||||
#!/bin/sh
|
||||
# Copyright 2025 timsaya
|
||||
|
||||
. /usr/share/libubox/jshn.sh
|
||||
|
||||
# 从UCI配置读取端口号,如果读取失败则使用默认端口8686
|
||||
BANDIX_PORT=$(uci get bandix.general.port 2>/dev/null || echo "8686")
|
||||
readonly BANDIX_API_BASE="http://127.0.0.1:$BANDIX_PORT"
|
||||
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"
|
||||
|
||||
# 通用函数:创建简单的JSON响应
|
||||
make_value() {
|
||||
json_init
|
||||
json_add_string "$1" "$2"
|
||||
json_dump
|
||||
json_cleanup
|
||||
}
|
||||
|
||||
# 通用函数:创建错误响应
|
||||
make_error() {
|
||||
json_init
|
||||
json_add_boolean "success" 0
|
||||
json_add_string "error" "$1"
|
||||
json_dump
|
||||
json_cleanup
|
||||
}
|
||||
|
||||
# 通用函数:创建成功响应
|
||||
make_success() {
|
||||
json_init
|
||||
json_add_boolean "success" 1
|
||||
[ -n "$1" ] && json_add_string "message" "$1"
|
||||
json_dump
|
||||
json_cleanup
|
||||
}
|
||||
|
||||
# 转义JSON字符串
|
||||
escape_json_string() {
|
||||
echo "$1" | sed 's/\\/\\\\/g; s/"/\\"/g; s/\n/\\n/g; s/\r/\\r/g; s/\t/\\t/g'
|
||||
}
|
||||
|
||||
# 获取设备状态
|
||||
get_device_status() {
|
||||
local api_result=$(curl -s --connect-timeout 2 --max-time 5 "$BANDIX_DEVICES_API" 2>/dev/null)
|
||||
|
||||
# 检查API调用是否成功
|
||||
if [ $? -ne 0 ] || [ -z "$api_result" ]; then
|
||||
echo '{"devices":[]}'
|
||||
return
|
||||
fi
|
||||
|
||||
# 提取 data 部分并返回
|
||||
local data_part=$(echo "$api_result" | jsonfilter -e '$.data' 2>/dev/null)
|
||||
if [ -n "$data_part" ]; then
|
||||
echo "$data_part"
|
||||
else
|
||||
echo '{"devices":[]}'
|
||||
fi
|
||||
}
|
||||
|
||||
# 获取历史指标(可选MAC)
|
||||
get_metrics() {
|
||||
local mac="$1"
|
||||
local url="$BANDIX_METRICS_API"
|
||||
if [ -n "$mac" ]; then
|
||||
# 转义MAC
|
||||
local mac_escaped=$(echo "$mac" | sed 's/\//\\\//g')
|
||||
url="$url?mac=$mac_escaped"
|
||||
fi
|
||||
|
||||
# logger "luci.bandix.metrics: entering mac=$mac url=$url"
|
||||
local api_result=$(curl -s --connect-timeout 1 --max-time 3 "$url" 2>/dev/null)
|
||||
local curl_exit_code=$?
|
||||
# logger "luci.bandix.metrics: curl finished with exit code: $curl_exit_code"
|
||||
|
||||
if [ $curl_exit_code -ne 0 ] || [ -z "$api_result" ]; then
|
||||
# logger "luci.bandix.metrics: curl failed or empty response, returning empty metrics"
|
||||
# 返回空结果(原始格式)
|
||||
echo '{"retention_seconds":600,"mac":"","metrics":[]}'
|
||||
return
|
||||
fi
|
||||
|
||||
# 使用 jsonfilter 提取 data 部分(根据文档,这是处理大JSON的推荐方式)
|
||||
local data_part=$(echo "$api_result" | jsonfilter -e '$.data' 2>/dev/null)
|
||||
if [ -n "$data_part" ]; then
|
||||
local data_size=${#data_part}
|
||||
# logger "luci.bandix.metrics: extracted data part using jsonfilter, size: $data_size bytes"
|
||||
echo "$data_part"
|
||||
return
|
||||
else
|
||||
# logger "luci.bandix.metrics: jsonfilter failed to extract data part"
|
||||
echo '{"retention_seconds":600,"mac":"","metrics":[]}'
|
||||
fi
|
||||
}
|
||||
|
||||
# 设置设备限速
|
||||
set_device_rate_limit() {
|
||||
local mac="$1"
|
||||
local wide_tx_rate_limit="$2"
|
||||
local wide_rx_rate_limit="$3"
|
||||
|
||||
# logger "luci.bandix: set_device_rate_limit 参数: mac=$mac tx=$wide_tx_rate_limit rx=$wide_rx_rate_limit"
|
||||
|
||||
# 验证参数
|
||||
if [ -z "$mac" ]; then
|
||||
make_error "MAC address is required"
|
||||
return
|
||||
fi
|
||||
|
||||
# 转义MAC地址中的特殊字符
|
||||
local mac_escaped=$(escape_json_string "$mac")
|
||||
|
||||
# 构建请求数据
|
||||
local request_data="{
|
||||
\"mac\": \"$mac\",
|
||||
\"wide_tx_rate_limit\": $wide_tx_rate_limit,
|
||||
\"wide_rx_rate_limit\": $wide_rx_rate_limit
|
||||
}"
|
||||
|
||||
# 记录请求数据(用于调试)
|
||||
# logger "luci.bandix: request_data=$request_data"
|
||||
|
||||
# 发送请求到bandix API
|
||||
local response=$(curl -s --connect-timeout 3 --max-time 10 -X POST -H "Content-Type: application/json" -d "$request_data" "$BANDIX_LIMITS_API" 2>/dev/null)
|
||||
|
||||
# logger "luci.bandix: API response=$response"
|
||||
|
||||
if [ $? -eq 0 ] && [ -n "$response" ]; then
|
||||
# 检查API响应是否包含success状态
|
||||
if echo "$response" | grep -q '"status":\s*"success"'; then
|
||||
make_success "Rate limit set successfully"
|
||||
else
|
||||
# API返回了响应但不是success状态
|
||||
json_init
|
||||
json_add_boolean "success" 0
|
||||
json_add_string "error" "API returned error response"
|
||||
json_add_string "response" "$response"
|
||||
json_dump
|
||||
json_cleanup
|
||||
fi
|
||||
else
|
||||
make_error "Failed to set rate limit"
|
||||
fi
|
||||
}
|
||||
|
||||
# 设置设备主机名绑定
|
||||
set_device_hostname() {
|
||||
local mac="$1"
|
||||
local hostname="$2"
|
||||
|
||||
# logger "luci.bandix: set_device_hostname 参数: mac=$mac hostname=$hostname"
|
||||
|
||||
# 验证参数
|
||||
if [ -z "$mac" ]; then
|
||||
make_error "MAC address is required"
|
||||
return
|
||||
fi
|
||||
|
||||
# hostname可以为空,表示删除绑定
|
||||
if [ -z "$hostname" ]; then
|
||||
hostname=""
|
||||
fi
|
||||
|
||||
# 转义特殊字符
|
||||
local mac_escaped=$(escape_json_string "$mac")
|
||||
local hostname_escaped=$(escape_json_string "$hostname")
|
||||
|
||||
# 构建请求数据
|
||||
local request_data="{
|
||||
\"mac\": \"$mac\",
|
||||
\"hostname\": \"$hostname\"
|
||||
}"
|
||||
|
||||
# 记录请求数据(用于调试)
|
||||
# logger "luci.bandix: hostname request_data=$request_data"
|
||||
|
||||
# 发送请求到bandix API
|
||||
local bindings_api="$BANDIX_API_BASE/api/traffic/bindings"
|
||||
local response=$(curl -s --connect-timeout 3 --max-time 10 -X POST -H "Content-Type: application/json" -d "$request_data" "$bindings_api" 2>/dev/null)
|
||||
|
||||
# logger "luci.bandix: hostname API response=$response"
|
||||
|
||||
if [ $? -eq 0 ] && [ -n "$response" ]; then
|
||||
# 检查API响应是否包含success状态
|
||||
if echo "$response" | grep -q '"status":\s*"success"'; then
|
||||
make_success "Hostname binding set successfully"
|
||||
else
|
||||
# API返回了响应但不是success状态
|
||||
json_init
|
||||
json_add_boolean "success" 0
|
||||
json_add_string "error" "API returned error response"
|
||||
json_add_string "response" "$response"
|
||||
json_dump
|
||||
json_cleanup
|
||||
fi
|
||||
else
|
||||
make_error "Failed to set hostname binding"
|
||||
fi
|
||||
}
|
||||
|
||||
# 获取连接监控数据
|
||||
get_connection_devices() {
|
||||
# 检查连接监控是否启用
|
||||
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
|
||||
|
||||
# 从 Bandix API 获取连接数据
|
||||
local response=$(curl -s --connect-timeout 2 --max-time 5 -X GET "$BANDIX_CONNECTION_API" 2>/dev/null)
|
||||
|
||||
# 检查API调用是否成功
|
||||
if [ $? -ne 0 ] || [ -z "$response" ]; then
|
||||
make_error "Failed to connect to Bandix service"
|
||||
return
|
||||
fi
|
||||
|
||||
# 直接返回API结果
|
||||
echo "$response"
|
||||
}
|
||||
|
||||
case "$1" in
|
||||
list)
|
||||
json_init
|
||||
json_add_object "getStatus"
|
||||
json_close_object
|
||||
|
||||
json_add_object "getMetrics"
|
||||
json_add_string "mac"
|
||||
json_close_object
|
||||
|
||||
json_add_object "setRateLimit"
|
||||
json_add_string "mac"
|
||||
json_add_int "wide_rx_rate_limit"
|
||||
json_add_int "wide_tx_rate_limit"
|
||||
json_close_object
|
||||
|
||||
json_add_object "setHostname"
|
||||
json_add_string "mac"
|
||||
json_add_string "hostname"
|
||||
json_close_object
|
||||
|
||||
json_add_object "getConnection"
|
||||
json_close_object
|
||||
|
||||
json_dump
|
||||
json_cleanup
|
||||
;;
|
||||
call)
|
||||
case "$2" in
|
||||
getStatus)
|
||||
get_device_status
|
||||
;;
|
||||
getMetrics)
|
||||
# logger "luci.bandix: getMetrics called"
|
||||
# 读取参数:优先 STDIN(带超时,避免阻塞),其次位置参数($3)
|
||||
# 很奇怪收到的是 received input: {"mac":"70:e2:84:2b:9f:42","ubus_rpc_session":"4b0c5dcd9baf9963e4f13647fa71228a"}
|
||||
mac=""
|
||||
input=""
|
||||
# 尝试读取一行 STDIN,1 秒超时以避免在无输入场景下阻塞
|
||||
if read -t 1 -r input; then
|
||||
:
|
||||
fi
|
||||
if [ -n "$input" ]; then
|
||||
# logger "luci.bandix: received input: $input"
|
||||
# 支持数组或对象两种风格
|
||||
mac="$(echo "$input" | jsonfilter -e '$[0]' 2>/dev/null)"
|
||||
[ -z "$mac" ] && mac="$(echo "$input" | jsonfilter -e '$.mac' 2>/dev/null)"
|
||||
else
|
||||
# 某些环境不会通过 STDIN 传参,改为读取位置参数
|
||||
[ -n "$3" ] && mac="$3"
|
||||
# logger "luci.bandix: received argv mac: $mac"
|
||||
fi
|
||||
get_metrics "$mac"
|
||||
;;
|
||||
setRateLimit)
|
||||
# logger "luci.bandix: setRateLimit called"
|
||||
|
||||
# 从 stdin 读取 JSON 参数
|
||||
read input
|
||||
# logger "luci.bandix: received input: $input"
|
||||
|
||||
if [ -n "$input" ]; then
|
||||
# 尝试解析数组格式的 JSON 参数 (LuCI RPC 通常使用数组格式)
|
||||
# 参数格式: ["mac", wide_tx_rate_limit, wide_rx_rate_limit]
|
||||
mac=$(echo "$input" | jsonfilter -e '$[0]' 2>/dev/null)
|
||||
wide_tx_rate_limit=$(echo "$input" | jsonfilter -e '$[1]' 2>/dev/null)
|
||||
wide_rx_rate_limit=$(echo "$input" | jsonfilter -e '$[2]' 2>/dev/null)
|
||||
|
||||
# 如果数组格式解析失败,尝试对象格式
|
||||
if [ -z "$mac" ]; then
|
||||
mac=$(echo "$input" | jsonfilter -e '$.mac' 2>/dev/null)
|
||||
wide_tx_rate_limit=$(echo "$input" | jsonfilter -e '$.wide_tx_rate_limit' 2>/dev/null)
|
||||
wide_rx_rate_limit=$(echo "$input" | jsonfilter -e '$.wide_rx_rate_limit' 2>/dev/null)
|
||||
fi
|
||||
|
||||
# logger "luci.bandix: parsed - mac=$mac tx=$wide_tx_rate_limit rx=$wide_rx_rate_limit"
|
||||
|
||||
# 验证参数
|
||||
if [ -n "$mac" ] && [ -n "$wide_tx_rate_limit" ] && [ -n "$wide_rx_rate_limit" ]; then
|
||||
# 调用限速设置函数
|
||||
set_device_rate_limit "$mac" "$wide_tx_rate_limit" "$wide_rx_rate_limit"
|
||||
else
|
||||
# logger "luci.bandix: setRateLimit 参数缺失或无效"
|
||||
make_error "Missing or invalid parameters"
|
||||
fi
|
||||
else
|
||||
# logger "luci.bandix: setRateLimit 没有接收到输入"
|
||||
make_error "No input received"
|
||||
fi
|
||||
;;
|
||||
setHostname)
|
||||
# logger "luci.bandix: setHostname called"
|
||||
|
||||
# 从 stdin 读取 JSON 参数
|
||||
read input
|
||||
# logger "luci.bandix: received input: $input"
|
||||
|
||||
if [ -n "$input" ]; then
|
||||
# 尝试解析数组格式的 JSON 参数 (LuCI RPC 通常使用数组格式)
|
||||
# 参数格式: ["mac", "hostname"]
|
||||
mac=$(echo "$input" | jsonfilter -e '$[0]' 2>/dev/null)
|
||||
hostname=$(echo "$input" | jsonfilter -e '$[1]' 2>/dev/null)
|
||||
|
||||
# 如果数组格式解析失败,尝试对象格式
|
||||
if [ -z "$mac" ]; then
|
||||
mac=$(echo "$input" | jsonfilter -e '$.mac' 2>/dev/null)
|
||||
hostname=$(echo "$input" | jsonfilter -e '$.hostname' 2>/dev/null)
|
||||
fi
|
||||
|
||||
# logger "luci.bandix: parsed - mac=$mac hostname=$hostname"
|
||||
|
||||
# 验证参数(hostname可以为空)
|
||||
if [ -n "$mac" ]; then
|
||||
# 调用主机名设置函数
|
||||
set_device_hostname "$mac" "$hostname"
|
||||
else
|
||||
# logger "luci.bandix: setHostname MAC参数缺失"
|
||||
make_error "Missing MAC address parameter"
|
||||
fi
|
||||
else
|
||||
# logger "luci.bandix: setHostname 没有接收到输入"
|
||||
make_error "No input received"
|
||||
fi
|
||||
;;
|
||||
getConnection)
|
||||
# logger "luci.bandix: getConnection called"
|
||||
get_connection_devices
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
esac
|
||||
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"admin/network/bandix": {
|
||||
"title": "Bandix",
|
||||
"order": 90,
|
||||
"action": {
|
||||
"type": "alias",
|
||||
"path": "admin/network/bandix/index"
|
||||
},
|
||||
"depends": {
|
||||
"acl": [ "luci-app-bandix" ],
|
||||
"uci": { "bandix": true }
|
||||
}
|
||||
},
|
||||
"admin/network/bandix/index": {
|
||||
"title": "Status",
|
||||
"order": 10,
|
||||
"action": {
|
||||
"type": "view",
|
||||
"path": "bandix/index"
|
||||
}
|
||||
},
|
||||
"admin/network/bandix/connection": {
|
||||
"title": "Connections",
|
||||
"order": 15,
|
||||
"action": {
|
||||
"type": "view",
|
||||
"path": "bandix/connection"
|
||||
}
|
||||
},
|
||||
"admin/network/bandix/settings": {
|
||||
"title": "Settings",
|
||||
"order": 20,
|
||||
"action": {
|
||||
"type": "view",
|
||||
"path": "bandix/settings"
|
||||
}
|
||||
}
|
||||
}
|
||||
12
luci-app-bandix/root/usr/share/luci/status/bandix.json
Normal file
12
luci-app-bandix/root/usr/share/luci/status/bandix.json
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"bandix": {
|
||||
"title": "Bandix 流量监控",
|
||||
"description": "局域网设备流量监控和统计服务",
|
||||
"instances": {
|
||||
"instance": {
|
||||
"type": "process",
|
||||
"init": "bandix"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
{
|
||||
"luci-app-bandix": {
|
||||
"description": "Grant access to bandix traffic monitoring",
|
||||
"read": {
|
||||
"ubus": {
|
||||
"luci.bandix": [
|
||||
"getStatus",
|
||||
"getMetrics",
|
||||
"setRateLimit",
|
||||
"getConnection",
|
||||
"setHostname"
|
||||
]
|
||||
},
|
||||
"uci": [
|
||||
"bandix"
|
||||
]
|
||||
},
|
||||
"write": {
|
||||
"ubus": {
|
||||
"luci.bandix": [
|
||||
"setRateLimit",
|
||||
"getStatus",
|
||||
"getMetrics",
|
||||
"getConnection",
|
||||
"setHostname"
|
||||
]
|
||||
},
|
||||
"uci": [
|
||||
"bandix"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user