mirror of
https://github.com/caiwx86/small-packages.git
synced 2026-07-29 01:41:51 +08:00
415 lines
11 KiB
HTML
415 lines
11 KiB
HTML
<%#
|
|
Copyright 2018-2019 Lienol <lawlienol@gmail.com>
|
|
Licensed to the public under the Apache License 2.0.
|
|
-%>
|
|
<%
|
|
require "luci.sys"
|
|
function is_js_luci()
|
|
return luci.sys.call('[ -f "/www/luci-static/resources/uci.js" ]') == 0
|
|
end
|
|
-%>
|
|
<% if is_js_luci() then -%>
|
|
<script type="text/javascript">
|
|
var cbi_t = [];
|
|
function cbi_t_add(section, tab) {
|
|
var t = document.getElementById('tab.' + section + '.' + tab);
|
|
var c = document.getElementById('container.' + section + '.' + tab);
|
|
|
|
if( t && c ) {
|
|
cbi_t[section] = (cbi_t[section] || [ ]);
|
|
cbi_t[section][tab] = { 'tab': t, 'container': c, 'cid': c.id };
|
|
}
|
|
}
|
|
|
|
function cbi_t_switch(section, tab) {
|
|
if( cbi_t[section] && cbi_t[section][tab] ) {
|
|
//在切换选项卡之前,先取消当前激活选项卡的全选状态
|
|
dechecked_all_node();
|
|
var o = cbi_t[section][tab];
|
|
var h = document.getElementById('tab.' + section);
|
|
for( var tid in cbi_t[section] ) {
|
|
var o2 = cbi_t[section][tid];
|
|
if( o.tab.id != o2.tab.id ) {
|
|
o2.tab.className = o2.tab.className.replace(/(^| )cbi-tab( |$)/, " cbi-tab-disabled ");
|
|
o2.container.style.display = 'none';
|
|
}
|
|
else {
|
|
if(h) h.value = tab;
|
|
o2.tab.className = o2.tab.className.replace(/(^| )cbi-tab-disabled( |$)/, " cbi-tab ");
|
|
o2.container.style.display = 'block';
|
|
}
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
</script>
|
|
<%- else %>
|
|
<script type="text/javascript">
|
|
(function() {
|
|
if (typeof(cbi_t_switch) === "function") {
|
|
var old_switch = cbi_t_switch;
|
|
cbi_t_switch = function(section, tab) {
|
|
dechecked_all_node();
|
|
return old_switch(section, tab);
|
|
};
|
|
}
|
|
})();
|
|
</script>
|
|
<%- end %>
|
|
|
|
<script type="text/javascript">
|
|
//<![CDATA[
|
|
window.addEventListener('load', function () {
|
|
const doms = document.getElementsByClassName('pingtime');
|
|
const ports = document.getElementsByClassName("socket-connected");
|
|
const transports = document.getElementsByClassName("transport");
|
|
const wsPaths = document.getElementsByClassName("wsPath");
|
|
const wsHosts = document.getElementsByClassName("wsHost");
|
|
const tlsHosts = document.getElementsByClassName("tlsHost");
|
|
const tlss = document.getElementsByClassName("tls");
|
|
const types = document.getElementsByClassName("type");
|
|
const protos = document.getElementsByClassName("proto");
|
|
const realities = document.getElementsByClassName("reality");
|
|
const startText = '<%:Start Detection%>';
|
|
const stopText = '<%:Stop Detection%>';
|
|
const maxConcurrent = 10;
|
|
let activeRunId = 0;
|
|
let probing = false;
|
|
let queueIndex = 0;
|
|
let toggleBtn = null;
|
|
|
|
const normalizeHeaderLayout = () => {
|
|
const headerRow = document.querySelector("#cbi-shadowsocksr-servers table tr");
|
|
if (!headerRow) {
|
|
return null;
|
|
}
|
|
const headerCells = headerRow.querySelectorAll("th");
|
|
if (headerCells.length > 1) {
|
|
const first = headerCells[0];
|
|
if (first && !first.textContent.trim()) {
|
|
first.parentNode.removeChild(first);
|
|
}
|
|
}
|
|
return headerRow;
|
|
};
|
|
|
|
const ensureProbeButton = () => {
|
|
if (toggleBtn) {
|
|
return toggleBtn;
|
|
}
|
|
const headerRow = normalizeHeaderLayout();
|
|
if (!headerRow) {
|
|
return null;
|
|
}
|
|
const headerCells = headerRow.querySelectorAll("th");
|
|
const targetHeader = headerCells.length ? headerCells[headerCells.length - 1] : null;
|
|
if (!targetHeader) {
|
|
return null;
|
|
}
|
|
targetHeader.style.width = "1%";
|
|
targetHeader.style.whiteSpace = "nowrap";
|
|
targetHeader.style.textAlign = "left";
|
|
toggleBtn = document.createElement("input");
|
|
toggleBtn.type = "button";
|
|
toggleBtn.id = "ssr-probe-toggle";
|
|
toggleBtn.className = "btn cbi-button cbi-button-apply";
|
|
toggleBtn.style.marginLeft = "8px";
|
|
toggleBtn.style.minWidth = "150px";
|
|
toggleBtn.value = startText;
|
|
toggleBtn.title = startText;
|
|
targetHeader.appendChild(toggleBtn);
|
|
return toggleBtn;
|
|
};
|
|
|
|
const setProbeState = (running) => {
|
|
probing = running;
|
|
const btn = ensureProbeButton();
|
|
if (btn) {
|
|
toggleBtn = btn;
|
|
toggleBtn.value = running ? stopText : startText;
|
|
toggleBtn.title = toggleBtn.value;
|
|
}
|
|
};
|
|
|
|
const resetProbeDisplay = () => {
|
|
for (let i = 0; i < doms.length; i++) {
|
|
if (doms[i]) {
|
|
doms[i].innerHTML = 'N/A';
|
|
}
|
|
if (ports[i]) {
|
|
ports[i].innerHTML = 'N/A';
|
|
}
|
|
}
|
|
};
|
|
|
|
const xhr = (index, runId) => {
|
|
return new Promise((res) => {
|
|
const dom = doms[index];
|
|
const port = ports[index];
|
|
const transport = transports[index];
|
|
const wsPath = wsPaths[index];
|
|
const wsHost = wsHosts[index];
|
|
const tlsHost = tlsHosts[index];
|
|
const tls = tlss[index];
|
|
const type = types[index];
|
|
const proto = protos[index];
|
|
const reality = realities[index];
|
|
if (!dom || !port || runId !== activeRunId) {
|
|
res();
|
|
return;
|
|
}
|
|
if (type && type.getAttribute("hint") === "clash") {
|
|
dom.innerHTML = '<font style="color:#999999">N/A</font>';
|
|
port.innerHTML = '<font style="color:#999999">N/A</font>';
|
|
res();
|
|
return;
|
|
}
|
|
port.innerHTML = '<font style=\"color:#0072c3\">connect</font>';
|
|
XHR.get('<%=luci.dispatcher.build_url("admin/services/shadowsocksr/ping")%>', {
|
|
index,
|
|
sid: dom.getAttribute("data-sid"),
|
|
domain: dom.getAttribute("hint"),
|
|
port: port.getAttribute("hint"),
|
|
transport: transport.getAttribute("hint"),
|
|
wsPath: wsPath.getAttribute("hint"),
|
|
host: wsHost ? wsHost.getAttribute("hint") : "",
|
|
tlsHost: tlsHost ? tlsHost.getAttribute("hint") : "",
|
|
tls: tls.getAttribute("hint"),
|
|
type: type.getAttribute("hint"),
|
|
proto: proto.getAttribute("hint"),
|
|
reality: reality ? reality.getAttribute("hint") : ""
|
|
},
|
|
(x, result) => {
|
|
if (runId !== activeRunId) {
|
|
res();
|
|
return;
|
|
}
|
|
result = result || {};
|
|
let col = '#ff0000';
|
|
if (result.ping) {
|
|
if (result.ping < 300) col = '#ff3300';
|
|
if (result.ping < 200) col = '#ff7700';
|
|
if (result.ping < 100) col = '#249400';
|
|
}
|
|
dom.innerHTML = `<font style=\"color:${col}\">${(result.ping ? result.ping : "--") + " ms"}</font>`;
|
|
if (result.socket) {
|
|
port.innerHTML = '<font style=\"color:#249400\">ok</font>';
|
|
} else {
|
|
port.innerHTML = '<font style=\"color:#ff0000\">fail</font>';
|
|
}
|
|
res();
|
|
});
|
|
});
|
|
};
|
|
|
|
const launchNext = (runId) => {
|
|
if (runId !== activeRunId || queueIndex >= doms.length) {
|
|
return Promise.resolve();
|
|
}
|
|
const index = queueIndex++;
|
|
return xhr(index, runId).then(() => launchNext(runId));
|
|
};
|
|
|
|
const startProbe = () => {
|
|
activeRunId++;
|
|
const runId = activeRunId;
|
|
queueIndex = 0;
|
|
resetProbeDisplay();
|
|
setProbeState(true);
|
|
|
|
const workers = [];
|
|
for (let i = 0; i < Math.min(maxConcurrent, doms.length); i++) {
|
|
workers.push(launchNext(runId));
|
|
}
|
|
|
|
Promise.all(workers).then(() => {
|
|
if (runId === activeRunId) {
|
|
setProbeState(false);
|
|
}
|
|
});
|
|
};
|
|
|
|
const stopProbe = () => {
|
|
activeRunId++;
|
|
setProbeState(false);
|
|
};
|
|
|
|
setProbeState(false);
|
|
|
|
ensureProbeButton();
|
|
if (toggleBtn) {
|
|
toggleBtn.onclick = function () {
|
|
if (probing) {
|
|
stopProbe();
|
|
} else {
|
|
startProbe();
|
|
}
|
|
};
|
|
}
|
|
});
|
|
|
|
function cbi_row_drop(fromId, toId, store, isToBottom) {
|
|
var fromNode = document.getElementById(fromId);
|
|
var toNode = document.getElementById(toId);
|
|
if (!fromNode || !toNode) return false;
|
|
|
|
var table = fromNode.parentNode;
|
|
while (table && table.nodeName.toLowerCase() != "table")
|
|
table = table.parentNode;
|
|
if (!table) return false;
|
|
|
|
var ids = [];
|
|
if (isToBottom) {
|
|
toNode.parentNode.appendChild(fromNode);
|
|
} else {
|
|
fromNode.parentNode.insertBefore(fromNode, toNode);
|
|
}
|
|
for (var idx = 1; idx < table.rows.length; idx++) {
|
|
table.rows[idx].className = table.rows[idx].className.replace(
|
|
/cbi-rowstyle-[12]/,
|
|
"cbi-rowstyle-" + (1 + (idx % 2))
|
|
);
|
|
|
|
if (table.rows[idx].id && table.rows[idx].id.match(/-([^\-]+)$/))
|
|
ids.push(RegExp.$1);
|
|
}
|
|
var input = document.getElementById(store);
|
|
var order = ids.join(" ");
|
|
if (input) input.value = order;
|
|
XHR.post('<%=luci.dispatcher.build_url("admin/services/shadowsocksr/save_order")%>', {
|
|
order: order,
|
|
page: '<%=self.server_page or 1%>',
|
|
page_size: '<%=self.server_page_size or 0%>'
|
|
}, function(x, result) {
|
|
result = result || {};
|
|
if (result.ret !== 1 && result.ret !== "1") {
|
|
window.location.reload();
|
|
return;
|
|
}
|
|
if (input) {
|
|
input.value = "";
|
|
}
|
|
});
|
|
return false;
|
|
}
|
|
|
|
// set tr draggable
|
|
function enableDragForTable(table_selector, store) {
|
|
var storeInput = document.getElementById(store);
|
|
if (!storeInput) {
|
|
return;
|
|
}
|
|
// 添加 CSS 样式
|
|
const style = document.createElement("style");
|
|
style.textContent = `
|
|
tr[draggable="true"] {
|
|
cursor: move;
|
|
user-select: none;
|
|
}
|
|
.cbi-button-up,
|
|
.cbi-button-down,
|
|
.cbi-button-top,
|
|
.cbi-button-bottom {
|
|
display: none !important;
|
|
}
|
|
`;
|
|
document.head.appendChild(style);
|
|
|
|
var trs = document.querySelectorAll(table_selector + " tr");
|
|
if (!trs || trs.length < 2) {
|
|
return;
|
|
}
|
|
|
|
function getDataRows() {
|
|
return Array.prototype.filter.call(
|
|
document.querySelectorAll(table_selector + " tr"),
|
|
function(row) {
|
|
return row.id && row.id.indexOf("cbi-shadowsocksr-") === 0;
|
|
}
|
|
);
|
|
}
|
|
|
|
function ondragstart(ev) {
|
|
ev.dataTransfer.setData("Text", ev.target.id);
|
|
}
|
|
function ondrop(ev) {
|
|
var from = ev.dataTransfer.getData("Text");
|
|
cbi_row_drop(from, this.id, store);
|
|
}
|
|
function ondragover(ev) {
|
|
ev.preventDefault();
|
|
ev.dataTransfer.dropEffect = "move";
|
|
}
|
|
|
|
// 上移一行
|
|
function moveUp(id) {
|
|
var trList = getDataRows();
|
|
var currentIndex = -1;
|
|
for (var i = 0; i < trList.length; i++) {
|
|
if (trList[i].id === id) {
|
|
currentIndex = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (currentIndex > 0) {
|
|
var prevRow = trList[currentIndex - 1];
|
|
cbi_row_drop(id, prevRow.id, store);
|
|
}
|
|
}
|
|
|
|
// 下移一行
|
|
function moveDown(id) {
|
|
var trList = getDataRows();
|
|
var currentIndex = -1;
|
|
for (var i = 0; i < trList.length; i++) {
|
|
if (trList[i].id === id) {
|
|
currentIndex = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (currentIndex < trList.length - 1) {
|
|
var nextRow = trList[currentIndex + 1];
|
|
var nextNextRow = trList[currentIndex + 2];
|
|
if (nextNextRow) {
|
|
cbi_row_drop(id, nextNextRow.id, store, false);
|
|
} else {
|
|
cbi_row_drop(id, nextRow.id, store, true);
|
|
}
|
|
}
|
|
}
|
|
|
|
function moveToTop(id) {
|
|
var trList = getDataRows();
|
|
if (trList.length > 0) {
|
|
cbi_row_drop(id, trList[0].id, store);
|
|
}
|
|
}
|
|
|
|
function moveToBottom(id) {
|
|
var trList = getDataRows();
|
|
if (trList.length > 0) {
|
|
var bottom = trList[trList.length - 1];
|
|
cbi_row_drop(id, bottom.id, store, true);
|
|
}
|
|
}
|
|
|
|
var dataRows = getDataRows();
|
|
for (let index = 0; index < dataRows.length; index++) {
|
|
const el = dataRows[index];
|
|
el.setAttribute("draggable", true);
|
|
el.ondragstart = ondragstart;
|
|
el.ondrop = ondrop;
|
|
el.ondragover = ondragover;
|
|
}
|
|
}
|
|
|
|
// enable
|
|
enableDragForTable(
|
|
"#cbi-shadowsocksr-servers table",
|
|
"cbi.sts.shadowsocksr.servers"
|
|
);
|
|
//]]>
|
|
</script>
|