🎄 Sync 2025-11-27 00:12:02

This commit is contained in:
actions-user
2025-11-27 00:12:02 +08:00
parent 4a85def28d
commit bf596d0fcb
6 changed files with 457 additions and 80 deletions

View File

@@ -10,7 +10,7 @@ LUCI_DEPENDS:=+luci-base +luci-lib-jsonc +curl +bandix
PKG_MAINTAINER:=timsaya PKG_MAINTAINER:=timsaya
PKG_VERSION:=0.9.0 PKG_VERSION:=0.9.1
PKG_RELEASE:=1 PKG_RELEASE:=1
include $(TOPDIR)/feeds/luci/luci.mk include $(TOPDIR)/feeds/luci/luci.mk

View File

@@ -375,27 +375,27 @@ return view.extend({
.bandix-table th:nth-child(1), .bandix-table th:nth-child(1),
.bandix-table td:nth-child(1) { .bandix-table td:nth-child(1) {
width: 25%; width: 27%;
} }
.bandix-table th:nth-child(2), .bandix-table th:nth-child(2),
.bandix-table td:nth-child(2) { .bandix-table td:nth-child(2) {
width: 20%; width: 22%;
} }
.bandix-table th:nth-child(3), .bandix-table th:nth-child(3),
.bandix-table td:nth-child(3) { .bandix-table td:nth-child(3) {
width: 20%; width: 22%;
} }
.bandix-table th:nth-child(4), .bandix-table th:nth-child(4),
.bandix-table td:nth-child(4) { .bandix-table td:nth-child(4) {
width: 25%; width: 12.5%;
} }
.bandix-table th:nth-child(5), .bandix-table th:nth-child(5),
.bandix-table td:nth-child(5) { .bandix-table td:nth-child(5) {
width: 10%; width: 16.5%;
} }
.schedule-rules-info { .schedule-rules-info {
@@ -1352,6 +1352,82 @@ return view.extend({
.history-tooltip .ht-kpi.up .ht-k-value { color: #f97316; } .history-tooltip .ht-kpi.up .ht-k-value { color: #f97316; }
.history-tooltip .ht-divider { height: 1px; background-color: currentColor; opacity: 0.3; margin: 8px 0; } .history-tooltip .ht-divider { height: 1px; background-color: currentColor; opacity: 0.3; margin: 8px 0; }
.history-tooltip .ht-section-title { font-weight: 600; font-size: 0.75rem; opacity: 0.7; margin: 4px 0 6px 0; } .history-tooltip .ht-section-title { font-weight: 600; font-size: 0.75rem; opacity: 0.7; margin: 4px 0 6px 0; }
/* Schedule Rules Tooltip */
.schedule-rules-tooltip {
position: fixed;
display: none;
width: 360px;
max-width: 90vw;
box-sizing: border-box;
padding: 12px;
z-index: 10000;
pointer-events: none;
font-size: 0.8125rem;
line-height: 1.5;
background-color: rgba(255, 255, 255, 0.98);
border: 1px solid rgba(0, 0, 0, 0.1);
border-radius: 6px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
color: #1f2937;
}
@media (prefers-color-scheme: dark) {
.schedule-rules-tooltip {
background-color: rgba(30, 30, 30, 0.98);
border-color: rgba(255, 255, 255, 0.2);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.4);
color: #e5e7eb;
}
}
.schedule-rules-tooltip .srt-title {
font-weight: 700;
margin-bottom: 8px;
font-size: 0.875rem;
}
.schedule-rules-tooltip .srt-rule-item {
padding: 8px 0;
border-bottom: 1px solid rgba(0, 0, 0, 0.1);
}
@media (prefers-color-scheme: dark) {
.schedule-rules-tooltip .srt-rule-item {
border-bottom-color: rgba(255, 255, 255, 0.15);
}
}
.schedule-rules-tooltip .srt-rule-item:last-child {
border-bottom: none;
}
.schedule-rules-tooltip .srt-rule-time {
font-weight: 600;
margin-bottom: 4px;
font-size: 0.75rem;
}
.schedule-rules-tooltip .srt-rule-days {
font-size: 0.75rem;
font-weight: 500;
opacity: 0.7;
margin-bottom: 4px;
}
.schedule-rules-tooltip .srt-rule-limits {
font-size: 0.875rem;
font-weight: 600;
opacity: 0.8;
}
.schedule-rules-tooltip .srt-rule-limits .srt-arrow {
font-size: 0.75rem;
font-weight: bold;
}
.schedule-rules-info {
}
`); `);
document.head.appendChild(style); document.head.appendChild(style);
@@ -1444,6 +1520,59 @@ return view.extend({
]) ])
]); ]);
// 创建全局的 Schedule Rules Tooltip 元素
var scheduleRulesTooltip = E('div', { 'class': 'schedule-rules-tooltip', 'id': 'schedule-rules-tooltip' });
document.body.appendChild(scheduleRulesTooltip);
// 构建规则列表的 HTML用于 tooltip
function buildScheduleRulesTooltipHtml(allRules, activeRules, speedUnit) {
if (!allRules || allRules.length === 0) {
return '';
}
var lines = [];
lines.push('<div class="srt-title">' + _('Schedule Rules') + ' (' + allRules.length + ')</div>');
allRules.forEach(function(rule, index) {
var startTime = rule.time_slot && rule.time_slot.start ? rule.time_slot.start : '';
var endTime = rule.time_slot && rule.time_slot.end ? rule.time_slot.end : '';
var days = rule.time_slot && rule.time_slot.days ? rule.time_slot.days : [];
var dayNames = {
1: _('Mon'),
2: _('Tue'),
3: _('Wed'),
4: _('Thu'),
5: _('Fri'),
6: _('Sat'),
7: _('Sun')
};
var daysText = days.length > 0 ? days.map(function(d) { return dayNames[d] || d; }).join(', ') : '-';
var uploadLimit = rule.wide_tx_rate_limit || 0;
var downloadLimit = rule.wide_rx_rate_limit || 0;
// 使用 isRuleActive 函数检查规则是否激活
var isActive = isRuleActive(rule);
// 箭头固定颜色(橙色和青色),样式与 WAN 字段一致
var uploadLimitText = '<span class="srt-arrow" style="color: #f97316;">↑</span>' + (uploadLimit > 0 ? formatByterate(uploadLimit, speedUnit) : _('Unlimited'));
var downloadLimitText = '<span class="srt-arrow" style="color: #06b6d4;">↓</span>' + (downloadLimit > 0 ? formatByterate(downloadLimit, speedUnit) : _('Unlimited'));
var activeMark = isActive ? '<span style="color: #10b981; margin-right: 4px;">●</span>' : '';
lines.push(
'<div class="srt-rule-item">' +
'<div class="srt-rule-time">' + activeMark + startTime + ' - ' + endTime + '</div>' +
'<div class="srt-rule-days">' + daysText + '</div>' +
'<div class="srt-rule-limits">' + uploadLimitText + ' ' + downloadLimitText + '</div>' +
'</div>'
);
});
return lines.join('');
}
// 设备信息模式切换 // 设备信息模式切换
var deviceModeButtons = view.querySelectorAll('.device-mode-btn'); var deviceModeButtons = view.querySelectorAll('.device-mode-btn');
@@ -2256,6 +2385,80 @@ return view.extend({
}); });
} }
// 合并多个生效规则的限制值
// 返回合并后的上传和下载限制(取所有规则中非零的最小值)
function mergeActiveRulesLimits(activeRules) {
if (!activeRules || activeRules.length === 0) {
return { uploadLimit: 0, downloadLimit: 0 };
}
var uploadLimits = [];
var downloadLimits = [];
activeRules.forEach(function(rule) {
var uploadLimit = rule.wide_tx_rate_limit || 0;
var downloadLimit = rule.wide_rx_rate_limit || 0;
// 只收集非零的限制值
if (uploadLimit > 0) {
uploadLimits.push(uploadLimit);
}
if (downloadLimit > 0) {
downloadLimits.push(downloadLimit);
}
});
// 取最小值(如果有多个规则都有限制,取最严格的限制)
var mergedUploadLimit = uploadLimits.length > 0 ? Math.min.apply(Math, uploadLimits) : 0;
var mergedDownloadLimit = downloadLimits.length > 0 ? Math.min.apply(Math, downloadLimits) : 0;
return {
uploadLimit: mergedUploadLimit,
downloadLimit: mergedDownloadLimit
};
}
// 获取多个规则的时间段显示文本
// 如果所有规则的时间段相同,显示时间段;如果不同,显示"多个时间段"
function getTimeSlotDisplayText(activeRules) {
if (!activeRules || activeRules.length === 0) {
return '';
}
if (activeRules.length === 1) {
// 单个规则,直接显示时间段
var rule = activeRules[0];
var startTime = rule.time_slot && rule.time_slot.start ? rule.time_slot.start : '';
var endTime = rule.time_slot && rule.time_slot.end ? rule.time_slot.end : '';
return startTime + '-' + endTime;
}
// 多个规则,检查时间段是否相同
var firstRule = activeRules[0];
var firstStartTime = firstRule.time_slot && firstRule.time_slot.start ? firstRule.time_slot.start : '';
var firstEndTime = firstRule.time_slot && firstRule.time_slot.end ? firstRule.time_slot.end : '';
var allSame = true;
for (var i = 1; i < activeRules.length; i++) {
var rule = activeRules[i];
var startTime = rule.time_slot && rule.time_slot.start ? rule.time_slot.start : '';
var endTime = rule.time_slot && rule.time_slot.end ? rule.time_slot.end : '';
if (startTime !== firstStartTime || endTime !== firstEndTime) {
allSame = false;
break;
}
}
if (allSame) {
// 所有规则时间段相同,显示时间段和规则数量
return firstStartTime + '-' + firstEndTime + ' (' + activeRules.length + ' ' + _('rules') + ')';
} else {
// 时间段不同,显示"多个时间段"
return _('Multiple time slots') + ' (' + activeRules.length + ' ' + _('rules') + ')';
}
}
// 排序状态管理 // 排序状态管理
var currentSortBy = localStorage.getItem('bandix_sort_by') || 'online'; // 默认按在线状态排序 var currentSortBy = localStorage.getItem('bandix_sort_by') || 'online'; // 默认按在线状态排序
var currentSortOrder = localStorage.getItem('bandix_sort_order') === 'true'; // false = 降序, true = 升序 var currentSortOrder = localStorage.getItem('bandix_sort_order') === 'true'; // false = 降序, true = 升序
@@ -3781,39 +3984,36 @@ function downsampleForMobile(data, labels, upSeries, downSeries, maxPoints) {
if (allDeviceRules.length === 0) { if (allDeviceRules.length === 0) {
rulesInfo.appendChild(E('div', { 'style': 'font-size: 0.75rem; opacity: 0.6;' }, '-')); rulesInfo.appendChild(E('div', { 'style': 'font-size: 0.75rem; opacity: 0.6;' }, '-'));
} else { } else {
// 显示规则数量
rulesInfo.appendChild(E('div', {
'style': 'font-size: 0.75rem; font-weight: 600; margin-bottom: 4px;'
}, allDeviceRules.length + ' ' + (allDeviceRules.length === 1 ? _('rule') : _('rules'))));
// 显示当前生效的规则 // 显示当前生效的规则
if (activeRules.length > 0) { if (activeRules.length > 0) {
var activeRule = activeRules[0]; // 显示第一个生效的规则 // 合并多个规则的限制值
var startTime = activeRule.time_slot && activeRule.time_slot.start ? activeRule.time_slot.start : ''; var mergedLimits = mergeActiveRulesLimits(activeRules);
var endTime = activeRule.time_slot && activeRule.time_slot.end ? activeRule.time_slot.end : ''; var uploadLimit = mergedLimits.uploadLimit;
var downloadLimit = mergedLimits.downloadLimit;
var uploadLimit = activeRule.wide_tx_rate_limit || 0;
var downloadLimit = activeRule.wide_rx_rate_limit || 0;
// 时间段和限速值放在同一行
var timeSlotText = startTime + '-' + endTime;
var limitsText = [];
// 即使限速是0也显示
limitsText.push('↑' + (uploadLimit > 0 ? formatByterate(uploadLimit, speedUnit) : _('Unlimited')));
limitsText.push('↓' + (downloadLimit > 0 ? formatByterate(downloadLimit, speedUnit) : _('Unlimited')));
// 显示规则数量
rulesInfo.appendChild(E('div', { rulesInfo.appendChild(E('div', {
'style': 'font-size: 0.75rem; color: #10b981; display: flex; align-items: center; gap: 8px; flex-wrap: wrap;' 'style': 'font-size: 0.75rem; font-weight: 600; margin-bottom: 4px;'
}, [ }, activeRules.length + ' ' + (activeRules.length === 1 ? _('rule') : _('rules'))));
E('span', {}, '● ' + timeSlotText),
E('span', { 'style': 'opacity: 0.8; font-size: 0.7rem;' }, limitsText.join(' '))
]));
if (activeRules.length > 1) { // 显示限速值(箭头固定颜色,文字默认颜色)
rulesInfo.appendChild(E('div', { var limitsContainer = E('div', {
'style': 'font-size: 0.7rem; opacity: 0.6; margin-top: 2px;' 'style': 'font-size: 0.75rem; display: flex; align-items: center; gap: 8px; flex-wrap: wrap;'
}, '+' + (activeRules.length - 1) + ' ' + _('more'))); });
}
// 上传限速(橙色箭头)
var uploadSpan = E('span', {});
uploadSpan.appendChild(E('span', { 'style': 'color: #f97316;' }, '↑'));
uploadSpan.appendChild(document.createTextNode(uploadLimit > 0 ? formatByterate(uploadLimit, speedUnit) : _('Unlimited')));
limitsContainer.appendChild(uploadSpan);
// 下载限速(青色箭头)
var downloadSpan = E('span', {});
downloadSpan.appendChild(E('span', { 'style': 'color: #06b6d4;' }, '↓'));
downloadSpan.appendChild(document.createTextNode(downloadLimit > 0 ? formatByterate(downloadLimit, speedUnit) : _('Unlimited')));
limitsContainer.appendChild(downloadSpan);
rulesInfo.appendChild(limitsContainer);
} else { } else {
rulesInfo.appendChild(E('div', { rulesInfo.appendChild(E('div', {
'style': 'font-size: 0.75rem; opacity: 0.5;' 'style': 'font-size: 0.75rem; opacity: 0.5;'
@@ -3821,6 +4021,125 @@ function downsampleForMobile(data, labels, upSeries, downSeries, maxPoints) {
} }
} }
// PC 端添加鼠标悬浮事件(显示所有规则)- 只要有规则就绑定事件
var screenWidth = window.innerWidth || document.documentElement.clientWidth;
if (screenWidth > 768 && allDeviceRules.length > 0) {
rulesInfo.onmouseenter = function(evt) {
var tooltip = document.getElementById('schedule-rules-tooltip');
if (!tooltip) return;
var html = buildScheduleRulesTooltipHtml(allDeviceRules, activeRules, speedUnit);
if (!html) return;
tooltip.innerHTML = html;
// 应用主题颜色
try {
var cbiSection = document.querySelector('.cbi-section');
var targetElement = cbiSection || document.querySelector('.main') || document.body;
var computedStyle = window.getComputedStyle(targetElement);
var bgColor = computedStyle.backgroundColor;
var textColor = computedStyle.color;
if (bgColor && bgColor !== 'rgba(0, 0, 0, 0)' && bgColor !== 'transparent') {
var rgbaMatch = bgColor.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*([\d.]+))?\)/);
if (rgbaMatch) {
var r = parseInt(rgbaMatch[1]);
var g = parseInt(rgbaMatch[2]);
var b = parseInt(rgbaMatch[3]);
var alpha = rgbaMatch[4] ? parseFloat(rgbaMatch[4]) : 1;
if (alpha < 0.95) {
tooltip.style.backgroundColor = 'rgb(' + r + ', ' + g + ', ' + b + ')';
} else {
tooltip.style.backgroundColor = bgColor;
}
} else {
tooltip.style.backgroundColor = bgColor;
}
}
if (textColor && textColor !== 'rgba(0, 0, 0, 0)') {
tooltip.style.color = textColor;
}
} catch(e) {}
// 先隐藏,设置内容后再显示以计算尺寸
tooltip.style.display = 'block';
tooltip.style.visibility = 'hidden';
tooltip.style.left = '-9999px';
tooltip.style.top = '-9999px';
// 强制浏览器计算尺寸
var tw = tooltip.offsetWidth || 0;
var th = tooltip.offsetHeight || 0;
if (tw === 0 || th === 0) {
tooltip.style.display = 'none';
return;
}
tooltip.style.visibility = 'visible';
var padding = 12;
var maxX = window.innerWidth - 4;
var maxY = window.innerHeight - 4;
var rect = evt.currentTarget.getBoundingClientRect();
var cx = rect.left + rect.width / 2;
var cy = rect.top + rect.height / 2;
// 计算位置:优先显示在右侧,如果空间不足则显示在左侧
var baseX = cx + padding;
var baseY = cy - th / 2;
if (baseX + tw > maxX) {
baseX = cx - tw - padding;
}
if (baseY < 4) baseY = 4;
if (baseY + th > maxY) baseY = maxY - th - 4;
tooltip.style.left = baseX + 'px';
tooltip.style.top = baseY + 'px';
};
rulesInfo.onmouseleave = function() {
var tooltip = document.getElementById('schedule-rules-tooltip');
if (tooltip) {
tooltip.style.display = 'none';
tooltip.style.visibility = 'visible';
}
};
rulesInfo.onmousemove = function(evt) {
var tooltip = document.getElementById('schedule-rules-tooltip');
if (!tooltip || tooltip.style.display === 'none') return;
var tw = tooltip.offsetWidth || 0;
var th = tooltip.offsetHeight || 0;
var padding = 12;
var maxX = window.innerWidth - 4;
var maxY = window.innerHeight - 4;
var rect = evt.currentTarget.getBoundingClientRect();
var cx = rect.left + rect.width / 2;
var cy = rect.top + rect.height / 2;
var baseX = cx + padding;
var baseY = cy - th / 2;
if (baseX + tw > maxX) {
baseX = cx - tw - padding;
}
if (baseY < 4) baseY = 4;
if (baseY + th > maxY) baseY = maxY - th - 4;
tooltip.style.left = baseX + 'px';
tooltip.style.top = baseY + 'px';
};
}
return E('td', {}, rulesInfo); return E('td', {}, rulesInfo);
})(), })(),
@@ -3889,41 +4208,25 @@ function downsampleForMobile(data, labels, upSeries, downSeries, maxPoints) {
var rulesContent = E('div', { 'class': 'device-card-rules-content' }); var rulesContent = E('div', { 'class': 'device-card-rules-content' });
// 规则数量
rulesContent.appendChild(E('div', {
'class': 'device-card-rules-count'
}, allDeviceRules.length + ' ' + (allDeviceRules.length === 1 ? _('rule') : _('rules'))));
if (activeRules.length > 0) { if (activeRules.length > 0) {
var activeRule = activeRules[0]; // 合并多个规则的限制值
var startTime = activeRule.time_slot && activeRule.time_slot.start ? activeRule.time_slot.start : ''; var mergedLimits = mergeActiveRulesLimits(activeRules);
var endTime = activeRule.time_slot && activeRule.time_slot.end ? activeRule.time_slot.end : ''; var uploadLimit = mergedLimits.uploadLimit;
var downloadLimit = mergedLimits.downloadLimit;
var uploadLimit = activeRule.wide_tx_rate_limit || 0; // 显示规则数量
var downloadLimit = activeRule.wide_rx_rate_limit || 0; rulesContent.appendChild(E('div', {
'class': 'device-card-rules-count'
}, activeRules.length + ' ' + (activeRules.length === 1 ? _('rule') : _('rules'))));
// 时间段和限速值放在同一行 // 显示限速值
var timeSlotText = startTime + '-' + endTime;
var limitsText = []; var limitsText = [];
// 即使限速是0也显示
limitsText.push('↑' + (uploadLimit > 0 ? formatByterate(uploadLimit, speedUnit) : _('Unlimited'))); limitsText.push('↑' + (uploadLimit > 0 ? formatByterate(uploadLimit, speedUnit) : _('Unlimited')));
limitsText.push('↓' + (downloadLimit > 0 ? formatByterate(downloadLimit, speedUnit) : _('Unlimited'))); limitsText.push('↓' + (downloadLimit > 0 ? formatByterate(downloadLimit, speedUnit) : _('Unlimited')));
// 生效规则的时间段和限速值 rulesContent.appendChild(E('div', {
var activeTimeInfo = E('div', {
'class': 'device-card-rules-active-time' 'class': 'device-card-rules-active-time'
}, [ }, limitsText.join(' ')));
E('span', {}, '● ' + timeSlotText),
E('span', { 'style': 'opacity: 0.8; font-size: 0.7rem; margin-left: 8px;' }, limitsText.join(' '))
]);
if (activeRules.length > 1) {
activeTimeInfo.appendChild(E('div', {
'class': 'device-card-rules-more'
}, '+' + (activeRules.length - 1) + ' ' + _('more')));
}
rulesContent.appendChild(activeTimeInfo);
} else { } else {
rulesContent.appendChild(E('div', { rulesContent.appendChild(E('div', {
'class': 'device-card-rules-inactive' 'class': 'device-card-rules-inactive'

View File

@@ -83,6 +83,7 @@ function index()
entry({"admin", "services", appname, "clear_all_nodes"}, call("clear_all_nodes")).leaf = true 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, "delete_select_nodes"}, call("delete_select_nodes")).leaf = true
entry({"admin", "services", appname, "get_node"}, call("get_node")).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, "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_node"}, call("subscribe_del_node")).leaf = true
entry({"admin", "services", appname, "subscribe_del_all"}, call("subscribe_del_all")).leaf = true entry({"admin", "services", appname, "subscribe_del_all"}, call("subscribe_del_all")).leaf = true
@@ -591,13 +592,12 @@ function delete_select_nodes()
end end
end end
function get_node() function get_node()
local id = http.formvalue("id") local id = http.formvalue("id")
local result = {} local result = {}
local show_node_info = api.uci_get_type("global_other", "show_node_info", "0") 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 if o and o.address and show_node_info == "1" then
local f = api.get_ipv6_full(o.address) local f = api.get_ipv6_full(o.address)
if f ~= "" then if f ~= "" then
@@ -611,14 +611,35 @@ function get_node()
result = uci:get_all(appname, id) result = uci:get_all(appname, id)
add_is_ipv6_key(result) add_is_ipv6_key(result)
else else
local default_nodes = {}
local other_nodes = {}
uci:foreach(appname, "nodes", function(t) uci:foreach(appname, "nodes", function(t)
add_is_ipv6_key(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) 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 end
http_write_json(result) http_write_json(result)
end 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() function update_rules()
local update = http.formvalue("update") local update = http.formvalue("update")
luci.sys.call("lua /usr/share/passwall/rule_update.lua log '" .. update .. "' > /dev/null 2>&1 &") luci.sys.call("lua /usr/share/passwall/rule_update.lua log '" .. update .. "' > /dev/null 2>&1 &")

View File

@@ -210,19 +210,28 @@ table td, .table .td {
document.getElementById("set_node_name").innerHTML = ""; document.getElementById("set_node_name").innerHTML = "";
} }
function _cbi_row_top(id) { function row_swap(btn, up) {
//此函数已经损坏,等待修复或其他解决方案。 const row = btn.closest("tr");
var dom = document.getElementById("cbi-passwall-" + id); if (!row) return;
if (dom) { const parent = row.parentNode;
var trs = document.getElementById("cbi-passwall-nodes").getElementsByClassName("cbi-section-table-row"); if (up) {
if (trs && trs.length > 0) { const prev = row.previousElementSibling;
for (var i = 0; i < trs.length; i++) { if (prev && !prev.classList.contains("cbi-section-table-titles")) {
var up = dom.getElementsByClassName("cbi-button-up"); parent.insertBefore(row, prev);
if (up) {
cbi_row_swap(up[0], true, 'cbi.sts.passwall.nodes');
}
}
} }
} else {
const next = row.nextElementSibling;
if (next) parent.insertBefore(next, row);
}
}
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);
} }
} }
@@ -305,6 +314,39 @@ table td, .table .td {
return { address: address, port: port }; return { address: address, port: port };
} }
function save_current_page_order(group) {
var table = document.getElementById("cbi-passwall-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.disabled = true;
var ids = [];
rows.forEach(function(row) {
var id = row.id.replace("cbi-passwall-", "");
ids.push(id);
});
XHR.get('<%=api.url("save_node_order")%>', {
group: group,
ids: ids.join(",")
},
function(x, result) {
if (btn) btn.disabled = false;
if (x && x.status === 200) {
alert("<%:Saved current page order successfully.%>");
} else {
alert("<%:Save failed!%>");
}
}
);
}
//获取当前使用的节点 //获取当前使用的节点
function get_now_use_node() { function get_now_use_node() {
XHR.get('<%=api.url("get_now_use_node")%>', null, XHR.get('<%=api.url("get_now_use_node")%>', null,
@@ -522,6 +564,7 @@ table td, .table .td {
</table> </table>
<div class="cbi-section-create cbi-tblsection-create"> <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-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}}')">
</div> </div>
</fieldset> </fieldset>
</script> </script>
@@ -537,11 +580,12 @@ table td, .table .td {
<td class="td cbi-value-field">{{url_test}}</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-section-table-cell nowrap cbi-section-actions">
<div class="node-wrapper"> <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="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-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-add" type="button" value="<%:Copy%>" onclick="copy_node('{{id}}')"/>
<input class="btn cbi-button cbi-button-up" type="button" value="<%:Move up%>" onclick="return row_swap(this, true)" title="<%:Move up%>">
<input class="btn cbi-button cbi-button-down" type="button" value="<%:Move down%>" onclick="return row_swap(this, false)" title="<%:Move down%>">
<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-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%>"> <input class="btn cbi-button cbi-button-remove" type="button" value="<%:Delete%>" onclick="del_node('{{id}}')" alt="<%:Delete%>" title="<%:Delete%>">
</div> </div>

View File

@@ -421,6 +421,12 @@ msgstr "节点备注"
msgid "Add Mode" msgid "Add Mode"
msgstr "添加方式" msgstr "添加方式"
msgid "Save Order"
msgstr "保存当前顺序"
msgid "Saved current page order successfully."
msgstr "保存当前页面顺序成功。"
msgid "Type" msgid "Type"
msgstr "类型" msgstr "类型"

View File

@@ -760,13 +760,16 @@ local function processData(szType, content)
-- 未指定peersni默认使用remote addr -- 未指定peersni默认使用remote addr
result.tls_host = params.peer or params.sni result.tls_host = params.peer or params.sni
end end
if params.allowInsecure then params.allowinsecure = params.allowinsecure or params.insecure
if params.allowinsecure then
-- 处理 insecure 参数 -- 处理 insecure 参数
if params.allowinsecure == "1" or params.allowinsecure == "0" then if params.allowinsecure == "1" or params.allowinsecure == "0" then
result.insecure = params.allowInsecure result.insecure = params.allowinsecure
else else
result.insecure = string.lower(params.allowinsecure) == "true" and "1" or "0" result.insecure = string.lower(params.allowinsecure) == "true" and "1" or "0"
end end
else
result.insecure = "0"
end end
if params.tfo then if params.tfo then
-- 处理 fast open 参数 -- 处理 fast open 参数