🔥 Sync 2025-12-03 00:14:03

This commit is contained in:
actions-user
2025-12-03 00:14:03 +08:00
parent eb5b1b8c4b
commit eb2d5f4c11
47 changed files with 3431 additions and 789 deletions

View File

@@ -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 <jjm2473@gmail.com>
include $(INCLUDE_DIR)/package.mk

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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);
}
}

View File

@@ -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) {

View File

@@ -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() {

View File

@@ -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 '<code style="background: rgba(0,0,0,0.1); padding: 2px 4px; border-radius: 3px; font-family: monospace; font-size: 0.9em;">' + escapeHtml(code) + '</code>';
});
// 处理换行GitHub markdown 使用 \r\n
text = text.replace(/\r\n/g, '\n');
// 处理标题
text = text.replace(/^### (.*$)/gm, '<h3 style="font-size: 1.1em; font-weight: 600; margin: 12px 0 8px 0;">$1</h3>');
text = text.replace(/^## (.*$)/gm, '<h2 style="font-size: 1.2em; font-weight: 600; margin: 14px 0 10px 0;">$1</h2>');
text = text.replace(/^# (.*$)/gm, '<h1 style="font-size: 1.3em; font-weight: 600; margin: 16px 0 12px 0;">$1</h1>');
// 处理粗体
text = text.replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>');
text = text.replace(/__(.+?)__/g, '<strong>$1</strong>');
// 处理斜体
text = text.replace(/\*(.+?)\*/g, function(match, content) {
// 避免匹配代码块中的 *
if (match.indexOf('<code') === -1 && match.indexOf('</code>') === -1) {
return '<em>' + content + '</em>';
}
return match;
});
text = text.replace(/_(.+?)_/g, function(match, content) {
// 避免匹配代码块中的 _
if (match.indexOf('<code') === -1 && match.indexOf('</code>') === -1) {
return '<em>' + content + '</em>';
}
return match;
});
// 处理链接
text = text.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2" target="_blank" rel="noopener" style="color: #3b82f6; text-decoration: underline;">$1</a>');
// 处理图片
text = text.replace(/!\[([^\]]*)\]\(([^)]+)\)/g, '<img src="$2" alt="$1" style="max-width: 100%; height: auto; border-radius: 4px; margin: 8px 0;" />');
// 处理无序列表
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('<ul style="margin: 8px 0; padding-left: 20px;">');
inList = true;
}
result.push('<li style="margin: 4px 0;">' + listMatch[1] + '</li>');
} else {
if (inList) {
result.push('</ul>');
inList = false;
}
result.push(line);
}
}
if (inList) {
result.push('</ul>');
}
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('<ol style="margin: 8px 0; padding-left: 20px;">');
inList = true;
}
result.push('<li style="margin: 4px 0;">' + orderedMatch[1] + '</li>');
} else {
if (inList) {
result.push('</ol>');
inList = false;
}
result.push(line);
}
}
if (inList) {
result.push('</ol>');
}
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 '<p style="margin: 8px 0; line-height: 1.6;">' + para + '</p>';
}
return para;
}).join('\n');
// 处理单行换行(两个空格或反斜杠结尾)
text = text.replace(/ \n/g, '<br />\n');
text = text.replace(/\\\n/g, '<br />\n');
// 恢复代码块
for (var j = 0; j < codeBlocks.length; j++) {
var block = codeBlocks[j];
text = text.replace(block.id, '<pre style="background: rgba(0,0,0,0.05); padding: 12px; border-radius: 4px; overflow-x: auto; font-family: monospace; font-size: 0.875rem; margin: 8px 0;"><code>' + block.content + '</code></pre>');
}
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, '<br>');
}
}).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();
}
});

View File

@@ -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."

View File

@@ -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 : "
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."

View File

@@ -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."

View File

@@ -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 "更新後、ブラウザのキャッシュを手動でクリアしてください。"

View File

@@ -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."

View File

@@ -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 "Не удалось перезапустить службу: "
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 "Пожалуйста, вручную очистите кеш браузера после обновления."

View File

@@ -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 "กรุณาล้างแคชเบราว์เซอร์ด้วยตนเองหลังจากอัปเดต"

View File

@@ -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 "重启服务失败:"
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 "有更新,点击前往设置"

View File

@@ -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 "重啟服務失敗:"
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 "更新後請手動清空瀏覽器快取。"

View File

@@ -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

View File

@@ -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": [

View File

