🔥 Sync 2025-12-03 00:14:03
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
});
|
||||
@@ -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."
|
||||
|
||||
@@ -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."
|
||||
@@ -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."
|
||||
|
||||
@@ -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 "更新後、ブラウザのキャッシュを手動でクリアしてください。"
|
||||
|
||||
@@ -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."
|
||||
|
||||
|
||||
@@ -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 "Пожалуйста, вручную очистите кеш браузера после обновления."
|
||||
@@ -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 "กรุณาล้างแคชเบราว์เซอร์ด้วยตนเองหลังจากอัปเดต"
|
||||
|
||||
@@ -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 "有更新,点击前往设置"
|
||||
@@ -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 "更新後請手動清空瀏覽器快取。"
|
||||
@@ -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
|
||||
|
||||
@@ -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": [
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
2
luci-app-passwall2/htdocs/luci-static/resources/view/passwall2/Sortable.min.js
vendored
Normal file
2
luci-app-passwall2/htdocs/luci-static/resources/view/passwall2/Sortable.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -390,6 +390,9 @@ o = s:option(Flag, _n("tls_allowInsecure"), translate("allowInsecure"), translat
|
||||
o.default = "0"
|
||||
o:depends({ [_n("tls")] = true, [_n("reality")] = false })
|
||||
|
||||
o = s:option(Value, _n("tls_chain_fingerprint"), translate("TLS Chain Fingerprint (SHA256)"), translate("Once set, connects only when the server’s chain fingerprint matches."))
|
||||
o:depends({ [_n("tls")] = true, [_n("reality")] = false })
|
||||
|
||||
o = s:option(Flag, _n("ech"), translate("ECH"))
|
||||
o.default = "0"
|
||||
o:depends({ [_n("tls")] = true, [_n("flow")] = "", [_n("reality")] = false })
|
||||
@@ -616,8 +619,14 @@ o = s:option(TextValue, _n("xhttp_extra"), " ", translate("An XHttpObject in JSO
|
||||
o:depends({ [_n("use_xhttp_extra")] = true })
|
||||
o.rows = 15
|
||||
o.wrap = "off"
|
||||
o.custom_cfgvalue = function(self, section, value)
|
||||
local raw = m:get(section, "xhttp_extra")
|
||||
if raw then
|
||||
return api.base64Decode(raw)
|
||||
end
|
||||
end
|
||||
o.custom_write = function(self, section, value)
|
||||
m:set(section, self.option:sub(1 + #option_prefix), value)
|
||||
m:set(section, "xhttp_extra", api.base64Encode(value))
|
||||
local success, data = pcall(jsonc.parse, value)
|
||||
if success and data then
|
||||
local address = (data.extra and data.extra.downloadSettings and data.extra.downloadSettings.address)
|
||||
@@ -640,7 +649,7 @@ o.validate = function(self, value)
|
||||
return value
|
||||
end
|
||||
o.custom_remove = function(self, section, value)
|
||||
m:del(section, self.option:sub(1 + #option_prefix))
|
||||
m:del(section, "xhttp_extra")
|
||||
m:del(section, "download_address")
|
||||
end
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
@@ -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 server’s 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
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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..."))
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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)"
|
||||
|
||||
@@ -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%);
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user