diff --git a/floatip/Makefile b/floatip/Makefile index 238ca00..b419288 100644 --- a/floatip/Makefile +++ b/floatip/Makefile @@ -7,8 +7,8 @@ include $(TOPDIR)/rules.mk PKG_NAME:=floatip -PKG_VERSION:=1.0.9 -PKG_RELEASE:=2 +PKG_VERSION:=1.0.10 +PKG_RELEASE:=1 PKG_MAINTAINER:=jjm2473 include $(INCLUDE_DIR)/package.mk diff --git a/floatip/files/floatip.init b/floatip/files/floatip.init index 3526426..2e28660 100755 --- a/floatip/files/floatip.init +++ b/floatip/files/floatip.init @@ -22,6 +22,11 @@ start_service() { logger -s -t floatip "LAN proto is not static" return 0 } + local lan_iface="`uci -q get network.lan.device`" + if [ -z "$lan_iface" ]; then + logger -s -t floatip "Cannot get LAN device" + return 0 + fi local set_ip set_prefix config_get set_ip "main" set_ip [[ -n "$set_ip" ]] || return 0 @@ -57,7 +62,7 @@ start_service() { return 0 } procd_open_instance - procd_set_param command /usr/libexec/floatip.sh "$set_prefix" + procd_set_param command /usr/libexec/floatip.sh "$lan_iface" "$set_prefix" procd_set_param stderr 1 procd_set_param file /etc/config/floatip procd_close_instance diff --git a/floatip/files/floatip.sh b/floatip/files/floatip.sh index a93b515..26f63bc 100755 --- a/floatip/files/floatip.sh +++ b/floatip/files/floatip.sh @@ -16,7 +16,7 @@ random() { # check host alive, timeout in 2 seconds host_alive() { ping -4 -c 2 -A -t 1 -W 1 -q "$1" >/dev/null - # arping -f -q -b -c 2 -w 2 -i 1 -I br-lan "$1" + # arping -f -q -b -c 2 -w 2 -i 1 -I $LAN_IFACE "$1" } set_up() { @@ -25,6 +25,7 @@ set_up() { if ! uci -q get network.floatip.ipaddr | grep -Fwq $ipaddr; then if [[ "x$(uci -q get network.floatip)" = xinterface ]]; then uci -q batch <<-EOF >/dev/null + set network.floatip.device=$LAN_IFACE delete network.floatip.ipaddr add_list network.floatip.ipaddr=$ipaddr EOF @@ -32,8 +33,8 @@ set_up() { uci -q batch <<-EOF >/dev/null set network.floatip=interface set network.floatip.proto=static + set network.floatip.device=$LAN_IFACE add_list network.floatip.ipaddr=$ipaddr - set network.floatip.device=br-lan set network.floatip.auto=0 EOF fi @@ -231,6 +232,13 @@ try_lock || { } echo "lock $LOCK_FILE success" >&2 +LAN_IFACE="$1" +[ -n "$LAN_IFACE" ] || { + echo "LAN_IFACE is not set" >&2 + exit 1 +} +shift + if [[ -n "$1" ]]; then [[ "$1" -ge 0 && "$1" -lt 32 ]] && DEFAULT_PREFIX=$1 fi diff --git a/floatip/files/floatip.uci-default b/floatip/files/floatip.uci-default index 3f7a62b..cce216a 100644 --- a/floatip/files/floatip.uci-default +++ b/floatip/files/floatip.uci-default @@ -6,6 +6,9 @@ uci -q batch <<-EOF >/dev/null commit ucitrack EOF +# don't change role if enabled +[[ "`uci -q get floatip.main.enabled`" = "1" ]] && exit 0 + [[ "`uci -q get network.lan.proto`" = "static" && -n "`uci -q get network.lan.gateway`" ]] || exit 0 uci -q batch <<-EOF >/dev/null diff --git a/luci-app-bandix/Makefile b/luci-app-bandix/Makefile index 33659d3..f0e2fd6 100644 --- a/luci-app-bandix/Makefile +++ b/luci-app-bandix/Makefile @@ -10,7 +10,7 @@ LUCI_DEPENDS:=+luci-base +luci-lib-jsonc +curl +bandix PKG_MAINTAINER:=timsaya -PKG_VERSION:=0.10.0 +PKG_VERSION:=0.10.3 PKG_RELEASE:=1 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 4d4c70f..f0e496b 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 @@ -190,7 +190,6 @@ return view.extend({ } .device-card { - background-color: var(--cbi-section-bg, #fff); border: 1px solid rgba(0, 0, 0, 0.1); border-radius: 8px; padding: 12px; @@ -200,7 +199,6 @@ return view.extend({ @media (prefers-color-scheme: dark) { .device-card { - background-color: var(--cbi-section-bg, rgba(30, 30, 30, 0.98)); border-color: rgba(255, 255, 255, 0.15); } } diff --git a/luci-app-bandix/htdocs/luci-static/resources/view/bandix/dns.js b/luci-app-bandix/htdocs/luci-static/resources/view/bandix/dns.js index 2baf7ec..2b2594e 100644 --- a/luci-app-bandix/htdocs/luci-static/resources/view/bandix/dns.js +++ b/luci-app-bandix/htdocs/luci-static/resources/view/bandix/dns.js @@ -502,7 +502,6 @@ return view.extend({ /* 移动端卡片样式 */ .dns-query-card { - background-color: var(--cbi-section-bg, #fff); border: 1px solid rgba(0, 0, 0, 0.1); border-radius: 8px; padding: 12px; @@ -512,7 +511,6 @@ return view.extend({ @media (prefers-color-scheme: dark) { .dns-query-card { - background-color: var(--cbi-section-bg, rgba(30, 30, 30, 0.98)); border-color: rgba(255, 255, 255, 0.15); } } @@ -1443,12 +1441,6 @@ return view.extend({ card.style.backgroundColor = bgColor; }); - // 应用到 DNS 查询卡片(移动端) - var dnsQueryCards = document.querySelectorAll('.dns-query-card'); - dnsQueryCards.forEach(function(card) { - card.style.backgroundColor = bgColor; - }); - // 应用到加载状态和错误状态 var loadingStates = document.querySelectorAll('.loading-state'); loadingStates.forEach(function(el) { 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 02eff18..039325e 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 @@ -190,6 +190,19 @@ var callDeleteScheduleLimit = rpc.declare({ expect: { success: true } }); +// 版本和更新检查 RPC +var callGetVersion = rpc.declare({ + object: 'luci.bandix', + method: 'getVersion', + expect: {} +}); + +var callCheckUpdate = rpc.declare({ + object: 'luci.bandix', + method: 'checkUpdate', + expect: {} +}); + return view.extend({ load: function () { return Promise.all([ @@ -228,6 +241,67 @@ return view.extend({ gap: 12px; } + .bandix-title-wrapper { + display: flex; + align-items: center; + gap: 12px; + } + + .bandix-version { + font-size: 0.875rem; + opacity: 0.5; + font-weight: 400; + } + + .bandix-version-wrapper { + display: inline-flex; + align-items: center; + gap: 12px; + flex-wrap: wrap; + } + + .bandix-version-item { + display: inline-flex; + align-items: center; + gap: 4px; + } + + .bandix-update-badge { + display: inline-block; + cursor: pointer; + padding: 2px 8px; + margin-left: 8px; + background-color: rgba(239, 68, 68, 0.1); + color: #ef4444; + border-radius: 4px; + font-size: 0.75rem; + font-weight: 600; + transition: all 0.2s ease; + } + + .bandix-update-badge:hover { + background-color: rgba(239, 68, 68, 0.2); + transform: translateY(-1px); + } + + @media (prefers-color-scheme: dark) { + .bandix-update-badge { + background-color: rgba(239, 68, 68, 0.2); + color: #f87171; + } + + .bandix-update-badge:hover { + background-color: rgba(239, 68, 68, 0.3); + } + } + + /* 移动端隐藏版本信息和更新徽章 */ + @media (max-width: 768px) { + .bandix-version-wrapper { + display: none; + } + } + .device-mode-group { display: inline-flex; border-radius: 4px; @@ -1120,7 +1194,10 @@ return view.extend({ padding: 8px 6px; font-size: 0.75rem; } - /* 移动端只显示 Realtime tab */ + /* 移动端隐藏 Realtime tab */ + .history-tab[data-range="realtime"] { + display: none !important; + } .history-tab[data-range="day"], .history-tab[data-range="week"], .history-tab[data-range="month"] { @@ -1145,7 +1222,9 @@ return view.extend({ .history-legend { display: flex; align-items: center; + justify-content: center; gap: 12px; + padding-right: 16px; } .legend-item { display: flex; align-items: center; gap: 6px; font-size: 0.875rem; } .legend-dot { width: 10px; height: 10px; border-radius: 50%; display: inline-block; } @@ -1189,7 +1268,9 @@ return view.extend({ .history-legend { margin-left: 0; margin-top: 8px; + width: 100%; justify-content: center; + padding-right: 0; } .history-header { flex-direction: column; @@ -1199,6 +1280,10 @@ return view.extend({ .history-card-body { padding: 12px; } + .device-card { + margin-left: 12px; + margin-right: 12px; + } .history-tooltip { width: calc(100vw - 32px); max-width: 320px; @@ -1234,7 +1319,6 @@ return view.extend({ } .device-card { - background-color: var(--cbi-section-bg, #fff); border: 1px solid rgba(0, 0, 0, 0.1); border-radius: 8px; padding: 12px; @@ -1244,7 +1328,6 @@ return view.extend({ @media (prefers-color-scheme: dark) { .device-card { - background-color: var(--cbi-section-bg, rgba(30, 30, 30, 0.98)); border-color: rgba(255, 255, 255, 0.15); } } @@ -1543,7 +1626,18 @@ return view.extend({ var view = E('div', { 'class': 'bandix-container' }, [ // 头部 E('div', { 'class': 'bandix-header' }, [ - E('h1', { 'class': 'bandix-title' }, _('Bandix Traffic Monitor')) + E('div', { 'class': 'bandix-title-wrapper' }, [ + E('h1', { 'class': 'bandix-title' }, _('Bandix Traffic Monitor')), + E('div', { 'class': 'bandix-version-wrapper' }, [ + E('div', { 'class': 'bandix-version-item' }, [ + E('span', { 'class': 'bandix-version', 'id': 'bandix-luci-version' }, ''), + ]), + E('div', { 'class': 'bandix-version-item' }, [ + E('span', { 'class': 'bandix-version', 'id': 'bandix-core-version' }, ''), + ]), + E('span', { 'class': 'bandix-update-badge', 'id': 'bandix-update-badge', 'style': 'display: none;' }, _('Update available')) + ]) + ]) ]), // 警告提示(包含在线设备数) @@ -1568,9 +1662,9 @@ return view.extend({ // 时间范围 Tab 切换 E('div', { 'class': 'history-tabs' }, [ E('button', { 'class': 'history-tab active', 'data-range': 'realtime', 'id': 'history-tab-realtime' }, _('Realtime')), - E('button', { 'class': 'history-tab', 'data-range': 'day', 'id': 'history-tab-day' }, _('Day')), - E('button', { 'class': 'history-tab', 'data-range': 'week', 'id': 'history-tab-week' }, _('Week')), - E('button', { 'class': 'history-tab', 'data-range': 'month', 'id': 'history-tab-month' }, _('Month')) + E('button', { 'class': 'history-tab', 'data-range': 'day', 'id': 'history-tab-day' }, _('Last 24 Hours')), + E('button', { 'class': 'history-tab', 'data-range': 'week', 'id': 'history-tab-week' }, _('Last 7 Days')), + E('button', { 'class': 'history-tab', 'data-range': 'month', 'id': 'history-tab-month' }, _('Last 30 Days')) ]) ]), E('div', { 'class': 'history-legend' }, [ @@ -2009,13 +2103,6 @@ return view.extend({ // 添加规则模态框取消按钮 document.getElementById('add-rule-cancel').addEventListener('click', hideAddRuleModal); - // 点击添加规则模态框背景关闭 - document.getElementById('add-rule-modal').addEventListener('click', function (e) { - if (e.target === this) { - hideAddRuleModal(); - } - }); - // 保存定时限速规则(从添加规则模态框) document.getElementById('add-rule-save').addEventListener('click', function() { if (!currentDevice) { @@ -2404,13 +2491,6 @@ return view.extend({ // 绑定关闭按钮事件 document.getElementById('modal-close').addEventListener('click', hideRateLimitModal); - // 点击模态框背景关闭 - document.getElementById('rate-limit-modal').addEventListener('click', function (e) { - if (e.target === this) { - hideRateLimitModal(); - } - }); - // 历史趋势:状态与工具 var latestDevices = []; var lastHistoryData = null; // 最近一次拉取的原始 metrics 数据 @@ -3395,6 +3475,30 @@ function formatLastOnlineExactTime(lastOnlineTs) { function formatRetentionSeconds(seconds) { if (!seconds || seconds <= 0) return ''; + + // 固定值映射 + if (seconds === 600) { + return _('Last 10 Minutes'); + } + if (seconds === 900) { + return _('Last 15 Minutes'); + } + if (seconds === 1800) { + return _('Last 30 Minutes'); + } + if (seconds === 3600) { + return _('Last 1 Hour'); + } + if (seconds === 86400) { + return _('Last 24 Hours'); + } + if (seconds === 604800) { + return _('Last 7 Days'); + } + if (seconds === 2592000) { + return _('Last 30 Days'); + } + var value; var unitKey; if (seconds < 60) { @@ -4644,6 +4748,68 @@ function downsampleForMobile(data, labels, upSeries, downSeries, maxPoints) { // 立即执行一次,不等待轮询 updateDeviceData(); fetchAllScheduleRules(); + + // 异步加载版本信息(不阻塞主流程) + (function() { + // 延迟执行,确保页面先完成初始化 + setTimeout(function() { + callGetVersion().then(function(result) { + if (result) { + // 显示 luci-app-bandix 版本 + var luciVersionEl = document.getElementById('bandix-luci-version'); + if (luciVersionEl && result.luci_app_version) { + luciVersionEl.textContent = result.luci_app_version; + } + + // 显示 bandix 版本 + var coreVersionEl = document.getElementById('bandix-core-version'); + if (coreVersionEl && result.bandix_version) { + coreVersionEl.textContent = result.bandix_version; + } + } + }).catch(function(err) { + // 静默失败,不影响页面功能 + console.debug('Failed to load version:', err); + }); + }, 100); + })(); + + // 异步检查更新(不阻塞主流程) + (function() { + // 延迟执行,确保页面先完成初始化,更新检查可能需要网络请求 + setTimeout(function() { + callCheckUpdate().then(function(result) { + if (!result) return; + + // 检查是否有更新(luci-app-bandix 或 bandix) + var hasUpdate = false; + if (result.luci_has_update === true || result.luci_has_update === '1' || result.luci_has_update === 1) { + hasUpdate = true; + } + if (result.bandix_has_update === true || result.bandix_has_update === '1' || result.bandix_has_update === 1) { + hasUpdate = true; + } + + // 显示或隐藏更新提示 + var updateBadge = document.getElementById('bandix-update-badge'); + if (updateBadge) { + if (hasUpdate) { + updateBadge.style.display = 'inline-block'; + // 点击跳转到设置页面 + updateBadge.onclick = function() { + window.location.href = '/cgi-bin/luci/admin/network/bandix/settings'; + }; + updateBadge.title = _('Update available, click to go to settings'); + } else { + updateBadge.style.display = 'none'; + } + } + }).catch(function(err) { + // 静默失败,不影响页面功能 + console.debug('Failed to check update:', err); + }); + }, 500); + })(); // 自动适应主题背景色和文字颜色的函数(仅应用于弹窗和 tooltip) function applyThemeColors() { 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 66042fc..c68c7e3 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 @@ -8,6 +8,146 @@ // 暗色模式检测已改为使用 CSS 媒体查询 @media (prefers-color-scheme: dark) +// 简单的 Markdown 解析函数 +function parseMarkdown(text) { + if (!text) return ''; + + // 转义 HTML 特殊字符 + function escapeHtml(html) { + var div = document.createElement('div'); + div.textContent = html; + return div.innerHTML; + } + + // 先处理代码块(避免其他格式化影响代码) + var codeBlocks = []; + var codeBlockIndex = 0; + text = text.replace(/```([\s\S]*?)```/g, function(match, code) { + var id = 'CODE_BLOCK_' + codeBlockIndex++; + codeBlocks.push({ + id: id, + content: escapeHtml(code.trim()) + }); + return id; + }); + + // 处理行内代码 + text = text.replace(/`([^`]+)`/g, function(match, code) { + return '' + escapeHtml(code) + ''; + }); + + // 处理换行(GitHub markdown 使用 \r\n) + text = text.replace(/\r\n/g, '\n'); + + // 处理标题 + text = text.replace(/^### (.*$)/gm, '

$1

'); + text = text.replace(/^## (.*$)/gm, '

$1

'); + text = text.replace(/^# (.*$)/gm, '

$1

'); + + // 处理粗体 + text = text.replace(/\*\*(.+?)\*\*/g, '$1'); + text = text.replace(/__(.+?)__/g, '$1'); + + // 处理斜体 + text = text.replace(/\*(.+?)\*/g, function(match, content) { + // 避免匹配代码块中的 * + if (match.indexOf('') === -1) { + return '' + content + ''; + } + return match; + }); + text = text.replace(/_(.+?)_/g, function(match, content) { + // 避免匹配代码块中的 _ + if (match.indexOf('') === -1) { + return '' + content + ''; + } + return match; + }); + + // 处理链接 + text = text.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '$1'); + + // 处理图片 + text = text.replace(/!\[([^\]]*)\]\(([^)]+)\)/g, '$1'); + + // 处理无序列表 + var lines = text.split('\n'); + var inList = false; + var result = []; + + for (var i = 0; i < lines.length; i++) { + var line = lines[i]; + var listMatch = line.match(/^[\s]*[-*+]\s+(.+)$/); + + if (listMatch) { + if (!inList) { + result.push('
    '); + inList = true; + } + result.push('
  • ' + listMatch[1] + '
  • '); + } else { + if (inList) { + result.push('
'); + inList = false; + } + result.push(line); + } + } + if (inList) { + result.push(''); + } + text = result.join('\n'); + + // 处理有序列表 + lines = text.split('\n'); + inList = false; + result = []; + + for (i = 0; i < lines.length; i++) { + line = lines[i]; + var orderedMatch = line.match(/^[\s]*\d+\.\s+(.+)$/); + + if (orderedMatch) { + if (!inList) { + result.push('
    '); + inList = true; + } + result.push('
  1. ' + orderedMatch[1] + '
  2. '); + } else { + if (inList) { + result.push('
'); + inList = false; + } + result.push(line); + } + } + if (inList) { + result.push(''); + } + text = result.join('\n'); + + // 处理段落(空行分隔) + text = text.split(/\n\s*\n/).map(function(para) { + para = para.trim(); + if (para && !para.match(/^<[h|u|o|l|i]/)) { + return '

' + para + '

'; + } + return para; + }).join('\n'); + + // 处理单行换行(两个空格或反斜杠结尾) + text = text.replace(/ \n/g, '
\n'); + text = text.replace(/\\\n/g, '
\n'); + + // 恢复代码块 + for (var j = 0; j < codeBlocks.length; j++) { + var block = codeBlocks[j]; + text = text.replace(block.id, '
' + block.content + '
'); + } + + return text; +} + // 声明 RPC 调用方法 var callClearData = rpc.declare({ object: 'luci.bandix', @@ -21,8 +161,27 @@ var callRestartService = rpc.declare({ expect: { } }); +var callGetVersion = rpc.declare({ + object: 'luci.bandix', + method: 'getVersion', + expect: { } +}); + +var callCheckUpdate = rpc.declare({ + object: 'luci.bandix', + method: 'checkUpdate', + expect: { } +}); + +var callInstallUpdate = rpc.declare({ + object: 'luci.bandix', + method: 'installUpdate', + params: ['package_type', 'download_url'] +}); + return view.extend({ load: function () { + var self = this; return Promise.all([ uci.load('bandix'), uci.load('network'), @@ -30,6 +189,18 @@ return view.extend({ uci.load('argon').catch(function () { // argon 配置可能不存在,忽略错误 return null; + }), + callGetVersion().then(function (result) { + // 存储版本信息到 view 实例 + self.versionInfo = result || {}; + return result; + }).catch(function (err) { + // 如果获取失败,使用默认值 + self.versionInfo = { + luci_app_version: _('Unknown'), + bandix_version: _('Unknown') + }; + return null; }) ]); }, @@ -114,20 +285,309 @@ return view.extend({ o.default = 'info'; o.rmempty = false; - // 添加数据目录设置(只读) - o = s.option(form.DummyValue, 'data_dir', _('Data Directory')); - 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', _('Feedback')); - o.href = 'https://github.com/timsaya'; - o.cfgvalue = function () { - return 'https://github.com/timsaya'; + // 添加数据目录设置(只读) + o = s.option(form.DummyValue, 'data_dir', _('Data Directory')); + 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, 'version', _('Version')); + o.cfgvalue = function () { + var versionInfo = this.map.view.versionInfo || {}; + var luciVersion = versionInfo.luci_app_version || _('Unknown'); + var bandixVersion = versionInfo.bandix_version || _('Unknown'); + return 'luci-app-bandix: ' + luciVersion + ' / bandix: ' + bandixVersion; + }; + + // 添加检查更新按钮 + var checkUpdateOption = s.option(form.Button, 'check_update', _('Check for Updates')); + checkUpdateOption.inputtitle = _('Check for Updates'); + checkUpdateOption.inputstyle = 'apply'; + checkUpdateOption.onclick = function () { + var button = this; + var originalText = button.inputtitle; + + // 显示加载状态 + button.inputtitle = _('Checking...'); + button.disabled = true; + + return callCheckUpdate().then(function (result) { + // 恢复按钮状态 + button.inputtitle = originalText; + button.disabled = false; + + if (!result) { + ui.addNotification(null, E('p', _('Failed to check for updates')), 'error'); + return; + } + + var messages = []; + var hasUpdate = false; + + // 显示调试信息(架构、包管理器和资产数量) + if (result.detected_arch) { + var debugInfo = E('div', { + 'style': 'font-size: 0.75rem; color: #6b7280; margin-bottom: 12px; padding: 8px; background: rgba(0,0,0,0.02); border-radius: 4px;' + }, [ + E('div', {}, _('Detected Architecture') + ': ' + result.detected_arch), + E('div', {}, _('Package Manager') + ': ' + (result.detected_pkg_manager || _('Unknown'))), + E('div', {}, _('LuCI Assets') + ': ' + (result.luci_asset_count || 0)), + E('div', {}, _('Bandix Assets') + ': ' + (result.bandix_asset_count || 0)) + ]); + messages.push(debugInfo); + } + + // 检查 luci-app-bandix 更新 + if (result.luci_has_update === true || result.luci_has_update === '1' || result.luci_has_update === 1) { + hasUpdate = true; + var luciContainer = E('div', { 'style': 'margin-bottom: 20px;' }); + + var luciMsg = E('p', { 'style': 'font-weight: 600; margin-bottom: 8px;' }, + _('LuCI App has update: ') + (result.current_luci_version || _('Unknown')) + + ' → ' + (result.latest_luci_version || _('Unknown')) + ); + luciContainer.appendChild(luciMsg); + + // 显示更新日志(解析 Markdown) + if (result.luci_release_body) { + var changelogDiv = E('div', { + 'style': 'background: rgba(0,0,0,0.05); padding: 12px; border-radius: 4px; margin: 8px 0; max-height: 300px; overflow-y: auto; font-size: 0.875rem; line-height: 1.6;' + }); + changelogDiv.innerHTML = parseMarkdown(result.luci_release_body); + luciContainer.appendChild(changelogDiv); + } + + // 检查是否有错误(找不到匹配格式的包) + if (result.luci_error) { + var errorMsg = E('div', { + 'style': 'background: rgba(239, 68, 68, 0.1); color: #dc2626; padding: 10px; border-radius: 4px; margin: 8px 0; font-size: 0.875rem;' + }, result.luci_error); + luciContainer.appendChild(errorMsg); + } + + // 添加下载安装按钮(只有在有下载链接时才显示) + if (result.luci_download_url) { + // 创建状态提示区域 + var statusDiv = E('div', { + 'style': 'margin-top: 8px; padding: 10px; border-radius: 4px; display: none;' + }); + luciContainer.appendChild(statusDiv); + + var installBtn = E('button', { + 'class': 'btn cbi-button-action', + 'style': 'margin-top: 8px;', + 'click': function() { + var installButton = this; + var originalBtnText = installButton.textContent; + + installButton.textContent = _('Installing...'); + installButton.disabled = true; + + // 显示开始安装的提示 + statusDiv.style.display = 'block'; + statusDiv.style.background = 'rgba(59, 130, 246, 0.1)'; + statusDiv.style.color = '#1e40af'; + statusDiv.textContent = _('Starting installation... The page will refresh automatically in 5 seconds.'); + + // 对于 luci-app-bandix 安装,由于会重启 uhttpd/rpcd 导致连接断开, + // 所以不等待响应,直接设置 5 秒后刷新页面 + callInstallUpdate('luci', result.luci_download_url).catch(function(err) { + // 忽略错误,因为连接可能会断开 + // 即使出错也会刷新页面,让用户看到最新状态 + }); + + // 5秒后自动刷新页面 + setTimeout(function() { + window.location.reload(); + }, 5000); + } + }, _('Download and Install')); + luciContainer.appendChild(installBtn); + } + + // 添加手动下载链接 + if (result.luci_update_url) { + var manualLink = E('a', { + 'href': result.luci_update_url, + 'target': '_blank', + 'style': 'display: inline-block; margin-left: 8px; margin-top: 8px;' + }, _('Manual Download')); + luciContainer.appendChild(manualLink); + } + + messages.push(luciContainer); + } else { + messages.push(E('p', {}, _('LuCI App is up to date: ') + (result.current_luci_version || result.latest_luci_version || _('Unknown')))); + } + + // 检查 bandix 更新 + if (result.bandix_has_update === true || result.bandix_has_update === '1' || result.bandix_has_update === 1) { + hasUpdate = true; + var bandixContainer = E('div', { 'style': 'margin-bottom: 20px;' }); + + var bandixMsg = E('p', { 'style': 'font-weight: 600; margin-bottom: 8px;' }, + _('Bandix has update: ') + (result.current_bandix_version || _('Unknown')) + + ' → ' + (result.latest_bandix_version || _('Unknown')) + ); + bandixContainer.appendChild(bandixMsg); + + // 显示更新日志(解析 Markdown) + if (result.bandix_release_body) { + var changelogDiv = E('div', { + 'style': 'background: rgba(0,0,0,0.05); padding: 12px; border-radius: 4px; margin: 8px 0; max-height: 300px; overflow-y: auto; font-size: 0.875rem; line-height: 1.6;' + }); + changelogDiv.innerHTML = parseMarkdown(result.bandix_release_body); + bandixContainer.appendChild(changelogDiv); + } + + // 检查是否有错误(找不到匹配格式的包) + if (result.bandix_error) { + var errorMsg = E('div', { + 'style': 'background: rgba(239, 68, 68, 0.1); color: #dc2626; padding: 10px; border-radius: 4px; margin: 8px 0; font-size: 0.875rem;' + }, result.bandix_error); + bandixContainer.appendChild(errorMsg); + } + + // 添加下载安装按钮(只有在有下载链接时才显示) + if (result.bandix_download_url) { + // 创建状态提示区域 + var statusDiv = E('div', { + 'style': 'margin-top: 8px; padding: 10px; border-radius: 4px; display: none;' + }); + bandixContainer.appendChild(statusDiv); + + var installBtn = E('button', { + 'class': 'btn cbi-button-action', + 'style': 'margin-top: 8px;', + 'click': function() { + var installButton = this; + var originalBtnText = installButton.textContent; + + installButton.textContent = _('Installing...'); + installButton.disabled = true; + + // 显示开始安装的提示 + statusDiv.style.display = 'block'; + statusDiv.style.background = 'rgba(59, 130, 246, 0.1)'; + statusDiv.style.color = '#1e40af'; + statusDiv.textContent = _('Starting installation...'); + + return callInstallUpdate('bandix', result.bandix_download_url).then(function(installResult) { + // 判断成功:success 为 true 或 step 为 completed + var isSuccess = installResult && ( + installResult.success === true || + installResult.success === 1 || + installResult.success === "true" || + installResult.success === "1" || + installResult.step === "completed" + ); + + if (isSuccess) { + installButton.textContent = originalBtnText; + installButton.disabled = false; + // 显示成功提示 + var successMsg = _('Bandix updated successfully!'); + if (installResult.message) { + successMsg = installResult.message; + } + if (installResult.restart_exit_code !== undefined) { + if (installResult.restart_exit_code === 0) { + successMsg += ' ' + _('Service restarted successfully.'); + } else { + successMsg += ' ' + _('Service restart may have failed. Please check manually.'); + } + } else { + successMsg += ' ' + _('Please restart the service.'); + } + statusDiv.style.background = 'rgba(34, 197, 94, 0.1)'; + statusDiv.style.color = '#15803d'; + statusDiv.textContent = successMsg + ' ' + _('Page will refresh in 3 seconds.'); + // 3秒后刷新页面 + setTimeout(function() { + window.location.reload(); + }, 3000); + } else { + installButton.textContent = originalBtnText; + installButton.disabled = false; + var errorMsg = installResult && installResult.error ? installResult.error : _('Installation failed'); + if (installResult && installResult.step) { + errorMsg = _('Installation failed at step: ') + installResult.step + '. ' + errorMsg; + } + if (installResult && installResult.output) { + // 截取输出信息的前500个字符,避免提示过长 + var output = installResult.output; + if (output.length > 500) { + output = output.substring(0, 500) + '...'; + } + errorMsg += '\n' + _('Details: ') + output; + } + statusDiv.style.background = 'rgba(239, 68, 68, 0.1)'; + statusDiv.style.color = '#dc2626'; + statusDiv.innerHTML = errorMsg.replace(/\n/g, '
'); + } + }).catch(function(err) { + installButton.textContent = originalBtnText; + installButton.disabled = false; + var errorMsg = _('Installation failed: ') + (err.message || err || _('Unknown error')); + statusDiv.style.background = 'rgba(239, 68, 68, 0.1)'; + statusDiv.style.color = '#dc2626'; + statusDiv.textContent = errorMsg; + }); + } + }, _('Download and Install')); + bandixContainer.appendChild(installBtn); + } + + // 添加手动下载链接 + if (result.bandix_update_url) { + var manualLink = E('a', { + 'href': result.bandix_update_url, + 'target': '_blank', + 'style': 'display: inline-block; margin-left: 8px; margin-top: 8px;' + }, _('Manual Download')); + bandixContainer.appendChild(manualLink); + } + + messages.push(bandixContainer); + } else { + messages.push(E('p', {}, _('Bandix is up to date: ') + (result.current_bandix_version || result.latest_bandix_version || _('Unknown')))); + } + + // 显示结果(添加关闭按钮) + var title = hasUpdate ? _('Updates Available') : _('No Updates Available'); + + // 添加红色提示文字(如果有更新) + if (hasUpdate) { + var warningMsg = E('div', { + 'style': 'background: rgba(239, 68, 68, 0.1); color: #dc2626; padding: 12px; border-radius: 4px; margin-bottom: 16px; font-weight: 600;' + }, _('Please clear your browser cache manually after updating.')); + messages.unshift(warningMsg); + } + + // 创建弹窗内容,包含关闭按钮 + var modalContent = E('div', {}, messages); + + // 添加关闭按钮 + var closeBtn = E('button', { + 'class': 'btn', + 'style': 'margin-top: 16px;', + 'click': ui.hideModal + }, _('Close')); + + modalContent.appendChild(closeBtn); + + ui.showModal(title, modalContent); + }).catch(function (err) { + // 恢复按钮状态 + button.inputtitle = originalText; + button.disabled = false; + ui.addNotification(null, E('p', _('Failed to check for updates: ') + (err.message || err)), 'error'); + }); + }; + + // 添加清空数据按钮 o = s.option(form.Button, 'clear_data', _('Clear Traffic Data')); o.inputtitle = _('Clear Traffic Data'); @@ -192,6 +652,14 @@ return view.extend({ ]); }; + + // 添加意见反馈信息 + o = s.option(form.DummyValue, 'feedback_info', _('Feedback')); + o.href = 'https://github.com/timsaya'; + o.cfgvalue = function () { + return 'https://github.com/timsaya'; + }; + // 2. 流量监控设置部分 (traffic) s = m.section(form.NamedSection, 'traffic', 'traffic', _('Traffic Monitor Settings')); s.description = _('Configure traffic monitoring related parameters'); @@ -242,7 +710,7 @@ return view.extend({ o.depends('traffic_persist_history', '1'); // 添加历史流量周期(秒) - o = s.option(form.ListValue, 'traffic_retention_seconds', _('Traffic History Period'), + o = s.option(form.ListValue, 'traffic_retention_seconds', _('Realtime Traffic Period'), _('10 minutes interval uses about 60 KB per device')); o.value('600', _('10 minutes')); o.value('900', _('15 minutes')); @@ -279,6 +747,9 @@ return view.extend({ o.default = '10000'; o.rmempty = false; + // 将 view 实例关联到 form map,以便在 cfgvalue 中访问 + m.view = this; + return m.render(); } }); \ No newline at end of file diff --git a/luci-app-bandix/po/es/bandix.po b/luci-app-bandix/po/es/bandix.po index ff28dee..6971bbe 100644 --- a/luci-app-bandix/po/es/bandix.po +++ b/luci-app-bandix/po/es/bandix.po @@ -671,8 +671,8 @@ msgstr "Tiempo de espera sin conexión" msgid "Set the timeout for device offline detection (seconds). Devices inactive for longer than this time will be marked as offline" msgstr "Establecer el tiempo de espera para la detección de dispositivos sin conexión (segundos). Los dispositivos inactivos por más tiempo serán marcados como sin conexión" -msgid "Traffic History Period" -msgstr "Período del historial de tráfico" +msgid "Realtime Traffic Period" +msgstr "Período de tráfico en tiempo real" msgid "10 minutes interval uses about 60 KB per device" msgstr "Intervalo de 10 minutos usa aproximadamente 60 KB por dispositivo" @@ -904,3 +904,120 @@ msgstr "¿Está seguro de que desea reiniciar el servicio Bandix?" msgid "Failed to restart service: " msgstr "Error al reiniciar el servicio: " + +msgid "Realtime" +msgstr "Tiempo real" + +msgid "Last 24 Hours" +msgstr "Últimas 24 horas" + +msgid "Last 7 Days" +msgstr "Últimos 7 días" + +msgid "Last 30 Days" +msgstr "Últimos 30 días" + +msgid "Last 10 Minutes" +msgstr "Últimos 10 minutos" + +msgid "Last 15 Minutes" +msgstr "Últimos 15 minutos" + +msgid "Last 30 Minutes" +msgstr "Últimos 30 minutos" + +msgid "Last 1 Hour" +msgstr "Última 1 hora" + +msgid "Starting installation..." +msgstr "Iniciando instalación..." + +msgid "Page will refresh in 3 seconds." +msgstr "La página se actualizará en 3 segundos." + +msgid "Installation failed" +msgstr "Instalación fallida" + +msgid "Installation failed at step: " +msgstr "Instalación fallida en el paso: " + +msgid "Details: " +msgstr "Detalles: " + +msgid "Installation failed: " +msgstr "Instalación fallida: " + +msgid "Unknown error" +msgstr "Error desconocido" + +msgid "Service restarted successfully." +msgstr "Servicio reiniciado exitosamente." + +msgid "Service restart may have failed. Please check manually." +msgstr "El reinicio del servicio puede haber fallado. Por favor, verifique manualmente." + +msgid "Please restart the service." +msgstr "Por favor, reinicie el servicio." + +msgid "LuCI App updated successfully!" +msgstr "¡Aplicación LuCI actualizada exitosamente!" + +msgid "Bandix updated successfully!" +msgstr "¡Bandix actualizado exitosamente!" + +msgid "LuCI App has update: " +msgstr "La aplicación LuCI tiene actualización: " + +msgid "Bandix has update: " +msgstr "Bandix tiene actualización: " + +msgid "LuCI App is up to date: " +msgstr "La aplicación LuCI está actualizada: " + +msgid "Bandix is up to date: " +msgstr "Bandix está actualizado: " + +msgid "Updates Available" +msgstr "Actualizaciones Disponibles" + +msgid "No Updates Available" +msgstr "No Hay Actualizaciones Disponibles" + +msgid "Detected Architecture" +msgstr "Arquitectura Detectada" + +msgid "Package Manager" +msgstr "Gestor de Paquetes" + +msgid "LuCI Assets" +msgstr "Recursos de LuCI" + +msgid "Bandix Assets" +msgstr "Recursos de Bandix" + +msgid "Failed to check for updates" +msgstr "Error al verificar actualizaciones" + +msgid "Failed to check for updates: " +msgstr "Error al verificar actualizaciones: " + +msgid "Checking..." +msgstr "Verificando..." + +msgid "Installing..." +msgstr "Instalando..." + +msgid "Download and Install" +msgstr "Descargar e Instalar" + +msgid "Manual Download" +msgstr "Descarga Manual" + +msgid "Close" +msgstr "Cerrar" + +msgid "Starting installation... The page will refresh automatically in 5 seconds." +msgstr "Iniciando instalación... La página se actualizará automáticamente en 5 segundos." + +msgid "Please clear your browser cache manually after updating." +msgstr "Por favor, borre manualmente la caché del navegador después de actualizar." diff --git a/luci-app-bandix/po/fr/bandix.po b/luci-app-bandix/po/fr/bandix.po index 796bbc6..18862fe 100644 --- a/luci-app-bandix/po/fr/bandix.po +++ b/luci-app-bandix/po/fr/bandix.po @@ -671,8 +671,8 @@ msgstr "Délai d'expiration hors ligne" msgid "Set the timeout for device offline detection (seconds). Devices inactive for longer than this time will be marked as offline" msgstr "设置设备离线判断的超时时间(秒)。超过此时间未活动的设备将被标记为离线" -msgid "Traffic History Period" -msgstr "Période d'historique du trafic" +msgid "Realtime Traffic Period" +msgstr "Période de trafic en temps réel" msgid "10 minutes interval uses about 60 KB per device" msgstr "Intervalle de 10 minutes utilise environ 60 Ko par appareil" @@ -903,4 +903,121 @@ msgid "Are you sure you want to restart the Bandix service?" msgstr "Êtes-vous sûr de vouloir redémarrer le service Bandix ?" msgid "Failed to restart service: " -msgstr "Échec du redémarrage du service : " \ No newline at end of file +msgstr "Échec du redémarrage du service : " + +msgid "Realtime" +msgstr "Temps réel" + +msgid "Last 24 Hours" +msgstr "Dernières 24 heures" + +msgid "Last 7 Days" +msgstr "7 derniers jours" + +msgid "Last 30 Days" +msgstr "30 derniers jours" + +msgid "Last 10 Minutes" +msgstr "10 dernières minutes" + +msgid "Last 15 Minutes" +msgstr "15 dernières minutes" + +msgid "Last 30 Minutes" +msgstr "30 dernières minutes" + +msgid "Last 1 Hour" +msgstr "Dernière heure" + +msgid "Starting installation..." +msgstr "Démarrage de l'installation..." + +msgid "Page will refresh in 3 seconds." +msgstr "La page sera actualisée dans 3 secondes." + +msgid "Installation failed" +msgstr "Échec de l'installation" + +msgid "Installation failed at step: " +msgstr "Échec de l'installation à l'étape : " + +msgid "Details: " +msgstr "Détails : " + +msgid "Installation failed: " +msgstr "Échec de l'installation : " + +msgid "Unknown error" +msgstr "Erreur inconnue" + +msgid "Service restarted successfully." +msgstr "Service redémarré avec succès." + +msgid "Service restart may have failed. Please check manually." +msgstr "Le redémarrage du service a peut-être échoué. Veuillez vérifier manuellement." + +msgid "Please restart the service." +msgstr "Veuillez redémarrer le service." + +msgid "LuCI App updated successfully!" +msgstr "Application LuCI mise à jour avec succès !" + +msgid "Bandix updated successfully!" +msgstr "Bandix mis à jour avec succès !" + +msgid "LuCI App has update: " +msgstr "L'application LuCI a une mise à jour : " + +msgid "Bandix has update: " +msgstr "Bandix a une mise à jour : " + +msgid "LuCI App is up to date: " +msgstr "L'application LuCI est à jour : " + +msgid "Bandix is up to date: " +msgstr "Bandix est à jour : " + +msgid "Updates Available" +msgstr "Mises à jour Disponibles" + +msgid "No Updates Available" +msgstr "Aucune Mise à jour Disponible" + +msgid "Detected Architecture" +msgstr "Architecture Détectée" + +msgid "Package Manager" +msgstr "Gestionnaire de Paquets" + +msgid "LuCI Assets" +msgstr "Ressources LuCI" + +msgid "Bandix Assets" +msgstr "Ressources Bandix" + +msgid "Failed to check for updates" +msgstr "Échec de la vérification des mises à jour" + +msgid "Failed to check for updates: " +msgstr "Échec de la vérification des mises à jour : " + +msgid "Checking..." +msgstr "Vérification..." + +msgid "Installing..." +msgstr "Installation..." + +msgid "Download and Install" +msgstr "Télécharger et Installer" + +msgid "Manual Download" +msgstr "Téléchargement Manuel" + +msgid "Close" +msgstr "Fermer" + +msgid "Starting installation... The page will refresh automatically in 5 seconds." +msgstr "Démarrage de l'installation... La page sera actualisée automatiquement dans 5 secondes." + +msgid "Please clear your browser cache manually after updating." +msgstr "Veuillez vider manuellement le cache du navigateur après la mise à jour." \ No newline at end of file diff --git a/luci-app-bandix/po/id/bandix.po b/luci-app-bandix/po/id/bandix.po index 293f29d..885a019 100644 --- a/luci-app-bandix/po/id/bandix.po +++ b/luci-app-bandix/po/id/bandix.po @@ -671,8 +671,8 @@ msgstr "Timeout offline" msgid "Set the timeout for device offline detection (seconds). Devices inactive for longer than this time will be marked as offline" msgstr "Atur timeout untuk deteksi perangkat offline (detik). Perangkat yang tidak aktif lebih lama dari waktu ini akan ditandai sebagai offline" -msgid "Traffic History Period" -msgstr "Periode riwayat lalu lintas" +msgid "Realtime Traffic Period" +msgstr "Periode lalu lintas waktu nyata" msgid "10 minutes interval uses about 60 KB per device" msgstr "Interval 10 menit menggunakan sekitar 60 KB per perangkat" @@ -904,3 +904,120 @@ msgstr "Apakah Anda yakin ingin memulai ulang layanan Bandix?" msgid "Failed to restart service: " msgstr "Gagal memulai ulang layanan: " + +msgid "Realtime" +msgstr "Waktu Nyata" + +msgid "Last 24 Hours" +msgstr "24 Jam Terakhir" + +msgid "Last 7 Days" +msgstr "7 Hari Terakhir" + +msgid "Last 30 Days" +msgstr "30 Hari Terakhir" + +msgid "Last 10 Minutes" +msgstr "10 Menit Terakhir" + +msgid "Last 15 Minutes" +msgstr "15 Menit Terakhir" + +msgid "Last 30 Minutes" +msgstr "30 Menit Terakhir" + +msgid "Last 1 Hour" +msgstr "1 Jam Terakhir" + +msgid "Starting installation..." +msgstr "Memulai instalasi..." + +msgid "Page will refresh in 3 seconds." +msgstr "Halaman akan dimuat ulang dalam 3 detik." + +msgid "Installation failed" +msgstr "Instalasi gagal" + +msgid "Installation failed at step: " +msgstr "Instalasi gagal pada langkah: " + +msgid "Details: " +msgstr "Detail: " + +msgid "Installation failed: " +msgstr "Instalasi gagal: " + +msgid "Unknown error" +msgstr "Kesalahan tidak diketahui" + +msgid "Service restarted successfully." +msgstr "Layanan berhasil dimulai ulang." + +msgid "Service restart may have failed. Please check manually." +msgstr "Mulai ulang layanan mungkin gagal. Silakan periksa secara manual." + +msgid "Please restart the service." +msgstr "Silakan mulai ulang layanan." + +msgid "LuCI App updated successfully!" +msgstr "Aplikasi LuCI berhasil diperbarui!" + +msgid "Bandix updated successfully!" +msgstr "Bandix berhasil diperbarui!" + +msgid "LuCI App has update: " +msgstr "Aplikasi LuCI memiliki pembaruan: " + +msgid "Bandix has update: " +msgstr "Bandix memiliki pembaruan: " + +msgid "LuCI App is up to date: " +msgstr "Aplikasi LuCI sudah terbaru: " + +msgid "Bandix is up to date: " +msgstr "Bandix sudah terbaru: " + +msgid "Updates Available" +msgstr "Pembaruan Tersedia" + +msgid "No Updates Available" +msgstr "Tidak Ada Pembaruan Tersedia" + +msgid "Detected Architecture" +msgstr "Arsitektur yang Terdeteksi" + +msgid "Package Manager" +msgstr "Manajer Paket" + +msgid "LuCI Assets" +msgstr "Aset LuCI" + +msgid "Bandix Assets" +msgstr "Aset Bandix" + +msgid "Failed to check for updates" +msgstr "Gagal memeriksa pembaruan" + +msgid "Failed to check for updates: " +msgstr "Gagal memeriksa pembaruan: " + +msgid "Checking..." +msgstr "Memeriksa..." + +msgid "Installing..." +msgstr "Menginstal..." + +msgid "Download and Install" +msgstr "Unduh dan Instal" + +msgid "Manual Download" +msgstr "Unduh Manual" + +msgid "Close" +msgstr "Tutup" + +msgid "Starting installation... The page will refresh automatically in 5 seconds." +msgstr "Memulai instalasi... Halaman akan dimuat ulang secara otomatis dalam 5 detik." + +msgid "Please clear your browser cache manually after updating." +msgstr "Harap hapus cache browser secara manual setelah memperbarui." diff --git a/luci-app-bandix/po/ja/bandix.po b/luci-app-bandix/po/ja/bandix.po index 8b5abc4..7979a0e 100644 --- a/luci-app-bandix/po/ja/bandix.po +++ b/luci-app-bandix/po/ja/bandix.po @@ -671,8 +671,8 @@ msgstr "オフラインタイムアウト" msgid "Set the timeout for device offline detection (seconds). Devices inactive for longer than this time will be marked as offline" msgstr "デバイスのオフライン検出のタイムアウトを設定(秒)。この時間より長く非アクティブなデバイスはオフラインとしてマークされます" -msgid "Traffic History Period" -msgstr "トラフィック履歴期間" +msgid "Realtime Traffic Period" +msgstr "リアルタイムトラフィック周期" msgid "10 minutes interval uses about 60 KB per device" msgstr "10分間隔でデバイスあたり約60KBを使用" @@ -904,3 +904,120 @@ msgstr "Bandix サービスを再起動してもよろしいですか?" msgid "Failed to restart service: " msgstr "サービスの再起動に失敗しました:" + +msgid "Realtime" +msgstr "リアルタイム" + +msgid "Last 24 Hours" +msgstr "過去24時間" + +msgid "Last 7 Days" +msgstr "過去7日間" + +msgid "Last 30 Days" +msgstr "過去30日間" + +msgid "Last 10 Minutes" +msgstr "過去10分" + +msgid "Last 15 Minutes" +msgstr "過去15分" + +msgid "Last 30 Minutes" +msgstr "過去30分" + +msgid "Last 1 Hour" +msgstr "過去1時間" + +msgid "Starting installation..." +msgstr "インストールを開始しています..." + +msgid "Page will refresh in 3 seconds." +msgstr "3秒後にページが更新されます。" + +msgid "Installation failed" +msgstr "インストール失敗" + +msgid "Installation failed at step: " +msgstr "インストールがステップで失敗しました:" + +msgid "Details: " +msgstr "詳細:" + +msgid "Installation failed: " +msgstr "インストール失敗:" + +msgid "Unknown error" +msgstr "不明なエラー" + +msgid "Service restarted successfully." +msgstr "サービスが正常に再起動されました。" + +msgid "Service restart may have failed. Please check manually." +msgstr "サービスの再起動が失敗した可能性があります。手動で確認してください。" + +msgid "Please restart the service." +msgstr "サービスを再起動してください。" + +msgid "LuCI App updated successfully!" +msgstr "LuCIアプリが正常に更新されました!" + +msgid "Bandix updated successfully!" +msgstr "Bandixが正常に更新されました!" + +msgid "LuCI App has update: " +msgstr "LuCIアプリに更新があります:" + +msgid "Bandix has update: " +msgstr "Bandixに更新があります:" + +msgid "LuCI App is up to date: " +msgstr "LuCIアプリは最新です:" + +msgid "Bandix is up to date: " +msgstr "Bandixは最新です:" + +msgid "Updates Available" +msgstr "利用可能な更新" + +msgid "No Updates Available" +msgstr "利用可能な更新なし" + +msgid "Detected Architecture" +msgstr "検出されたアーキテクチャ" + +msgid "Package Manager" +msgstr "パッケージマネージャー" + +msgid "LuCI Assets" +msgstr "LuCIアセット" + +msgid "Bandix Assets" +msgstr "Bandixアセット" + +msgid "Failed to check for updates" +msgstr "更新の確認に失敗しました" + +msgid "Failed to check for updates: " +msgstr "更新の確認に失敗しました:" + +msgid "Checking..." +msgstr "確認中..." + +msgid "Installing..." +msgstr "インストール中..." + +msgid "Download and Install" +msgstr "ダウンロードしてインストール" + +msgid "Manual Download" +msgstr "手動ダウンロード" + +msgid "Close" +msgstr "閉じる" + +msgid "Starting installation... The page will refresh automatically in 5 seconds." +msgstr "インストールを開始しています... ページは5秒後に自動的に更新されます。" + +msgid "Please clear your browser cache manually after updating." +msgstr "更新後、ブラウザのキャッシュを手動でクリアしてください。" diff --git a/luci-app-bandix/po/pl/bandix.po b/luci-app-bandix/po/pl/bandix.po index 491fd3c..6c5c771 100644 --- a/luci-app-bandix/po/pl/bandix.po +++ b/luci-app-bandix/po/pl/bandix.po @@ -671,8 +671,8 @@ msgstr "Limit czasu offline" msgid "Set the timeout for device offline detection (seconds). Devices inactive for longer than this time will be marked as offline" msgstr "Ustaw limit czasu wykrywania urządzeń offline (sekundy). Urządzenia nieaktywne dłużej niż ten czas zostaną oznaczone jako offline" -msgid "Traffic History Period" -msgstr "Okres historii ruchu" +msgid "Realtime Traffic Period" +msgstr "Okres ruchu w czasie rzeczywistym" msgid "10 minutes interval uses about 60 KB per device" msgstr "Interwał 10 minut używa około 60 KB na urządzenie" @@ -905,3 +905,120 @@ msgstr "Czy na pewno chcesz uruchomić ponownie usługę Bandix?" msgid "Failed to restart service: " msgstr "Nie udało się uruchomić ponownie usługi: " +msgid "Realtime" +msgstr "Czas rzeczywisty" + +msgid "Last 24 Hours" +msgstr "Ostatnie 24 godziny" + +msgid "Last 7 Days" +msgstr "Ostatnie 7 dni" + +msgid "Last 30 Days" +msgstr "Ostatnie 30 dni" + +msgid "Last 10 Minutes" +msgstr "Ostatnie 10 minut" + +msgid "Last 15 Minutes" +msgstr "Ostatnie 15 minut" + +msgid "Last 30 Minutes" +msgstr "Ostatnie 30 minut" + +msgid "Last 1 Hour" +msgstr "Ostatnia 1 godzina" + +msgid "Starting installation..." +msgstr "Rozpoczynanie instalacji..." + +msgid "Page will refresh in 3 seconds." +msgstr "Strona odświeży się za 3 sekundy." + +msgid "Installation failed" +msgstr "Instalacja nie powiodła się" + +msgid "Installation failed at step: " +msgstr "Instalacja nie powiodła się w kroku: " + +msgid "Details: " +msgstr "Szczegóły: " + +msgid "Installation failed: " +msgstr "Instalacja nie powiodła się: " + +msgid "Unknown error" +msgstr "Nieznany błąd" + +msgid "Service restarted successfully." +msgstr "Usługa została pomyślnie uruchomiona ponownie." + +msgid "Service restart may have failed. Please check manually." +msgstr "Ponowne uruchomienie usługi mogło się nie powieść. Sprawdź ręcznie." + +msgid "Please restart the service." +msgstr "Proszę ponownie uruchomić usługę." + +msgid "LuCI App updated successfully!" +msgstr "Aplikacja LuCI została pomyślnie zaktualizowana!" + +msgid "Bandix updated successfully!" +msgstr "Bandix został pomyślnie zaktualizowany!" + +msgid "LuCI App has update: " +msgstr "Aplikacja LuCI ma aktualizację: " + +msgid "Bandix has update: " +msgstr "Bandix ma aktualizację: " + +msgid "LuCI App is up to date: " +msgstr "Aplikacja LuCI jest aktualna: " + +msgid "Bandix is up to date: " +msgstr "Bandix jest aktualny: " + +msgid "Updates Available" +msgstr "Dostępne Aktualizacje" + +msgid "No Updates Available" +msgstr "Brak Dostępnych Aktualizacji" + +msgid "Detected Architecture" +msgstr "Wykryta Architektura" + +msgid "Package Manager" +msgstr "Menedżer Pakietów" + +msgid "LuCI Assets" +msgstr "Zasoby LuCI" + +msgid "Bandix Assets" +msgstr "Zasoby Bandix" + +msgid "Failed to check for updates" +msgstr "Nie udało się sprawdzić aktualizacji" + +msgid "Failed to check for updates: " +msgstr "Nie udało się sprawdzić aktualizacji: " + +msgid "Checking..." +msgstr "Sprawdzanie..." + +msgid "Installing..." +msgstr "Instalowanie..." + +msgid "Download and Install" +msgstr "Pobierz i Zainstaluj" + +msgid "Manual Download" +msgstr "Pobieranie Ręczne" + +msgid "Close" +msgstr "Zamknij" + +msgid "Starting installation... The page will refresh automatically in 5 seconds." +msgstr "Rozpoczynanie instalacji... Strona odświeży się automatycznie za 5 sekund." + +msgid "Please clear your browser cache manually after updating." +msgstr "Po aktualizacji należy ręcznie wyczyścić pamięć podręczną przeglądarki." + diff --git a/luci-app-bandix/po/ru/bandix.po b/luci-app-bandix/po/ru/bandix.po index 697a002..8cbe7c3 100644 --- a/luci-app-bandix/po/ru/bandix.po +++ b/luci-app-bandix/po/ru/bandix.po @@ -671,8 +671,8 @@ msgstr "Таймаут офлайн" msgid "Set the timeout for device offline detection (seconds). Devices inactive for longer than this time will be marked as offline" msgstr "设置设备离线判断的超时时间(秒)。超过此时间未活动的设备将被标记为离线" -msgid "Traffic History Period" -msgstr "Период истории трафика" +msgid "Realtime Traffic Period" +msgstr "Период трафика в реальном времени" msgid "10 minutes interval uses about 60 KB per device" msgstr "Интервал 10 минут использует около 60 КБ на устройство" @@ -903,4 +903,121 @@ msgid "Are you sure you want to restart the Bandix service?" msgstr "Вы уверены, что хотите перезапустить службу Bandix?" msgid "Failed to restart service: " -msgstr "Не удалось перезапустить службу: " \ No newline at end of file +msgstr "Не удалось перезапустить службу: " + +msgid "Realtime" +msgstr "В реальном времени" + +msgid "Last 24 Hours" +msgstr "Последние 24 часа" + +msgid "Last 7 Days" +msgstr "Последние 7 дней" + +msgid "Last 30 Days" +msgstr "Последние 30 дней" + +msgid "Last 10 Minutes" +msgstr "Последние 10 минут" + +msgid "Last 15 Minutes" +msgstr "Последние 15 минут" + +msgid "Last 30 Minutes" +msgstr "Последние 30 минут" + +msgid "Last 1 Hour" +msgstr "Последний 1 час" + +msgid "Starting installation..." +msgstr "Начало установки..." + +msgid "Page will refresh in 3 seconds." +msgstr "Страница обновится через 3 секунды." + +msgid "Installation failed" +msgstr "Установка не удалась" + +msgid "Installation failed at step: " +msgstr "Установка не удалась на шаге: " + +msgid "Details: " +msgstr "Подробности: " + +msgid "Installation failed: " +msgstr "Установка не удалась: " + +msgid "Unknown error" +msgstr "Неизвестная ошибка" + +msgid "Service restarted successfully." +msgstr "Служба успешно перезапущена." + +msgid "Service restart may have failed. Please check manually." +msgstr "Перезапуск службы мог не удаться. Пожалуйста, проверьте вручную." + +msgid "Please restart the service." +msgstr "Пожалуйста, перезапустите службу." + +msgid "LuCI App updated successfully!" +msgstr "Приложение LuCI успешно обновлено!" + +msgid "Bandix updated successfully!" +msgstr "Bandix успешно обновлен!" + +msgid "LuCI App has update: " +msgstr "Приложение LuCI имеет обновление: " + +msgid "Bandix has update: " +msgstr "Bandix имеет обновление: " + +msgid "LuCI App is up to date: " +msgstr "Приложение LuCI актуально: " + +msgid "Bandix is up to date: " +msgstr "Bandix актуален: " + +msgid "Updates Available" +msgstr "Доступны Обновления" + +msgid "No Updates Available" +msgstr "Нет Доступных Обновлений" + +msgid "Detected Architecture" +msgstr "Обнаруженная Архитектура" + +msgid "Package Manager" +msgstr "Менеджер Пакетов" + +msgid "LuCI Assets" +msgstr "Ресурсы LuCI" + +msgid "Bandix Assets" +msgstr "Ресурсы Bandix" + +msgid "Failed to check for updates" +msgstr "Не удалось проверить обновления" + +msgid "Failed to check for updates: " +msgstr "Не удалось проверить обновления: " + +msgid "Checking..." +msgstr "Проверка..." + +msgid "Installing..." +msgstr "Установка..." + +msgid "Download and Install" +msgstr "Скачать и Установить" + +msgid "Manual Download" +msgstr "Ручная Загрузка" + +msgid "Close" +msgstr "Закрыть" + +msgid "Starting installation... The page will refresh automatically in 5 seconds." +msgstr "Начало установки... Страница автоматически обновится через 5 секунд." + +msgid "Please clear your browser cache manually after updating." +msgstr "Пожалуйста, вручную очистите кеш браузера после обновления." \ No newline at end of file diff --git a/luci-app-bandix/po/th/bandix.po b/luci-app-bandix/po/th/bandix.po index 68af5d0..8cd145f 100644 --- a/luci-app-bandix/po/th/bandix.po +++ b/luci-app-bandix/po/th/bandix.po @@ -671,8 +671,8 @@ msgstr "หมดเวลาออฟไลน์" msgid "Set the timeout for device offline detection (seconds). Devices inactive for longer than this time will be marked as offline" msgstr "ตั้งค่าเวลาหมดเวลาสำหรับการตรวจจับอุปกรณ์ออฟไลน์ (วินาที) อุปกรณ์ที่ไม่ใช้งานนานกว่านี้จะถูกทำเครื่องหมายว่าออฟไลน์" -msgid "Traffic History Period" -msgstr "ระยะเวลาประวัติการจราจร" +msgid "Realtime Traffic Period" +msgstr "ระยะเวลาทราฟฟิกเรียลไทม์" msgid "10 minutes interval uses about 60 KB per device" msgstr "ช่วงเวลา 10 นาทีใช้ประมาณ 60 KB ต่ออุปกรณ์" @@ -904,3 +904,120 @@ msgstr "คุณแน่ใจหรือไม่ว่าต้องกา msgid "Failed to restart service: " msgstr "ล้มเหลวในการรีสตาร์ทบริการ: " + +msgid "Realtime" +msgstr "เรียลไทม์" + +msgid "Last 24 Hours" +msgstr "24 ชั่วโมงล่าสุด" + +msgid "Last 7 Days" +msgstr "7 วันที่ผ่านมา" + +msgid "Last 30 Days" +msgstr "30 วันที่ผ่านมา" + +msgid "Last 10 Minutes" +msgstr "10 นาทีที่ผ่านมา" + +msgid "Last 15 Minutes" +msgstr "15 นาทีที่ผ่านมา" + +msgid "Last 30 Minutes" +msgstr "30 นาทีที่ผ่านมา" + +msgid "Last 1 Hour" +msgstr "1 ชั่วโมงที่ผ่านมา" + +msgid "Starting installation..." +msgstr "กำลังเริ่มการติดตั้ง..." + +msgid "Page will refresh in 3 seconds." +msgstr "หน้าจะรีเฟรชใน 3 วินาที" + +msgid "Installation failed" +msgstr "การติดตั้งล้มเหลว" + +msgid "Installation failed at step: " +msgstr "การติดตั้งล้มเหลวในขั้นตอน: " + +msgid "Details: " +msgstr "รายละเอียด: " + +msgid "Installation failed: " +msgstr "การติดตั้งล้มเหลว: " + +msgid "Unknown error" +msgstr "ข้อผิดพลาดที่ไม่ทราบสาเหตุ" + +msgid "Service restarted successfully." +msgstr "รีสตาร์ทบริการสำเร็จแล้ว" + +msgid "Service restart may have failed. Please check manually." +msgstr "การรีสตาร์ทบริการอาจล้มเหลว กรุณาตรวจสอบด้วยตนเอง" + +msgid "Please restart the service." +msgstr "กรุณารีสตาร์ทบริการ" + +msgid "LuCI App updated successfully!" +msgstr "อัปเดตแอป LuCI สำเร็จแล้ว!" + +msgid "Bandix updated successfully!" +msgstr "อัปเดต Bandix สำเร็จแล้ว!" + +msgid "LuCI App has update: " +msgstr "แอป LuCI มีการอัปเดต: " + +msgid "Bandix has update: " +msgstr "Bandix มีการอัปเดต: " + +msgid "LuCI App is up to date: " +msgstr "แอป LuCI เป็นเวอร์ชันล่าสุด: " + +msgid "Bandix is up to date: " +msgstr "Bandix เป็นเวอร์ชันล่าสุด: " + +msgid "Updates Available" +msgstr "มีการอัปเดต" + +msgid "No Updates Available" +msgstr "ไม่มีอัปเดต" + +msgid "Detected Architecture" +msgstr "สถาปัตยกรรมที่ตรวจพบ" + +msgid "Package Manager" +msgstr "ตัวจัดการแพ็คเกจ" + +msgid "LuCI Assets" +msgstr "ทรัพยากร LuCI" + +msgid "Bandix Assets" +msgstr "ทรัพยากร Bandix" + +msgid "Failed to check for updates" +msgstr "ตรวจสอบอัปเดตล้มเหลว" + +msgid "Failed to check for updates: " +msgstr "ตรวจสอบอัปเดตล้มเหลว: " + +msgid "Checking..." +msgstr "กำลังตรวจสอบ..." + +msgid "Installing..." +msgstr "กำลังติดตั้ง..." + +msgid "Download and Install" +msgstr "ดาวน์โหลดและติดตั้ง" + +msgid "Manual Download" +msgstr "ดาวน์โหลดด้วยตนเอง" + +msgid "Close" +msgstr "ปิด" + +msgid "Starting installation... The page will refresh automatically in 5 seconds." +msgstr "กำลังเริ่มการติดตั้ง... หน้าจะรีเฟรชอัตโนมัติใน 5 วินาที" + +msgid "Please clear your browser cache manually after updating." +msgstr "กรุณาล้างแคชเบราว์เซอร์ด้วยตนเองหลังจากอัปเดต" diff --git a/luci-app-bandix/po/zh_Hans/bandix.po b/luci-app-bandix/po/zh_Hans/bandix.po index 369a9d9..4b43ce3 100644 --- a/luci-app-bandix/po/zh_Hans/bandix.po +++ b/luci-app-bandix/po/zh_Hans/bandix.po @@ -671,8 +671,8 @@ msgstr "离线超时时间" msgid "Set the timeout for device offline detection (seconds). Devices inactive for longer than this time will be marked as offline" msgstr "设置设备离线判断的超时时间(秒)。超过此时间未活动的设备将被标记为离线" -msgid "Traffic History Period" -msgstr "历史流量周期" +msgid "Realtime Traffic Period" +msgstr "实时流量周期" msgid "10 minutes interval uses about 60 KB per device" msgstr "10分钟历史周期,每个设备占用 60KB" @@ -903,4 +903,127 @@ msgid "Are you sure you want to restart the Bandix service?" msgstr "确定要重启 Bandix 服务吗?" msgid "Failed to restart service: " -msgstr "重启服务失败:" \ No newline at end of file +msgstr "重启服务失败:" + +msgid "Realtime" +msgstr "实时" + +msgid "Last 24 Hours" +msgstr "最近24小时" + +msgid "Last 7 Days" +msgstr "最近7天" + +msgid "Last 30 Days" +msgstr "最近30天" + +msgid "Last 10 Minutes" +msgstr "最近10分钟" + +msgid "Last 15 Minutes" +msgstr "最近15分钟" + +msgid "Last 30 Minutes" +msgstr "最近30分钟" + +msgid "Last 1 Hour" +msgstr "最近1小时" + +msgid "Starting installation..." +msgstr "开始安装..." + +msgid "Page will refresh in 3 seconds." +msgstr "页面将在3秒后刷新。" + +msgid "Installation failed" +msgstr "安装失败" + +msgid "Installation failed at step: " +msgstr "安装在步骤失败:" + +msgid "Details: " +msgstr "详细信息:" + +msgid "Installation failed: " +msgstr "安装失败:" + +msgid "Unknown error" +msgstr "未知错误" + +msgid "Service restarted successfully." +msgstr "服务重启成功。" + +msgid "Service restart may have failed. Please check manually." +msgstr "服务重启可能失败,请手动检查。" + +msgid "Please restart the service." +msgstr "请重启服务。" + +msgid "LuCI App updated successfully!" +msgstr "LuCI 应用更新成功!" + +msgid "Bandix updated successfully!" +msgstr "Bandix 更新成功!" + +msgid "LuCI App has update: " +msgstr "LuCI 应用有更新:" + +msgid "Bandix has update: " +msgstr "Bandix 有更新:" + +msgid "LuCI App is up to date: " +msgstr "LuCI 应用已是最新版本:" + +msgid "Bandix is up to date: " +msgstr "Bandix 已是最新版本:" + +msgid "Updates Available" +msgstr "有可用更新" + +msgid "No Updates Available" +msgstr "无可用更新" + +msgid "Detected Architecture" +msgstr "检测到的架构" + +msgid "Package Manager" +msgstr "包管理器" + +msgid "LuCI Assets" +msgstr "LuCI 资源" + +msgid "Bandix Assets" +msgstr "Bandix 资源" + +msgid "Failed to check for updates" +msgstr "检查更新失败" + +msgid "Failed to check for updates: " +msgstr "检查更新失败:" + +msgid "Checking..." +msgstr "检查中..." + +msgid "Installing..." +msgstr "安装中..." + +msgid "Download and Install" +msgstr "下载并安装" + +msgid "Manual Download" +msgstr "手动下载" + +msgid "Close" +msgstr "关闭" + +msgid "Starting installation... The page will refresh automatically in 5 seconds." +msgstr "开始安装... 页面将在5秒后自动刷新。" + +msgid "Please clear your browser cache manually after updating." +msgstr "更新后请手动清空浏览器缓存。" + +msgid "Update available" +msgstr "有更新" + +msgid "Update available, click to go to settings" +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 f34822c..11c913d 100644 --- a/luci-app-bandix/po/zh_Hant/bandix.po +++ b/luci-app-bandix/po/zh_Hant/bandix.po @@ -671,8 +671,8 @@ msgstr "離線超时時間" msgid "Set the timeout for device offline detection (seconds). Devices inactive for longer than this time will be marked as offline" msgstr "設定設備離線判斷的超时時間(秒)。超過此時間未活動的設備将被標記为離線" -msgid "Traffic History Period" -msgstr "歷史流量週期" +msgid "Realtime Traffic Period" +msgstr "即時流量週期" msgid "10 minutes interval uses about 60 KB per device" msgstr "10分鐘歷史週期,每个設備佔用 60KB" @@ -903,4 +903,121 @@ msgid "Are you sure you want to restart the Bandix service?" msgstr "確定要重啟 Bandix 服務嗎?" msgid "Failed to restart service: " -msgstr "重啟服務失敗:" \ No newline at end of file +msgstr "重啟服務失敗:" + +msgid "Realtime" +msgstr "即時" + +msgid "Last 24 Hours" +msgstr "最近24小時" + +msgid "Last 7 Days" +msgstr "最近7天" + +msgid "Last 30 Days" +msgstr "最近30天" + +msgid "Last 10 Minutes" +msgstr "最近10分鐘" + +msgid "Last 15 Minutes" +msgstr "最近15分鐘" + +msgid "Last 30 Minutes" +msgstr "最近30分鐘" + +msgid "Last 1 Hour" +msgstr "最近1小時" + +msgid "Starting installation..." +msgstr "開始安裝..." + +msgid "Page will refresh in 3 seconds." +msgstr "頁面將在3秒後重新整理。" + +msgid "Installation failed" +msgstr "安裝失敗" + +msgid "Installation failed at step: " +msgstr "安裝在步驟失敗:" + +msgid "Details: " +msgstr "詳細資訊:" + +msgid "Installation failed: " +msgstr "安裝失敗:" + +msgid "Unknown error" +msgstr "未知錯誤" + +msgid "Service restarted successfully." +msgstr "服務重啟成功。" + +msgid "Service restart may have failed. Please check manually." +msgstr "服務重啟可能失敗,請手動檢查。" + +msgid "Please restart the service." +msgstr "請重啟服務。" + +msgid "LuCI App updated successfully!" +msgstr "LuCI 應用更新成功!" + +msgid "Bandix updated successfully!" +msgstr "Bandix 更新成功!" + +msgid "LuCI App has update: " +msgstr "LuCI 應用有更新:" + +msgid "Bandix has update: " +msgstr "Bandix 有更新:" + +msgid "LuCI App is up to date: " +msgstr "LuCI 應用已是最新版本:" + +msgid "Bandix is up to date: " +msgstr "Bandix 已是最新版本:" + +msgid "Updates Available" +msgstr "有可用更新" + +msgid "No Updates Available" +msgstr "無可用更新" + +msgid "Detected Architecture" +msgstr "檢測到的架構" + +msgid "Package Manager" +msgstr "套件管理器" + +msgid "LuCI Assets" +msgstr "LuCI 資源" + +msgid "Bandix Assets" +msgstr "Bandix 資源" + +msgid "Failed to check for updates" +msgstr "檢查更新失敗" + +msgid "Failed to check for updates: " +msgstr "檢查更新失敗:" + +msgid "Checking..." +msgstr "檢查中..." + +msgid "Installing..." +msgstr "安裝中..." + +msgid "Download and Install" +msgstr "下載並安裝" + +msgid "Manual Download" +msgstr "手動下載" + +msgid "Close" +msgstr "關閉" + +msgid "Starting installation... The page will refresh automatically in 5 seconds." +msgstr "開始安裝... 頁面將在5秒後自動重新整理。" + +msgid "Please clear your browser cache manually after updating." +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 144d13a..67f9c29 100755 --- a/luci-app-bandix/root/usr/libexec/rpcd/luci.bandix +++ b/luci-app-bandix/root/usr/libexec/rpcd/luci.bandix @@ -487,6 +487,443 @@ restart_service() { fi } +# 获取版本信息 +get_version() { + json_init + + # 获取 luci-app-bandix 版本 + local luci_version="" + if [ -f "/usr/lib/opkg/info/luci-app-bandix.control" ]; then + luci_version=$(grep "^Version:" /usr/lib/opkg/info/luci-app-bandix.control 2>/dev/null | cut -d' ' -f2) + fi + # 如果无法从 control 文件获取,尝试从 opkg 命令获取 + if [ -z "$luci_version" ]; then + luci_version=$(opkg info luci-app-bandix 2>/dev/null | grep "^Version:" | cut -d' ' -f2) + fi + # 如果还是无法获取,使用默认值 + if [ -z "$luci_version" ]; then + luci_version="Unknown" + fi + + # 获取 bandix 版本 + local bandix_version="" + if [ -f "/usr/lib/opkg/info/bandix.control" ]; then + bandix_version=$(grep "^Version:" /usr/lib/opkg/info/bandix.control 2>/dev/null | cut -d' ' -f2) + fi + # 如果无法从 control 文件获取,尝试从 opkg 命令获取 + if [ -z "$bandix_version" ]; then + bandix_version=$(opkg info bandix 2>/dev/null | grep "^Version:" | cut -d' ' -f2) + fi + # 如果还是无法获取,尝试从 bandix API 获取(如果服务运行中) + if [ -z "$bandix_version" ]; then + local api_result=$(curl -s --connect-timeout 2 --max-time 5 "$BANDIX_API_BASE/api/version" 2>/dev/null) + if [ $? -eq 0 ] && [ -n "$api_result" ]; then + bandix_version=$(echo "$api_result" | jsonfilter -e '$.version' 2>/dev/null) + [ -z "$bandix_version" ] && bandix_version=$(echo "$api_result" | jsonfilter -e '$.data.version' 2>/dev/null) + fi + fi + # 如果还是无法获取,使用默认值 + if [ -z "$bandix_version" ]; then + bandix_version="Unknown" + fi + + json_add_string "luci_app_version" "$luci_version" + json_add_string "bandix_version" "$bandix_version" + json_dump + json_cleanup +} + +# 提取主版本号(去掉修订号,如 -r1, -r2 等) +extract_base_version() { + local version="$1" + # 去掉 -r1, -r2 等修订号后缀 + echo "$version" | sed 's/-r[0-9]*$//' +} + +# 检测包管理器类型 +get_package_manager() { + # 先尝试运行 opkg -v,检查是否能成功执行并返回版本信息 + local opkg_output=$(opkg -v 2>&1) + if [ $? -eq 0 ] && [ -n "$opkg_output" ] && echo "$opkg_output" | grep -qE "(opkg|version)"; then + echo "opkg" + return + fi + + # 再尝试运行 apk -v,检查是否能成功执行(不是 "not found") + local apk_output=$(apk -v 2>&1) + if [ $? -eq 0 ] && [ -n "$apk_output" ] && ! echo "$apk_output" | grep -qiE "(not found|command not found|no such file)"; then + echo "apk" + return + fi + + # 默认返回 opkg + echo "opkg" +} + +# 获取系统架构 +get_system_arch() { + # 优先从 /etc/os-release 获取 OPENWRT_ARCH + if [ -f "/etc/os-release" ]; then + local arch=$(grep "^OPENWRT_ARCH=" /etc/os-release 2>/dev/null | cut -d'=' -f2 | tr -d '"') + if [ -n "$arch" ]; then + echo "$arch" + return + fi + fi + + # 如果无法获取,尝试从 opkg 获取 + local arch=$(opkg print-architecture 2>/dev/null | head -n 1 | awk '{print $2}') + if [ -n "$arch" ]; then + echo "$arch" + return + fi + + # 最后尝试从 uname 获取 + uname -m 2>/dev/null +} + +# 检查更新 +check_update() { + json_init + + # 获取当前版本 + local current_luci_version="" + if [ -f "/usr/lib/opkg/info/luci-app-bandix.control" ]; then + current_luci_version=$(grep "^Version:" /usr/lib/opkg/info/luci-app-bandix.control 2>/dev/null | cut -d' ' -f2) + fi + if [ -z "$current_luci_version" ]; then + current_luci_version=$(opkg info luci-app-bandix 2>/dev/null | grep "^Version:" | cut -d' ' -f2) + fi + + local current_bandix_version="" + if [ -f "/usr/lib/opkg/info/bandix.control" ]; then + current_bandix_version=$(grep "^Version:" /usr/lib/opkg/info/bandix.control 2>/dev/null | cut -d' ' -f2) + fi + if [ -z "$current_bandix_version" ]; then + current_bandix_version=$(opkg info bandix 2>/dev/null | grep "^Version:" | cut -d' ' -f2) + fi + + # 检查 luci-app-bandix 更新 + local luci_latest_version="" + local luci_has_update="0" + local luci_update_url="" + local luci_release_body="" + local luci_download_url="" + + local luci_api_result=$(curl -s --connect-timeout 5 --max-time 10 "https://api.github.com/repos/timsaya/luci-app-bandix/releases/latest" 2>/dev/null) + if [ $? -eq 0 ] && [ -n "$luci_api_result" ]; then + luci_latest_version=$(echo "$luci_api_result" | jsonfilter -e '$.tag_name' 2>/dev/null) + # 去掉 v 前缀 + luci_latest_version=${luci_latest_version#v} + luci_update_url=$(echo "$luci_api_result" | jsonfilter -e '$.html_url' 2>/dev/null) + luci_release_body=$(echo "$luci_api_result" | jsonfilter -e '$.body' 2>/dev/null) + + # 获取架构信息和包管理器类型 + local arch=$(get_system_arch) + local pkg_manager=$(get_package_manager) + + # 根据包管理器类型确定优先的文件扩展名 + local preferred_ext="" + if [ "$pkg_manager" = "opkg" ]; then + preferred_ext="ipk" + elif [ "$pkg_manager" = "apk" ]; then + preferred_ext="apk" + fi + + # 查找对应架构的 ipk/apk 文件 + # 只查找匹配包管理器格式的包,找不到就报错 + # 使用循环遍历 assets 数组,直到找不到元素为止 + local i=0 + local asset_count=0 + + # 先统计 assets 数量 + while true; do + local asset_name=$(echo "$luci_api_result" | jsonfilter -e "$.assets[$i].name" 2>/dev/null) + if [ -z "$asset_name" ]; then + break + fi + asset_count=$((asset_count + 1)) + i=$((i + 1)) + done + + # 查找匹配包管理器格式的 _all 包 + if [ -n "$preferred_ext" ] && [ "$asset_count" -gt 0 ]; then + i=0 + while true; do + local asset_name=$(echo "$luci_api_result" | jsonfilter -e "$.assets[$i].name" 2>/dev/null) + if [ -z "$asset_name" ]; then + break + fi + # 匹配 _all 格式,且必须是优先格式 + if echo "$asset_name" | grep -qE "(luci-app-bandix.*_all|luci-app-bandix-.*-all)\.${preferred_ext}$"; then + luci_download_url=$(echo "$luci_api_result" | jsonfilter -e "$.assets[$i].browser_download_url" 2>/dev/null) + break + fi + i=$((i + 1)) + done + fi + + # 如果没找到 _all,尝试找包含当前架构的(只匹配包管理器格式) + # 例如:luci-app-bandix_0.10.0-r1_aarch64_cortex-a53.ipk + if [ -z "$luci_download_url" ] && [ -n "$arch" ] && [ -n "$preferred_ext" ] && [ "$asset_count" -gt 0 ]; then + i=0 + while true; do + local asset_name=$(echo "$luci_api_result" | jsonfilter -e "$.assets[$i].name" 2>/dev/null) + if [ -z "$asset_name" ]; then + break + fi + # 匹配格式:*_架构.优先扩展名 + # 例如:文件名包含 "_aarch64_cortex-a53." 且以 .ipk 或 .apk 结尾 + # 同时排除 i18n 包(只匹配主包) + if echo "$asset_name" | grep -qE "^luci-app-bandix[^-]" && \ + echo "$asset_name" | grep -qE "\.${preferred_ext}$" && \ + echo "$asset_name" | grep -q "_${arch}\."; then + luci_download_url=$(echo "$luci_api_result" | jsonfilter -e "$.assets[$i].browser_download_url" 2>/dev/null) + break + fi + i=$((i + 1)) + done + fi + + # 提取主版本号进行比较(忽略修订号) + local current_luci_base=$(extract_base_version "$current_luci_version") + local latest_luci_base=$(extract_base_version "$luci_latest_version") + + # 比较主版本号 + if [ -n "$current_luci_base" ] && [ -n "$latest_luci_base" ] && [ "$current_luci_base" != "$latest_luci_base" ]; then + luci_has_update="1" + fi + fi + + # 检查 bandix 更新 + local bandix_latest_version="" + local bandix_has_update="0" + local bandix_update_url="" + local bandix_release_body="" + local bandix_download_url="" + + local bandix_api_result=$(curl -s --connect-timeout 5 --max-time 10 "https://api.github.com/repos/timsaya/openwrt-bandix/releases/latest" 2>/dev/null) + if [ $? -eq 0 ] && [ -n "$bandix_api_result" ]; then + bandix_latest_version=$(echo "$bandix_api_result" | jsonfilter -e '$.tag_name' 2>/dev/null) + # 去掉 v 前缀 + bandix_latest_version=${bandix_latest_version#v} + bandix_update_url=$(echo "$bandix_api_result" | jsonfilter -e '$.html_url' 2>/dev/null) + bandix_release_body=$(echo "$bandix_api_result" | jsonfilter -e '$.body' 2>/dev/null) + + # 获取架构信息和包管理器类型 + local arch=$(get_system_arch) + local pkg_manager=$(get_package_manager) + + # 根据包管理器类型确定优先的文件扩展名 + local preferred_ext="" + if [ "$pkg_manager" = "opkg" ]; then + preferred_ext="ipk" + elif [ "$pkg_manager" = "apk" ]; then + preferred_ext="apk" + fi + + # 查找对应架构的 ipk/apk 文件 + # bandix 包格式:bandix-版本-r修订号_架构.apk 或 bandix-版本-r修订号_架构.ipk + # 或者:bandix_版本-r修订号_架构.apk 或 bandix_版本-r修订号_架构.ipk + # 例如:bandix-0.10.2-r1_aarch64_cortex-a53.apk 或 bandix_0.10.2-r1_aarch64_cortex-a53.apk + local bandix_asset_count=0 + + # 先统计 assets 数量 + if [ -n "$arch" ]; then + local i=0 + while true; do + local asset_name=$(echo "$bandix_api_result" | jsonfilter -e "$.assets[$i].name" 2>/dev/null) + if [ -z "$asset_name" ]; then + break + fi + bandix_asset_count=$((bandix_asset_count + 1)) + i=$((i + 1)) + done + + # 只查找匹配包管理器格式的架构特定包 + # 如果找不到匹配格式的包,就不设置 download_url,后续会报错 + if [ -n "$preferred_ext" ] && [ "$bandix_asset_count" -gt 0 ]; then + i=0 + while true; do + local asset_name=$(echo "$bandix_api_result" | jsonfilter -e "$.assets[$i].name" 2>/dev/null) + if [ -z "$asset_name" ]; then + break + fi + # 匹配格式:bandix-*_架构.优先扩展名 或 bandix_*_架构.优先扩展名 + # 例如:bandix-0.10.2-r1_aarch64_cortex-a53.ipk 或 bandix_0.10.2-r1_aarch64_cortex-a53.ipk + if echo "$asset_name" | grep -qE "^(bandix-|bandix_)" && \ + echo "$asset_name" | grep -qE "\.${preferred_ext}$" && \ + echo "$asset_name" | grep -q "_${arch}\."; then + bandix_download_url=$(echo "$bandix_api_result" | jsonfilter -e "$.assets[$i].browser_download_url" 2>/dev/null) + break + fi + i=$((i + 1)) + done + fi + fi + + # 提取主版本号进行比较(忽略修订号) + local current_bandix_base=$(extract_base_version "$current_bandix_version") + local latest_bandix_base=$(extract_base_version "$bandix_latest_version") + + # 比较主版本号 + if [ -n "$current_bandix_base" ] && [ -n "$latest_bandix_base" ] && [ "$current_bandix_base" != "$latest_bandix_base" ]; then + bandix_has_update="1" + fi + fi + + json_add_string "current_luci_version" "$current_luci_version" + json_add_string "current_bandix_version" "$current_bandix_version" + json_add_string "latest_luci_version" "$luci_latest_version" + json_add_string "latest_bandix_version" "$bandix_latest_version" + # 使用字符串 "1" 或 "0" 表示布尔值,在 JavaScript 中可以转换为布尔值 + json_add_string "luci_has_update" "$luci_has_update" + json_add_string "bandix_has_update" "$bandix_has_update" + json_add_string "luci_update_url" "$luci_update_url" + json_add_string "bandix_update_url" "$bandix_update_url" + json_add_string "luci_release_body" "$luci_release_body" + json_add_string "bandix_release_body" "$bandix_release_body" + # 检查是否找到了下载链接,如果没找到则添加错误信息 + local luci_error="" + local bandix_error="" + + if [ -z "$luci_download_url" ] && [ "$luci_has_update" = "1" ]; then + local pkg_manager=$(get_package_manager) + local preferred_ext="" + if [ "$pkg_manager" = "opkg" ]; then + preferred_ext="ipk" + elif [ "$pkg_manager" = "apk" ]; then + preferred_ext="apk" + fi + if [ -n "$preferred_ext" ]; then + luci_error="No ${preferred_ext} package found for luci-app-bandix" + else + luci_error="Unknown package manager, cannot determine package format" + fi + fi + + if [ -z "$bandix_download_url" ] && [ "$bandix_has_update" = "1" ]; then + local pkg_manager=$(get_package_manager) + local preferred_ext="" + if [ "$pkg_manager" = "opkg" ]; then + preferred_ext="ipk" + elif [ "$pkg_manager" = "apk" ]; then + preferred_ext="apk" + fi + if [ -n "$preferred_ext" ]; then + bandix_error="No ${preferred_ext} package found for bandix" + else + bandix_error="Unknown package manager, cannot determine package format" + fi + fi + + json_add_string "luci_download_url" "$luci_download_url" + json_add_string "bandix_download_url" "$bandix_download_url" + json_add_string "luci_error" "$luci_error" + json_add_string "bandix_error" "$bandix_error" + # 添加调试信息:架构、包管理器和资产数量 + local detected_arch=$(get_system_arch) + local detected_pkg_manager=$(get_package_manager) + json_add_string "detected_arch" "$detected_arch" + json_add_string "detected_pkg_manager" "$detected_pkg_manager" + json_add_int "luci_asset_count" "$asset_count" + json_add_int "bandix_asset_count" "$bandix_asset_count" + json_dump + json_cleanup +} + +# 下载并安装更新 +install_update() { + local package_type="$1" # "luci" 或 "bandix" + local download_url="$2" + + if [ -z "$package_type" ] || [ -z "$download_url" ]; then + make_error "Missing parameters" + return + fi + + json_init + + # 步骤1: 下载文件到 /tmp + local filename=$(basename "$download_url") + local filepath="/tmp/$filename" + + json_add_string "step" "downloading" + json_add_string "filepath" "$filepath" + + # 使用 wget 下载文件 + wget -q --timeout=10 -O "$filepath" "$download_url" 2>&1 + local download_exit_code=$? + + if [ $download_exit_code -ne 0 ] || [ ! -f "$filepath" ]; then + rm -f "$filepath" + json_add_boolean "success" 0 + json_add_string "error" "Failed to download package" + json_add_string "step" "download_failed" + json_dump + json_cleanup + return + fi + + # 步骤2: 安装包 + json_add_string "step" "installing" + local install_result="" + local install_exit_code=1 + + if command -v opkg >/dev/null 2>&1; then + # OpenWrt 使用 opkg + install_result=$(opkg install "$filepath" 2>&1) + install_exit_code=$? + elif command -v apk >/dev/null 2>&1; then + # Alpine 使用 apk + install_result=$(apk add --allow-untrusted "$filepath" 2>&1) + install_exit_code=$? + else + rm -f "$filepath" + json_add_boolean "success" 0 + json_add_string "error" "No package manager found (opkg or apk)" + json_add_string "step" "install_failed" + json_dump + json_cleanup + return + fi + + # 清理下载的文件 + rm -f "$filepath" + + if [ $install_exit_code -ne 0 ]; then + json_add_boolean "success" 0 + json_add_string "error" "Failed to install package" + json_add_string "output" "$install_result" + json_add_string "step" "install_failed" + json_dump + json_cleanup + return + fi + + # 步骤3: 如果是 bandix 包,重启服务 + if [ "$package_type" = "bandix" ]; then + json_add_string "step" "restarting" + if [ -f "/etc/init.d/bandix" ]; then + local restart_result=$(/etc/init.d/bandix restart 2>&1) + local restart_exit_code=$? + json_add_string "restart_output" "$restart_result" + json_add_int "restart_exit_code" "$restart_exit_code" + else + json_add_string "restart_output" "Service script not found: /etc/init.d/bandix" + json_add_int "restart_exit_code" 1 + fi + fi + + # 返回成功结果 + json_add_boolean "success" 1 + json_add_string "message" "Package installed successfully" + json_add_string "output" "$install_result" + json_add_string "step" "completed" + json_dump + json_cleanup +} + case "$1" in list) json_init @@ -555,6 +992,20 @@ case "$1" in json_add_object "restartService" json_close_object + json_add_object "getVersion" + json_close_object + + json_add_object "getSystemArch" + json_close_object + + json_add_object "checkUpdate" + json_close_object + + json_add_object "installUpdate" + json_add_string "package_type" + json_add_string "download_url" + json_close_object + json_dump json_cleanup ;; @@ -826,6 +1277,44 @@ case "$1" in # logger "luci.bandix: restartService called" restart_service ;; + getVersion) + # logger "luci.bandix: getVersion called" + get_version + ;; + getSystemArch) + # logger "luci.bandix: getSystemArch called" + local arch=$(get_system_arch) + json_init + json_add_string "arch" "$arch" + json_dump + json_cleanup + ;; + checkUpdate) + # logger "luci.bandix: checkUpdate called" + check_update + ;; + installUpdate) + # logger "luci.bandix: installUpdate called" + # 从 stdin 读取 JSON 参数 + read input + + if [ -n "$input" ]; then + # 解析参数 + package_type=$(echo "$input" | jsonfilter -e '$[0]' 2>/dev/null) + [ -z "$package_type" ] && package_type=$(echo "$input" | jsonfilter -e '$.package_type' 2>/dev/null) + + download_url=$(echo "$input" | jsonfilter -e '$[1]' 2>/dev/null) + [ -z "$download_url" ] && download_url=$(echo "$input" | jsonfilter -e '$.download_url' 2>/dev/null) + + if [ -n "$package_type" ] && [ -n "$download_url" ]; then + install_update "$package_type" "$download_url" + else + make_error "Missing package_type or download_url parameter" + fi + else + make_error "No input received" + fi + ;; esac ;; esac 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 453abdc..ca741a7 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 @@ -17,7 +17,11 @@ "setScheduleLimit", "deleteScheduleLimit", "clearData", - "restartService" + "restartService", + "getVersion", + "getSystemArch", + "checkUpdate", + "installUpdate" ] }, "uci": [ @@ -40,7 +44,11 @@ "setScheduleLimit", "deleteScheduleLimit", "clearData", - "restartService" + "restartService", + "getVersion", + "getSystemArch", + "checkUpdate", + "installUpdate" ] }, "uci": [ diff --git a/luci-app-passwall/luasrc/view/passwall/node_list/node_list.htm b/luci-app-passwall/luasrc/view/passwall/node_list/node_list.htm index 7e6c3c3..494c091 100644 --- a/luci-app-passwall/luasrc/view/passwall/node_list/node_list.htm +++ b/luci-app-passwall/luasrc/view/passwall/node_list/node_list.htm @@ -296,13 +296,22 @@ table td, .table .td { document.getElementById("set_node_name").innerHTML = ""; } - function row_top(btn) { + function row_top(btn, group) { const row = btn.closest("tr"); if (!row) return; const parent = row.parentNode; let firstDataRow = parent.querySelector("tr:not(.cbi-section-table-titles)"); if (firstDataRow && firstDataRow !== row) { parent.insertBefore(row, firstDataRow); + let save_order_btn = document.getElementById("save_order_btn_" + group); + if (save_order_btn) { + const new_order = get_node_order(group); + if (!arraysEqual(new_order, origin_group_node_order[group])) { + save_order_btn.style.display = null; + } else { + save_order_btn.style.display = "none"; + } + } } } @@ -414,6 +423,23 @@ table td, .table .td { return { address: address, port: port }; } + function get_node_order(group) { + let table = document.getElementById("cbi-passwall-nodes-" + group + "-table"); + if (!table) { + return; + } + let rows = table.querySelectorAll("tr.cbi-section-table-row"); + if (!rows || rows.length === 0) { + return; + } + var ids = []; + rows.forEach(function(row) { + var id = row.id.replace("cbi-passwall-", ""); + ids.push(id); + }); + return ids; + } + function save_current_page_order(group) { var table = document.getElementById("cbi-passwall-nodes-" + group + "-table"); if (!table) { @@ -426,7 +452,10 @@ table td, .table .td { return; } var btn = document.getElementById("save_order_btn_" + group); - if (btn) btn.disabled = true; + if (btn) { + btn.style.display = "none"; + btn.disabled = true; + } var ids = []; rows.forEach(function(row) { var id = row.id.replace("cbi-passwall-", ""); @@ -437,9 +466,16 @@ table td, .table .td { ids: ids.join(",") }, function(x, result) { - if (btn) btn.disabled = false; + if (btn) { + btn.style.display = null; + btn.disabled = false; + } if (x && x.status === 200) { + origin_group_node_order[group] = get_node_order(group); alert("<%:Saved current page order successfully.%>"); + if (btn) { + btn.style.display = "none"; + } } else { alert("<%:Save failed!%>"); } @@ -649,9 +685,20 @@ table td, .table .td { } } + function arraysEqual(a, b) { + if (a === b) return true; + if (a == null || b == null) return false; + if (a.length !== b.length) return false; + for (let i = 0; i < a.length; i++) { + if (a[i] !== b[i]) return false; + } + return true; + } + //列表拖动重排 function initSortableForTable(table) { if (!table) return null; + let group = table.id.replace("cbi-passwall-nodes-", "").replace("-table", "") var root = table.querySelector('tbody') || table; if (root._sortable_initialized) return root._sortable_instance; root._sortable_initialized = true; @@ -664,8 +711,16 @@ table td, .table .td { forceFallback: false, swapThreshold: 0.65, onEnd: function (evt) { - //var group = evt.to.id.replace("cbi-passwall-nodes-", "").replace("-table", ""); //save_current_page_order(group); // 自动提交保存 + let save_order_btn = document.getElementById("save_order_btn_" + group); + if (save_order_btn) { + const new_order = get_node_order(group); + if (!arraysEqual(new_order, origin_group_node_order[group])) { + save_order_btn.style.display = null; + } else { + save_order_btn.style.display = "none"; + } + } } }; try { @@ -721,7 +776,7 @@ table td, .table .td {
- +
@@ -740,7 +795,7 @@ table td, .table .td { {{url_test}}
- + /{{id}}'" alt="<%:Edit%>" title="<%:Edit%>"> @@ -907,6 +962,11 @@ table td, .table .td { cbi_t_switch("passwall.nodes", default_group) } + origin_group_node_order = {}; + for (let group in group_nodes) { + origin_group_node_order[group] = get_node_order(group); + } + initAllSortable(group_nodes); //clear expire data diff --git a/luci-app-passwall2/Makefile b/luci-app-passwall2/Makefile index 76bbb22..76dd6f8 100644 --- a/luci-app-passwall2/Makefile +++ b/luci-app-passwall2/Makefile @@ -5,7 +5,7 @@ include $(TOPDIR)/rules.mk PKG_NAME:=luci-app-passwall2 -PKG_VERSION:=25.11.18 +PKG_VERSION:=25.12.2 PKG_RELEASE:=1 PKG_PO_VERSION:=$(PKG_VERSION) diff --git a/luci-app-passwall2/htdocs/luci-static/resources/view/passwall2/Sortable.min.js b/luci-app-passwall2/htdocs/luci-static/resources/view/passwall2/Sortable.min.js new file mode 100644 index 0000000..95423a6 --- /dev/null +++ b/luci-app-passwall2/htdocs/luci-static/resources/view/passwall2/Sortable.min.js @@ -0,0 +1,2 @@ +/*! Sortable 1.15.6 - MIT | git://github.com/SortableJS/Sortable.git */ +!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):(t=t||self).Sortable=e()}(this,function(){"use strict";function e(e,t){var n,o=Object.keys(e);return Object.getOwnPropertySymbols&&(n=Object.getOwnPropertySymbols(e),t&&(n=n.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),o.push.apply(o,n)),o}function I(o){for(var t=1;tt.length)&&(e=t.length);for(var n=0,o=new Array(e);n"===e[0]&&(e=e.substring(1)),t))try{if(t.matches)return t.matches(e);if(t.msMatchesSelector)return t.msMatchesSelector(e);if(t.webkitMatchesSelector)return t.webkitMatchesSelector(e)}catch(t){return}}function g(t){return t.host&&t!==document&&t.host.nodeType?t.host:t.parentNode}function P(t,e,n,o){if(t){n=n||document;do{if(null!=e&&(">"!==e[0]||t.parentNode===n)&&f(t,e)||o&&t===n)return t}while(t!==n&&(t=g(t)))}return null}var m,v=/\s+/g;function k(t,e,n){var o;t&&e&&(t.classList?t.classList[n?"add":"remove"](e):(o=(" "+t.className+" ").replace(v," ").replace(" "+e+" "," "),t.className=(o+(n?" "+e:"")).replace(v," ")))}function R(t,e,n){var o=t&&t.style;if(o){if(void 0===n)return document.defaultView&&document.defaultView.getComputedStyle?n=document.defaultView.getComputedStyle(t,""):t.currentStyle&&(n=t.currentStyle),void 0===e?n:n[e];o[e=!(e in o||-1!==e.indexOf("webkit"))?"-webkit-"+e:e]=n+("string"==typeof n?"":"px")}}function b(t,e){var n="";if("string"==typeof t)n=t;else do{var o=R(t,"transform")}while(o&&"none"!==o&&(n=o+" "+n),!e&&(t=t.parentNode));var i=window.DOMMatrix||window.WebKitCSSMatrix||window.CSSMatrix||window.MSCSSMatrix;return i&&new i(n)}function D(t,e,n){if(t){var o=t.getElementsByTagName(e),i=0,r=o.length;if(n)for(;i=n.left-e&&i<=n.right+e,e=r>=n.top-e&&r<=n.bottom+e;return o&&e?a=t:void 0}}),a);if(e){var n,o={};for(n in t)t.hasOwnProperty(n)&&(o[n]=t[n]);o.target=o.rootEl=e,o.preventDefault=void 0,o.stopPropagation=void 0,e[K]._onDragOver(o)}}var i,r,a}function Ft(t){Z&&Z.parentNode[K]._isOutsideThisEl(t.target)}function jt(t,e){if(!t||!t.nodeType||1!==t.nodeType)throw"Sortable: `el` must be an HTMLElement, not ".concat({}.toString.call(t));this.el=t,this.options=e=a({},e),t[K]=this;var n,o,i={group:null,sort:!0,disabled:!1,store:null,handle:null,draggable:/^[uo]l$/i.test(t.nodeName)?">li":">*",swapThreshold:1,invertSwap:!1,invertedSwapThreshold:null,removeCloneOnHide:!0,direction:function(){return kt(t,this.options)},ghostClass:"sortable-ghost",chosenClass:"sortable-chosen",dragClass:"sortable-drag",ignore:"a, img",filter:null,preventOnFilter:!0,animation:0,easing:null,setData:function(t,e){t.setData("Text",e.textContent)},dropBubble:!1,dragoverBubble:!1,dataIdAttr:"data-id",delay:0,delayOnTouchOnly:!1,touchStartThreshold:(Number.parseInt?Number:window).parseInt(window.devicePixelRatio,10)||1,forceFallback:!1,fallbackClass:"sortable-fallback",fallbackOnBody:!1,fallbackTolerance:0,fallbackOffset:{x:0,y:0},supportPointer:!1!==jt.supportPointer&&"PointerEvent"in window&&(!u||c),emptyInsertThreshold:5};for(n in z.initializePlugins(this,t,i),i)n in e||(e[n]=i[n]);for(o in Rt(e),this)"_"===o.charAt(0)&&"function"==typeof this[o]&&(this[o]=this[o].bind(this));this.nativeDraggable=!e.forceFallback&&It,this.nativeDraggable&&(this.options.touchStartThreshold=1),e.supportPointer?h(t,"pointerdown",this._onTapStart):(h(t,"mousedown",this._onTapStart),h(t,"touchstart",this._onTapStart)),this.nativeDraggable&&(h(t,"dragover",this),h(t,"dragenter",this)),St.push(this.el),e.store&&e.store.get&&this.sort(e.store.get(this)||[]),a(this,A())}function Ht(t,e,n,o,i,r,a,l){var s,c,u=t[K],d=u.options.onMove;return!window.CustomEvent||y||w?(s=document.createEvent("Event")).initEvent("move",!0,!0):s=new CustomEvent("move",{bubbles:!0,cancelable:!0}),s.to=e,s.from=t,s.dragged=n,s.draggedRect=o,s.related=i||e,s.relatedRect=r||X(e),s.willInsertAfter=l,s.originalEvent=a,t.dispatchEvent(s),c=d?d.call(u,s,a):c}function Lt(t){t.draggable=!1}function Kt(){xt=!1}function Wt(t){return setTimeout(t,0)}function zt(t){return clearTimeout(t)}jt.prototype={constructor:jt,_isOutsideThisEl:function(t){this.el.contains(t)||t===this.el||(vt=null)},_getDirection:function(t,e){return"function"==typeof this.options.direction?this.options.direction.call(this,t,e,Z):this.options.direction},_onTapStart:function(e){if(e.cancelable){var n=this,o=this.el,t=this.options,i=t.preventOnFilter,r=e.type,a=e.touches&&e.touches[0]||e.pointerType&&"touch"===e.pointerType&&e,l=(a||e).target,s=e.target.shadowRoot&&(e.path&&e.path[0]||e.composedPath&&e.composedPath()[0])||l,c=t.filter;if(!function(t){Ot.length=0;var e=t.getElementsByTagName("input"),n=e.length;for(;n--;){var o=e[n];o.checked&&Ot.push(o)}}(o),!Z&&!(/mousedown|pointerdown/.test(r)&&0!==e.button||t.disabled)&&!s.isContentEditable&&(this.nativeDraggable||!u||!l||"SELECT"!==l.tagName.toUpperCase())&&!((l=P(l,t.draggable,o,!1))&&l.animated||et===l)){if(it=j(l),at=j(l,t.draggable),"function"==typeof c){if(c.call(this,e,l,this))return V({sortable:n,rootEl:s,name:"filter",targetEl:l,toEl:o,fromEl:o}),U("filter",n,{evt:e}),void(i&&e.preventDefault())}else if(c=c&&c.split(",").some(function(t){if(t=P(s,t.trim(),o,!1))return V({sortable:n,rootEl:t,name:"filter",targetEl:l,fromEl:o,toEl:o}),U("filter",n,{evt:e}),!0}))return void(i&&e.preventDefault());t.handle&&!P(s,t.handle,o,!1)||this._prepareDragStart(e,a,l)}}},_prepareDragStart:function(t,e,n){var o,i=this,r=i.el,a=i.options,l=r.ownerDocument;n&&!Z&&n.parentNode===r&&(o=X(n),J=r,$=(Z=n).parentNode,tt=Z.nextSibling,et=n,st=a.group,ut={target:jt.dragged=Z,clientX:(e||t).clientX,clientY:(e||t).clientY},ft=ut.clientX-o.left,gt=ut.clientY-o.top,this._lastX=(e||t).clientX,this._lastY=(e||t).clientY,Z.style["will-change"]="all",o=function(){U("delayEnded",i,{evt:t}),jt.eventCanceled?i._onDrop():(i._disableDelayedDragEvents(),!s&&i.nativeDraggable&&(Z.draggable=!0),i._triggerDragStart(t,e),V({sortable:i,name:"choose",originalEvent:t}),k(Z,a.chosenClass,!0))},a.ignore.split(",").forEach(function(t){D(Z,t.trim(),Lt)}),h(l,"dragover",Bt),h(l,"mousemove",Bt),h(l,"touchmove",Bt),a.supportPointer?(h(l,"pointerup",i._onDrop),this.nativeDraggable||h(l,"pointercancel",i._onDrop)):(h(l,"mouseup",i._onDrop),h(l,"touchend",i._onDrop),h(l,"touchcancel",i._onDrop)),s&&this.nativeDraggable&&(this.options.touchStartThreshold=4,Z.draggable=!0),U("delayStart",this,{evt:t}),!a.delay||a.delayOnTouchOnly&&!e||this.nativeDraggable&&(w||y)?o():jt.eventCanceled?this._onDrop():(a.supportPointer?(h(l,"pointerup",i._disableDelayedDrag),h(l,"pointercancel",i._disableDelayedDrag)):(h(l,"mouseup",i._disableDelayedDrag),h(l,"touchend",i._disableDelayedDrag),h(l,"touchcancel",i._disableDelayedDrag)),h(l,"mousemove",i._delayedDragTouchMoveHandler),h(l,"touchmove",i._delayedDragTouchMoveHandler),a.supportPointer&&h(l,"pointermove",i._delayedDragTouchMoveHandler),i._dragStartTimer=setTimeout(o,a.delay)))},_delayedDragTouchMoveHandler:function(t){t=t.touches?t.touches[0]:t;Math.max(Math.abs(t.clientX-this._lastX),Math.abs(t.clientY-this._lastY))>=Math.floor(this.options.touchStartThreshold/(this.nativeDraggable&&window.devicePixelRatio||1))&&this._disableDelayedDrag()},_disableDelayedDrag:function(){Z&&Lt(Z),clearTimeout(this._dragStartTimer),this._disableDelayedDragEvents()},_disableDelayedDragEvents:function(){var t=this.el.ownerDocument;p(t,"mouseup",this._disableDelayedDrag),p(t,"touchend",this._disableDelayedDrag),p(t,"touchcancel",this._disableDelayedDrag),p(t,"pointerup",this._disableDelayedDrag),p(t,"pointercancel",this._disableDelayedDrag),p(t,"mousemove",this._delayedDragTouchMoveHandler),p(t,"touchmove",this._delayedDragTouchMoveHandler),p(t,"pointermove",this._delayedDragTouchMoveHandler)},_triggerDragStart:function(t,e){e=e||"touch"==t.pointerType&&t,!this.nativeDraggable||e?this.options.supportPointer?h(document,"pointermove",this._onTouchMove):h(document,e?"touchmove":"mousemove",this._onTouchMove):(h(Z,"dragend",this),h(J,"dragstart",this._onDragStart));try{document.selection?Wt(function(){document.selection.empty()}):window.getSelection().removeAllRanges()}catch(t){}},_dragStarted:function(t,e){var n;Dt=!1,J&&Z?(U("dragStarted",this,{evt:e}),this.nativeDraggable&&h(document,"dragover",Ft),n=this.options,t||k(Z,n.dragClass,!1),k(Z,n.ghostClass,!0),jt.active=this,t&&this._appendGhost(),V({sortable:this,name:"start",originalEvent:e})):this._nulling()},_emulateDragOver:function(){if(dt){this._lastX=dt.clientX,this._lastY=dt.clientY,Xt();for(var t=document.elementFromPoint(dt.clientX,dt.clientY),e=t;t&&t.shadowRoot&&(t=t.shadowRoot.elementFromPoint(dt.clientX,dt.clientY))!==e;)e=t;if(Z.parentNode[K]._isOutsideThisEl(t),e)do{if(e[K])if(e[K]._onDragOver({clientX:dt.clientX,clientY:dt.clientY,target:t,rootEl:e})&&!this.options.dragoverBubble)break}while(e=g(t=e));Yt()}},_onTouchMove:function(t){if(ut){var e=this.options,n=e.fallbackTolerance,o=e.fallbackOffset,i=t.touches?t.touches[0]:t,r=Q&&b(Q,!0),a=Q&&r&&r.a,l=Q&&r&&r.d,e=At&&wt&&E(wt),a=(i.clientX-ut.clientX+o.x)/(a||1)+(e?e[0]-Tt[0]:0)/(a||1),l=(i.clientY-ut.clientY+o.y)/(l||1)+(e?e[1]-Tt[1]:0)/(l||1);if(!jt.active&&!Dt){if(n&&Math.max(Math.abs(i.clientX-this._lastX),Math.abs(i.clientY-this._lastY))E.right+10||S.clientY>x.bottom&&S.clientX>x.left:S.clientY>E.bottom+10||S.clientX>x.right&&S.clientY>x.top)||m.animated)){if(m&&(t=n,e=r,C=X(B((_=this).el,0,_.options,!0)),_=L(_.el,_.options,Q),e?t.clientX<_.left-10||t.clientY /dev/null 2>&1 &") @@ -631,7 +653,7 @@ function restore_backup() fp:close() if chunk_index + 1 == total_chunks then api.sys.call("echo '' > /tmp/log/passwall2.log") - api.log(string.format(" * PassWall2 %s", i18n.translate("Configuration file uploaded successfully…"))) + api.log(0, string.format(" * PassWall2 %s", i18n.translate("Configuration file uploaded successfully…"))) local temp_dir = '/tmp/passwall2_bak' api.sys.call("mkdir -p " .. temp_dir) if api.sys.call("tar -xzf " .. file_path .. " -C " .. temp_dir) == 0 then @@ -641,13 +663,13 @@ function restore_backup() api.sys.call("cp -f " .. temp_file .. " " .. backup_file) end end - api.log(string.format(" * PassWall2 %s", i18n.translate("Configuration restored successfully…"))) - api.log(string.format(" * PassWall2 %s", i18n.translate("Service restarting…"))) + api.log(0, string.format(" * PassWall2 %s", i18n.translate("Configuration restored successfully…"))) + api.log(0, string.format(" * PassWall2 %s", i18n.translate("Service restarting…"))) luci.sys.call('/etc/init.d/passwall2 restart > /dev/null 2>&1 &') luci.sys.call('/etc/init.d/passwall2_server restart > /dev/null 2>&1 &') result = { status = "success", message = "Upload completed", path = file_path } else - api.log(string.format(" * PassWall2 %s", i18n.translate("Configuration file decompression failed, please try again!"))) + api.log(0, string.format(" * PassWall2 %s", i18n.translate("Configuration file decompression failed, please try again!"))) result = { status = "error", message = "Decompression failed" } end api.sys.call("rm -rf " .. temp_dir) diff --git a/luci-app-passwall2/luasrc/model/cbi/passwall2/client/node_subscribe.lua b/luci-app-passwall2/luasrc/model/cbi/passwall2/client/node_subscribe.lua index cd7cc81..327b488 100644 --- a/luci-app-passwall2/luasrc/model/cbi/passwall2/client/node_subscribe.lua +++ b/luci-app-passwall2/luasrc/model/cbi/passwall2/client/node_subscribe.lua @@ -209,7 +209,7 @@ o.cfgvalue = function(t, n) str = str ~= "" and "
" .. str or "" local num = 0 m.uci:foreach(appname, "nodes", function(s) - if s["group"] ~= "" and s["group"] == remark then + if s["group"] and s["group"]:lower() == remark:lower() then num = num + 1 end end) diff --git a/luci-app-passwall2/luasrc/model/cbi/passwall2/client/type/ray.lua b/luci-app-passwall2/luasrc/model/cbi/passwall2/client/type/ray.lua index fbd6aab..231965e 100644 --- a/luci-app-passwall2/luasrc/model/cbi/passwall2/client/type/ray.lua +++ b/luci-app-passwall2/luasrc/model/cbi/passwall2/client/type/ray.lua @@ -390,6 +390,9 @@ o = s:option(Flag, _n("tls_allowInsecure"), translate("allowInsecure"), translat o.default = "0" o:depends({ [_n("tls")] = true, [_n("reality")] = false }) +o = s:option(Value, _n("tls_chain_fingerprint"), translate("TLS Chain Fingerprint (SHA256)"), translate("Once set, connects only when the server’s chain fingerprint matches.")) +o:depends({ [_n("tls")] = true, [_n("reality")] = false }) + o = s:option(Flag, _n("ech"), translate("ECH")) o.default = "0" o:depends({ [_n("tls")] = true, [_n("flow")] = "", [_n("reality")] = false }) @@ -616,8 +619,14 @@ o = s:option(TextValue, _n("xhttp_extra"), " ", translate("An XHttpObject in JSO o:depends({ [_n("use_xhttp_extra")] = true }) o.rows = 15 o.wrap = "off" +o.custom_cfgvalue = function(self, section, value) + local raw = m:get(section, "xhttp_extra") + if raw then + return api.base64Decode(raw) + end +end o.custom_write = function(self, section, value) - m:set(section, self.option:sub(1 + #option_prefix), value) + m:set(section, "xhttp_extra", api.base64Encode(value)) local success, data = pcall(jsonc.parse, value) if success and data then local address = (data.extra and data.extra.downloadSettings and data.extra.downloadSettings.address) @@ -640,7 +649,7 @@ o.validate = function(self, value) return value end o.custom_remove = function(self, section, value) - m:del(section, self.option:sub(1 + #option_prefix)) + m:del(section, "xhttp_extra") m:del(section, "download_address") end diff --git a/luci-app-passwall2/luasrc/passwall2/api.lua b/luci-app-passwall2/luasrc/passwall2/api.lua index d17f591..b561926 100644 --- a/luci-app-passwall2/luasrc/passwall2/api.lua +++ b/luci-app-passwall2/luasrc/passwall2/api.lua @@ -31,8 +31,8 @@ if lang == "auto" then end i18n.setlanguage(lang) -function log(...) - local result = os.date("%Y-%m-%d %H:%M:%S: ") .. table.concat({...}, " ") +function echolog(...) + local result = table.concat({...}, " ") local f, err = io.open(LOG_FILE, "a") if f and err == nil then f:write(result .. "\n") @@ -40,6 +40,23 @@ function log(...) end end +function echolog_date(...) + local result = os.date("%Y-%m-%d %H:%M:%S: ") .. table.concat({...}, " ") + echolog(result) +end + +function log(level, ...) + local indent = "" + if level >= 1 then + for i = 1, level, 1 do + indent = indent .. " " + end + echolog_date(indent .. "- " .. table.concat({...}, " ")) + else + echolog_date(table.concat({...}, " ")) + end +end + function is_old_uci() return sys.call("grep -E 'require[ \t]*\"uci\"' /usr/lib/lua/luci/model/uci.lua >/dev/null 2>&1") == 0 end @@ -118,19 +135,15 @@ function exec_call(cmd) end function base64Decode(text) - local raw = text if not text then return '' end - text = text:gsub("%z", "") - text = text:gsub("%c", "") - text = text:gsub("_", "/") - text = text:gsub("-", "+") - local mod4 = #text % 4 - text = text .. string.sub('====', mod4 + 1) - local result = nixio.bin.b64decode(text) + local encoded = text:gsub("%z", ""):gsub("%c", ""):gsub("_", "/"):gsub("-", "+") + local mod4 = #encoded % 4 + encoded = encoded .. string.sub('====', mod4 + 1) + local result = nixio.bin.b64decode(encoded) if result then return result:gsub("%z", "") else - return raw + return text end end @@ -229,9 +242,13 @@ function url(...) return require "luci.dispatcher".build_url(url) end -function trim(text) - if not text or text == "" then return "" end - return text:match("^%s*(.-)%s*$") +function trim(s) + local len = #s + local i, j = 1, len + while i <= len and s:byte(i) <= 32 do i = i + 1 end + while j >= i and s:byte(j) <= 32 do j = j - 1 end + if i > j then return "" end + return s:sub(i, j) end function split(full, sep) @@ -450,6 +467,8 @@ end function get_valid_nodes() local show_node_info = uci_get_type("global_other", "show_node_info") or "0" local nodes = {} + local default_nodes = {} + local other_nodes = {} uci:foreach(appname, "nodes", function(e) e.id = e[".name"] if e.type and e.remarks then @@ -458,7 +477,11 @@ function get_valid_nodes() if type == "sing-box" then type = "Sing-Box" end e["remark"] = "%s:[%s] " % {type .. " " .. i18n.translatef(e.protocol), e.remarks} e["node_type"] = "special" - nodes[#nodes + 1] = e + if not e.group or e.group == "" then + default_nodes[#default_nodes + 1] = e + else + other_nodes[#other_nodes + 1] = e + end end local port = e.port or e.hysteria_hop or e.hysteria2_hop if port and e.address then @@ -498,11 +521,17 @@ function get_valid_nodes() e["remark"] = "%s:[%s] %s:%s" % {type, e.remarks, address, port} end e.node_type = "normal" - nodes[#nodes + 1] = e + if not e.group or e.group == "" then + default_nodes[#default_nodes + 1] = e + else + other_nodes[#other_nodes + 1] = e + end end end end end) + for i = 1, #default_nodes do nodes[#nodes + 1] = default_nodes[i] end + for i = 1, #other_nodes do nodes[#nodes + 1] = other_nodes[i] end return nodes end diff --git a/luci-app-passwall2/luasrc/passwall2/util_xray.lua b/luci-app-passwall2/luasrc/passwall2/util_xray.lua index 503a8db..02cf298 100644 --- a/luci-app-passwall2/luasrc/passwall2/util_xray.lua +++ b/luci-app-passwall2/luasrc/passwall2/util_xray.lua @@ -151,6 +151,7 @@ function gen_outbound(flag, node, tag, proxy_table) serverName = node.tls_serverName, allowInsecure = (node.tls_allowInsecure == "1") and true or false, fingerprint = (node.type == "Xray" and node.utls == "1" and node.fingerprint and node.fingerprint ~= "") and node.fingerprint or nil, + pinnedPeerCertificateChainSha256 = node.tls_chain_fingerprint and { node.tls_chain_fingerprint } or nil, echConfigList = (node.ech == "1") and node.ech_config or nil, echForceQuery = (node.ech == "1") and (node.ech_ForceQuery or "none") or nil } or nil, @@ -218,7 +219,7 @@ function gen_outbound(flag, node, tag, proxy_table) host = node.xhttp_host, -- If the code contains an "extra" section, retrieve the contents of "extra"; otherwise, assign the value directly to "extra". extra = node.xhttp_extra and (function() - local success, parsed = pcall(jsonc.parse, node.xhttp_extra) + local success, parsed = pcall(jsonc.parse, api.base64Decode(node.xhttp_extra)) if success then return parsed.extra or parsed else diff --git a/luci-app-passwall2/luasrc/view/passwall2/log/log.htm b/luci-app-passwall2/luasrc/view/passwall2/log/log.htm index 0df1e39..4e6714e 100644 --- a/luci-app-passwall2/luasrc/view/passwall2/log/log.htm +++ b/luci-app-passwall2/luasrc/view/passwall2/log/log.htm @@ -3,13 +3,19 @@ local api = require "luci.passwall2.api" -%> <% if api.is_js_luci() then -%> @@ -102,11 +195,7 @@ table td, .table .td { function cbi_t_switch(section, tab) { if( cbi_t[section] && cbi_t[section][tab] ) { // Before switching tabs, first deselect all currently active tabs. - var btn = document.getElementById("select_all_btn"); - if (btn) { - dechecked_all_node(btn); - } - + dechecked_all_node(); var o = cbi_t[section][tab]; var h = document.getElementById('tab.' + section); for( var tid in cbi_t[section] ) { @@ -131,10 +220,7 @@ table td, .table .td { if (typeof(cbi_t_switch) === "function") { var old_switch = cbi_t_switch; cbi_t_switch = function(section, tab) { - var btn = document.getElementById("select_all_btn"); - if (btn) { - dechecked_all_node(btn); - } + dechecked_all_node(); return old_switch(section, tab); }; } @@ -225,47 +311,70 @@ table td, .table .td { document.getElementById("set_node_div").style.display="none"; document.getElementById("set_node_name").innerHTML = ""; } - - function _cbi_row_top(id) { - //It has been damaged and awaits repair or other solutions. - var dom = document.getElementById("cbi-passwall2-" + id); - if (dom) { - var trs = document.getElementById("cbi-passwall2-nodes").getElementsByClassName("cbi-section-table-row"); - if (trs && trs.length > 0) { - for (var i = 0; i < trs.length; i++) { - var up = dom.getElementsByClassName("cbi-button-up"); - if (up) { - cbi_row_swap(up[0], true, 'cbi.sts.passwall2.nodes'); - } - } - } + + function row_top(btn) { + const row = btn.closest("tr"); + if (!row) return; + const parent = row.parentNode; + let firstDataRow = parent.querySelector("tr:not(.cbi-section-table-titles)"); + if (firstDataRow && firstDataRow !== row) { + parent.insertBefore(row, firstDataRow); } } - + + function set_select_all_state(sectionChecked) { + var visibleContainer = document.querySelector('#cbi-passwall2-nodes > .cbi-tabcontainer[style*="display:block"], #cbi-passwall2-nodes > .cbi-tabcontainer[style*="display: block"]'); + if (!visibleContainer) return; + var nodes = visibleContainer.getElementsByClassName("nodes_select"); + var selectAllChk = visibleContainer.querySelector(".nodes_select_all"); + var selectAllBtn = document.getElementById("select_all_btn"); + for (var i = 0; i < nodes.length; i++) { + nodes[i].checked = sectionChecked; + } + if (selectAllChk) { + selectAllChk.checked = sectionChecked; + selectAllChk.title = sectionChecked ? "<%:DeSelect all%>" : "<%:Select all%>"; + selectAllChk.setAttribute("onclick", sectionChecked ? "dechecked_all_node(this)" : "checked_all_node(this)"); + } + if (selectAllBtn) { + selectAllBtn.value = sectionChecked ? "<%:DeSelect all%>" : "<%:Select all%>"; + selectAllBtn.setAttribute("onclick", sectionChecked ? "dechecked_all_node(this)" : "checked_all_node(this)"); + } + } + function checked_all_node(btn) { - var visibleContainer = document.querySelector('#cbi-passwall2-nodes > .cbi-tabcontainer[style*="display:block"], #cbi-passwall2-nodes > .cbi-tabcontainer[style*="display: block"]'); - if (!visibleContainer) return; - var doms = visibleContainer.getElementsByClassName("nodes_select"); - if (doms && doms.length > 0) { - for (var i = 0 ; i < doms.length; i++) { - doms[i].checked = true; - } - btn.value = "<%:DeSelect all%>"; - btn.setAttribute("onclick", "dechecked_all_node(this)"); - } + set_select_all_state(true); } - + function dechecked_all_node(btn) { + set_select_all_state(false); + } + + function update_select_state() { var visibleContainer = document.querySelector('#cbi-passwall2-nodes > .cbi-tabcontainer[style*="display:block"], #cbi-passwall2-nodes > .cbi-tabcontainer[style*="display: block"]'); if (!visibleContainer) return; - var doms = visibleContainer.getElementsByClassName("nodes_select"); - if (doms && doms.length > 0) { - for (var i = 0 ; i < doms.length; i++) { - doms[i].checked = false; - } - btn.value = "<%:Select all%>"; - btn.setAttribute("onclick", "checked_all_node(this)"); + var nodes = visibleContainer.getElementsByClassName("nodes_select"); + if (!nodes.length) return; + var selectAllChk = visibleContainer.querySelector(".nodes_select_all"); + var selectAllBtn = document.getElementById("select_all_btn"); + var checkedCount = 0; + for (var i = 0; i < nodes.length; i++) { + if (nodes[i].checked) checkedCount++; } + var allChecked = checkedCount === nodes.length; + var title = allChecked ? "<%:DeSelect all%>" : "<%:Select all%>"; + var onclickFunc = allChecked ? "dechecked_all_node(this)" : "checked_all_node(this)"; + + function updateElement(el) { + if (!el) return; + if ("checked" in el) el.checked = allChecked; + if ("title" in el) el.title = title; + if ("value" in el) el.value = title; + el.setAttribute("onclick", onclickFunc); + } + + updateElement(selectAllChk); + updateElement(selectAllBtn); } function delete_select_nodes() { @@ -321,6 +430,66 @@ table td, .table .td { return { address: address, port: port }; } + function get_node_order(group) { + let table = document.getElementById("cbi-passwall2-nodes-" + group + "-table"); + if (!table) { + return; + } + let rows = table.querySelectorAll("tr.cbi-section-table-row"); + if (!rows || rows.length === 0) { + return; + } + var ids = []; + rows.forEach(function(row) { + var id = row.id.replace("cbi-passwall2-", ""); + ids.push(id); + }); + return ids; + } + + function save_current_page_order(group) { + var table = document.getElementById("cbi-passwall2-nodes-" + group + "-table"); + if (!table) { + alert("<%:No table!%>"); + return; + } + var rows = table.querySelectorAll("tr.cbi-section-table-row"); + if (!rows || rows.length === 0) { + alert("<%:No nodes!%>"); + return; + } + var btn = document.getElementById("save_order_btn_" + group); + if (btn) { + btn.style.display = "none"; + btn.disabled = true; + } + var ids = []; + rows.forEach(function(row) { + var id = row.id.replace("cbi-passwall2-", ""); + ids.push(id); + }); + XHR.get('<%=api.url("save_node_order")%>', { + group: group, + ids: ids.join(",") + }, + function(x, result) { + if (btn) { + btn.style.display = null; + btn.disabled = false; + } + if (x && x.status === 200) { + origin_group_node_order[group] = get_node_order(group); + alert("<%:Saved current page order successfully.%>"); + if (btn) { + btn.style.display = "none"; + } + } else { + alert("<%:Save failed!%>"); + } + } + ); + } + function get_now_use_node() { XHR.get('<%=api.url("get_now_use_node")%>', null, function(x, result) { @@ -505,6 +674,79 @@ table td, .table .td { } } } + + function arraysEqual(a, b) { + if (a === b) return true; + if (a == null || b == null) return false; + if (a.length !== b.length) return false; + for (let i = 0; i < a.length; i++) { + if (a[i] !== b[i]) return false; + } + return true; + } + + // List drag and rearrange + function initSortableForTable(table) { + if (!table) return null; + let group = table.id.replace("cbi-passwall2-nodes-", "").replace("-table", "") + var root = table.querySelector('tbody') || table; + if (root._sortable_initialized) return root._sortable_instance; + root._sortable_initialized = true; + var opts = { + handle: ".drag-handle", + draggable: "tr.cbi-section-table-row", + animation: 150, + ghostClass: "dragging-row", + fallbackOnBody: true, + forceFallback: false, + swapThreshold: 0.65, + onEnd: function (evt) { + //save_current_page_order(group); // Auto save + let save_order_btn = document.getElementById("save_order_btn_" + group); + if (save_order_btn) { + const new_order = get_node_order(group); + if (!arraysEqual(new_order, origin_group_node_order[group])) { + save_order_btn.style.display = null; + } else { + save_order_btn.style.display = "none"; + } + } + } + }; + try { + var instance = Sortable.create(root, opts); + root._sortable_instance = instance; + return instance; + } catch (err) { + root._sortable_initialized = false; + console.error("Sortable init failed:", err); + return null; + } + } + + function initAllSortable(group_nodes) { + if (typeof Sortable === 'undefined') { + var retries = 0; + var maxRetries = 25; + var t = setInterval(function () { + retries++; + if (typeof Sortable !== 'undefined') { + clearInterval(t); + for (var group in group_nodes) { + var table = document.getElementById("cbi-passwall2-nodes-" + group + "-table"); + initSortableForTable(table); + } + } else if (retries >= maxRetries) { + clearInterval(t); + } + }, 200); + } else { + for (var group in group_nodes) { + var table = document.getElementById("cbi-passwall2-nodes-" + group + "-table"); + initSortableForTable(table); + } + } + } @@ -512,6 +754,9 @@ table td, .table .td {
+ @@ -522,6 +767,7 @@ table td, .table .td {
+ + <%:Remarks%> Ping TCPing
+
@@ -531,19 +777,21 @@ table td, .table .td { - {{remarks}} - {{ping}} - {{tcping}} - {{url_test}} - + + + + {{remarks}} + {{ping}} + {{tcping}} + {{url_test}} +
- - - + /{{id}}'" alt="<%:Edit%>" title="<%:Edit%>"> +
@@ -705,6 +953,13 @@ table td, .table .td { cbi_t_switch("passwall2.nodes", default_group) } + origin_group_node_order = {}; + for (let group in group_nodes) { + origin_group_node_order[group] = get_node_order(group); + } + + initAllSortable(group_nodes); + //clear expire data if (localStorage && localStorage.length > 0) { const now = Date.now(); diff --git a/luci-app-passwall2/luasrc/view/passwall2/server/log.htm b/luci-app-passwall2/luasrc/view/passwall2/server/log.htm index 4bb5c2d..28d9dd2 100644 --- a/luci-app-passwall2/luasrc/view/passwall2/server/log.htm +++ b/luci-app-passwall2/luasrc/view/passwall2/server/log.htm @@ -3,13 +3,19 @@ local api = require "luci.passwall2.api" -%> diff --git a/luci-theme-kucat/Makefile b/luci-theme-kucat/Makefile index 7f97d49..9f9483a 100644 --- a/luci-theme-kucat/Makefile +++ b/luci-theme-kucat/Makefile @@ -10,12 +10,11 @@ THEME_TITLE:=Kucat Theme PKG_NAME:=luci-theme-$(THEME_NAME) LUCI_TITLE:=Kucat Theme by sirpdboy LUCI_DEPENDS:=+wget +curl +jsonfilter -PKG_VERSION:=3.1.5 -PKG_RELEASE:=20251130 +PKG_VERSION:=3.1.6 +PKG_RELEASE:=20251202 define Package/luci-theme-$(THEME_NAME)/conffiles /www/luci-static/resources/background/ -/www/luci-static/kucat/background/ endef include $(TOPDIR)/feeds/luci/luci.mk diff --git a/luci-theme-kucat/htdocs/luci-static/kucat/css/style.css b/luci-theme-kucat/htdocs/luci-static/kucat/css/style.css index 8611786..26e97b6 100644 --- a/luci-theme-kucat/htdocs/luci-static/kucat/css/style.css +++ b/luci-theme-kucat/htdocs/luci-static/kucat/css/style.css @@ -562,7 +562,6 @@ footer a { #view { display: flex; flex-direction: column; - /* gap: 1rem; */ min-width: inherit; overflow-x: auto; overflow-y: hidden @@ -997,8 +996,6 @@ body[class*="node-"]>.main>.main-left>.nav>.slide>.menu.active::before { display: flex; height: 4.8rem; background-clip: padding-box; - /*border-bottom: 1px solid var(--menu-fontcolor); - margin: 0rem 1rem;*/ justify-content: center; align-items: center; padding: 2rem @@ -1223,7 +1220,7 @@ input, line-height: 1; background-image: none; background-color: var(--input-bgcolor); - color: var(--inputtext-color); + color: var(--body-color); border: 1px solid var(--inputborder-color); border-radius: var(--radius2); transition: box-shadow 0.3s ease; @@ -1357,6 +1354,7 @@ select:not([multiple="multiple"]):focus, input:not(.cbi-button):focus, .cbi-dropdown:focus { border: 1px solid rgba(var(--primary-rgbm), 0.7); + color: var(--inputtext-color); box-shadow: 0 1px 6px var(--input-boxhovercolor) } @@ -1661,17 +1659,13 @@ textarea { -webkit-transition: color 100ms ease, border-color 100ms ease, opacity 100ms ease; outline: none; background-color: var(--input-bgcolor); - color: var(--inputtext-color); + color: var(--body-color); border: 1px solid var(--inputborder-color); word-wrap: break-word; white-space: pre-wrap; padding: 10px } -.cbi-input-textarea:focus, -textarea:focus { - box-shadow: 0 0.1rem 0.3rem var(--input-boxcolor) -} .cbi-dynlist { line-height: 1.3; @@ -2067,7 +2061,9 @@ body.modal-overlay-active #modal_overlay { margin-bottom: .5em; max-width: 100% } - +.modal>.cbi-map { + overflow-x: unset; +} .modal>.button-row .btn { line-height: 1.3 } @@ -2263,7 +2259,7 @@ tr>td, tr>th, .tr>.td, .tr>.th, .cbi-section-table-row::before, #cbi-wireless>#w padding: 0.7rem 1rem; } table .tr>.td.cbi-value-field>[id*="ifc-description"] { - text-align: center; + text-align: left; font-weight: 400 !important } @@ -2404,9 +2400,12 @@ div>.table>.tr:nth-of-type(2n+1):hover { .cbi-value { display: inline-block; width: 100%; - line-height: 2.3rem; } +.cbi-value>.cbi-value-field, +.cbi-value>.cbi-value-title { + line-height: 2.3rem; +} .cbi-value:first-child { padding-top: 1rem } @@ -2546,11 +2545,10 @@ td>.ifacebadge, .nft-rules tr>td, .nft-rules tr>th { text-align: left !important; + line-height: 1.5; + padding: 0.4rem 0.8rem !important; } -.nft-rules tr>td{ - line-height: 2rem; -} .network-status-table { display: flex @@ -3107,7 +3105,6 @@ body.lang_pl.node-main-login .cbi-value-title { #view>div, #view>table { backdrop-filter: var(--ufilter); - /* width: 100%; */ -webkit-backdrop-filter: var(--ufilter) } @@ -3375,13 +3372,17 @@ body.lang_pl.node-main-login .cbi-value-title { } [data-page^="admin-services-openclash"] .cbi-section, -[data-page^="admin-services-openclash"] .cbi-section>div, -[data-page^="admin-services-openclash"] .cbi-tabmenu { +[data-page^="admin-services-openclash"] .cbi-section>div{ background-color: rgba(var(--primary-rgbbody), 0.2) !important; border: 0px solid #ddd !important; box-shadow: none !important } +[data-page^="admin-services-openclash"] .cbi-tabmenu { + background-color: rgba(var(--primary-rgbbody), 0.2) !important; + border: 0px solid #ddd !important; + +} [data-page^="admin-services-openclash"] #tab { border: 0px solid #ddd !important; box-shadow: none !important @@ -3667,7 +3668,7 @@ pre { .container .alert, .container .alert-message, .cbi-map-descr+fieldset { - margin-top: 1rem + margin: 1% } button, @@ -3962,6 +3963,7 @@ input, align-items: center; flex: 1; } + } @media only screen and (max-width: 768px) { @@ -4068,7 +4070,6 @@ input, display: flex; align-items: center; flex-wrap: wrap; - width: 100%; padding: 0.5rem } diff --git a/luci-theme-kucat/root/usr/libexec/rpcd/luci.kucatget b/luci-theme-kucat/root/usr/libexec/rpcd/luci.kucatget index 830be54..50dd71d 100644 --- a/luci-theme-kucat/root/usr/libexec/rpcd/luci.kucatget +++ b/luci-theme-kucat/root/usr/libexec/rpcd/luci.kucatget @@ -37,8 +37,9 @@ chmod 755 "$PIC_LINK_DIR" "$PIC_TMP_DIR" # 获取可用的WAN接口 get_available_wan_interfaces() { - # 获取所有活跃的网络接口 - for iface in $(ip route | grep default | awk '{print $5}' | sort -u); do + ifaces=$(ip route | grep default | awk '{print $5}' | sort -u) + [ $ifaces = 'br-lan' ] && return + for iface in $ifaces; do if [ -n "$iface" ] && ip link show "$iface" 2>/dev/null | grep -q "state UP"; then echo "$iface" fi @@ -94,6 +95,7 @@ http_request() { esac } + # 使用可用工具进行HTTP请求(支持自定义header和多WAN接口) http_request_with_header() { local url="$1" @@ -178,38 +180,21 @@ fetch_daily_word() { ;; esac } - -check_network_curl() { - # 尝试访问知名网站,超时3秒 - if curl -s --connect-timeout 3 -I "https://www.qq.com" > /dev/null 2>&1; then - echo 1 - else - echo 2 - fi -} -check_network_wget() { - # 尝试下载小文件,超时3秒 - if wget -T 3 -q --spider "https://www.qq.com" > /dev/null 2>&1; then - echo 1 - else - echo 2 - fi -} check_network() { - # 检查DNS解析 - if ! nslookup "www.qq.com" > /dev/null 2>&1; then - echo 2 - return + if ! nslookup "www.qq.com" 114.114.114.114 >/dev/null 2>&1 ; then + return 1 # 失败 fi + + for test_url in "https://www.qq.com" "https://www.baidu.com" "https://www.taobao.com"; do + if curl -s --connect-timeout 3 -I "$test_url" >/dev/null 2>&1; then + return 0 # 成功 + fi + done - # 检查HTTP连接 - if curl -s --connect-timeout 3 "https://www.qq.com" > /dev/null 2>&1; then - echo 1 - else - echo 2 - fi + return 1 # 失败 } + try_update_word() { local lock="$WORD_LOCK" exec 200>"$lock" @@ -283,20 +268,21 @@ try_down_pic() { case "$tool" in "curl") - if [ -z "$interfaces" ]; then - curl -kLfsm 30 "$bgurl" -o "${tmp_file}" > /dev/null 2>&1 && download_success=1 - else + if [ -n "$interfaces" ] && [ "$interfaces" != "" ] ; then + # 尝试每个可用的WAN接口 for iface in $interfaces; do - if curl --interface "$iface" -kLfsm 30 "$bgurl" -o "${tmp_file}" > /dev/null 2>&1; then + if curl --interface "$iface" -kLfsm 3 "$bgurl" -o "${tmp_file}" > /dev/null 2>&1; then download_success=1 break fi done + else + curl -kLfsm 3 "$bgurl" -o "${tmp_file}" > /dev/null 2>&1 && download_success=1 fi ;; "wget") - wget -q -T 30 -t 2 -O "${tmp_file}" "$bgurl" > /dev/null 2>&1 && download_success=1 + wget -q -T 3 -t 2 -O "${tmp_file}" "$bgurl" > /dev/null 2>&1 && download_success=1 ;; esac @@ -398,7 +384,7 @@ case "$1" in "get_url") read -r input if [ -s "$PIC_CACHE" ]; then - pictime=$(cat $PIC_CACHE | grep $(date +%Y%m%d)) + pictime=$(grep $(date +%Y%m%d) $PIC_CACHE) if [ -n "$pictime" ]; then json_init json_add_string "url" "$PIC_RE_URL" @@ -426,7 +412,7 @@ case "$1" in "get_word") read -r input if [ -s "$WORD_CACHE" ]; then - wordtime=$(cat $WORD_CACHE | grep $(date +%Y%m%d)) + wordtime=$(grep $(date +%Y%m%d) $WORD_CACHE ) if [ -n "$wordtime" ]; then json_init json_add_string "word" "$(cat "$WORD_CACHE" | head -n1)" diff --git a/luci-theme-kucat/ucode/template/themes/kucat/header.ut b/luci-theme-kucat/ucode/template/themes/kucat/header.ut index b059e15..f70c569 100644 --- a/luci-theme-kucat/ucode/template/themes/kucat/header.ut +++ b/luci-theme-kucat/ucode/template/themes/kucat/header.ut @@ -200,7 +200,7 @@ if (dayword == "1") { --font-d: {{ fontd }};--font-z: {{ fontz }};--font-x: {{ fontx }};--ufilter: {{ ufilter }}; {% if (automode == 'dark'): %} --menu-item-titlebg-color: rgba(var(--primary-rgbm),0.32);--body-hover-bgcolor: rgba(255,255,255,0.05);--menu-hover-color: #f5f5f5f5;--menu-fontcolor: #bbb;--primarytextcolor: #bbb;--primary-title-color: #ccc;--menu-color: #ddd;--title-color: #ddd;--body-color: #bbb; - --primary-rgbbody:33,45,60;--inputtext-color: #ccc; --inputborder-color: rgba(255,255,255,0.2); --input-bgcolor: rgba(0,0,0,0.2); --input-boxcolor: rgba(255,255,255,0.22); --input-boxhovercolor: rgba(255,255,255,0.32); --input-checkcolor: rgba(255,255,255,0.7);--filter-color:invert(90%); + --primary-rgbbody:33,45,60;--inputtext-color: #ccc; --inputborder-color: rgba(255,255,255,0.2); --input-bgcolor: rgba(0,0,0,0.12); --input-boxcolor: rgba(255,255,255,0.22); --input-boxhovercolor: rgba(255,255,255,0.32); --input-checkcolor: rgba(255,255,255,0.7);--filter-color:invert(90%); {% else %} --menu-item-titlebg-color: rgba(var(--primary-rgbm),0.22);--body-hover-bgcolor: rgba(50,50,50,0.05);--menu-hover-color: #fff;--menu-fontcolor: #f5f5f5f5;--primarytextcolor: #505063;--primary-title-color: #4d4d5d;--menu-color: #eee;--title-color: #3c3c3c;--body-color: #424242; --primary-rgbbody:244,245,247;--inputtext-color: #383838; --inputborder-color: rgba(0,0,0,0.2); --input-bgcolor: rgba(255,255,255,0.2); --input-boxcolor: rgba(0,0,0,0.22); --input-boxhovercolor: rgba(0,0,0,0.32); --input-checkcolor: rgba(var(--primary-rgbm),1);--filter-color:invert(10%); diff --git a/nikki/Makefile b/nikki/Makefile index 27605ad..18a5988 100644 --- a/nikki/Makefile +++ b/nikki/Makefile @@ -1,15 +1,15 @@ include $(TOPDIR)/rules.mk PKG_NAME:=nikki -PKG_VERSION:=2025.11.09 +PKG_VERSION:=2025.12.01 PKG_RELEASE:=1 PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz PKG_SOURCE_SUBDIR:=$(PKG_NAME)-$(PKG_VERSION) PKG_SOURCE_PROTO:=git PKG_SOURCE_URL:=https://github.com/MetaCubeX/mihomo.git -PKG_SOURCE_VERSION:=v1.19.16 -PKG_MIRROR_HASH:=35893f8458d21fac840b150d94b8c60e3ce3a50769e196cbcf197f23a070c076 +PKG_SOURCE_VERSION:=v1.19.17 +PKG_MIRROR_HASH:=79d4d6f23e4a0bcec7846a96b954aa96da00bfffd9b6afe5d06d76f03baf0d22 PKG_LICENSE:=GPL3.0+ PKG_MAINTAINER:=Joseph Mory diff --git a/openwrt-bandix/Makefile b/openwrt-bandix/Makefile index 4177929..47b2fbc 100644 --- a/openwrt-bandix/Makefile +++ b/openwrt-bandix/Makefile @@ -1,8 +1,8 @@ include $(TOPDIR)/rules.mk PKG_NAME:=bandix -PKG_VERSION:=0.10.2 -PKG_RELEASE:=1 +PKG_VERSION:=0.10.3 +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.10.2 +RUST_BANDIX_VERSION:=0.10.3 RUST_BINARY_FILENAME:=bandix-$(RUST_BANDIX_VERSION)-$(RUSTC_TARGET_ARCH).tar.gz diff --git a/openwrt-bandix/files/bandix.init b/openwrt-bandix/files/bandix.init index 46d28da..0ed457f 100644 --- a/openwrt-bandix/files/bandix.init +++ b/openwrt-bandix/files/bandix.init @@ -25,6 +25,18 @@ start_service() { local dns_enabled local dns_max_records + # 这个逻辑在每次服务启动时执行,确保备份配置始终正确 + if [ -f /etc/sysupgrade.conf ]; then + # 添加数据目录到备份列表 + if ! grep -q "^/usr/share/bandix$" /etc/sysupgrade.conf; then + echo "/usr/share/bandix" >> /etc/sysupgrade.conf + fi + # 添加配置文件到备份列表 + if ! grep -q "^/etc/config/bandix$" /etc/sysupgrade.conf; then + echo "/etc/config/bandix" >> /etc/sysupgrade.conf + fi + fi + config_load 'bandix' config_get iface 'general' 'iface' config_get port 'general' 'port' @@ -75,7 +87,7 @@ start_service() { procd_open_instance bandix procd_set_param command $PROG $args - procd_set_param respawn ${respawn_threshold:-3600} ${respawn_timeout:-5} ${respawn_retry:-5} + procd_set_param respawn ${respawn_threshold:-3600} ${respawn_timeout:-10} ${respawn_retry:-10} procd_set_param stderr 1 procd_set_param stdout 1 procd_set_param pidfile /var/run/bandix.pid diff --git a/xray-core/Makefile b/xray-core/Makefile index f7dc4c6..4957050 100644 --- a/xray-core/Makefile +++ b/xray-core/Makefile @@ -1,12 +1,12 @@ include $(TOPDIR)/rules.mk PKG_NAME:=xray-core -PKG_VERSION:=25.12.1 +PKG_VERSION:=25.12.2 PKG_RELEASE:=1 PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz PKG_SOURCE_URL:=https://codeload.github.com/XTLS/Xray-core/tar.gz/v$(PKG_VERSION)? -PKG_HASH:=f1ab22b87e9e446d5e96cb00a8593b85826cc8a1bbc960e0eb0081293d6a32ab +PKG_HASH:=e39c40b85decddea0b59719dae33df26aa149ac6fc673e7db9266e731cc2b3ad PKG_MAINTAINER:=Tianling Shen PKG_LICENSE:=MPL-2.0