@@ -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 {
</table>
<div class="cbi-section-create cbi-tblsection-create">
<input class="cbi-button cbi-button-add" type="button" value="<%:Add%>" onclick="to_add_node()">
<input class="cbi-button cbi-button-apply" type="button" id="save_order_btn_{{group}}" value="<%:Save Order%>" onclick="save_current_page_order('{{group}}')">
<input class="cbi-button cbi-button-apply" style="display: none" type="button" id="save_order_btn_{{group}}" value="<%:Save Order%>" onclick="save_current_page_order('{{group}}')">
</div>
</fieldset>
</script>
@@ -740,7 +795,7 @@ table td, .table .td {
<td class="td cbi-value-field pw-urltest">{{url_test}}</td>
<td class="td cbi-section-table-cell nowrap cbi-section-actions pw-actions">
<div class="node-wrapper">
<input class="btn cbi-button cbi-button-edit" type="button" value="<%:To Top%>" onclick="row_top(this)" title="<%:To Top%>"/>
<input class="btn cbi-button cbi-button-edit" type="button" value="<%:To Top%>" onclick="row_top(this, '{{group}}')" title="<%:To Top%>"/>
<input class="btn cbi-button cbi-button-apply" type="button" value="<%:Use%>" id="apply_{{id}}" onclick="open_set_node_div('{{id}}')"/>
<input class="btn cbi-button cbi-button-add" type="button" value="<%:Copy%>" onclick="copy_node('{{id}}')"/>
<input class="btn cbi-button cbi-button-edit" type="button" value="<%:Edit%>" onclick="location.href='<%=api.url("node_config")%>/{{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

View File

@@ -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)

File diff suppressed because one or more lines are too long

View File

@@ -80,6 +80,7 @@ function index()
entry({"admin", "services", appname, "clear_all_nodes"}, call("clear_all_nodes")).leaf = true
entry({"admin", "services", appname, "delete_select_nodes"}, call("delete_select_nodes")).leaf = true
entry({"admin", "services", appname, "get_node"}, call("get_node")).leaf = true
entry({"admin", "services", appname, "save_node_order"}, call("save_node_order")).leaf = true
entry({"admin", "services", appname, "update_rules"}, call("update_rules")).leaf = true
entry({"admin", "services", appname, "subscribe_del_node"}, call("subscribe_del_node")).leaf = true
entry({"admin", "services", appname, "subscribe_del_all"}, call("subscribe_del_all")).leaf = true
@@ -504,7 +505,7 @@ function get_node()
local result = {}
local show_node_info = api.uci_get_type("global_other", "show_node_info", "0")
function add_is_ipv6_key(o)
local function add_is_ipv6_key(o)
if o and o.address and show_node_info == "1" then
local f = api.get_ipv6_full(o.address)
if f ~= "" then
@@ -518,14 +519,35 @@ function get_node()
result = uci:get_all(appname, id)
add_is_ipv6_key(result)
else
local default_nodes = {}
local other_nodes = {}
uci:foreach(appname, "nodes", function(t)
add_is_ipv6_key(t)
result[#result + 1] = t
if not t.group or t.group == "" then
default_nodes[#default_nodes + 1] = t
else
other_nodes[#other_nodes + 1] = t
end
end)
for i = 1, #default_nodes do result[#result + 1] = default_nodes[i] end
for i = 1, #other_nodes do result[#result + 1] = other_nodes[i] end
end
http_write_json(result)
end
function save_node_order()
local ids = http.formvalue("ids") or ""
local new_order = {}
for id in ids:gmatch("([^,]+)") do
new_order[#new_order + 1] = id
end
for idx, name in ipairs(new_order) do
luci.sys.call(string.format("uci -q reorder %s.%s=%d", appname, name, idx - 1))
end
api.sh_uci_commit(appname)
http_write_json({ status = "ok" })
end
function update_rules()
local update = http.formvalue("update")
luci.sys.call("lua /usr/share/passwall2/rule_update.lua log '" .. update .. "' > /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)

View File

@@ -209,7 +209,7 @@ o.cfgvalue = function(t, n)
str = str ~= "" and "<br>" .. 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)

View File

@@ -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 servers 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

View File

@@ -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

View File

@@ -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

View File

@@ -3,13 +3,19 @@ local api = require "luci.passwall2.api"
-%>
<script type="text/javascript">
//<![CDATA[
var log_textarea = null;
var first_load_done = false;
function scrollToBottom() {
log_textarea.scrollTop = log_textarea.scrollHeight;
}
function clearlog(btn) {
XHR.get('<%=api.url("clear_log")%>', null,
function(x, data) {
if(x && x.status == 200) {
var log_textarea = document.getElementById('log_textarea');
if(x && x.status == 200 && log_textarea) {
log_textarea.innerHTML = "";
log_textarea.scrollTop = log_textarea.scrollHeight;
scrollToBottom();
}
}
);
@@ -17,8 +23,16 @@ local api = require "luci.passwall2.api"
XHR.poll(5, '<%=api.url("get_log")%>', null,
function(x, data) {
if(x && x.status == 200) {
var log_textarea = document.getElementById('log_textarea');
if (!log_textarea) log_textarea = document.getElementById('log_textarea');
var wasBottom = (log_textarea.scrollTop + log_textarea.clientHeight >= log_textarea.scrollHeight - 10);
log_textarea.innerHTML = x.responseText;
if (!first_load_done) {
scrollToBottom();
first_load_done = true;
}
else if (wasBottom) {
scrollToBottom();
}
}
}
);

View File

@@ -52,7 +52,7 @@ local api = require "luci.passwall2.api"
function add_node() {
var nodes_link = document.getElementById("nodes_link").value;
var group = (document.querySelector('#addlink_group_custom input[type="hidden"]')?.value || "default");
nodes_link = nodes_link.replace(/\t/g, "").replace(/\r\n|\r/g, "\n").trim();
nodes_link = nodes_link.replace(/\t/g, "").replace(/\r\n|\r/g, "\n").replace(/<[^>]*>/g, '').trim();
if (nodes_link != "") {
var s = nodes_link.split('://');
if (s.length > 1) {

View File

@@ -18,6 +18,7 @@ if node then
end
end
-%>
<script src="<%=resource%>/view/<%=api.appname%>/Sortable.min.js"></script>
<style>
table th, .table .th {
@@ -55,9 +56,9 @@ table td, .table .td {
}
@media (prefers-color-scheme: dark) {
._now_use_bg {
background: #4a90e2 !important;
}
._now_use_bg {
background: #4a90e2 !important;
}
}
.td.cbi-section-actions {
@@ -70,20 +71,112 @@ table td, .table .td {
gap: 4px;
}
.node-wrapper .cbi-input-checkbox {
flex-grow: 0 !important;
flex-basis: auto;
}
.cbi-tabmenu > li {
margin-right: 2px !important;
margin-right: 2px !important;
}
.cbi-tabmenu > li:last-child {
margin-right: 0 !important;
margin-right: 0 !important;
}
.node-wrapper .drag-handle {
cursor: grab !important;
display: inline-flex;
align-items: center;
justify-content: center;
font-size: 20px;
font-weight: 100;
padding: 0 !important;
line-height: inherit;
user-select: none;
color: #00000070;
align-self: stretch;
}
#cbi-passwall2-nodes .pw-checkbox, #cbi-passwall2-nodes th:nth-child(1) {
padding-right: 0px;
}
#select_all_btn {
display: none;
}
/* enable flex for small screens*/
@media screen and (max-width: 1152px) {
.cbi-section-table-row {
display: flex;
flex-direction: row;
flex-wrap: wrap;
justify-content: space-between;
}
#cbi-passwall2-nodes-default-fieldset {
margin: 0;
}
.cbi-section-table-titles {
display: none !important;
}
/* meticulously control how each component occupies the limited space we have */
#cbi-passwall2-nodes .pw-checkbox, #cbi-passwall2-nodes th:nth-child(1) {
flex: 0 0 40px;
min-width: 0;
}
#cbi-passwall2-nodes .pw-remark {
flex: 1 1 30%;
min-width: 0;
}
#cbi-passwall2-nodes .pw-ping, #cbi-passwall2-nodes .pw-tcping, #cbi-passwall2-nodes .pw-urltest {
flex: 0 0 50px;
white-space: nowrap;
min-width: 0;
}
.pw-actions {
padding-top: 0 !important;
border-top-width: 0 !important;
flex: 1 1 350px;
}
#select_all_btn {
display: inline-block !important;
}
}
/* shrink actionbar even further for mobile devices */
@media screen and (max-width: 500px) {
.node-wrapper {
gap: 0;
}
.cbi-button {
margin-left: 0 !important;
margin-right: 1px !important;
}
.pw-actions {
padding-left: 5px!important;
padding-right: 5px !important;
}
}
.sortable-chosen {
background-color: rgba(220, 235, 245, 0.4) !important;
opacity: 0.7;
}
.sortable-ghost {
background: #cce5ff !important;
height: 3px !important;
}
.dragging-row {
background-color: rgba(131, 191, 255, 0.7) !important;
box-shadow: 0 4px 6px rgba(0,0,0,0.1);
}
</style>
<% 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);
}
}
}
</script>
@@ -512,6 +754,9 @@ table td, .table .td {
<fieldset class="cbi-section cbi-tblsection" id="cbi-passwall2-nodes-{{group}}-fieldset">
<table class="table cbi-section-table" id="cbi-passwall2-nodes-{{group}}-table" style="">
<tr class="tr cbi-section-table-titles anonymous">
<th class="th cbi-section-table-cell" style="width:20px">
<input class="cbi-input-checkbox nodes_select_all" type="checkbox" onclick="checked_all_node(this)" title="<%:Select all%>"/>
</th>
<th class="th cbi-section-table-cell" style="width:40%"><%:Remarks%></th>
<th class="th cbi-section-table-cell" style="width:8%">Ping</th>
<th class="th cbi-section-table-cell" style="width:8%">TCPing</th>
@@ -522,6 +767,7 @@ table td, .table .td {
</table>
<div class="cbi-section-create cbi-tblsection-create">
<input class="cbi-button cbi-button-add" type="button" value="<%:Add%>" onclick="to_add_node()">
<input class="cbi-button cbi-button-apply" style="display: none" type="button" id="save_order_btn_{{group}}" value="<%:Save Order%>" onclick="save_current_page_order('{{group}}')">
</div>
</fieldset>
</script>
@@ -531,19 +777,21 @@ table td, .table .td {
<input class="hidden" id="cbid.passwall2.{{id}}.remarks" value="{{remarks_val}}"/>
<input class="hidden" id="cbid.passwall2.{{id}}.address" value="{{address_val}}"/>
<input class="hidden" id="cbid.passwall2.{{id}}.port" value="{{port_val}}"/>
<td class="td cbi-value-field">{{remarks}}</td>
<td class="td cbi-value-field">{{ping}}</td>
<td class="td cbi-value-field">{{tcping}}</td>
<td class="td cbi-value-field">{{url_test}}</td>
<td class="td cbi-section-table-cell nowrap cbi-section-actions">
<td class="td cbi-value-field pw-checkbox">
<input class="cbi-input-checkbox nodes_select" type="checkbox" cbid="{{id}}" onclick="update_select_state()"/>
</td>
<td class="td cbi-value-field pw-remark">{{remarks}}</td>
<td class="td cbi-value-field pw-ping">{{ping}}</td>
<td class="td cbi-value-field pw-tcping">{{tcping}}</td>
<td class="td cbi-value-field pw-urltest">{{url_test}}</td>
<td class="td cbi-section-table-cell nowrap cbi-section-actions pw-actions">
<div class="node-wrapper">
<!--It has been damaged and awaits repair or other solutions.-->
<!--<input class="btn cbi-button" type="button" value="<%:To Top%>" onclick="_cbi_row_top('{{id}}')"/>-->
<input class="cbi-input-checkbox nodes_select" type="checkbox" cbid="{{id}}" />
<input class="btn cbi-button cbi-button-edit" type="button" value="<%:To Top%>" onclick="row_top(this)" title="<%:To Top%>"/>
<input class="btn cbi-button cbi-button-apply" type="button" value="<%:Use%>" id="apply_{{id}}" onclick="open_set_node_div('{{id}}')"/>
<input class="btn cbi-button cbi-button-add" type="button" value="<%:Copy%>" onclick="copy_node('{{id}}')"/>
<input class="btn cbi-button cbi-button-edit" type="button" value="<%:Edit%>" onclick="location.href='<%=api.url("node_config")%>/{{id}}'" alt="<%:Edit%>" title="<%:Edit%>">
<input class="btn cbi-button cbi-button-remove" type="button" value="<%:Delete%>" onclick="del_node('{{id}}')" alt="<%:Delete%>" title="<%:Delete%>">
<span class="drag-handle center" title="<%:Drag to reorder%>"></span>
</div>
</td>
</tr>
@@ -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();

View File

@@ -3,13 +3,19 @@ local api = require "luci.passwall2.api"
-%>
<script type="text/javascript">
//<![CDATA[
var log_textarea = null;
var first_load_done = false;
function scrollToBottom() {
log_textarea.scrollTop = log_textarea.scrollHeight;
}
function clear_log(btn) {
XHR.get('<%=api.url("server_clear_log")%>', null,
function(x, data) {
if(x && x.status == 200) {
var log_textarea = document.getElementById('log_textarea');
if(x && x.status == 200 && log_textarea) {
log_textarea.innerHTML = "";
log_textarea.scrollTop = log_textarea.scrollHeight;
scrollToBottom();
}
}
);
@@ -17,9 +23,16 @@ local api = require "luci.passwall2.api"
XHR.poll(3, '<%=api.url("server_get_log")%>', null,
function(x, data) {
if(x && x.status == 200) {
var log_textarea = document.getElementById('log_textarea');
if (!log_textarea) log_textarea = document.getElementById('log_textarea');
var wasBottom = (log_textarea.scrollTop + log_textarea.clientHeight >= log_textarea.scrollHeight - 10);
log_textarea.innerHTML = x.responseText;
log_textarea.scrollTop = log_textarea.scrollHeight;
if (!first_load_done) {
scrollToBottom();
first_load_done = true;
}
else if (wasBottom) {
scrollToBottom();
}
}
}
);

View File

@@ -325,6 +325,15 @@ msgstr "节点备注"
msgid "Add Mode"
msgstr "添加方式"
msgid "Save Order"
msgstr "保存当前顺序"
msgid "Saved current page order successfully."
msgstr "保存当前页面顺序成功。"
msgid "Drag to reorder"
msgstr "拖动以重排"
msgid "Type"
msgstr "类型"
@@ -1573,6 +1582,12 @@ msgstr "协议参数。 如果启用会随机浪费流量。"
msgid "Protocol parameter. Enable length block encryption."
msgstr "协议参数。启用长度块加密。"
msgid "TLS Chain Fingerprint (SHA256)"
msgstr "TLS 证书链指纹SHA256"
msgid "Once set, connects only when the servers chain fingerprint matches."
msgstr "设置后,仅在服务器证书链指纹匹配时连接。"
msgid "ECH Config"
msgstr "ECH 配置"
@@ -2180,7 +2195,7 @@ msgid "%s type node subscriptions are not currently supported, skip this node."
msgstr "暂时不支持 %s 类型的节点订阅,跳过此节点。"
msgid "Update [%s]"
msgstr "'更新【%s】"
msgstr "更新【%s】"
msgid "Matching node:"
msgstr "匹配节点:"
@@ -2203,6 +2218,9 @@ msgstr "第五匹配节点:"
msgid "Unable to find the best matching node, now replaced with:"
msgstr "无法找到最匹配的节点,当前已更换为:"
msgid "Unable to find a new node. Please confirm and process manually."
msgstr "无法匹配到新节点,请手动确认处理。"
msgid "No node information updates are available."
msgstr "没有可用的节点信息更新。"

File diff suppressed because it is too large Load Diff

View File

@@ -7,9 +7,7 @@ local jsonc = api.jsonc
local uci = api.uci
local sys = api.sys
local log = function(...)
api.log(...)
end
local log = api.log
function get_ip_port_from(str)
local result_port = sys.exec("echo -n " .. str .. " | sed -n 's/^.*[:#]\\([0-9]*\\)$/\\1/p'")
@@ -40,8 +38,8 @@ local bind_local = uci:get(appname, "@global_haproxy[0]", "bind_local") or "0"
local bind_address = "0.0.0.0"
if bind_local == "1" then bind_address = "127.0.0.1" end
log("HAProxy: ")
log(" * " .. api.i18n.translatef("Console Port: %s", console_port))
log(0, "HAProxy: ")
log(1, api.i18n.translatef("Console Port: %s", console_port))
fs.mkdir(haproxy_path)
local haproxy_file = haproxy_path .. "/" .. haproxy_conf
@@ -150,7 +148,7 @@ uci:foreach(appname, "haproxy_config", function(t)
t.server_port = server_port
table.insert(listens[listen_port], t)
else
log(" - " .. api.i18n.translate("Discard one obviously invalid node."))
log(1, api.i18n.translate("Discard one obviously invalid node."))
end
end
end)
@@ -164,7 +162,7 @@ end
table.sort(sortTable, function(a,b) return (a < b) end)
for i, port in pairs(sortTable) do
log(" + " .. api.i18n.translatef("Entrance %s:%s", bind_address, port))
log(1, api.i18n.translatef("Entrance %s:%s", bind_address, port))
f_out:write("\n" .. string.format([[
listen %s
@@ -210,7 +208,7 @@ listen %s
sys.call(string.format("/usr/share/passwall2/app.sh add_ip2route %s %s", o.origin_address, o.export))
end
log(string.format(" | - " .. api.i18n.translatef("Node: %s:%s, Weight: %s", o.origin_address, o.origin_port, o.lbweight)))
log(2, string.format(api.i18n.translatef("Node: %s:%s, Weight: %s", o.origin_address, o.origin_port, o.lbweight)))
end
end

View File

@@ -84,7 +84,7 @@ function restart(var)
local LOG = var["-LOG"]
sys.call("/etc/init.d/dnsmasq restart >/dev/null 2>&1")
if LOG == "1" then
api.log(api.i18n.translate("Restart dnsmasq service."))
api.log(0, api.i18n.translate("Restart dnsmasq service."))
end
end
@@ -111,7 +111,7 @@ function logic_restart(var)
sys.call("/etc/init.d/dnsmasq restart >/dev/null 2>&1")
end
if LOG == "1" then
api.log(api.i18n.translate("Restart dnsmasq service."))
api.log(0, api.i18n.translate("Restart dnsmasq service."))
end
end

View File

@@ -3,6 +3,7 @@
local api = require "luci.passwall2.api"
local name = api.appname
local fs = api.fs
local log = api.log
local sys = api.sys
local uci = api.uci
local jsonc = api.jsonc
@@ -19,24 +20,11 @@ local asset_location = uci:get_first(name, 'global_rules', "v2ray_location_asset
-- Custom geo file
local geoip_api = uci:get_first(name, 'global_rules', "geoip_url", "https://api.github.com/repos/Loyalsoldier/v2ray-rules-dat/releases/latest")
local geosite_api = uci:get_first(name, 'global_rules', "geosite_url", "https://api.github.com/repos/Loyalsoldier/v2ray-rules-dat/releases/latest")
--
local use_nft = uci:get(name, "@global_forwarding[0]", "use_nft") or "0"
if arg3 == "cron" then
arg2 = nil
end
local log = function(...)
if arg1 then
if arg1 == "log" then
api.log(...)
elseif arg1 == "print" then
local result = os.date("%Y-%m-%d %H:%M:%S: ") .. table.concat({...}, " ")
print(result)
end
end
end
-- curl
local function curl(url, file)
local args = {
@@ -68,7 +56,7 @@ local function fetch_geoip()
if fs.access(asset_location .. "geoip.dat") then
sys.call(string.format("cp -f %s %s", asset_location .. "geoip.dat", "/tmp/geoip.dat"))
if sys.call('sha256sum -c /tmp/geoip.dat.sha256sum > /dev/null 2>&1') == 0 then
log(api.i18n.translatef("%s version is the same and does not need to be updated.", "geoip"))
log(1, api.i18n.translatef("%s version is the same and does not need to be updated.", "geoip"))
return 1
end
end
@@ -78,10 +66,10 @@ local function fetch_geoip()
if sys.call('sha256sum -c /tmp/geoip.dat.sha256sum > /dev/null 2>&1') == 0 then
sys.call(string.format("mkdir -p %s && cp -f %s %s", asset_location, "/tmp/geoip.dat", asset_location .. "geoip.dat"))
reboot = 1
log(api.i18n.translatef("%s update success.", "geoip"))
log(1, api.i18n.translatef("%s update success.", "geoip"))
return 1
else
log(api.i18n.translatef("%s update failed, please try again later.", "geoip"))
log(1, api.i18n.translatef("%s update failed, please try again later.", "geoip"))
end
break
end
@@ -92,7 +80,7 @@ local function fetch_geoip()
end
end
if json.message then
log(json.message)
log(2, json.message)
end
end,
function(e)
@@ -120,7 +108,7 @@ local function fetch_geosite()
if fs.access(asset_location .. "geosite.dat") then
sys.call(string.format("cp -f %s %s", asset_location .. "geosite.dat", "/tmp/geosite.dat"))
if sys.call('sha256sum -c /tmp/geosite.dat.sha256sum > /dev/null 2>&1') == 0 then
log(api.i18n.translatef("%s version is the same and does not need to be updated.", "geosite"))
log(1, api.i18n.translatef("%s version is the same and does not need to be updated.", "geosite"))
return 1
end
end
@@ -130,10 +118,10 @@ local function fetch_geosite()
if sys.call('sha256sum -c /tmp/geosite.dat.sha256sum > /dev/null 2>&1') == 0 then
sys.call(string.format("mkdir -p %s && cp -f %s %s", asset_location, "/tmp/geosite.dat", asset_location .. "geosite.dat"))
reboot = 1
log(api.i18n.translatef("%s update success.", "geosite"))
log(1, api.i18n.translatef("%s update success.", "geosite"))
return 1
else
log(api.i18n.translatef("%s update failed, please try again later.", "geosite"))
log(1, api.i18n.translatef("%s update failed, please try again later.", "geosite"))
end
break
end
@@ -144,7 +132,7 @@ local function fetch_geosite()
end
end
if json.message then
log(json.message)
log(2, json.message)
end
end,
function(e)
@@ -170,17 +158,17 @@ if geoip_update == 0 and geosite_update == 0 then
os.exit(0)
end
log(api.i18n.translate("Start updating the rules..."))
log(0, api.i18n.translate("Start updating the rules..."))
if tonumber(geoip_update) == 1 then
log(api.i18n.translatef("%s Start updating...", "geoip"))
log(1, api.i18n.translatef("%s Start updating...", "geoip"))
local status = fetch_geoip()
os.remove("/tmp/geoip.dat")
os.remove("/tmp/geoip.dat.sha256sum")
end
if tonumber(geosite_update) == 1 then
log(api.i18n.translatef("%s Start updating...", "geosite"))
log(1, api.i18n.translatef("%s Start updating...", "geosite"))
local status = fetch_geosite()
os.remove("/tmp/geosite.dat")
os.remove("/tmp/geosite.dat.sha256sum")
@@ -197,8 +185,8 @@ if reboot == 1 then
end
end
log(api.i18n.translate("Restart the service and apply the new rules."))
log(1, api.i18n.translate("Restart the service and apply the new rules."))
uci:set(name, "@global[0]", "flush_set", "1")
api.uci_save(uci, name, true, true)
end
log(api.i18n.translate("The rules have been updated..."))
log(0, api.i18n.translate("The rules have been updated..."))

View File

@@ -19,6 +19,7 @@ local jsonParse, jsonStringify = luci.jsonc.parse, luci.jsonc.stringify
local base64Decode = api.base64Decode
local uci = api.uci
local fs = api.fs
local log = api.log
local i18n = api.i18n
uci:revert(appname)
@@ -98,17 +99,6 @@ local function is_filter_keyword(value)
end
local nodeResult = {} -- update result
local debug = false
local log = function(...)
if debug == true then
local result = os.date("%Y-%m-%d %H:%M:%S: ") .. table.concat({...}, " ")
print(result)
else
api.log(...)
end
end
local nodes_table = {}
for k, e in ipairs(api.get_valid_nodes()) do
if e.node_type == "normal" then
@@ -458,7 +448,7 @@ local function set_ss_implementation(result)
result.type = 'sing-box'
result.protocol = 'shadowsocks'
else
log(i18n.translatef("Skipping the %s node is due to incompatibility with the %s core program or incorrect node usage type settings.", "SS", "SS"))
log(2, i18n.translatef("Skipping the %s node is due to incompatibility with the %s core program or incorrect node usage type settings.", "SS", "SS"))
return nil
end
return result
@@ -466,7 +456,7 @@ end
-- Processing data
local function processData(szType, content, add_mode, group)
--log(content, add_mode, group)
--log(2, content, add_mode, group)
local result = {
timeout = 60,
add_mode = add_mode, -- `0` for manual configuration, `1` for import, `2` for subscription
@@ -475,7 +465,7 @@ local function processData(szType, content, add_mode, group)
--ssr://base64(host:port:protocol:method:obfs:base64pass/?obfsparam=base64param&protoparam=base64param&remarks=base64remarks&group=base64group&udpport=0&uot=0)
if szType == 'ssr' then
if not has_ssr then
log(i18n.translatef("Skipping the %s node is due to incompatibility with the %s core program or incorrect node usage type settings.", "SSR", "shadowsocksr-libev"))
log(2, i18n.translatef("Skipping the %s node is due to incompatibility with the %s core program or incorrect node usage type settings.", "SSR", "shadowsocksr-libev"))
return nil
end
result.type = "SSR"
@@ -499,8 +489,8 @@ local function processData(szType, content, add_mode, group)
end
result.obfs_param = base64Decode(params.obfsparam)
result.protocol_param = base64Decode(params.protoparam)
local group = base64Decode(params.group)
if group then result.group = group end
-- local ssr_group = base64Decode(params.group)
-- if ssr_group then result.ssr_group = ssr_group end
result.remarks = base64Decode(params.remarks)
elseif szType == 'vmess' then
local info = jsonParse(content)
@@ -509,7 +499,7 @@ local function processData(szType, content, add_mode, group)
elseif vmess_type_default == "xray" and has_xray then
result.type = "Xray"
else
log(i18n.translatef("Skipping the %s node is due to incompatibility with the %s core program or incorrect node usage type settings.", "VMess", "VMess"))
log(2, i18n.translatef("Skipping the %s node is due to incompatibility with the %s core program or incorrect node usage type settings.", "VMess", "VMess"))
return nil
end
result.alter_id = info.aid
@@ -623,7 +613,7 @@ local function processData(szType, content, add_mode, group)
end
if result.type == "sing-box" and (result.transport == "mkcp" or result.transport == "xhttp") then
log(i18n.translatef("Skip node: %s. Because Sing-Box does not support the %s protocol's %s transmission method, Xray needs to be used instead.", result.remarks, szType, result.transport))
log(2, i18n.translatef("Skip node: %s. Because Sing-Box does not support the %s protocol's %s transmission method, Xray needs to be used instead.", result.remarks, szType, result.transport))
return nil
end
elseif szType == "ss" then
@@ -931,7 +921,7 @@ local function processData(szType, content, add_mode, group)
result.type = 'Xray'
result.protocol = 'trojan'
else
log(i18n.translatef("Skipping the %s node is due to incompatibility with the %s core program or incorrect node usage type settings.", "Trojan", "Trojan"))
log(2, i18n.translatef("Skipping the %s node is due to incompatibility with the %s core program or incorrect node usage type settings.", "Trojan", "Trojan"))
return nil
end
@@ -1077,7 +1067,7 @@ local function processData(szType, content, add_mode, group)
result.alpn = params.alpn
if result.type == "sing-box" and (result.transport == "mkcp" or result.transport == "xhttp") then
log(i18n.translatef("Skip node: %s. Because Sing-Box does not support the %s protocol's %s transmission method, Xray needs to be used instead.", result.remarks, szType, result.transport))
log(2, i18n.translatef("Skip node: %s. Because Sing-Box does not support the %s protocol's %s transmission method, Xray needs to be used instead.", result.remarks, szType, result.transport))
return nil
end
end
@@ -1098,7 +1088,7 @@ local function processData(szType, content, add_mode, group)
elseif vless_type_default == "xray" and has_xray then
result.type = "Xray"
else
log(i18n.translatef("Skipping the %s node is due to incompatibility with the %s core program or incorrect node usage type settings.", "VLESS", "VLESS"))
log(2, i18n.translatef("Skipping the %s node is due to incompatibility with the %s core program or incorrect node usage type settings.", "VLESS", "VLESS"))
return nil
end
result.protocol = "vless"
@@ -1212,7 +1202,7 @@ local function processData(szType, content, add_mode, group)
result.xhttp_path = params.path
result.xhttp_mode = params.mode or "auto"
result.use_xhttp_extra = (params.extra and params.extra ~= "") and "1" or nil
result.xhttp_extra = (params.extra and params.extra ~= "") and params.extra or nil
result.xhttp_extra = (params.extra and params.extra ~= "") and api.base64Encode(params.extra) or nil
local success, Data = pcall(jsonParse, params.extra)
if success and Data then
local address = (Data.extra and Data.extra.downloadSettings and Data.extra.downloadSettings.address)
@@ -1264,7 +1254,7 @@ local function processData(szType, content, add_mode, group)
end
if result.type == "sing-box" and (result.transport == "mkcp" or result.transport == "xhttp") then
log(i18n.translatef("Skip node: %s. Because Sing-Box does not support the %s protocol's %s transmission method, Xray needs to be used instead.", result.remarks, szType, result.transport))
log(2, i18n.translatef("Skip node: %s. Because Sing-Box does not support the %s protocol's %s transmission method, Xray needs to be used instead.", result.remarks, szType, result.transport))
return nil
end
end
@@ -1273,7 +1263,7 @@ local function processData(szType, content, add_mode, group)
result.type = 'sing-box'
result.protocol = "hysteria"
else
log(i18n.translatef("Skip the %s node because the %s core program is not installed.", "Hysteria", "Hysteria", "Sing-Box"))
log(2, i18n.translatef("Skip the %s node because the %s core program is not installed.", "Hysteria", "Hysteria", "Sing-Box"))
return nil
end
@@ -1381,7 +1371,7 @@ local function processData(szType, content, add_mode, group)
result.hysteria2_obfs = params["obfs-password"] or params["obfs_password"]
end
else
log(i18n.translatef("Skipping the %s node is due to incompatibility with the %s core program or incorrect node usage type settings.", "Hysteria2", "Hysteria2"))
log(2, i18n.translatef("Skipping the %s node is due to incompatibility with the %s core program or incorrect node usage type settings.", "Hysteria2", "Hysteria2"))
return nil
end
elseif szType == 'tuic' then
@@ -1389,7 +1379,7 @@ local function processData(szType, content, add_mode, group)
result.type = 'sing-box'
result.protocol = "tuic"
else
log(i18n.translatef("Skip the %s node because the %s core program is not installed.", "Tuic", "Tuic", "Sing-Box"))
log(2, i18n.translatef("Skip the %s node because the %s core program is not installed.", "Tuic", "Tuic", "Sing-Box"))
return nil
end
@@ -1449,7 +1439,7 @@ local function processData(szType, content, add_mode, group)
result.type = 'sing-box'
result.protocol = "anytls"
else
log(i18n.translatef("Skip the %s node because the %s core program is not installed.", "AnyTLS", "AnyTLS", "Sing-Box 1.12"))
log(2, i18n.translatef("Skip the %s node because the %s core program is not installed.", "AnyTLS", "AnyTLS", "Sing-Box 1.12"))
return nil
end
@@ -1513,12 +1503,12 @@ local function processData(szType, content, add_mode, group)
local singbox_version = api.get_app_version("sing-box")
local version_ge_1_12 = api.compare_versions(singbox_version:match("[^v]+"), ">=", "1.12.0")
if not has_singbox or not version_ge_1_12 then
log(i18n.translatef("Skip the %s node, as %s type nodes require Sing-Box version 1.12 or higher.", result.remarks, szType))
log(2, i18n.translatef("Skip the %s node, as %s type nodes require Sing-Box version 1.12 or higher.", result.remarks, szType))
return nil
end
end
else
log(i18n.translatef("%s type node subscriptions are not currently supported, skip this node.", szType))
log(2, i18n.translatef("%s type node subscriptions are not currently supported, skip this node.", szType))
return nil
end
if not result.remarks or result.remarks == "" then
@@ -1597,6 +1587,10 @@ local function truncate_nodes(group)
end
local function select_node(nodes, config, parentConfig)
local log_level = 1
if parentConfig then
log_level = log_level + 1
end
if config.currentNode then
local server
-- Special priority: cfgid
@@ -1604,7 +1598,7 @@ local function select_node(nodes, config, parentConfig)
for index, node in pairs(nodes) do
if node[".name"] == config.currentNode[".name"] then
if config.log == nil or config.log == true then
log(i18n.translatef("Update [%s]", config.remarks) .. " " .. i18n.translatef("Matching node:") .. " " .. node.remarks)
log(log_level, i18n.translatef("Update [%s]", config.remarks) .. " " .. i18n.translatef("Matching node:") .. " " .. node.remarks)
end
server = node[".name"]
break
@@ -1618,7 +1612,7 @@ local function select_node(nodes, config, parentConfig)
if node.type and node.remarks and node.address and node.port then
if node.type == config.currentNode.type and node.remarks == config.currentNode.remarks and (node.address .. ':' .. node.port == config.currentNode.address .. ':' .. config.currentNode.port) then
if config.log == nil or config.log == true then
log(i18n.translatef("Update [%s]", config.remarks) .. " " .. i18n.translatef("First Matching node:") .. " " .. node.remarks)
log(log_level, i18n.translatef("Update [%s]", config.remarks) .. " " .. i18n.translatef("First Matching node:") .. " " .. node.remarks)
end
server = node[".name"]
break
@@ -1634,7 +1628,7 @@ local function select_node(nodes, config, parentConfig)
if node.type and node.address and node.port then
if node.type == config.currentNode.type and (node.address .. ':' .. node.port == config.currentNode.address .. ':' .. config.currentNode.port) then
if config.log == nil or config.log == true then
log(i18n.translatef("Update [%s]", config.remarks) .. " " .. i18n.translatef("Second Matching node:") .. " " .. node.remarks)
log(log_level, i18n.translatef("Update [%s]", config.remarks) .. " " .. i18n.translatef("Second Matching node:") .. " " .. node.remarks)
end
server = node[".name"]
break
@@ -1650,7 +1644,7 @@ local function select_node(nodes, config, parentConfig)
if node.address and node.port then
if node.address .. ':' .. node.port == config.currentNode.address .. ':' .. config.currentNode.port then
if config.log == nil or config.log == true then
log(i18n.translatef("Update [%s]", config.remarks) .. " " .. i18n.translatef("Third Matching node:") .. " " .. node.remarks)
log(log_level, i18n.translatef("Update [%s]", config.remarks) .. " " .. i18n.translatef("Third Matching node:") .. " " .. node.remarks)
end
server = node[".name"]
break
@@ -1666,7 +1660,7 @@ local function select_node(nodes, config, parentConfig)
if node.address then
if node.address == config.currentNode.address then
if config.log == nil or config.log == true then
log(i18n.translatef("Update [%s]", config.remarks) .. " " .. i18n.translatef("Fourth Matching node:") .. " " .. node.remarks)
log(log_level, i18n.translatef("Update [%s]", config.remarks) .. " " .. i18n.translatef("Fourth Matching node:") .. " " .. node.remarks)
end
server = node[".name"]
break
@@ -1682,7 +1676,7 @@ local function select_node(nodes, config, parentConfig)
if node.remarks then
if node.remarks == config.currentNode.remarks then
if config.log == nil or config.log == true then
log(i18n.translatef("Update [%s]", config.remarks) .. " " .. i18n.translatef("Fifth Matching node:") .. " " .. node.remarks)
log(log_level, i18n.translatef("Update [%s]", config.remarks) .. " " .. i18n.translatef("Fifth Matching node:") .. " " .. node.remarks)
end
server = node[".name"]
break
@@ -1696,7 +1690,7 @@ local function select_node(nodes, config, parentConfig)
if not server then
if #nodes_table > 0 then
if config.log == nil or config.log == true then
log(i18n.translatef("Update [%s]", config.remarks) .. " " .. i18n.translatef("Unable to find the best matching node, now replaced with:") .. " " .. nodes_table[1].remarks)
log(log_level, i18n.translatef("Update [%s]", config.remarks) .. " " .. i18n.translatef("Unable to find the best matching node, now replaced with:") .. " " .. nodes_table[1].remarks)
end
server = nodes_table[1][".name"]
end
@@ -1718,7 +1712,7 @@ end
local function update_node(manual)
if next(nodeResult) == nil then
log(i18n.translatef("No node information updates are available."))
log(1, i18n.translatef("No node information updates are available."))
return
end
@@ -1786,12 +1780,15 @@ local function update_node(manual)
for _, config in pairs(CONFIG) do
if config.currentNodes and #config.currentNodes > 0 then
if config.remarks and config.currentNodes[1].log ~= false then
log('----【' .. config.remarks .. '】----')
log(1, i18n.translatef("Update [%s]", config.remarks))
end
for kk, vv in pairs(config.currentNodes) do
select_node(nodes, vv, config)
end
config.set(config)
if not config.newNodes or #config.newNodes == 0 then
log(1, i18n.translatef("[%s]", config.remarks) .. " " .. i18n.translate("Unable to find a new node. Please confirm and process manually."))
end
else
select_node(nodes, config)
end
@@ -1806,7 +1803,9 @@ local function update_node(manual)
end
end
luci.sys.call("/etc/init.d/" .. appname .. " restart > /dev/null 2>&1 &")
if manual ~= 1 then
luci.sys.call("/etc/init.d/" .. appname .. " restart > /dev/null 2>&1 &")
end
end
local function parse_link(raw, add_mode, group, cfgid)
@@ -1859,17 +1858,17 @@ local function parse_link(raw, add_mode, group, cfgid)
end
end
else
log(i18n.translatef("Skip unknown types:") .. " " .. szType)
log(2, i18n.translatef("Skip unknown types:") .. " " .. szType)
end
-- log(result)
-- log(2, result)
if result then
if result.error_msg then
log(i18n.translatef("Discard node: %s, Reason:", result.remarks) .. " " .. result.error_msg)
log(2, i18n.translatef("Discard node: %s, Reason:", result.remarks) .. " " .. result.error_msg)
elseif not result.type then
log(i18n.translatef("Discard node: %s, Reason:", result.remarks) .. " " .. i18n.translatef("No usable binary was found."))
log(2, i18n.translatef("Discard node: %s, Reason:", result.remarks) .. " " .. i18n.translatef("No usable binary was found."))
elseif (add_mode == "2" and is_filter_keyword(result.remarks)) or not result.address or result.remarks == "NULL" or result.address == "127.0.0.1" or
(not datatypes.hostname(result.address) and not (api.is_ip(result.address))) then
log(i18n.translatef("Discard filter nodes: %s type node %s", result.type, result.remarks))
log(2, i18n.translatef("Discard filter nodes: %s type node %s", result.type, result.remarks))
else
tinsert(node_list, result)
end
@@ -1878,8 +1877,8 @@ local function parse_link(raw, add_mode, group, cfgid)
end
end
end, function (err)
--log(err)
log(v, i18n.translatef("Parsing error, skip this node."))
--log(2, err)
log(2, v, i18n.translatef("Parsing error, skip this node."))
end
)
end
@@ -1890,10 +1889,10 @@ local function parse_link(raw, add_mode, group, cfgid)
list = node_list
}
end
log(i18n.translatef("Successfully resolved the [%s] node, number: %s", group, #node_list))
log(2, i18n.translatef("Successfully resolved the [%s] node, number: %s", group, #node_list))
else
if add_mode == "2" then
log(i18n.translatef("Get subscription content for [%s] is empty. This may be due to an invalid subscription address or a network problem. Please diagnose the issue!", group))
log(2, i18n.translatef("Get subscription content for [%s] is empty. This may be due to an invalid subscription address or a network problem. Please diagnose the issue!", group))
end
end
end
@@ -1984,7 +1983,7 @@ local execute = function()
local ua = value.user_agent
local access_mode = value.access_mode
local result = (not access_mode) and i18n.translatef("Auto") or (access_mode == "direct" and i18n.translatef("Direct") or (access_mode == "proxy" and i18n.translatef("Proxy") or i18n.translatef("Auto")))
log(i18n.translatef("Start subscribing: %s", '' .. remark .. '' .. url .. ' [' .. result .. ']'))
log(1, i18n.translatef("Start subscribing: %s", '' .. remark .. '' .. url .. ' [' .. result .. ']'))
local tmp_file = "/tmp/" .. cfgid
value.http_code = curl(url, tmp_file, ua, access_mode)
if value.http_code ~= 200 then
@@ -1998,7 +1997,7 @@ local execute = function()
local old_md5 = value.md5 or ""
local new_md5 = luci.sys.exec("md5sum " .. tmp_file .. " 2>/dev/null | awk '{print $1}'"):gsub("\n", "")
if not manual_sub and old_md5 == new_md5 then
log(i18n.translatef("Subscription: [%s] No changes, no update required.", remark))
log(1, i18n.translatef("Subscription: [%s] No changes, no update required.", remark))
else
parse_link(raw_data, "2", remark, cfgid)
uci:set(appname, cfgid, "md5", new_md5)
@@ -2021,7 +2020,7 @@ local execute = function()
if #fail_list > 0 then
for index, value in ipairs(fail_list) do
log(i18n.translatef("[%s] Subscription failed. This could be due to an invalid subscription address or a network issue. Please diagnose the problem! [%s]", value.remark, tostring(value.http_code)))
log(1, i18n.translatef("[%s] Subscription failed. This could be due to an invalid subscription address or a network issue. Please diagnose the problem! [%s]", value.remark, tostring(value.http_code)))
end
end
update_node(0)
@@ -2030,13 +2029,13 @@ end
if arg[1] then
if arg[1] == "start" then
log(i18n.translatef("Start subscribing..."))
log(0, i18n.translatef("Start subscribing..."))
xpcall(execute, function(e)
log(e)
log(debug.traceback())
log(i18n.translatef("Error, restoring service."))
log(1, e)
log(1, debug.traceback())
log(1, i18n.translatef("Error, restoring service."))
end)
log(i18n.translatef("Subscription complete...") .. "\n")
log(0, i18n.translatef("Subscription complete...") .. "\n")
elseif arg[1] == "add" then
local f = assert(io.open("/tmp/links.conf", 'r'))
local raw = f:read('*all')

View File

@@ -11,8 +11,13 @@
{%
import { getuid, getspnam } from 'luci.core';
import { cursor } from 'uci';
const boardinfo = ubus.call('system', 'board');
const uci = cursor();
uci.load('aurora');
const colors = uci.get_all('aurora', 'colors') || {};
const dark_colors = uci.get_all('aurora', 'dark_colors') || {};
http.prepare_content('text/html; charset=UTF-8');
-%}
@@ -67,6 +72,20 @@
{% if (css): %}
<style title="text/css">{{ css }}</style>
{% endif %}
{% if (length(colors) || length(dark_colors)): %}
<style>
:root {
{% for (let key in colors): %}
--{{ replace(key, '_', '-') }}: {{ colors[key] }};
{% endfor %}
}
[data-darkmode="true"] {
{% for (let key in dark_colors): %}
--{{ replace(key, '_', '-') }}: {{ dark_colors[key] }};
{% endfor %}
}
</style>
{% endif %}
<script src="{{ dispatcher.build_url('admin/translations', dispatcher.lang) }}"></script>
<script src="{{ resource }}/cbi.js"></script>
</head>

View File

@@ -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

View File

@@ -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
}

View File

@@ -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)"

View File

@@ -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%);

View File

@@ -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 <morytyann@gmail.com>

View File

@@ -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

View File

@@ -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

View File

@@ -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 <cnsztl@immortalwrt.org>
PKG_LICENSE:=MPL-2.0