mirror of
https://github.com/caiwx86/small-packages.git
synced 2026-09-14 04:14:40 +08:00
update 2026-08-09 04:21:56
This commit is contained in:
@@ -55,8 +55,8 @@ local appname = api.appname
|
||||
return;
|
||||
}
|
||||
let to_url = '<%=api.url("node_config")%>/' + node_select_value;
|
||||
if (node_select_value.indexOf("Socks_") === 0) {
|
||||
to_url = '<%=api.url("socks_config")%>/' + node_select_value.substring("Socks_".length);
|
||||
if (node_select_value.indexOf("socks_") === 0) {
|
||||
to_url = '<%=api.url("socks_config")%>/' + node_select_value;
|
||||
}
|
||||
location.href = to_url;
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
const selectNode = document.getElementById(dom_id + "_node");
|
||||
val = selectNode.value;
|
||||
}
|
||||
if (val == "" || val.startsWith("_") || val.startsWith("Socks_") || !normal_list.find(element => element.id == val)) {
|
||||
if (val == "" || val.startsWith("_") || val.startsWith("socks_") || !normal_list.find(element => element.id == val)) {
|
||||
const hiddenSelect = document.getElementById(cbid);
|
||||
const panel = document.getElementById(cbid + ".panel");
|
||||
const display = document.getElementById(cbid + ".display");
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<%
|
||||
local api = require "luci.passwall.api"
|
||||
-%>
|
||||
<%+header%>
|
||||
<script type="text/javascript">
|
||||
//<![CDATA[
|
||||
var log_textarea = null;
|
||||
@@ -43,3 +44,4 @@ local api = require "luci.passwall.api"
|
||||
<input class="btn cbi-button cbi-button-remove" type="button" onclick="clearlog()" value="<%:Clear logs%>" />
|
||||
<textarea id="log_textarea" class="cbi-input-textarea" style="width: 100%;margin-top: 10px;" data-update="change" rows="40" wrap="off" readonly="readonly"></textarea>
|
||||
</fieldset>
|
||||
<%+footer%>
|
||||
|
||||
@@ -1,556 +0,0 @@
|
||||
<%
|
||||
local api = require "luci.passwall.api"
|
||||
-%>
|
||||
|
||||
<script type="text/javascript">
|
||||
//<![CDATA[
|
||||
function ajax_add_node(link, group) {
|
||||
const dom = document.getElementById('modal-mask');
|
||||
dom.style.display = 'block';
|
||||
dom.innerHTML = '<div style="position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);color:#fff;"><%:Processing, please wait…%></div>';
|
||||
|
||||
const chunkSize = 1000; // 分片发送以突破uhttpd的限制,每块1000字符
|
||||
const totalChunks = Math.ceil(link.length / chunkSize);
|
||||
let currentChunk = 0;
|
||||
|
||||
function sendNextChunk() {
|
||||
if (currentChunk < totalChunks) {
|
||||
const chunk = link.substring(currentChunk * chunkSize, (currentChunk + 1) * chunkSize);
|
||||
const xhr = new XMLHttpRequest();
|
||||
xhr.open('POST', '<%=api.url("link_add_node")%>', true);
|
||||
xhr.onload = function () {
|
||||
if (xhr.status === 200) {
|
||||
currentChunk++;
|
||||
sendNextChunk();
|
||||
} else {
|
||||
dom.innerHTML = '';
|
||||
dom.style.display = 'none';
|
||||
alert("<%:Error%>");
|
||||
return;
|
||||
}
|
||||
};
|
||||
xhr.onerror = function () {
|
||||
dom.innerHTML = '';
|
||||
dom.style.display = 'none';
|
||||
alert("<%:Network Error%>");
|
||||
return;
|
||||
};
|
||||
const formData = new FormData();
|
||||
formData.append("chunk", chunk);
|
||||
formData.append("chunk_index", currentChunk);
|
||||
formData.append("total_chunks", totalChunks);
|
||||
formData.append("group", group);
|
||||
xhr.send(formData);
|
||||
} else {
|
||||
window.location.href = '<%=api.url("node_list")%>';
|
||||
}
|
||||
}
|
||||
sendNextChunk();
|
||||
}
|
||||
|
||||
function decodeIfBase64(str) {
|
||||
try {
|
||||
let s = str.replace(/-/g, '+').replace(/_/g, '/');
|
||||
while (s.length % 4) s += '=';
|
||||
const decoded = decodeURIComponent(
|
||||
atob(s).split('').map(c =>
|
||||
'%' + c.charCodeAt(0).toString(16).padStart(2, '0')
|
||||
).join('')
|
||||
);
|
||||
if (btoa(unescape(encodeURIComponent(decoded))).replace(/=+$/, '') === s.replace(/=+$/, '')) {
|
||||
return decoded;
|
||||
}
|
||||
} catch (e) {}
|
||||
return str;
|
||||
}
|
||||
|
||||
function getBg(el) {
|
||||
if (!el) return null;
|
||||
const style = getComputedStyle(el);
|
||||
const bgImage = style.backgroundImage;
|
||||
const bgColor = style.backgroundColor;
|
||||
return (bgImage !== 'none' || !/rgba\([^,]+,[^,]+,[^,]+,\s*0\)/.test(bgColor) && bgColor !== 'transparent')
|
||||
? style.background
|
||||
: null;
|
||||
};
|
||||
|
||||
function open_add_link_div() {
|
||||
document.body.classList.add('modal-open');
|
||||
document.getElementById('modal-mask').style.display = 'block';
|
||||
const addLinkDiv = document.getElementById("add_link_div");
|
||||
addLinkDiv.style.background = getBg(document.querySelector('.cbi-section')) || getBg(document.body) || '';
|
||||
addLinkDiv.style.display = "block";
|
||||
if (!addLinkDiv._dropdown_inited) {
|
||||
addLinkDiv._dropdown_inited = true;
|
||||
dropdown_list_fun("addlink_group_custom");
|
||||
}
|
||||
document.getElementById("nodes_link").focus();
|
||||
}
|
||||
|
||||
function close_add_link_div() {
|
||||
document.getElementById('modal-mask').style.display = 'none';
|
||||
document.getElementById("add_link_div").style.display = "none";
|
||||
document.body.classList.remove('modal-open');
|
||||
}
|
||||
|
||||
function add_node() {
|
||||
let nodes_link = document.getElementById("nodes_link").value;
|
||||
const group = (document.querySelector('#addlink_group_custom input[type="hidden"]')?.value || "default");
|
||||
nodes_link = nodes_link.replace(/\t/g, "").replace(/\r\n|\r/g, "\n").replace(/<[^>]*>/g, '').trim();
|
||||
if (nodes_link != "") {
|
||||
nodes_link = decodeIfBase64(nodes_link);
|
||||
let s = nodes_link.split('://');
|
||||
if (s.length > 1) {
|
||||
close_add_link_div();
|
||||
ajax_add_node(nodes_link, group);
|
||||
} else {
|
||||
alert("<%:Please enter the correct link.%>");
|
||||
}
|
||||
} else {
|
||||
document.getElementById("nodes_link").focus();
|
||||
}
|
||||
}
|
||||
|
||||
function clear_all_nodes() {
|
||||
if (confirm('<%:Are you sure to clear all nodes?%>') == true){
|
||||
XHR.get('<%=api.url("clear_all_nodes")%>', null,
|
||||
function(x, data) {
|
||||
if(x && x.status == 200) {
|
||||
window.location.href = '<%=api.url("node_list")%>';
|
||||
} else {
|
||||
alert("<%:Error%>");
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function open_reassign_group_div() {
|
||||
const ids = [];
|
||||
const visibleContainer = document.querySelector('#cbi-passwall-nodes > .cbi-tabcontainer[style*="display:block"], #cbi-passwall-nodes > .cbi-tabcontainer[style*="display: block"]');
|
||||
if (!visibleContainer) return;
|
||||
const doms = visibleContainer.getElementsByClassName("nodes_select");
|
||||
if (doms && doms.length > 0) {
|
||||
for (let i = 0 ; i < doms.length; i++) {
|
||||
if (doms[i].checked) {
|
||||
ids.push(doms[i].getAttribute("cbid"))
|
||||
}
|
||||
}
|
||||
if (ids.length > 0) {
|
||||
document.body.classList.add('modal-open');
|
||||
document.getElementById('modal-mask').style.display = 'block';
|
||||
const reassignGroupDiv = document.getElementById("reassign_group_div");
|
||||
reassignGroupDiv.style.background = getBg(document.querySelector('.cbi-section')) || getBg(document.body) || '';
|
||||
reassignGroupDiv.style.display = "block";
|
||||
if (!reassignGroupDiv._dropdown_inited) {
|
||||
reassignGroupDiv._dropdown_inited = true;
|
||||
dropdown_list_fun("reassign_group_custom");
|
||||
}
|
||||
} else {
|
||||
alert("<%:You no select nodes !%>");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function close_reassign_group_div() {
|
||||
document.getElementById('modal-mask').style.display = 'none';
|
||||
document.getElementById("reassign_group_div").style.display = "none";
|
||||
document.body.classList.remove('modal-open');
|
||||
}
|
||||
|
||||
function reassign_group() {
|
||||
const ids = [];
|
||||
const group = (document.querySelector('#reassign_group_custom input[type="hidden"]')?.value || "default");
|
||||
const visibleContainer = document.querySelector('#cbi-passwall-nodes > .cbi-tabcontainer[style*="display:block"], #cbi-passwall-nodes > .cbi-tabcontainer[style*="display: block"]');
|
||||
if (!visibleContainer) return;
|
||||
const doms = visibleContainer.getElementsByClassName("nodes_select");
|
||||
if (doms && doms.length > 0) {
|
||||
for (let i = 0 ; i < doms.length; i++) {
|
||||
if (doms[i].checked) {
|
||||
ids.push(doms[i].getAttribute("cbid"))
|
||||
}
|
||||
}
|
||||
if (ids.length > 0) {
|
||||
XHR.get('<%=api.url("reassign_group")%>', {
|
||||
group: group,
|
||||
ids: ids.join(",")
|
||||
},
|
||||
function(x, data) {
|
||||
if (x && x.status == 200) {
|
||||
window.location.href = '<%=api.url("node_list")%>';
|
||||
} else {
|
||||
alert("<%:Error%>");
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//自定义分组下拉列表事件
|
||||
function dropdown_list_fun(div_id) {
|
||||
const dropdown = document.getElementById(div_id);
|
||||
if (!dropdown) return;
|
||||
|
||||
const display = dropdown.querySelector(".selected-display");
|
||||
const displayText = display.querySelector(".text");
|
||||
const list = dropdown.querySelector(".dropdown-list");
|
||||
const hidden = dropdown.querySelector('input[type="hidden"]');
|
||||
const input = dropdown.querySelector(".create-item-input");
|
||||
|
||||
display.addEventListener("click", function() {
|
||||
list.style.display = list.style.display === "none" ? "block" : "none";
|
||||
input.value = "";
|
||||
//input.focus();
|
||||
});
|
||||
|
||||
function selectItem(li) {
|
||||
list.querySelectorAll(".dropdown-item").forEach(function(el){
|
||||
el.classList.remove("selected");
|
||||
});
|
||||
li.classList.add("selected");
|
||||
hidden.value = li.dataset.value;
|
||||
displayText.textContent = li.dataset.value || "<%:default%>";
|
||||
list.style.display = "none";
|
||||
}
|
||||
|
||||
list.addEventListener("click", function(e){
|
||||
const li = e.target.closest(".dropdown-item");
|
||||
if (!li || li.classList.contains("custom-input")) return;
|
||||
selectItem(li);
|
||||
});
|
||||
|
||||
input.addEventListener("keydown", function(e){
|
||||
const isEnter = e.key === "Enter" || e.keyCode === 13;
|
||||
if (!isEnter) return;
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
|
||||
const val = input.value.trim();
|
||||
if (!val) return;
|
||||
|
||||
if (val.toLowerCase() === "default") {
|
||||
const emptyLi = Array.from(list.querySelectorAll(".dropdown-item"))
|
||||
.find(function(el){ return !el.dataset.value; });
|
||||
if (emptyLi) selectItem(emptyLi);
|
||||
input.value = "";
|
||||
return;
|
||||
}
|
||||
|
||||
let li = Array.from(list.querySelectorAll(".dropdown-item")).find(function(el){
|
||||
return el.dataset.value && el.dataset.value.toLowerCase() === val.toLowerCase();
|
||||
});
|
||||
if (!li) {
|
||||
li = document.createElement("li");
|
||||
li.className = "dropdown-item";
|
||||
li.dataset.value = val;
|
||||
li.textContent = val;
|
||||
list.insertBefore(li, input.parentNode);
|
||||
}
|
||||
|
||||
input.value = "";
|
||||
selectItem(li);
|
||||
});
|
||||
// 从tab中读取分组名称
|
||||
function InsertGroup(list, input) {
|
||||
const tabs = document.querySelectorAll(".cbi-tabmenu li");
|
||||
if (!tabs.length) {
|
||||
setTimeout(() => InsertGroup(list, input), 50);
|
||||
return;
|
||||
}
|
||||
|
||||
tabs.forEach(function(li){
|
||||
const group = li.id.split('.').pop();
|
||||
if(group === "default") return;
|
||||
if(Array.from(list.querySelectorAll(".dropdown-item")).some(el => el.dataset.value === group)) return;
|
||||
const newLi = document.createElement("li");
|
||||
newLi.className = "dropdown-item";
|
||||
newLi.dataset.value = group;
|
||||
newLi.textContent = group;
|
||||
list.insertBefore(newLi, input.parentNode);
|
||||
});
|
||||
}
|
||||
InsertGroup(list, input);
|
||||
|
||||
// 点击外部时自动收起
|
||||
document.addEventListener("click", function(e) {
|
||||
if (!dropdown.contains(e.target)) {
|
||||
list.style.display = "none";
|
||||
}
|
||||
});
|
||||
}
|
||||
//]]>
|
||||
</script>
|
||||
|
||||
<div id="modal-mask"></div>
|
||||
|
||||
<div id="add_link_div">
|
||||
<div id="add_link_modal_container">
|
||||
<h3><%:Add the node via the link%></h3>
|
||||
<div class="value-custom">
|
||||
<textarea id="nodes_link" rows="10"></textarea>
|
||||
<p id="nodes_link_text"><%:Enter share links, one per line. Subscription links are not supported!%></p>
|
||||
</div>
|
||||
<div class="value-custom">
|
||||
<div class="value-field-custom">
|
||||
<label class="value-title-custom" for="addlink_group_custom"><%:Group Name%></label>
|
||||
<div id="addlink_group_custom" class="custom-dropdown">
|
||||
<div class="selected-display">
|
||||
<span class="text"><%:default%></span>
|
||||
<span class="arrow-down-small"></span>
|
||||
</div>
|
||||
<ul class="dropdown-list" style="display:none;">
|
||||
<li class="dropdown-item selected" data-value=""><%:default%></li>
|
||||
<li class="dropdown-item custom-input">
|
||||
<input type="text" placeholder="-- <%:custom%> --" class="create-item-input" inputmode="text" enterkeyhint="done">
|
||||
</li>
|
||||
</ul>
|
||||
<input type="hidden" name="addlink_group" value="">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="add_link_button_container">
|
||||
<input class="btn cbi-button cbi-button-add" type="button" onclick="add_node()" value="<%:Add%>" />
|
||||
<input class="btn cbi-button cbi-button-remove" type="button" onclick="close_add_link_div()" value="<%:Close%>" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="reassign_group_div">
|
||||
<div id="reassign_group_modal_container">
|
||||
<h3><%:Reassign Node Group%></h3>
|
||||
<div class="value-custom">
|
||||
<div class="value-field-custom">
|
||||
<label class="value-title-custom" for="reassign_group_custom"><%:Group Name%></label>
|
||||
<div id="reassign_group_custom" class="custom-dropdown">
|
||||
<div class="selected-display">
|
||||
<span class="text"><%:default%></span>
|
||||
<span class="arrow-down-small"></span>
|
||||
</div>
|
||||
<ul class="dropdown-list" style="display:none;">
|
||||
<li class="dropdown-item selected" data-value=""><%:default%></li>
|
||||
<li class="dropdown-item custom-input">
|
||||
<input type="text" placeholder="-- <%:custom%> --" class="create-item-input" inputmode="text" enterkeyhint="done">
|
||||
</li>
|
||||
</ul>
|
||||
<input type="hidden" name="to_group" value="">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="reassign_group_button_container">
|
||||
<input class="btn cbi-button cbi-button-edit" type="button" onclick="reassign_group()" value="<%:Save%>" />
|
||||
<input class="btn cbi-button cbi-button-remove" type="button" onclick="close_reassign_group_div()" value="<%:Close%>" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<div class="pw-toolbar">
|
||||
<div class="pw-toolbar-field">
|
||||
<input class="btn cbi-button cbi-button-add" type="button" onclick="to_add_node()" value="<%:Add%>" />
|
||||
<input class="btn cbi-button cbi-button-add" type="button" onclick="open_add_link_div()" value="<%:Add the node via the link%>" />
|
||||
<input class="btn cbi-button cbi-button-remove" type="button" onclick="clear_all_nodes()" value="<%:Clear all nodes%>" />
|
||||
<input class="btn cbi-button cbi-button-remove" type="button" onclick="delete_select_nodes()" value="<%:Delete select nodes%>" />
|
||||
<input class="btn cbi-button cbi-button-edit" type="button" id="select_all_btn" onclick="checked_all_node(this)" value="<%:Select all%>" />
|
||||
<input class="btn cbi-button cbi-button-edit" type="button" onclick="open_reassign_group_div()" value="<%:Reassign Group%>" />
|
||||
<input class="btn cbi-button cbi-button-apply" type="submit" name="cbi.apply" value="<%:Save & Apply%>" />
|
||||
<!--<input class="btn cbi-button cbi-button-save" type="submit" name="cbi.save" value="<%:Save%>" />-->
|
||||
<!--<input class="btn cbi-button cbi-button-reset" type="button" value="<%:Reset%>" onclick="location.href='<%=REQUEST_URI%>'" />-->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.pw-toolbar {
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.pw-toolbar-field {
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
display: inline-block;
|
||||
padding: 5px 0 5px;
|
||||
}
|
||||
|
||||
#modal-mask {
|
||||
display: none;
|
||||
position: fixed;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
background: rgba(0,0,0,0.4);
|
||||
z-index: 999;
|
||||
}
|
||||
|
||||
body.modal-open {
|
||||
overflow: hidden;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
body.modal-open #add_link_div,
|
||||
body.modal-open #reassign_group_div {
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
#add_link_div, #reassign_group_div {
|
||||
display: none;
|
||||
position: fixed;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
background: white;
|
||||
padding: 20px;
|
||||
border: 2px solid #ccc;
|
||||
box-shadow: 0 0 10px rgba(0,0,0,0.5);
|
||||
z-index: 1000;
|
||||
width: 90%;
|
||||
max-width: 500px;
|
||||
}
|
||||
|
||||
#add_link_modal_container, #reassign_group_modal_container {
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding-bottom: 20px;
|
||||
}
|
||||
|
||||
#nodes_link {
|
||||
width: 100%;
|
||||
height: 180px;
|
||||
resize: vertical;
|
||||
font-family: monospace;
|
||||
padding: 5px;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
#add_link_div h3,
|
||||
#reassign_group_div h3 {
|
||||
background: inherit;
|
||||
}
|
||||
|
||||
#nodes_link_text {
|
||||
color: red;
|
||||
font-size: 14px;
|
||||
margin-top: 5px;
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#add_link_button_container, #reassign_group_button_container {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
max-width: 300px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.value-custom {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
margin: 10px 0;
|
||||
padding: 0px 5px 0px 5px;
|
||||
}
|
||||
|
||||
.value-field-custom {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.value-title-custom {
|
||||
font-size: 13px;
|
||||
line-height: 28px;
|
||||
white-space: nowrap;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.custom-dropdown {
|
||||
position: relative;
|
||||
border: 1px solid #d9d9d9;
|
||||
border-radius: 2px;
|
||||
width: 180px;
|
||||
height: 28px;
|
||||
font-size: 13px;
|
||||
background: #fff;
|
||||
cursor: pointer;
|
||||
box-sizing: border-box;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.selected-display {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 0 8px;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
box-sizing: border-box;
|
||||
font-size: 12px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.selected-display:hover {
|
||||
background-color: #f7f7f7;
|
||||
}
|
||||
|
||||
.dropdown-list {
|
||||
position: absolute;
|
||||
top: calc(100% + 2px);
|
||||
left: -1px;
|
||||
width: calc(100% + 2px);
|
||||
border: 1px solid #d9d9d9;
|
||||
border-radius: 2px;
|
||||
border-top: none;
|
||||
box-shadow: 0 1px 3px rgba(0,0,0,0.15);
|
||||
background: #fff;
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
max-height: 200px;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
z-index: 100;
|
||||
box-sizing: border-box;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.dropdown-item {
|
||||
padding: 4px 8px;
|
||||
line-height: 20px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.dropdown-item.selected {
|
||||
background-color: #1e90ff;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.create-item-input::placeholder {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.dropdown-item.custom-input input {
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
padding: 3px;
|
||||
font-size: 13px;
|
||||
line-height: 20px;
|
||||
border: 1px solid #ccc;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.arrow-down-small {
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-left: 4px solid transparent;
|
||||
border-right: 4px solid transparent;
|
||||
border-top: 5px solid #555;
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
}
|
||||
</style>
|
||||
@@ -166,8 +166,205 @@ table td, .table .td {
|
||||
}
|
||||
</style>
|
||||
|
||||
<% if api.is_js_luci() then -%>
|
||||
<style>
|
||||
.pw-toolbar {
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.pw-toolbar-field {
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
display: inline-block;
|
||||
padding: 5px 0 5px;
|
||||
}
|
||||
|
||||
#modal-mask {
|
||||
display: none;
|
||||
position: fixed;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
background: rgba(0,0,0,0.4);
|
||||
z-index: 999;
|
||||
}
|
||||
|
||||
body.modal-open {
|
||||
overflow: hidden;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
body.modal-open #add_link_div,
|
||||
body.modal-open #reassign_group_div {
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
#add_link_div, #reassign_group_div {
|
||||
display: none;
|
||||
position: fixed;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
background: white;
|
||||
padding: 20px;
|
||||
border: 2px solid #ccc;
|
||||
box-shadow: 0 0 10px rgba(0,0,0,0.5);
|
||||
z-index: 1000;
|
||||
width: 90%;
|
||||
max-width: 500px;
|
||||
}
|
||||
|
||||
#add_link_modal_container, #reassign_group_modal_container {
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding-bottom: 20px;
|
||||
}
|
||||
|
||||
#nodes_link {
|
||||
width: 100%;
|
||||
height: 180px;
|
||||
resize: vertical;
|
||||
font-family: monospace;
|
||||
padding: 5px;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
#add_link_div h3,
|
||||
#reassign_group_div h3 {
|
||||
background: inherit;
|
||||
}
|
||||
|
||||
#nodes_link_text {
|
||||
color: red;
|
||||
font-size: 14px;
|
||||
margin-top: 5px;
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#add_link_button_container, #reassign_group_button_container {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
max-width: 300px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.value-custom {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
margin: 10px 0;
|
||||
padding: 0px 5px 0px 5px;
|
||||
}
|
||||
|
||||
.value-field-custom {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.value-title-custom {
|
||||
font-size: 13px;
|
||||
line-height: 28px;
|
||||
white-space: nowrap;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.custom-dropdown {
|
||||
position: relative;
|
||||
border: 1px solid #d9d9d9;
|
||||
border-radius: 2px;
|
||||
width: 180px;
|
||||
height: 28px;
|
||||
font-size: 13px;
|
||||
background: #fff;
|
||||
cursor: pointer;
|
||||
box-sizing: border-box;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.selected-display {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 0 8px;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
box-sizing: border-box;
|
||||
font-size: 12px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.selected-display:hover {
|
||||
background-color: #f7f7f7;
|
||||
}
|
||||
|
||||
.dropdown-list {
|
||||
position: absolute;
|
||||
top: calc(100% + 2px);
|
||||
left: -1px;
|
||||
width: calc(100% + 2px);
|
||||
border: 1px solid #d9d9d9;
|
||||
border-radius: 2px;
|
||||
border-top: none;
|
||||
box-shadow: 0 1px 3px rgba(0,0,0,0.15);
|
||||
background: #fff;
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
max-height: 200px;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
z-index: 100;
|
||||
box-sizing: border-box;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.dropdown-item {
|
||||
padding: 4px 8px;
|
||||
line-height: 20px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.dropdown-item.selected {
|
||||
background-color: #1e90ff;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.create-item-input::placeholder {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.dropdown-item.custom-input input {
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
padding: 3px;
|
||||
font-size: 13px;
|
||||
line-height: 20px;
|
||||
border: 1px solid #ccc;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.arrow-down-small {
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-left: 4px solid transparent;
|
||||
border-right: 4px solid transparent;
|
||||
border-top: 5px solid #555;
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script type="text/javascript">
|
||||
<% if api.is_js_luci() then -%>
|
||||
var cbi_t = [];
|
||||
function cbi_t_add(section, tab) {
|
||||
var t = document.getElementById('tab.' + section + '.' + tab);
|
||||
@@ -200,9 +397,7 @@ table td, .table .td {
|
||||
}
|
||||
return false
|
||||
}
|
||||
</script>
|
||||
<%- else %>
|
||||
<script type="text/javascript">
|
||||
(function() {
|
||||
if (typeof(cbi_t_switch) === "function") {
|
||||
var old_switch = cbi_t_switch;
|
||||
@@ -212,16 +407,16 @@ table td, .table .td {
|
||||
};
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
<%- end %>
|
||||
</script>
|
||||
|
||||
<script type="text/javascript">
|
||||
//<![CDATA[
|
||||
let auto_detection_time = "<%=api.uci_get_type("global_other", "auto_detection_time", "0")%>"
|
||||
let show_node_info = "<%=api.uci_get_type("global_other", "show_node_info", "0")%>"
|
||||
var ajaxList = [];
|
||||
|
||||
var ajax = {
|
||||
list: [],
|
||||
post: function(url, data, fn_success, timeout, fn_timeout) {
|
||||
var xhr = new XMLHttpRequest();
|
||||
var code = ajax.encode(data);
|
||||
@@ -250,7 +445,7 @@ table td, .table .td {
|
||||
fn_success(xhr, json);
|
||||
}
|
||||
};
|
||||
ajaxList.push(xhr);
|
||||
this.list.push(xhr);
|
||||
xhr.send(code);
|
||||
},
|
||||
encode: function(obj) {
|
||||
@@ -270,65 +465,63 @@ table td, .table .td {
|
||||
}
|
||||
|
||||
return obj;
|
||||
},
|
||||
abortAll: function() {
|
||||
this.list.forEach(xhr => xhr.abort());
|
||||
this.list = [];
|
||||
}
|
||||
}
|
||||
|
||||
function ajaxAbortAll() {
|
||||
ajaxList.forEach(xhr => xhr.abort());
|
||||
ajaxList = [];
|
||||
}
|
||||
|
||||
function to_add_node() {
|
||||
ajaxAbortAll();
|
||||
const dom = document.getElementsByClassName("cbi-tab")[0];
|
||||
const current_group = dom ? (dom.getAttribute("group_name") || "default") : "default";
|
||||
window.location.href = '<%=api.url("add_node")%>?redirect=1&group=' + current_group;
|
||||
}
|
||||
|
||||
function to_edit_node(cbi_id) {
|
||||
ajaxAbortAll();
|
||||
location.href='<%=api.url("node_config")%>/' + cbi_id;
|
||||
}
|
||||
|
||||
function copy_node(cbi_id) {
|
||||
ajaxAbortAll();
|
||||
window.location.href = '<%=api.url("copy_node")%>' + "?section=" + cbi_id;
|
||||
}
|
||||
|
||||
function del_node(cbi_id) {
|
||||
ajaxAbortAll();
|
||||
window.location.href = '<%=api.url("delete_select_nodes")%>' + "?redirect=1&ids=" + cbi_id;
|
||||
}
|
||||
|
||||
var section = "";
|
||||
function open_set_node_div(cbi_id) {
|
||||
section = cbi_id;
|
||||
document.getElementById("set_node_div").style.display="block";
|
||||
var node_name = document.getElementById("cbid.<%=appname%>." + cbi_id + ".remarks").value;
|
||||
document.getElementById("set_node_name").innerHTML = node_name;
|
||||
}
|
||||
|
||||
function close_set_node_div() {
|
||||
document.getElementById("set_node_div").style.display="none";
|
||||
document.getElementById("set_node_name").innerHTML = "";
|
||||
}
|
||||
|
||||
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";
|
||||
function node_action(action, cbi_id, a) {
|
||||
ajax.abortAll();
|
||||
switch (action) {
|
||||
case "add":
|
||||
const dom = document.getElementsByClassName("cbi-tab")[0];
|
||||
const current_group = dom ? (dom.getAttribute("group_name") || "default") : "default";
|
||||
window.location.href='<%=api.url("add_node")%>?redirect=1&group=' + current_group;
|
||||
break;
|
||||
case "copy":
|
||||
window.location.href = '<%=api.url("copy_node")%>' + "?section=" + cbi_id;
|
||||
break;
|
||||
case "edit":
|
||||
window.location.href='<%=api.url("node_config")%>/' + cbi_id;
|
||||
break;
|
||||
case "del":
|
||||
window.location.href = '<%=api.url("delete_select_nodes")%>' + "?redirect=1&ids=" + cbi_id;
|
||||
break;
|
||||
case "row_top":
|
||||
let btn = a.btn;
|
||||
let group = a.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";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
case "del_all":
|
||||
if (confirm('<%:Are you sure to clear all nodes?%>') == true){
|
||||
XHR.get('<%=api.url("clear_all_nodes")%>', null,
|
||||
function(x, data) {
|
||||
if(x && x.status == 200) {
|
||||
window.location.href = '<%=api.url("node_list")%>';
|
||||
} else {
|
||||
alert("<%:Error%>");
|
||||
}
|
||||
});
|
||||
}
|
||||
break;
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
@@ -403,6 +596,7 @@ table td, .table .td {
|
||||
if (confirm('<%:Are you sure to delete select nodes?%>') == true) {
|
||||
dom.style.display = 'block';
|
||||
dom.innerHTML = '<div style="position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);color:#fff;"><%:Processing, please wait…%></div>';
|
||||
ajax.abortAll();
|
||||
XHR.get('<%=api.url("delete_select_nodes")%>', {
|
||||
ids: ids.join()
|
||||
},
|
||||
@@ -416,7 +610,7 @@ table td, .table .td {
|
||||
box.remove();
|
||||
}
|
||||
*/
|
||||
ajaxAbortAll();
|
||||
ajax.abortAll();
|
||||
window.location.href = '<%=api.url("node_list")%>';
|
||||
} else {
|
||||
alert("<%:Error%>");
|
||||
@@ -430,13 +624,6 @@ table td, .table .td {
|
||||
}
|
||||
}
|
||||
|
||||
function set_node(protocol) {
|
||||
if (confirm('<%:Are you sure set to%> ' + protocol.toUpperCase() + '<%:the server?%>')==true){
|
||||
ajaxAbortAll();
|
||||
window.location.href = '<%=api.url("set_node")%>?protocol=' + protocol + '§ion=' + section;
|
||||
}
|
||||
}
|
||||
|
||||
function get_address_full(id) {
|
||||
var address = (document.getElementById("cbid.<%=appname%>." + id + ".address") || {}).value || "";
|
||||
var port = (document.getElementById("cbid.<%=appname%>." + id + ".port") || {}).value || "";
|
||||
@@ -484,6 +671,7 @@ table td, .table .td {
|
||||
var id = row.id.replace("cbi-<%=appname%>-", "");
|
||||
ids.push(id);
|
||||
});
|
||||
ajax.abortAll();
|
||||
XHR.get('<%=api.url("save_node_order")%>', {
|
||||
group: group,
|
||||
ids: ids.join(",")
|
||||
@@ -741,16 +929,6 @@ 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;
|
||||
@@ -821,6 +999,292 @@ table td, .table .td {
|
||||
}
|
||||
</script>
|
||||
|
||||
<script type="text/javascript">
|
||||
//<![CDATA[
|
||||
//Pop-up logic
|
||||
|
||||
// Custom group dropdown list events
|
||||
function dropdown_list_fun(div_id) {
|
||||
const dropdown = document.getElementById(div_id);
|
||||
if (!dropdown) return;
|
||||
|
||||
const display = dropdown.querySelector(".selected-display");
|
||||
const displayText = display.querySelector(".text");
|
||||
const list = dropdown.querySelector(".dropdown-list");
|
||||
const hidden = dropdown.querySelector('input[type="hidden"]');
|
||||
const input = dropdown.querySelector(".create-item-input");
|
||||
|
||||
display.addEventListener("click", function() {
|
||||
list.style.display = list.style.display === "none" ? "block" : "none";
|
||||
input.value = "";
|
||||
//input.focus();
|
||||
});
|
||||
|
||||
function selectItem(li) {
|
||||
list.querySelectorAll(".dropdown-item").forEach(function(el){
|
||||
el.classList.remove("selected");
|
||||
});
|
||||
li.classList.add("selected");
|
||||
hidden.value = li.dataset.value;
|
||||
displayText.textContent = li.dataset.value || "<%:default%>";
|
||||
list.style.display = "none";
|
||||
}
|
||||
|
||||
list.addEventListener("click", function(e){
|
||||
const li = e.target.closest(".dropdown-item");
|
||||
if (!li || li.classList.contains("custom-input")) return;
|
||||
selectItem(li);
|
||||
});
|
||||
|
||||
input.addEventListener("keydown", function(e){
|
||||
const isEnter = e.key === "Enter" || e.keyCode === 13;
|
||||
if (!isEnter) return;
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
|
||||
const val = input.value.trim();
|
||||
if (!val) return;
|
||||
|
||||
if (val.toLowerCase() === "default") {
|
||||
const emptyLi = Array.from(list.querySelectorAll(".dropdown-item"))
|
||||
.find(function(el){ return !el.dataset.value; });
|
||||
if (emptyLi) selectItem(emptyLi);
|
||||
input.value = "";
|
||||
return;
|
||||
}
|
||||
|
||||
let li = Array.from(list.querySelectorAll(".dropdown-item")).find(function(el){
|
||||
return el.dataset.value && el.dataset.value.toLowerCase() === val.toLowerCase();
|
||||
});
|
||||
if (!li) {
|
||||
li = document.createElement("li");
|
||||
li.className = "dropdown-item";
|
||||
li.dataset.value = val;
|
||||
li.textContent = val;
|
||||
list.insertBefore(li, input.parentNode);
|
||||
}
|
||||
|
||||
input.value = "";
|
||||
selectItem(li);
|
||||
});
|
||||
// Read group names from tab
|
||||
function InsertGroup(list, input) {
|
||||
const tabs = document.querySelectorAll(".cbi-tabmenu li");
|
||||
if (!tabs.length) {
|
||||
setTimeout(() => InsertGroup(list, input), 50);
|
||||
return;
|
||||
}
|
||||
|
||||
tabs.forEach(function(li){
|
||||
const group = li.id.split('.').pop();
|
||||
if(group === "default") return;
|
||||
if(Array.from(list.querySelectorAll(".dropdown-item")).some(el => el.dataset.value === group)) return;
|
||||
const newLi = document.createElement("li");
|
||||
newLi.className = "dropdown-item";
|
||||
newLi.dataset.value = group;
|
||||
newLi.textContent = group;
|
||||
list.insertBefore(newLi, input.parentNode);
|
||||
});
|
||||
}
|
||||
InsertGroup(list, input);
|
||||
|
||||
// Automatically collapses when clicking on the outside.
|
||||
document.addEventListener("click", function(e) {
|
||||
if (!dropdown.contains(e.target)) {
|
||||
list.style.display = "none";
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function getBg(el) {
|
||||
if (!el) return null;
|
||||
const style = getComputedStyle(el);
|
||||
const bgImage = style.backgroundImage;
|
||||
const bgColor = style.backgroundColor;
|
||||
return (bgImage !== 'none' || !/rgba\([^,]+,[^,]+,[^,]+,\s*0\)/.test(bgColor) && bgColor !== 'transparent')
|
||||
? style.background
|
||||
: null;
|
||||
}
|
||||
|
||||
const add_link_div = {
|
||||
open() {
|
||||
document.body.classList.add('modal-open');
|
||||
document.getElementById('modal-mask').style.display = 'block';
|
||||
const addLinkDiv = document.getElementById("add_link_div");
|
||||
addLinkDiv.style.background = getBg(document.querySelector('.cbi-section')) || getBg(document.body) || '';
|
||||
addLinkDiv.style.display = "block";
|
||||
if (!addLinkDiv._dropdown_inited) {
|
||||
addLinkDiv._dropdown_inited = true;
|
||||
dropdown_list_fun("addlink_group_custom");
|
||||
}
|
||||
document.getElementById("nodes_link").focus();
|
||||
},
|
||||
close() {
|
||||
document.getElementById('modal-mask').style.display = 'none';
|
||||
document.getElementById("add_link_div").style.display = "none";
|
||||
document.body.classList.remove('modal-open');
|
||||
},
|
||||
add_node() {
|
||||
let nodes_link = document.getElementById("nodes_link").value;
|
||||
const group = (document.querySelector('#addlink_group_custom input[type="hidden"]')?.value || "default");
|
||||
nodes_link = nodes_link.replace(/\t/g, "").replace(/\r\n|\r/g, "\n").replace(/<[^>]*>/g, '').trim();
|
||||
if (nodes_link != "") {
|
||||
nodes_link = decodeIfBase64(nodes_link);
|
||||
let s = nodes_link.split('://');
|
||||
if (s.length > 1) {
|
||||
this.close();
|
||||
this.ajax_add_node(nodes_link, group);
|
||||
} else {
|
||||
alert("<%:Please enter the correct link.%>");
|
||||
}
|
||||
} else {
|
||||
document.getElementById("nodes_link").focus();
|
||||
}
|
||||
},
|
||||
ajax_add_node(link, group) {
|
||||
const dom = document.getElementById('modal-mask');
|
||||
dom.style.display = 'block';
|
||||
dom.innerHTML = '<div style="position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);color:#fff;"><%:Processing, please wait…%></div>';
|
||||
|
||||
const chunkSize = 1000; // Fragmented sending to overcome uhttpd's limitations, with each fragment containing 1000 characters.
|
||||
const totalChunks = Math.ceil(link.length / chunkSize);
|
||||
let currentChunk = 0;
|
||||
|
||||
function sendNextChunk() {
|
||||
if (currentChunk < totalChunks) {
|
||||
const chunk = link.substring(currentChunk * chunkSize, (currentChunk + 1) * chunkSize);
|
||||
const xhr = new XMLHttpRequest();
|
||||
xhr.open('POST', '<%=api.url("link_add_node")%>', true);
|
||||
xhr.onload = function () {
|
||||
if (xhr.status === 200) {
|
||||
currentChunk++;
|
||||
sendNextChunk();
|
||||
} else {
|
||||
dom.innerHTML = '';
|
||||
dom.style.display = 'none';
|
||||
alert("<%:Error%>");
|
||||
return;
|
||||
}
|
||||
};
|
||||
xhr.onerror = function () {
|
||||
dom.innerHTML = '';
|
||||
dom.style.display = 'none';
|
||||
alert("<%:Network Error%>");
|
||||
return;
|
||||
};
|
||||
const formData = new FormData();
|
||||
formData.append("chunk", chunk);
|
||||
formData.append("chunk_index", currentChunk);
|
||||
formData.append("total_chunks", totalChunks);
|
||||
formData.append("group", group);
|
||||
ajax.abortAll();
|
||||
xhr.send(formData);
|
||||
} else {
|
||||
window.location.href = '<%=api.url("node_list")%>';
|
||||
}
|
||||
}
|
||||
sendNextChunk();
|
||||
}
|
||||
}
|
||||
|
||||
const reassign_group_div = {
|
||||
open() {
|
||||
const ids = [];
|
||||
const visibleContainer = document.querySelector('#cbi-passwall-nodes > .cbi-tabcontainer[style*="display:block"], #cbi-passwall-nodes > .cbi-tabcontainer[style*="display: block"]');
|
||||
if (!visibleContainer) return;
|
||||
const doms = visibleContainer.getElementsByClassName("nodes_select");
|
||||
if (doms && doms.length > 0) {
|
||||
for (let i = 0 ; i < doms.length; i++) {
|
||||
if (doms[i].checked) {
|
||||
ids.push(doms[i].getAttribute("cbid"))
|
||||
}
|
||||
}
|
||||
if (ids.length > 0) {
|
||||
document.body.classList.add('modal-open');
|
||||
document.getElementById('modal-mask').style.display = 'block';
|
||||
const reassignGroupDiv = document.getElementById("reassign_group_div");
|
||||
reassignGroupDiv.style.background = getBg(document.querySelector('.cbi-section')) || getBg(document.body) || '';
|
||||
reassignGroupDiv.style.display = "block";
|
||||
if (!reassignGroupDiv._dropdown_inited) {
|
||||
reassignGroupDiv._dropdown_inited = true;
|
||||
dropdown_list_fun("reassign_group_custom");
|
||||
}
|
||||
} else {
|
||||
alert("<%:You no select nodes !%>");
|
||||
}
|
||||
}
|
||||
},
|
||||
close() {
|
||||
document.getElementById('modal-mask').style.display = 'none';
|
||||
document.getElementById("reassign_group_div").style.display = "none";
|
||||
document.body.classList.remove('modal-open');
|
||||
},
|
||||
action() {
|
||||
const ids = [];
|
||||
const group = (document.querySelector('#reassign_group_custom input[type="hidden"]')?.value || "default");
|
||||
const visibleContainer = document.querySelector('#cbi-passwall-nodes > .cbi-tabcontainer[style*="display:block"], #cbi-passwall-nodes > .cbi-tabcontainer[style*="display: block"]');
|
||||
if (!visibleContainer) return;
|
||||
const doms = visibleContainer.getElementsByClassName("nodes_select");
|
||||
if (doms && doms.length > 0) {
|
||||
for (let i = 0 ; i < doms.length; i++) {
|
||||
if (doms[i].checked) {
|
||||
ids.push(doms[i].getAttribute("cbid"))
|
||||
}
|
||||
}
|
||||
if (ids.length > 0) {
|
||||
ajax.abortAll();
|
||||
XHR.get('<%=api.url("reassign_group")%>', {
|
||||
group: group,
|
||||
ids: ids.join(",")
|
||||
},
|
||||
function(x, data) {
|
||||
if (x && x.status == 200) {
|
||||
window.location.href = '<%=api.url("node_list")%>';
|
||||
} else {
|
||||
alert("<%:Error%>");
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const set_node_div = {
|
||||
section: '',
|
||||
open(cbi_id) {
|
||||
this.section = cbi_id;
|
||||
document.getElementById("set_node_div").style.display="block";
|
||||
const node_name = document.getElementById("cbid.<%=appname%>." + cbi_id + ".remarks").value;
|
||||
document.getElementById("set_node_name").innerHTML = node_name;
|
||||
},
|
||||
close() {
|
||||
document.getElementById("set_node_div").style.display="none";
|
||||
document.getElementById("set_node_name").innerHTML = "";
|
||||
},
|
||||
set(protocol) {
|
||||
if (confirm('<%:Are you sure set to%> ' + protocol.toUpperCase() + ' <%:the server?%>') == true){
|
||||
ajax.abortAll();
|
||||
window.location.href = '<%=api.url("set_node")%>?protocol=' + protocol + '§ion=' + this.section;
|
||||
}
|
||||
}
|
||||
}
|
||||
//]]>
|
||||
</script>
|
||||
|
||||
<div class="pw-toolbar">
|
||||
<div class="pw-toolbar-field">
|
||||
<input class="btn cbi-button cbi-button-add" type="button" onclick="node_action('add')" value="<%:Add%>" />
|
||||
<input class="btn cbi-button cbi-button-add" type="button" onclick="add_link_div.open()" value="<%:Add the node via the link%>" />
|
||||
<input class="btn cbi-button cbi-button-remove" type="button" onclick="node_action('del_all')" value="<%:Clear all nodes%>" />
|
||||
<input class="btn cbi-button cbi-button-remove" type="button" onclick="delete_select_nodes()" value="<%:Delete select nodes%>" />
|
||||
<input class="btn cbi-button cbi-button-edit" type="button" id="select_all_btn" onclick="checked_all_node(this)" value="<%:Select all%>" />
|
||||
<input class="btn cbi-button cbi-button-edit" type="button" onclick="reassign_group_div.open()" value="<%:Reassign Group%>" />
|
||||
<input class="btn cbi-button cbi-button-apply" type="submit" name="cbi.apply" value="<%:Save & Apply%>" />
|
||||
<!--<input class="btn cbi-button cbi-button-save" type="submit" name="cbi.save" value="<%:Save%>" />-->
|
||||
<!--<input class="btn cbi-button cbi-button-reset" type="button" value="<%:Reset%>" onclick="location.href='<%=REQUEST_URI%>'" />-->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script type="text/template" id="nodes-table-template">
|
||||
<fieldset class="cbi-section cbi-tblsection" id="cbi-<%=appname%>-nodes-{{group}}-fieldset">
|
||||
<table class="table cbi-section-table" id="cbi-<%=appname%>-nodes-{{group}}-table" style="">
|
||||
@@ -837,7 +1301,7 @@ table td, .table .td {
|
||||
{{node-tr}}
|
||||
</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-add" type="button" value="<%:Add%>" onclick="node_action('add')">
|
||||
<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>
|
||||
@@ -857,11 +1321,11 @@ 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, '{{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="to_edit_node('{{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-edit" type="button" value="<%:To Top%>" onclick="node_action('row_top', null, {btn:this, group:'{{group}}'})" title="<%:To Top%>"/>
|
||||
<input class="btn cbi-button cbi-button-apply" type="button" value="<%:Use%>" id="apply_{{id}}" onclick="set_node_div.open('{{id}}')"/>
|
||||
<input class="btn cbi-button cbi-button-add" type="button" value="<%:Copy%>" onclick="node_action('copy', '{{id}}')"/>
|
||||
<input class="btn cbi-button cbi-button-edit" type="button" value="<%:Edit%>" onclick="node_action('edit', '{{id}}')" alt="<%:Edit%>" title="<%:Edit%>">
|
||||
<input class="btn cbi-button cbi-button-remove" type="button" value="<%:Delete%>" onclick="node_action('del', '{{id}}')" alt="<%:Delete%>" title="<%:Delete%>">
|
||||
<span class="drag-handle center" title="<%:Drag to reorder%>">⠿</span>
|
||||
</div>
|
||||
</td>
|
||||
@@ -872,6 +1336,8 @@ table td, .table .td {
|
||||
</fieldset>
|
||||
|
||||
<script type="text/javascript">
|
||||
//init logic
|
||||
|
||||
function get_remarks_name(o) {
|
||||
let str = "";
|
||||
let remarks = o["remarks"] || "";
|
||||
@@ -948,9 +1414,8 @@ table td, .table .td {
|
||||
group_nodes[_node.group].push(_node)
|
||||
}
|
||||
|
||||
var tab_ul_html = '<ul class="cbi-tabmenu">'
|
||||
var tab_ul_li_html = ''
|
||||
var tab_content_html = '<fieldset class="cbi-section-node cbi-section-node-tabbed" id="cbi-<%=appname%>-nodes">'
|
||||
var tab_content_html = ''
|
||||
var nodes_table_template = document.getElementById("nodes-table-template");
|
||||
var node_template = document.getElementById("node-tr-template");
|
||||
var default_group = null
|
||||
@@ -1025,11 +1490,21 @@ table td, .table .td {
|
||||
'</div>'
|
||||
}
|
||||
|
||||
tab_ul_html += tab_ul_li_html + '</ul>'
|
||||
tab_content_html += '</fieldset>'
|
||||
var tab_html = tab_ul_html + tab_content_html
|
||||
function addTabDOM(li, content) {
|
||||
let tab_ul_html = '<ul class="cbi-tabmenu">';
|
||||
let tab_content_html = '<fieldset class="cbi-section-shunt_rule cbi-section-shunt_rule-tabbed" id="cbi-<%=appname%>-nodes">';
|
||||
if (li) {
|
||||
tab_ul_html += li;
|
||||
}
|
||||
if (content) {
|
||||
tab_content_html += content;
|
||||
}
|
||||
tab_ul_html += '</ul>';
|
||||
tab_content_html += '</fieldset>';
|
||||
return tab_ul_html + tab_content_html;
|
||||
}
|
||||
|
||||
document.getElementById("node_list").innerHTML = tab_html
|
||||
document.getElementById("node_list").innerHTML = addTabDOM(tab_ul_li_html, tab_content_html);
|
||||
|
||||
for (let group in group_nodes) {
|
||||
cbi_t_add("<%=appname%>.nodes", group)
|
||||
@@ -1071,20 +1546,8 @@ table td, .table .td {
|
||||
|
||||
//Node list option saving logic
|
||||
document.addEventListener("DOMContentLoaded", function () {
|
||||
function waitForElement(selector, callback) {
|
||||
const el = document.querySelector(selector);
|
||||
if (el) return callback(el);
|
||||
const observer = new MutationObserver(() => {
|
||||
const el = document.querySelector(selector);
|
||||
if (el) {
|
||||
observer.disconnect();
|
||||
callback(el);
|
||||
}
|
||||
});
|
||||
observer.observe(document.body, { childList: true, subtree: true });
|
||||
}
|
||||
|
||||
function onChange(option, value) {
|
||||
ajax.abortAll();
|
||||
XHR.get('<%=api.url("save_node_list_opt")%>', {
|
||||
option: option,
|
||||
value: value
|
||||
@@ -1118,23 +1581,88 @@ table td, .table .td {
|
||||
links.forEach(link => {
|
||||
link.addEventListener('click', (e) => {
|
||||
if (link.getAttribute('target') !== '_blank') {
|
||||
ajaxAbortAll();
|
||||
ajax.abortAll();
|
||||
}
|
||||
});
|
||||
});
|
||||
window.addEventListener('beforeunload', (event) => {
|
||||
ajax.abortAll();
|
||||
});
|
||||
});
|
||||
//]]>
|
||||
</script>
|
||||
|
||||
<div id="modal-mask"></div>
|
||||
|
||||
<div id="add_link_div">
|
||||
<div id="add_link_modal_container">
|
||||
<h3><%:Add the node via the link%></h3>
|
||||
<div class="value-custom">
|
||||
<textarea id="nodes_link" rows="10"></textarea>
|
||||
<p id="nodes_link_text"><%:Enter share links, one per line. Subscription links are not supported!%></p>
|
||||
</div>
|
||||
<div class="value-custom">
|
||||
<div class="value-field-custom">
|
||||
<label class="value-title-custom" for="addlink_group_custom"><%:Group Name%></label>
|
||||
<div id="addlink_group_custom" class="custom-dropdown">
|
||||
<div class="selected-display">
|
||||
<span class="text"><%:default%></span>
|
||||
<span class="arrow-down-small"></span>
|
||||
</div>
|
||||
<ul class="dropdown-list" style="display:none;">
|
||||
<li class="dropdown-item selected" data-value=""><%:default%></li>
|
||||
<li class="dropdown-item custom-input">
|
||||
<input type="text" placeholder="-- <%:custom%> --" class="create-item-input" inputmode="text" enterkeyhint="done">
|
||||
</li>
|
||||
</ul>
|
||||
<input type="hidden" name="addlink_group" value="">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="add_link_button_container">
|
||||
<input class="btn cbi-button cbi-button-add" type="button" onclick="add_link_div.add_node()" value="<%:Add%>" />
|
||||
<input class="btn cbi-button cbi-button-remove" type="button" onclick="add_link_div.close()" value="<%:Close%>" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="reassign_group_div">
|
||||
<div id="reassign_group_modal_container">
|
||||
<h3><%:Reassign Node Group%></h3>
|
||||
<div class="value-custom">
|
||||
<div class="value-field-custom">
|
||||
<label class="value-title-custom" for="reassign_group_custom"><%:Group Name%></label>
|
||||
<div id="reassign_group_custom" class="custom-dropdown">
|
||||
<div class="selected-display">
|
||||
<span class="text"><%:default%></span>
|
||||
<span class="arrow-down-small"></span>
|
||||
</div>
|
||||
<ul class="dropdown-list" style="display:none;">
|
||||
<li class="dropdown-item selected" data-value=""><%:default%></li>
|
||||
<li class="dropdown-item custom-input">
|
||||
<input type="text" placeholder="-- <%:custom%> --" class="create-item-input" inputmode="text" enterkeyhint="done">
|
||||
</li>
|
||||
</ul>
|
||||
<input type="hidden" name="to_group" value="">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="reassign_group_button_container">
|
||||
<input class="btn cbi-button cbi-button-edit" type="button" onclick="reassign_group_div.action()" value="<%:Save%>" />
|
||||
<input class="btn cbi-button cbi-button-remove" type="button" onclick="reassign_group_div.close()" value="<%:Close%>" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="display: -webkit-flex; display: flex; -webkit-align-items: center; align-items: center; -webkit-justify-content: center; justify-content: center;">
|
||||
<div id="set_node_div" style="text-align: center;">
|
||||
<div class="cbi-value" style="display: flex; justify-content: center; align-items: center; margin: 8px 0;">
|
||||
<font color=#666><%:You choose node is:%></font><a style="color: red" id="set_node_name"></a>
|
||||
</div>
|
||||
<div class="cbi-value" style="display: flex; justify-content: center; gap: 8px; margin: 8px 0;">
|
||||
<input class="btn cbi-button cbi-button-edit" type="button" onclick="set_node('tcp')" value="TCP" />
|
||||
<input class="btn cbi-button cbi-button-edit" type="button" onclick="set_node('udp')" value="UDP" />
|
||||
<input class="btn cbi-button cbi-button-remove" type="button" onclick="close_set_node_div()" value="<%:Close%>" />
|
||||
<input class="btn cbi-button cbi-button-edit" type="button" onclick="set_node_div.set('tcp')" value="TCP" />
|
||||
<input class="btn cbi-button cbi-button-edit" type="button" onclick="set_node_div.set('udp')" value="UDP" />
|
||||
<input class="btn cbi-button cbi-button-remove" type="button" onclick="set_node_div.close()" value="<%:Close%>" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -254,16 +254,6 @@ 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;
|
||||
|
||||
@@ -3,6 +3,9 @@ local api = self.api
|
||||
-%>
|
||||
<script type="text/javascript">
|
||||
//<![CDATA[
|
||||
let node_id = "<%=self.section%>";
|
||||
let node_config_url = '<%=api.url("server_user")%>/' + node_id;
|
||||
|
||||
document.addEventListener("DOMContentLoaded", function () {
|
||||
waitForElement('select[name*="<%=self.config%>"][name*="type"]', function(el) {
|
||||
let o_val = el.value;
|
||||
@@ -12,12 +15,12 @@ local api = self.api
|
||||
let save = true;
|
||||
if (save) {
|
||||
//document.getElementsByClassName('cbi-button-save')[0].click();
|
||||
update_node({
|
||||
enable: getOption("enable").value,
|
||||
remarks: getOption("remarks").value,
|
||||
type: getOption("type").value,
|
||||
server_update_config(node_id, {
|
||||
enable: getOption("<%=self.config%>", node_id, "enable").value,
|
||||
remarks: getOption("<%=self.config%>", node_id, "remarks").value,
|
||||
type: getOption("<%=self.config%>", node_id, "type").value,
|
||||
protocol: ''
|
||||
});
|
||||
}, node_config_url);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,51 +1,15 @@
|
||||
<%
|
||||
local api = self.api
|
||||
-%>
|
||||
<script type="text/javascript">
|
||||
//<![CDATA[
|
||||
let node_id = "<%=self.section%>";
|
||||
let node_config_url = '<%=api.url("server_user")%>/' + node_id;
|
||||
|
||||
function waitForElement(selector, callback) {
|
||||
const el = document.querySelector(selector);
|
||||
if (el) return callback(el);
|
||||
const observer = new MutationObserver(() => {
|
||||
const el = document.querySelector(selector);
|
||||
if (el) {
|
||||
observer.disconnect();
|
||||
callback(el);
|
||||
}
|
||||
});
|
||||
observer.observe(document.body, { childList: true, subtree: true });
|
||||
}
|
||||
|
||||
function getOption(opt) {
|
||||
var obj;
|
||||
var id = 'cbid.<%=self.config%>.' + node_id + '.' + opt;
|
||||
obj = document.getElementsByName(id)[0] || document.getElementById(id);
|
||||
if (obj) {
|
||||
var combobox = document.getElementById('cbi.combobox.' + id);
|
||||
if (combobox) {
|
||||
obj.combobox = combobox;
|
||||
}
|
||||
var div = document.getElementById(id);
|
||||
if (div && div.getElementsByTagName("li").length > 0) {
|
||||
obj = div;
|
||||
}
|
||||
return obj;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function update_node(data) {
|
||||
XHR.get('<%=api.url("server_user_update")%>', {
|
||||
id: node_id,
|
||||
function server_update_config(cfgid, data, redirect_url) {
|
||||
XHR.get('<%=self.api.url("server_update_config")%>', {
|
||||
id: cfgid,
|
||||
data: JSON.stringify(data)
|
||||
},
|
||||
function(x, data) {
|
||||
if (x && x.status == 200 && data.code == 1) {
|
||||
window.location.href = node_config_url;
|
||||
if (redirect_url) {
|
||||
window.location.href = redirect_url;
|
||||
}
|
||||
}
|
||||
else {
|
||||
alert("<%:Error%>");
|
||||
|
||||
Reference in New Issue
Block a user