🐶 Sync 2025-11-02 14:26:26
This commit is contained in:
@@ -0,0 +1,35 @@
|
||||
<%
|
||||
local api = require "luci.passwall.api"
|
||||
-%>
|
||||
<script type="text/javascript">
|
||||
//<![CDATA[
|
||||
document.addEventListener("DOMContentLoaded", function () {
|
||||
setTimeout(function () {
|
||||
var selects = document.querySelectorAll("select[id*='dns_shunt']");
|
||||
selects.forEach(function (select) {
|
||||
if (select.value === "chinadns-ng") {
|
||||
addLogLink(select);
|
||||
}
|
||||
select.addEventListener("change", function () {
|
||||
var existingLogLink = select.parentElement.querySelector("a.log-link");
|
||||
if (existingLogLink) {
|
||||
existingLogLink.remove();
|
||||
}
|
||||
if (select.value === "chinadns-ng") {
|
||||
addLogLink(select);
|
||||
}
|
||||
});
|
||||
});
|
||||
function addLogLink(select) {
|
||||
var logLink = document.createElement("a");
|
||||
logLink.innerHTML = "<%:Log%>";
|
||||
logLink.href = "#";
|
||||
logLink.className = "log-link";
|
||||
logLink.style.marginLeft = "10px";
|
||||
logLink.setAttribute("onclick", "window.open('" + '<%=api.url("get_chinadns_log") .. "?flag=" .. section%>' + "', '_blank')");
|
||||
select.insertAdjacentElement("afterend", logLink);
|
||||
}
|
||||
}, 1000);
|
||||
});
|
||||
//]]>
|
||||
</script>
|
||||
@@ -0,0 +1,210 @@
|
||||
<%
|
||||
local api = require "luci.passwall.api"
|
||||
local com = require "luci.passwall.com"
|
||||
local version = {}
|
||||
-%>
|
||||
|
||||
<script type="text/javascript">
|
||||
//<![CDATA[
|
||||
var appInfoList = new Array();
|
||||
var inProgressCount = 0;
|
||||
var tokenStr = '<%=token%>';
|
||||
var checkUpdateText = '<%:Check update%>';
|
||||
var forceUpdateText = '<%:Force update%>';
|
||||
var retryText = '<%:Retry%>';
|
||||
var noUpdateText = '<%:It is the latest version%>';
|
||||
var updateSuccessText = '<%:Update successful%>';
|
||||
var clickToUpdateText = '<%:Click to update%>';
|
||||
var inProgressText = '<%:Updating...%>';
|
||||
var unexpectedErrorText = '<%:Unexpected error%>';
|
||||
var updateInProgressNotice = '<%:Updating, are you sure to close?%>';
|
||||
var downloadingText = '<%:Downloading...%>';
|
||||
var decompressioningText = '<%:Unpacking...%>';
|
||||
var movingText = '<%:Moving...%>';
|
||||
|
||||
//window.onload = function () {};
|
||||
|
||||
function addPageNotice() {
|
||||
if (inProgressCount === 0) {
|
||||
window.onbeforeunload = function (e) {
|
||||
e.returnValue = updateInProgressNotice;
|
||||
return updateInProgressNotice;
|
||||
};
|
||||
}
|
||||
inProgressCount++;
|
||||
}
|
||||
|
||||
function removePageNotice() {
|
||||
inProgressCount--;
|
||||
if (inProgressCount === 0) {
|
||||
window.onbeforeunload = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
function onUpdateSuccess(btn) {
|
||||
if (btn) {
|
||||
btn.value = updateSuccessText;
|
||||
btn.placeholder = updateSuccessText;
|
||||
btn.disabled = true;
|
||||
}
|
||||
|
||||
if (inProgressCount === 0) {
|
||||
window.setTimeout(function () {
|
||||
window.location.reload();
|
||||
}, 1000);
|
||||
}
|
||||
}
|
||||
|
||||
function onRequestError(btn, errorMessage) {
|
||||
btn.disabled = false;
|
||||
btn.value = retryText;
|
||||
|
||||
var ckeckDetailElm = document.getElementById(btn.id + '-detail');
|
||||
if (errorMessage && ckeckDetailElm) {
|
||||
ckeckDetailElm.textContent = errorMessage
|
||||
}
|
||||
}
|
||||
|
||||
function onBtnClick(btn, app) {
|
||||
if (appInfoList[app] === undefined) {
|
||||
checkUpdate(btn, app);
|
||||
} else {
|
||||
doUpdate(btn, app);
|
||||
}
|
||||
}
|
||||
|
||||
function checkUpdate(btn, app) {
|
||||
btn.disabled = true;
|
||||
btn.value = inProgressText;
|
||||
|
||||
addPageNotice();
|
||||
|
||||
var ckeckDetailElm = document.getElementById(btn.id + '-detail');
|
||||
if (ckeckDetailElm) {
|
||||
ckeckDetailElm.textContent = "";
|
||||
}
|
||||
XHR.get('<%=api.url("check_")%>' + app, {
|
||||
token: tokenStr,
|
||||
arch: ''
|
||||
}, function (x, json) {
|
||||
removePageNotice();
|
||||
if (json.code) {
|
||||
appInfoList[app] = undefined;
|
||||
onRequestError(btn, json.error);
|
||||
} else {
|
||||
appInfoList[app] = json;
|
||||
if (json.has_update) {
|
||||
btn.disabled = false;
|
||||
btn.value = clickToUpdateText;
|
||||
btn.placeholder = clickToUpdateText;
|
||||
|
||||
if (ckeckDetailElm) {
|
||||
var urlNode = '';
|
||||
if (json.remote_version) {
|
||||
urlNode = '<em style="color:red;">' + json.remote_version + '</em>';
|
||||
if (json.html_url) {
|
||||
urlNode = '<a href="' + json.html_url + '" target="_blank">' + urlNode + '</a>';
|
||||
}
|
||||
}
|
||||
ckeckDetailElm.innerHTML = urlNode;
|
||||
}
|
||||
} else {
|
||||
btn.disabled = true;
|
||||
btn.value = noUpdateText;
|
||||
window['_' + app + '-force_btn'].style.display = "inline";
|
||||
}
|
||||
}
|
||||
}, 300);
|
||||
}
|
||||
|
||||
function doUpdate(btn, app) {
|
||||
btn.disabled = true;
|
||||
btn.value = downloadingText;
|
||||
|
||||
addPageNotice();
|
||||
|
||||
var appUpdateUrl = '<%=api.url("update_")%>' + app;
|
||||
var appInfo = appInfoList[app];
|
||||
// Download file
|
||||
XHR.get(appUpdateUrl, {
|
||||
token: tokenStr,
|
||||
url: appInfo ? appInfo.data.browser_download_url : '',
|
||||
size: appInfo ? appInfo.data.size / 1024 : null
|
||||
}, function (x, json) {
|
||||
if (json.code) {
|
||||
removePageNotice();
|
||||
onRequestError(btn, json.error);
|
||||
} else if (json.zip) {
|
||||
btn.value = decompressioningText;
|
||||
|
||||
// Extract file
|
||||
XHR.get(appUpdateUrl, {
|
||||
token: tokenStr,
|
||||
task: 'extract',
|
||||
file: json.file,
|
||||
subfix: appInfo ? appInfo.type : ''
|
||||
}, function (x, json) {
|
||||
if (json.code) {
|
||||
removePageNotice();
|
||||
onRequestError(btn, json.error);
|
||||
} else {
|
||||
move(btn, appUpdateUrl, json.file);
|
||||
}
|
||||
}, 300)
|
||||
} else {
|
||||
move(btn, appUpdateUrl, json.file);
|
||||
}
|
||||
}, 300)
|
||||
}
|
||||
|
||||
function move(btn, url, file) {
|
||||
btn.value = movingText;
|
||||
|
||||
// Move file to target dir
|
||||
XHR.get(url, {
|
||||
token: tokenStr,
|
||||
task: 'move',
|
||||
file: file
|
||||
}, function (x, json) {
|
||||
removePageNotice();
|
||||
if (json.code) {
|
||||
onRequestError(btn, json.error);
|
||||
} else {
|
||||
onUpdateSuccess(btn);
|
||||
}
|
||||
}, 300)
|
||||
}
|
||||
//]]>
|
||||
</script>
|
||||
|
||||
<div class="cbi-value">
|
||||
<label class="cbi-value-title">Passwall <%:Version%></label>
|
||||
<div class="cbi-value-field">
|
||||
<!--div class="cbi-value-description"-->
|
||||
<span>【 <%=api.get_version()%> 】</span>
|
||||
<input class="btn cbi-button cbi-button-apply" type="button" id="passwall-check_btn"
|
||||
onclick="onBtnClick(this,'passwall');" value="<%:Check update%>" />
|
||||
<span id="passwall-check_btn-detail"></span>
|
||||
<!--/div-->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<%for _, k in ipairs(com.order) do
|
||||
local v = com[k]
|
||||
version[k] = api.get_app_version(k)%>
|
||||
<div class="cbi-value">
|
||||
<label class="cbi-value-title"><%=v.name%>
|
||||
<%:Version%>
|
||||
</label>
|
||||
<div class="cbi-value-field">
|
||||
<!--div class="cbi-value-description"-->
|
||||
<span>【 <%=version[k] ~="" and version[k] or translate("Null") %> 】</span>
|
||||
<input class="btn cbi-button cbi-button-apply" type="button" id="_<%=k%>-check_btn"
|
||||
onclick="onBtnClick(this,'<%=k%>');" value="<%:Check update%>" />
|
||||
<input class="btn cbi-button cbi-button-apply" type="button" id="_<%=k%>-force_btn"
|
||||
onclick="doUpdate(this,'<%=k%>');" value="<%:Force update%>" style="display:none"/>
|
||||
<span id="_<%=k%>-check_btn-detail"></span>
|
||||
<!--/div-->
|
||||
</div>
|
||||
</div>
|
||||
<%end%>
|
||||
3
luci-app-passwall/luasrc/view/passwall/cbi/hidevalue.htm
Normal file
3
luci-app-passwall/luasrc/view/passwall/cbi/hidevalue.htm
Normal file
@@ -0,0 +1,3 @@
|
||||
<div id="cbi-<%=self.config.."-"..section.."-"..self.option%>" data-index="<%=self.index%>" data-depends="<%=pcdata(self:deplist2json(section))%>" style="display: none !important">
|
||||
<input type="hidden" id="<%=cbid%>" value="<%=pcdata(self:cfgvalue(section) or self.default or "")%>" />
|
||||
</div>
|
||||
224
luci-app-passwall/luasrc/view/passwall/global/backup.htm
Normal file
224
luci-app-passwall/luasrc/view/passwall/global/backup.htm
Normal file
@@ -0,0 +1,224 @@
|
||||
<%
|
||||
local api = require "luci.passwall.api"
|
||||
-%>
|
||||
|
||||
<div class="cbi-section">
|
||||
<h3><%:Backup and Restore%></h3>
|
||||
<div class="cbi-section-descr">
|
||||
<%:Backup or Restore Client and Server Configurations.%>
|
||||
<br>
|
||||
<font color="red"><%:Note: Restoring configurations across different versions may cause compatibility issues.%></font>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="cbi-value" id="_backup_div">
|
||||
<label class="cbi-value-title"><%:Create Backup File%></label>
|
||||
<div class="cbi-value-field">
|
||||
<input class="btn cbi-button cbi-button-save" type="button" onclick="dl_backup()" value="<%:DL Backup%>" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="cbi-value" id="_upload_div">
|
||||
<label class="cbi-value-title"><%:Restore Backup File%></label>
|
||||
<div class="cbi-value-field">
|
||||
<input class="btn cbi-button cbi-button-apply" type="button" onclick="show_upload_win()" value="<%:RST Backup%>" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="cbi-value" id="_reset_div">
|
||||
<label class="cbi-value-title"><%:Restore to default configuration%></label>
|
||||
<div class="cbi-value-field">
|
||||
<input class="btn cbi-button cbi-button-reset" type="button" onclick="do_reset()" value="<%:Do Reset%>" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="cbi-value"></div>
|
||||
|
||||
<div id="upload-modal" class="up-modal" style="display:none;">
|
||||
<div class="up-modal-content">
|
||||
<h3><%:Restore Backup File%></h3>
|
||||
<div class="up-cbi-value-field">
|
||||
<input class="cbi-input-file" type="file" id="ulfile" accept=".tar.gz" />
|
||||
</div>
|
||||
<div class="up-button-container">
|
||||
<input class="btn cbi-button cbi-button-apply" type="button" id="upload-btn" onclick="do_upload()" value="<%:UL Restore%>" />
|
||||
<input class="btn cbi-button cbi-button-remove" type="button" onclick="close_upload_win()" value="<%:CLOSE WIN%>" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.up-modal {
|
||||
position: fixed;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
background: white;
|
||||
padding: 20px;
|
||||
border: 2px solid #ccc;
|
||||
border-radius: 5px;
|
||||
box-shadow: 0 0 10px rgba(0,0,0,0.5);
|
||||
z-index: 1000;
|
||||
width: 90%;
|
||||
max-width: 400px;
|
||||
}
|
||||
|
||||
.up-modal-content {
|
||||
width: 100%;
|
||||
max-width: 400px;
|
||||
text-align: center;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.up-button-container {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
max-width: 250px;
|
||||
}
|
||||
|
||||
.up-cbi-value-field {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
margin-top: 15px;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
function show_upload_win(btn) {
|
||||
document.getElementById("upload-modal").style.display = "block";
|
||||
}
|
||||
|
||||
function close_upload_win(btn) {
|
||||
document.getElementById("ulfile").value = "";
|
||||
document.getElementById("upload-modal").style.display = "none";
|
||||
}
|
||||
|
||||
function dl_backup(btn) {
|
||||
fetch('<%= api.url("create_backup") %>', {
|
||||
method: 'POST'
|
||||
})
|
||||
.then(response => {
|
||||
if (!response.ok) {
|
||||
throw new Error("备份失败!");
|
||||
}
|
||||
const filename = response.headers.get("X-Backup-Filename");
|
||||
if (!filename) {
|
||||
return;
|
||||
}
|
||||
return response.blob().then(blob => ({ blob, filename }));
|
||||
})
|
||||
.then(result => {
|
||||
if (!result) return;
|
||||
const { blob, filename } = result;
|
||||
const url = window.URL.createObjectURL(blob);
|
||||
const a = document.createElement("a");
|
||||
a.href = url;
|
||||
a.download = filename;
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
a.remove();
|
||||
window.URL.revokeObjectURL(url);
|
||||
})
|
||||
.catch(error => alert(error.message));
|
||||
}
|
||||
|
||||
function do_reset(btn) {
|
||||
if (confirm("<%: Do you want to restore the client to default settings?%>")) {
|
||||
setTimeout(function () {
|
||||
if (confirm("<%: Are you sure you want to restore the client to default settings?%>")) {
|
||||
var xhr1 = new XMLHttpRequest();
|
||||
xhr1.open("GET",'<%= api.url("clear_log") %>', true);
|
||||
xhr1.send();
|
||||
var xhr2 = new XMLHttpRequest();
|
||||
xhr2.open("GET",'<%= api.url("reset_config") %>', true);
|
||||
xhr2.send();
|
||||
window.location.href = '<%= api.url("log") %>'
|
||||
}
|
||||
}, 1000);
|
||||
}
|
||||
}
|
||||
|
||||
function do_upload(btn) {
|
||||
const fileInput = document.getElementById("ulfile");
|
||||
const file = fileInput.files[0];
|
||||
if (!file) {
|
||||
alert("<%:Please select a file first.%>");
|
||||
return;
|
||||
}
|
||||
if (!file.name.endsWith(".tar.gz")) {
|
||||
alert("<%:Invalid file type. Please upload a .tar.gz file.%>");
|
||||
fileInput.value = "";
|
||||
return;
|
||||
}
|
||||
const maxSize = 10 * 1024 * 1024; // 10MB
|
||||
if (file.size > maxSize) {
|
||||
alert("<%:File size exceeds 10MB limit.%>");
|
||||
fileInput.value = "";
|
||||
return;
|
||||
}
|
||||
|
||||
const reader = new FileReader();
|
||||
reader.onload = function (e) {
|
||||
const binaryString = e.target.result; // ArrayBuffer
|
||||
const binary = new Uint8Array(binaryString);
|
||||
let binaryText = "";
|
||||
for (let i = 0; i < binary.length; i++) {
|
||||
binaryText += String.fromCharCode(binary[i]);
|
||||
}
|
||||
|
||||
const base64Data = btoa(binaryText);
|
||||
|
||||
const targetByteSize = 64 * 1024; // 分片大小 64KB
|
||||
let chunkSize = Math.floor(targetByteSize * 4 / 3);
|
||||
chunkSize = chunkSize + (4 - (chunkSize % 4)) % 4;
|
||||
const totalChunks = Math.ceil(base64Data.length / chunkSize);
|
||||
let currentChunk = 0;
|
||||
|
||||
function sendNextChunk() {
|
||||
if (currentChunk < totalChunks) {
|
||||
const chunk = base64Data.substring(currentChunk * chunkSize, (currentChunk + 1) * chunkSize);
|
||||
const xhr = new XMLHttpRequest();
|
||||
xhr.open("POST", '<%= api.url("restore_backup") %>', true);
|
||||
xhr.onreadystatechange = function () {
|
||||
if (xhr.readyState === 4) {
|
||||
if (xhr.status === 200) {
|
||||
const resp = JSON.parse(xhr.responseText);
|
||||
if (resp.status === "success") {
|
||||
currentChunk++;
|
||||
document.getElementById("upload-btn").value = "Uploading... " + Math.floor((currentChunk / totalChunks) * 100) + "%";
|
||||
sendNextChunk();
|
||||
} else {
|
||||
alert("Upload error: " + resp.message);
|
||||
document.getElementById("upload-btn").value = "<%:UL Restore%>";
|
||||
}
|
||||
} else {
|
||||
alert("Upload failed with status " + xhr.status);
|
||||
document.getElementById("upload-btn").value = "<%:UL Restore%>";
|
||||
}
|
||||
}
|
||||
};
|
||||
const formData = new FormData();
|
||||
formData.append("filename", file.name);
|
||||
formData.append("chunk", chunk);
|
||||
formData.append("chunk_index", currentChunk);
|
||||
formData.append("total_chunks", totalChunks);
|
||||
xhr.send(formData);
|
||||
} else {
|
||||
//alert("Upload completed.");
|
||||
document.getElementById("upload-btn").value = "<%:UL Restore%>";
|
||||
window.location.href = '<%= api.url("log") %>'
|
||||
}
|
||||
}
|
||||
sendNextChunk();
|
||||
};
|
||||
reader.readAsArrayBuffer(file);
|
||||
}
|
||||
|
||||
</script>
|
||||
66
luci-app-passwall/luasrc/view/passwall/global/faq.htm
Normal file
66
luci-app-passwall/luasrc/view/passwall/global/faq.htm
Normal file
@@ -0,0 +1,66 @@
|
||||
<%
|
||||
local api = require "luci.passwall.api"
|
||||
-%>
|
||||
<style>
|
||||
.dns-con {
|
||||
padding: 1rem;
|
||||
}
|
||||
.faq-title {
|
||||
color: var(--primary);
|
||||
font-weight: bolder;
|
||||
margin-bottom: 0.5rem;
|
||||
display: inline-block;
|
||||
}
|
||||
.reset-title {
|
||||
color: var(--primary);
|
||||
font-weight: bolder;
|
||||
margin-bottom: 0.3rem;
|
||||
display: inline-block;
|
||||
margin-top: 1.2rem;
|
||||
text-decoration: underline;
|
||||
}
|
||||
.dns-item {
|
||||
margin-bottom: 0.8rem;
|
||||
line-height:1.2rem;
|
||||
}
|
||||
.dns-list {
|
||||
text-indent:1rem;
|
||||
line-height: 1.2rem;
|
||||
}
|
||||
</style>
|
||||
<div class="dns-con">
|
||||
<div id="faq_dns">
|
||||
<ul>
|
||||
<b class="faq-title"><%:DNS related issues:%></b>
|
||||
<li class="dns-item">1. <span><%:Certain browsers such as Chrome have built-in DNS service, which may affect DNS resolution settings. You can go to 'Settings -> Privacy and security -> Use secure DNS' menu to turn it off.%></span></li>
|
||||
<li class="dns-item">2. <span><%:If you are unable to access the internet after reboot, please try clearing the cache of your terminal devices (make sure to close all open browser application windows first, this step is especially important):%></span>
|
||||
<ul><li class="dns-list"> ◦ <span><%:For Windows systems, open Command Prompt and run the command 'ipconfig /flushdns'.%></span></li>
|
||||
<li class="dns-list"> ◦ <span><%:For Mac systems, open Terminal and run the command 'sudo killall -HUP mDNSResponder'.%></span></li>
|
||||
<li class="dns-list"> ◦ <span><%:For mobile devices, you can clear it by reconnecting to the network, such as toggling Airplane Mode and reconnecting to WiFi.%></span></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="dns-item">3. <span><%:Please make sure your device's network settings point both the DNS server and default gateway to this router, to ensure DNS queries are properly routed.%></span></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div id="faq_reset"></div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
var origin = window.location.origin;
|
||||
var hide_url = origin + "<%=api.url("hide")%>";
|
||||
var show_url = origin + "<%=api.url("show")%>";
|
||||
|
||||
function hide(url) {
|
||||
if (confirm('<%:Are you sure to hide?%>') == true) {
|
||||
window.location.href = hide_url;
|
||||
}
|
||||
}
|
||||
|
||||
var dom = document.getElementById("faq_reset");
|
||||
if (dom) {
|
||||
var li = "";
|
||||
li += "<a href='#' class='reset-title' onclick='hide()'>" + "<%: Hide in main menu:%>"+ "</a>" + "<br />" + "<%: Browser access: %>" + "<a href='#' onclick='hide()'>" + hide_url + "</a>" + "<br />";
|
||||
li += "<a href='#' class='reset-title'>" + "<%: Show in main menu:%>"+ "</a>" + "<br />" +"<%: Browser access: %>" + "<a href='#'>" + show_url + "</a>" + "<br />";
|
||||
dom.innerHTML = li;
|
||||
}
|
||||
</script>
|
||||
180
luci-app-passwall/luasrc/view/passwall/global/footer.htm
Normal file
180
luci-app-passwall/luasrc/view/passwall/global/footer.htm
Normal file
@@ -0,0 +1,180 @@
|
||||
<%
|
||||
local api = require "luci.passwall.api"
|
||||
-%>
|
||||
<script type="text/javascript">
|
||||
//<![CDATA[
|
||||
function go() {
|
||||
var _status = document.getElementsByClassName('_status');
|
||||
for (var i = 0; i < _status.length; i++) {
|
||||
var id = _status[i].getAttribute("socks_id");
|
||||
XHR.get('<%=api.url("socks_status")%>', {
|
||||
index: i,
|
||||
id: id
|
||||
},
|
||||
function(x, result) {
|
||||
var index = result.index;
|
||||
var div = '';
|
||||
var div1 = '<font style="font-weight:bold;" color="green">✓</font> ';
|
||||
var div2 = '<font style="font-weight:bold;" color="red">X</font> ';
|
||||
|
||||
if (result.socks_status) {
|
||||
div += div1;
|
||||
} else {
|
||||
div += div2;
|
||||
}
|
||||
if (result.use_http) {
|
||||
if (result.http_status) {
|
||||
div += div1;
|
||||
} else {
|
||||
div += div2;
|
||||
}
|
||||
}
|
||||
_status[index].innerHTML = div;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
var global_id = null;
|
||||
var global = document.getElementById("cbi-passwall-global");
|
||||
if (global) {
|
||||
var node = global.getElementsByClassName("cbi-section-node")[0];
|
||||
var node_id = node.getAttribute("id");
|
||||
global_id = node_id;
|
||||
var reg1 = new RegExp("(?<=" + node_id + "-).*?(?=(_node))")
|
||||
for (var i = 0; i < node.childNodes.length; i++) {
|
||||
if (node.childNodes[i].childNodes && node.childNodes[i].childNodes.length > 0) {
|
||||
for (var k = 0; k < node.childNodes[i].childNodes.length; k++) {
|
||||
try {
|
||||
var dom = node.childNodes[i].childNodes[k];
|
||||
if (dom.id) {
|
||||
var s = dom.id.match(reg1);
|
||||
if (s) {
|
||||
var cbi_id = global_id + "-"
|
||||
var dom_id = dom.id.split(cbi_id).join(cbi_id.split("-").join(".")).split("cbi.").join("cbid.")
|
||||
var node_select = document.getElementsByName(dom_id)[0];
|
||||
var node_select_value = node_select.value;
|
||||
if (node_select_value && node_select_value != "" && node_select_value.indexOf("_default") != 0 && node_select_value.indexOf("_direct") != 0 && node_select_value.indexOf("_blackhole") != 0) {
|
||||
if (global_id != null && node_select_value.indexOf("tcp") == 0) {
|
||||
var d = global_id + "-tcp_node";
|
||||
d = d.replace("cbi-", "cbid-").replace(new RegExp("-", 'g'), ".");
|
||||
var dom = document.getElementsByName(d)[0];
|
||||
var _node_select_value = dom.value;
|
||||
if (_node_select_value && _node_select_value != "") {
|
||||
node_select_value = _node_select_value;
|
||||
}
|
||||
}
|
||||
|
||||
if (node_select.tagName == "INPUT") {
|
||||
node_select = document.getElementById("cbi.combobox." + dom_id);
|
||||
}
|
||||
|
||||
var new_html = ""
|
||||
if (true) {
|
||||
var 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);
|
||||
}
|
||||
var new_a = document.createElement("a");
|
||||
new_a.innerHTML = "<%:Edit%>";
|
||||
new_a.href = "#";
|
||||
new_a.setAttribute("onclick", "location.href='" + to_url + "'");
|
||||
new_html = new_a.outerHTML;
|
||||
}
|
||||
|
||||
if (s[0] == "tcp" || s[0] == "udp") {
|
||||
var log_a = document.createElement("a");
|
||||
log_a.innerHTML = "<%:Log%>";
|
||||
log_a.href = "#";
|
||||
log_a.setAttribute("onclick", "window.open('" + '<%=api.url("get_redir_log")%>' + "?name=default&proto=" + s[0] + "', '_blank')");
|
||||
new_html += "  " + log_a.outerHTML;
|
||||
}
|
||||
|
||||
node_select.insertAdjacentHTML("afterend", "  " + new_html);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch(err) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var socks = document.getElementById("cbi-passwall-socks");
|
||||
if (socks) {
|
||||
var socks_enabled_dom = document.getElementById(global_id + "-socks_enabled");
|
||||
socks_enabled_dom.parentNode.removeChild(socks_enabled_dom);
|
||||
var descr = socks.getElementsByClassName("cbi-section-descr")[0];
|
||||
descr.outerHTML = socks_enabled_dom.outerHTML;
|
||||
rows = socks.getElementsByClassName("cbi-section-table-row");
|
||||
for (var i = 0; i < rows.length; i++) {
|
||||
try {
|
||||
var row = rows[i];
|
||||
var id = row.id;
|
||||
if (!id) continue;
|
||||
var dom_id = id + "-node";
|
||||
var node = document.getElementById(dom_id);
|
||||
var dom_id = dom_id.replace("cbi-", "cbid-").replace(new RegExp("-", 'g'), ".");
|
||||
var node_select = document.getElementsByName(dom_id)[0];
|
||||
var node_select_value = node_select.value;
|
||||
if (node_select_value && node_select_value != "") {
|
||||
var v = document.getElementById(dom_id + "-" + node_select_value);
|
||||
if (v) {
|
||||
node_select.title = v.text;
|
||||
} else {
|
||||
node_select.title = node_select.options[node_select.options.selectedIndex].text;
|
||||
}
|
||||
|
||||
var new_html = ""
|
||||
|
||||
var new_a = document.createElement("a");
|
||||
new_a.innerHTML = "<%:Edit%>";
|
||||
new_a.href = "#";
|
||||
new_a.setAttribute("onclick","location.href='" + '<%=api.url("node_config")%>' + "/" + node_select_value + "'");
|
||||
new_html = new_a.outerHTML;
|
||||
|
||||
var log_a = document.createElement("a");
|
||||
log_a.innerHTML = "<%:Log%>";
|
||||
log_a.href = "#";
|
||||
log_a.setAttribute("onclick", "window.open('" + '<%=api.url("get_socks_log")%>' + "?name=" + id.replace("cbi-passwall-", "") + "', '_blank')");
|
||||
new_html += " " + log_a.outerHTML;
|
||||
|
||||
node_select.insertAdjacentHTML("afterend", "  " + new_html);
|
||||
}
|
||||
} catch(err) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
setTimeout("go()", 1000);
|
||||
|
||||
document.addEventListener("DOMContentLoaded", function () {
|
||||
setTimeout(function () {
|
||||
var selects = document.querySelectorAll("select[id*='dns_shunt']");
|
||||
selects.forEach(function (select, index) {
|
||||
if (select.value === "chinadns-ng") {
|
||||
addLogLink(select);
|
||||
}
|
||||
select.addEventListener("change", function () {
|
||||
var existingLogLink = select.parentElement.querySelector("a.log-link");
|
||||
if (existingLogLink) {
|
||||
existingLogLink.remove();
|
||||
}
|
||||
if (select.value === "chinadns-ng") {
|
||||
addLogLink(select);
|
||||
}
|
||||
});
|
||||
});
|
||||
function addLogLink(select) {
|
||||
var logLink = document.createElement("a");
|
||||
logLink.innerHTML = "<%:Log%>";
|
||||
logLink.href = "#";
|
||||
logLink.className = "log-link";
|
||||
logLink.style.marginLeft = "10px";
|
||||
logLink.setAttribute("onclick", "window.open('" + '<%=api.url("get_chinadns_log")%>' + "?flag=default', '_blank')");
|
||||
select.insertAdjacentElement("afterend", logLink);
|
||||
}
|
||||
}, 1000);
|
||||
});
|
||||
//]]>
|
||||
</script>
|
||||
110
luci-app-passwall/luasrc/view/passwall/global/proxy.htm
Normal file
110
luci-app-passwall/luasrc/view/passwall/global/proxy.htm
Normal file
@@ -0,0 +1,110 @@
|
||||
<div class="cbi-value" id="cbi-<%=self.config.."-"..section.."-"..self.option%>" data-index="<%=self.index%>" data-depends="<%=pcdata(self:deplist2json(section))%>">
|
||||
<label class="cbi-value-title">
|
||||
<%:Switch Mode%>
|
||||
</label>
|
||||
<div class="cbi-value-field">
|
||||
<div>
|
||||
<input class="btn cbi-button cbi-button-apply" type="button" onclick="switch_gfw_mode()" value="<%:GFW List%>" />
|
||||
<input class="btn cbi-button cbi-button-apply" type="button" onclick="switch_chnroute_mode()" value="<%:Not China List%>" />
|
||||
<input class="btn cbi-button cbi-button-apply" type="button" onclick="switch_returnhome_mode()" value="<%:China List%>" />
|
||||
<input class="btn cbi-button cbi-button-apply" type="button" onclick="switch_global_mode()" value="<%:Global Proxy%>" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
var opt = {
|
||||
base: 'cbid.passwall.<%=self.cfgid or section%>',
|
||||
client: true,
|
||||
get: function (opt) {
|
||||
var obj;
|
||||
var id = this.base + '.' + 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;
|
||||
}
|
||||
},
|
||||
set: function (opt, val) {
|
||||
var obj;
|
||||
obj = this.get(opt);
|
||||
if (obj) {
|
||||
var event = document.createEvent("HTMLEvents");
|
||||
event.initEvent("change", true, true);
|
||||
if (obj.type === 'checkbox') {
|
||||
obj.checked = val;
|
||||
} else {
|
||||
obj.value = val;
|
||||
if (obj.combobox) {
|
||||
obj.combobox.value = val;
|
||||
}
|
||||
|
||||
var list = obj.getElementsByTagName("li");
|
||||
if (list.length > 0) {
|
||||
for (var i = 0; i < list.length; i++) {
|
||||
var li = list[i];
|
||||
var data = li.getAttribute("data-value");
|
||||
li.removeAttribute("selected");
|
||||
li.removeAttribute("display");
|
||||
if (data && data == val) {
|
||||
li.setAttribute("selected", true);
|
||||
li.setAttribute("display", "0");
|
||||
}
|
||||
}
|
||||
var input = document.getElementsByName(obj.id)[0];
|
||||
if (input) {
|
||||
input.value = val;
|
||||
} else {
|
||||
var input = document.createElement("input");
|
||||
input.setAttribute("type", "hidden");
|
||||
input.setAttribute("name", obj.id);
|
||||
input.setAttribute("value", val);
|
||||
obj.appendChild(input);
|
||||
}
|
||||
}
|
||||
}
|
||||
try {
|
||||
obj.dispatchEvent(event);
|
||||
} catch (err) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function switch_gfw_mode() {
|
||||
opt.set("use_gfw_list", true);
|
||||
opt.set("chn_list", "0");
|
||||
opt.set("tcp_proxy_mode", "disable");
|
||||
opt.set("udp_proxy_mode", "disable");
|
||||
}
|
||||
|
||||
function switch_chnroute_mode() {
|
||||
opt.set("use_gfw_list", true);
|
||||
opt.set("chn_list", "direct");
|
||||
opt.set("tcp_proxy_mode", "proxy");
|
||||
opt.set("udp_proxy_mode", "proxy");
|
||||
}
|
||||
|
||||
function switch_returnhome_mode() {
|
||||
opt.set("use_gfw_list", false);
|
||||
opt.set("chn_list", "proxy");
|
||||
opt.set("tcp_proxy_mode", "disable");
|
||||
opt.set("udp_proxy_mode", "disable");
|
||||
}
|
||||
|
||||
function switch_global_mode() {
|
||||
opt.set("use_gfw_list", false);
|
||||
opt.set("chn_list", "0");
|
||||
opt.set("tcp_proxy_mode", "proxy");
|
||||
opt.set("udp_proxy_mode", "proxy");
|
||||
}
|
||||
</script>
|
||||
307
luci-app-passwall/luasrc/view/passwall/global/status.htm
Normal file
307
luci-app-passwall/luasrc/view/passwall/global/status.htm
Normal file
File diff suppressed because one or more lines are too long
49
luci-app-passwall/luasrc/view/passwall/haproxy/js.htm
Normal file
49
luci-app-passwall/luasrc/view/passwall/haproxy/js.htm
Normal file
@@ -0,0 +1,49 @@
|
||||
|
||||
<script type="text/javascript">
|
||||
//<![CDATA[
|
||||
document.addEventListener("DOMContentLoaded", function () {
|
||||
let monitorStartTime = Date.now();
|
||||
|
||||
const monitorInterval = setInterval(function () {
|
||||
if (Date.now() - monitorStartTime > 3000) {
|
||||
clearInterval(monitorInterval);
|
||||
return;
|
||||
}
|
||||
|
||||
const rows = Array.from(document.querySelectorAll("tr.cbi-section-table-row"))
|
||||
.filter(row => !row.classList.contains("placeholder")); // 排除无配置行
|
||||
|
||||
if (rows.length <= 1) return;
|
||||
|
||||
const lastRow = rows[rows.length - 1];
|
||||
const secondLastRow = rows[rows.length - 2];
|
||||
|
||||
const lastInput = lastRow.querySelector("input[name$='.haproxy_port']");
|
||||
const secondLastInput = secondLastRow.querySelector("input[name$='.haproxy_port']");
|
||||
|
||||
if (!lastInput || !secondLastInput) return;
|
||||
|
||||
// 如果还没绑定 change 事件,绑定一次
|
||||
if (!lastInput.dataset.bindChange) {
|
||||
lastInput.dataset.bindChange = "1";
|
||||
lastInput.addEventListener("input", () => {
|
||||
lastInput.dataset.userModified = "1";
|
||||
});
|
||||
}
|
||||
|
||||
// 如果用户手动修改过,就不再自动设置
|
||||
if (lastInput.dataset.userModified === "1") return;
|
||||
|
||||
const lastVal = lastInput.value.trim();
|
||||
const secondLastVal = secondLastInput.value.trim();
|
||||
|
||||
const lbssHiddenInput = lastRow.querySelector("div.cbi-dropdown > div > input[type='hidden'][name$='.lbss']");
|
||||
if (!lbssHiddenInput) {
|
||||
if (lastVal !== secondLastVal && secondLastVal !== "" && secondLastVal !== "0") {
|
||||
lastInput.value = secondLastVal;
|
||||
}
|
||||
}
|
||||
}, 300);
|
||||
});
|
||||
//]]>
|
||||
</script>
|
||||
26
luci-app-passwall/luasrc/view/passwall/haproxy/status.htm
Normal file
26
luci-app-passwall/luasrc/view/passwall/haproxy/status.htm
Normal file
@@ -0,0 +1,26 @@
|
||||
<%
|
||||
local api = require "luci.passwall.api"
|
||||
local console_port = api.uci_get_type("@global_haproxy[0]", "console_port", "")
|
||||
-%>
|
||||
<p id="_status"></p>
|
||||
|
||||
<script type="text/javascript">//<![CDATA[
|
||||
XHR.poll(3, '<%=api.url("haproxy_status")%>', null,
|
||||
function(x, result) {
|
||||
if (x && x.status == 200) {
|
||||
var _status = document.getElementById('_status');
|
||||
if (_status) {
|
||||
if (result) {
|
||||
_status.innerHTML = '<input type="button" class="btn cbi-button cbi-button-apply" value="<%:Enter interface%>" onclick="openwebui()" />';
|
||||
} else {
|
||||
_status.innerHTML = '';
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
function openwebui(){
|
||||
var url = window.location.hostname + ":<%=console_port%>";
|
||||
window.open('http://' + url, 'target', '');
|
||||
}
|
||||
//]]></script>
|
||||
31
luci-app-passwall/luasrc/view/passwall/log/log.htm
Normal file
31
luci-app-passwall/luasrc/view/passwall/log/log.htm
Normal file
@@ -0,0 +1,31 @@
|
||||
<%
|
||||
local api = require "luci.passwall.api"
|
||||
-%>
|
||||
<script type="text/javascript">
|
||||
//<![CDATA[
|
||||
function clearlog(btn) {
|
||||
XHR.get('<%=api.url("clear_log")%>', null,
|
||||
function(x, data) {
|
||||
if(x && x.status == 200) {
|
||||
var log_textarea = document.getElementById('log_textarea');
|
||||
log_textarea.innerHTML = "";
|
||||
log_textarea.scrollTop = log_textarea.scrollHeight;
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
XHR.poll(5, '<%=api.url("get_log")%>', null,
|
||||
function(x, data) {
|
||||
if(x && x.status == 200) {
|
||||
var log_textarea = document.getElementById('log_textarea');
|
||||
log_textarea.innerHTML = x.responseText;
|
||||
}
|
||||
}
|
||||
);
|
||||
//]]>
|
||||
</script>
|
||||
<fieldset class="cbi-section" id="_log_fieldset">
|
||||
<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>
|
||||
@@ -0,0 +1,164 @@
|
||||
<%
|
||||
local api = require "luci.passwall.api"
|
||||
-%>
|
||||
|
||||
<script type="text/javascript">
|
||||
//<![CDATA[
|
||||
function ajax_add_node(link) {
|
||||
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 {
|
||||
alert("<%:Error%>");
|
||||
return;
|
||||
}
|
||||
};
|
||||
xhr.onerror = function () {
|
||||
alert("<%:Network Error%>");
|
||||
return;
|
||||
};
|
||||
const formData = new FormData();
|
||||
formData.append("chunk", chunk);
|
||||
formData.append("chunk_index", currentChunk);
|
||||
formData.append("total_chunks", totalChunks);
|
||||
xhr.send(formData);
|
||||
} else {
|
||||
window.location.href = '<%=api.url("node_list")%>';
|
||||
}
|
||||
}
|
||||
sendNextChunk();
|
||||
}
|
||||
|
||||
function open_add_link_div() {
|
||||
document.getElementById("add_link_div").style.display = "block";
|
||||
document.getElementById("nodes_link").focus();
|
||||
}
|
||||
|
||||
function close_add_link_div() {
|
||||
document.getElementById("add_link_div").style.display = "none";
|
||||
}
|
||||
|
||||
function add_node() {
|
||||
var nodes_link = document.getElementById("nodes_link").value;
|
||||
nodes_link = nodes_link.replace(/\t/g, "").replace(/\r\n|\r/g, "\n").trim();
|
||||
if (nodes_link != "") {
|
||||
var s = nodes_link.split('://');
|
||||
if (s.length > 1) {
|
||||
ajax_add_node(nodes_link);
|
||||
}
|
||||
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%>");
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
//]]>
|
||||
</script>
|
||||
|
||||
<div id="add_link_div">
|
||||
<div id="add_link_modal_container">
|
||||
<h3><%:Add the node via the link%></h3>
|
||||
<div class="cbi-value">
|
||||
<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 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 class="cbi-value">
|
||||
<label class="cbi-value-title"></label>
|
||||
<div class="cbi-value-field">
|
||||
<input class="btn cbi-button cbi-button-add" type="submit" name="cbi.cts.passwall.nodes." 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" type="button" onclick="checked_all_node(this)" value="<%:Select all%>" />
|
||||
<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 id="div_node_count"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
#add_link_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 {
|
||||
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;
|
||||
}
|
||||
|
||||
#nodes_link_text {
|
||||
color: red;
|
||||
font-size: 14px;
|
||||
margin-top: 5px;
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#add_link_button_container {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
max-width: 300px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
</style>
|
||||
1663
luci-app-passwall/luasrc/view/passwall/node_list/link_share_man.htm
Normal file
1663
luci-app-passwall/luasrc/view/passwall/node_list/link_share_man.htm
Normal file
File diff suppressed because it is too large
Load Diff
464
luci-app-passwall/luasrc/view/passwall/node_list/node_list.htm
Normal file
464
luci-app-passwall/luasrc/view/passwall/node_list/node_list.htm
Normal file
@@ -0,0 +1,464 @@
|
||||
<%
|
||||
local api = require "luci.passwall.api"
|
||||
-%>
|
||||
|
||||
<style>
|
||||
table th, .table .th {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
table td, .table .td {
|
||||
text-align: center;
|
||||
/* white-space: nowrap; */
|
||||
word-break: keep-all;
|
||||
}
|
||||
|
||||
#set_node_div {
|
||||
display: none;
|
||||
width: 30rem;
|
||||
position: fixed;
|
||||
top:50%;
|
||||
padding-top: 30px;
|
||||
z-index: 99;
|
||||
text-align: center;
|
||||
background: white;
|
||||
box-shadow: darkgrey 10px 10px 30px 5px;
|
||||
}
|
||||
|
||||
._now_use {
|
||||
color: red !important;
|
||||
}
|
||||
|
||||
._now_use_bg {
|
||||
background: #5e72e445 !important;
|
||||
}
|
||||
|
||||
.ping a:hover{
|
||||
text-decoration : underline;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
._now_use_bg {
|
||||
background: #4a90e2 !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<script type="text/javascript">
|
||||
//<![CDATA[
|
||||
let auto_detection_time = "<%=api.uci_get_type("@global_other[0]", "auto_detection_time", "0")%>"
|
||||
|
||||
var node_list = {};
|
||||
var node_count = 0;
|
||||
|
||||
var ajax = {
|
||||
post: function(url, data, fn_success, timeout, fn_timeout) {
|
||||
var xhr = new XMLHttpRequest();
|
||||
var code = ajax.encode(data);
|
||||
xhr.open("POST", url, true);
|
||||
xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
|
||||
|
||||
if (timeout && timeout > 1000) {
|
||||
xhr.timeout = timeout;
|
||||
}
|
||||
if (fn_timeout) {
|
||||
xhr.ontimeout = function() {
|
||||
fn_timeout(xhr);
|
||||
}
|
||||
}
|
||||
xhr.onreadystatechange = function() {
|
||||
if(xhr.readyState == 4 && (xhr.status == 200 || xhr.status == 304)) {
|
||||
var json = null;
|
||||
if (xhr.getResponseHeader("Content-Type") == "application/json") {
|
||||
try {
|
||||
json = eval('(' + xhr.responseText + ')');
|
||||
}
|
||||
catch(e) {
|
||||
json = null;
|
||||
}
|
||||
}
|
||||
fn_success(xhr, json);
|
||||
}
|
||||
};
|
||||
xhr.send(code);
|
||||
},
|
||||
encode: function(obj) {
|
||||
obj = obj ? obj : { };
|
||||
obj['_'] = Math.random();
|
||||
|
||||
if (typeof obj == 'object')
|
||||
{
|
||||
var code = '';
|
||||
var self = this;
|
||||
|
||||
for (var k in obj)
|
||||
code += (code ? '&' : '') +
|
||||
k + '=' + encodeURIComponent(obj[k]);
|
||||
|
||||
return code;
|
||||
}
|
||||
|
||||
return obj;
|
||||
}
|
||||
}
|
||||
|
||||
function copy_node(cbi_id) {
|
||||
window.location.href = '<%=api.url("copy_node")%>' + "?section=" + 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.passwall." + 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 _cbi_row_top(id) {
|
||||
var dom = document.getElementById("cbi-passwall-" + id);
|
||||
if (dom) {
|
||||
var trs = document.getElementById("cbi-passwall-nodes").getElementsByClassName("cbi-section-table-row");
|
||||
if (trs && trs.length > 0) {
|
||||
for (var i = 0; i < trs.length; i++) {
|
||||
var up = dom.getElementsByClassName("cbi-button-up");
|
||||
if (up) {
|
||||
cbi_row_swap(up[0], true, 'cbi.sts.passwall.nodes');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function checked_all_node(btn) {
|
||||
var doms = document.getElementById("cbi-passwall-nodes").getElementsByClassName("nodes_select");
|
||||
if (doms && doms.length > 0) {
|
||||
for (var i = 0 ; i < doms.length; i++) {
|
||||
doms[i].checked = true;
|
||||
}
|
||||
btn.value = "<%:DeSelect all%>";
|
||||
btn.setAttribute("onclick", "dechecked_all_node(this)");
|
||||
}
|
||||
}
|
||||
|
||||
function dechecked_all_node(btn) {
|
||||
var doms = document.getElementById("cbi-passwall-nodes").getElementsByClassName("nodes_select");
|
||||
if (doms && doms.length > 0) {
|
||||
for (var i = 0 ; i < doms.length; i++) {
|
||||
doms[i].checked = false;
|
||||
}
|
||||
btn.value = "<%:Select all%>";
|
||||
btn.setAttribute("onclick", "checked_all_node(this)");
|
||||
}
|
||||
}
|
||||
|
||||
function delete_select_nodes() {
|
||||
var ids = [];
|
||||
var doms = document.getElementById("cbi-passwall-nodes").getElementsByClassName("nodes_select");
|
||||
if (doms && doms.length > 0) {
|
||||
for (var i = 0 ; i < doms.length; i++) {
|
||||
if (doms[i].checked) {
|
||||
ids.push(doms[i].getAttribute("cbid"))
|
||||
}
|
||||
}
|
||||
if (ids.length > 0) {
|
||||
if (confirm('<%:Are you sure to delete select nodes?%>') == true){
|
||||
XHR.get('<%=api.url("delete_select_nodes")%>', {
|
||||
ids: ids.join()
|
||||
},
|
||||
function(x, data) {
|
||||
if (x && x.status == 200) {
|
||||
/*
|
||||
for (var i = 0 ; i < ids.length; i++) {
|
||||
var box = document.getElementById("cbi-passwall-" + ids[i]);
|
||||
box.remove();
|
||||
}
|
||||
*/
|
||||
window.location.href = '<%=api.url("node_list")%>';
|
||||
}
|
||||
else {
|
||||
alert("<%:Error%>");
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
if (ids.length <= 0) {
|
||||
alert("<%:You no select nodes !%>");
|
||||
}
|
||||
}
|
||||
|
||||
function set_node(protocol) {
|
||||
if (confirm('<%:Are you sure set to%> ' + protocol.toUpperCase() + '<%:the server?%>')==true){
|
||||
window.location.href = '<%=api.url("set_node")%>?protocol=' + protocol + '§ion=' + section;
|
||||
}
|
||||
}
|
||||
|
||||
function get_address_full(id) {
|
||||
var address = (document.getElementById("cbid.passwall." + id + ".address") || {}).value || "";
|
||||
var port = (document.getElementById("cbid.passwall." + id + ".port") || {}).value || "";
|
||||
//判断是否含有汉字
|
||||
var reg = /[\u4E00-\u9FFF]+/;
|
||||
address = !reg.test(address) ? address : "";
|
||||
return { address: address, port: port };
|
||||
}
|
||||
|
||||
//获取当前使用的节点
|
||||
function get_now_use_node() {
|
||||
XHR.get('<%=api.url("get_now_use_node")%>', null,
|
||||
function(x, result) {
|
||||
var id = result["TCP"];
|
||||
if (id) {
|
||||
var dom = document.getElementById("cbi-passwall-" + id);
|
||||
if (dom) {
|
||||
dom.title = "当前使用的 TCP 节点";
|
||||
dom.classList.add("_now_use_bg");
|
||||
//var v = "<a style='color: red'>当前TCP节点:</a>" + document.getElementById("cbid.passwall." + id + ".remarks").value;
|
||||
//document.getElementById("cbi-passwall-" + id + "-remarks").innerHTML = v;
|
||||
var dom_remarks = document.getElementById("cbi-passwall-" + id + "-remarks");
|
||||
if (dom_remarks) {
|
||||
dom_remarks.classList.add("_now_use");
|
||||
}
|
||||
}
|
||||
}
|
||||
id = result["UDP"];
|
||||
if (id) {
|
||||
var dom = document.getElementById("cbi-passwall-" + id);
|
||||
if (dom) {
|
||||
if (result["TCP"] == result["UDP"]) {
|
||||
dom.title = "当前使用的 TCP/UDP 节点";
|
||||
} else {
|
||||
dom.title = "当前使用的 UDP 节点";
|
||||
}
|
||||
dom.classList.add("_now_use_bg");
|
||||
var dom_remarks = document.getElementById("cbi-passwall-" + id + "-remarks");
|
||||
if (dom_remarks) {
|
||||
dom_remarks.classList.add("_now_use");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function urltest_node(cbi_id, dom) {
|
||||
if (cbi_id != null) {
|
||||
dom.onclick = null
|
||||
dom.innerText = "<%:Check...%>";
|
||||
XHR.get('<%=api.url("urltest_node")%>', {
|
||||
id: cbi_id
|
||||
},
|
||||
function(x, result) {
|
||||
if(x && x.status == 200) {
|
||||
if (result.use_time == null || result.use_time.trim() == "") {
|
||||
dom.outerHTML = "<font style='color:red'><%:Timeout%></font>";
|
||||
} else {
|
||||
var color = "red";
|
||||
var use_time = result.use_time;
|
||||
use_time = parseInt(use_time) + 1;
|
||||
if (use_time < 1000) {
|
||||
color = "green";
|
||||
} else if (use_time < 2000) {
|
||||
color = "#fb9a05";
|
||||
} else {
|
||||
color = "red";
|
||||
}
|
||||
dom.outerHTML = "<font style='color:" + color + "'>" + use_time + " ms" + "</font>";
|
||||
}
|
||||
} else {
|
||||
dom.outerHTML = "<font style='color:red'><%:Error%></font>";
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function ping_node(cbi_id, dom, type) {
|
||||
var full = get_address_full(cbi_id);
|
||||
if ((type == "icmp" && full.address != "" ) || (type == "tcping" && full.address != "" && full.port != "")) {
|
||||
dom.onclick = null
|
||||
dom.innerText = "<%:Check...%>";
|
||||
XHR.get('<%=api.url("ping_node")%>', {
|
||||
address: full.address,
|
||||
port: full.port,
|
||||
type: type
|
||||
},
|
||||
function(x, result) {
|
||||
if(x && x.status == 200) {
|
||||
if (result.ping == null || result.ping.trim() == "") {
|
||||
dom.outerHTML = "<font style='color:red'><%:Timeout%></font>";
|
||||
} else {
|
||||
var ping = parseInt(result.ping);
|
||||
if (ping < 100)
|
||||
dom.outerHTML = "<font style='color:green'>" + result.ping + " ms" + "</font>";
|
||||
else if (ping < 200)
|
||||
dom.outerHTML = "<font style='color:#fb9a05'>" + result.ping + " ms" + "</font>";
|
||||
else if (ping >= 200)
|
||||
dom.outerHTML = "<font style='color:red'>" + result.ping + " ms" + "</font>";
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/* 自动Ping */
|
||||
function pingAllNodes() {
|
||||
if (auto_detection_time == "icmp" || auto_detection_time == "tcping") {
|
||||
var nodes = [];
|
||||
const ping_value = document.getElementsByClassName(auto_detection_time == "tcping" ? 'tcping_value' : 'ping_value');
|
||||
for (var i = 0; i < ping_value.length; i++) {
|
||||
var cbi_id = ping_value[i].getAttribute("cbiid");
|
||||
var full = get_address_full(cbi_id);
|
||||
if ((auto_detection_time == "icmp" && full.address != "" ) || (auto_detection_time == "tcping" && full.address != "" && full.port != "")) {
|
||||
var flag = false;
|
||||
//当有多个相同地址和端口时合在一起
|
||||
for (var j = 0; j < nodes.length; j++) {
|
||||
if (nodes[j].address == full.address && nodes[j].port == full.port) {
|
||||
nodes[j].indexs = nodes[j].indexs + "," + i;
|
||||
flag = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (flag)
|
||||
continue;
|
||||
nodes.push({
|
||||
indexs: i + "",
|
||||
address: full.address,
|
||||
port: full.port
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const _xhr = (index) => {
|
||||
return new Promise((res) => {
|
||||
const dom = nodes[index];
|
||||
if (!dom) res()
|
||||
ajax.post('<%=api.url("ping_node")%>', {
|
||||
index: dom.indexs,
|
||||
address: dom.address,
|
||||
port: dom.port,
|
||||
type: auto_detection_time
|
||||
},
|
||||
function(x, result) {
|
||||
if (x && x.status == 200) {
|
||||
var strs = dom.indexs.split(",");
|
||||
for (var i = 0; i < strs.length; i++) {
|
||||
if (result.ping == null || result.ping.trim() == "") {
|
||||
ping_value[strs[i]].innerHTML = "<font style='color:red'><%:Timeout%></font>";
|
||||
} else {
|
||||
var ping = parseInt(result.ping);
|
||||
if (ping < 100)
|
||||
ping_value[strs[i]].innerHTML = "<font style='color:green'>" + result.ping + " ms" + "</font>";
|
||||
else if (ping < 200)
|
||||
ping_value[strs[i]].innerHTML = "<font style='color:#fb9a05'>" + result.ping + " ms" + "</font>";
|
||||
else if (ping >= 200)
|
||||
ping_value[strs[i]].innerHTML = "<font style='color:red'>" + result.ping + " ms" + "</font>";
|
||||
}
|
||||
}
|
||||
}
|
||||
res();
|
||||
},
|
||||
5000,
|
||||
function(x) {
|
||||
var strs = dom.indexs.split(",");
|
||||
for (var i = 0; i < strs.length; i++) {
|
||||
ping_value[strs[i]].innerHTML = "<font style='color:red'><%:Timeout%></font>";
|
||||
}
|
||||
res();
|
||||
}
|
||||
);
|
||||
})
|
||||
}
|
||||
|
||||
let task = -1;
|
||||
const thread = () => {
|
||||
task = task + 1
|
||||
if (nodes[task]) {
|
||||
_xhr(task).then(thread);
|
||||
}
|
||||
}
|
||||
for (let i = 0; i < 20; i++) {
|
||||
thread()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var edit_btn = document.getElementById("cbi-passwall-nodes").getElementsByClassName("cbi-button cbi-button-edit");
|
||||
for (var i = 0; i < edit_btn.length; i++) {
|
||||
try {
|
||||
var onclick_str = edit_btn[i].getAttribute("onclick");
|
||||
var id = onclick_str.substring(onclick_str.lastIndexOf('/') + 1, onclick_str.length - 1);
|
||||
var td = edit_btn[i].parentNode;
|
||||
var new_div = "";
|
||||
//添加"勾选"框
|
||||
new_div += '<input class="cbi-input-checkbox nodes_select" type="checkbox" cbid="' + id + '" /> ';
|
||||
//添加"置顶"按钮
|
||||
new_div += '<input class="btn cbi-button" type="button" value="<%:To Top%>" onclick="_cbi_row_top(\'' + id + '\')"/> ';
|
||||
//添加"应用"按钮
|
||||
new_div += '<input class="btn cbi-button cbi-button-apply" type="button" value="<%:Use%>" id="apply_' + id + '" onclick="open_set_node_div(\'' + id + '\')"/> ';
|
||||
//添加"复制"按钮
|
||||
new_div += '<input class="btn cbi-button cbi-button-add" type="button" value="<%:Copy%>" onclick="copy_node(\'' + id + '\')"/> ';
|
||||
td.innerHTML = new_div + td.innerHTML;
|
||||
|
||||
var obj = {};
|
||||
obj.id = id;
|
||||
obj.type = document.getElementById("cbid.passwall." + id + ".type").value;
|
||||
var address_dom = document.getElementById("cbid.passwall." + id + ".address");
|
||||
var port_dom = document.getElementById("cbid.passwall." + id + ".port");
|
||||
if (address_dom && port_dom) {
|
||||
obj.address = address_dom.value;
|
||||
obj.port = port_dom.value;
|
||||
}
|
||||
|
||||
node_count++;
|
||||
var add_from = document.getElementById("cbid.passwall." + id + ".add_from").value;
|
||||
if (node_list[add_from])
|
||||
node_list[add_from].push(obj);
|
||||
else
|
||||
node_list[add_from] = [];
|
||||
|
||||
}
|
||||
catch(err) {
|
||||
console.error(err);
|
||||
}
|
||||
}
|
||||
|
||||
get_now_use_node();
|
||||
|
||||
if (true) {
|
||||
var str = "";
|
||||
for (var add_from in node_list) {
|
||||
var num = node_list[add_from].length + 1;
|
||||
if (add_from == "") {
|
||||
add_from = "<%:Self add%>";
|
||||
}
|
||||
str += add_from + " " + "<%:Node num%>: <a style='color: red'>" + num + "</a>   ";
|
||||
}
|
||||
document.getElementById("div_node_count").innerHTML = "<div style='margin-top:5px'>" + str + "</div>";
|
||||
}
|
||||
|
||||
//UI渲染完成后再自动Ping
|
||||
window.onload = function () {
|
||||
setTimeout(function () {
|
||||
pingAllNodes();
|
||||
}, 800);
|
||||
};
|
||||
|
||||
//]]>
|
||||
</script>
|
||||
|
||||
<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">
|
||||
<div class="cbi-value"><%:You choose node is:%><a style="color: red" id="set_node_name"></a></div>
|
||||
<div class="cbi-value">
|
||||
<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%>" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
111
luci-app-passwall/luasrc/view/passwall/node_subscribe/js.htm
Normal file
111
luci-app-passwall/luasrc/view/passwall/node_subscribe/js.htm
Normal file
@@ -0,0 +1,111 @@
|
||||
<%
|
||||
local api = require "luci.passwall.api"
|
||||
-%>
|
||||
<script type="text/javascript">
|
||||
//<![CDATA[
|
||||
var appname = "<%= api.appname %>"
|
||||
|
||||
function confirmDeleteNode(remark) {
|
||||
if (!confirm("<%:Delete the subscribed node%>: " + remark + " ?"))
|
||||
return false;
|
||||
|
||||
fetch('<%= api.url("subscribe_del_node") %>?remark=' + encodeURIComponent(remark), {
|
||||
method: "GET"
|
||||
}).then(res => {
|
||||
if (res.ok) {
|
||||
location.reload();
|
||||
} else {
|
||||
alert("<%:Failed to delete.%>");
|
||||
}
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
function confirmDeleteAll() {
|
||||
if (!confirm("<%:Are you sure you want to delete all subscribed nodes?%>"))
|
||||
return false;
|
||||
|
||||
fetch('<%= api.url("subscribe_del_all") %>', {
|
||||
method: "GET"
|
||||
}).then(res => {
|
||||
if (res.ok) {
|
||||
location.reload();
|
||||
} else {
|
||||
alert("<%:Failed to delete.%>");
|
||||
}
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
function ManualSubscribe(sectionId) {
|
||||
var urlInput = document.querySelector("input[name='cbid." + appname + "." + sectionId + ".url']");
|
||||
var currentUrl = urlInput ? urlInput.value.trim() : "";
|
||||
if (!currentUrl) {
|
||||
alert("<%:Subscribe URL cannot be empty.%>");
|
||||
return;
|
||||
}
|
||||
|
||||
fetch('<%= api.url("subscribe_manual") %>?section='
|
||||
+ encodeURIComponent(sectionId)
|
||||
+ '&url='
|
||||
+ encodeURIComponent(currentUrl))
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (!data.success) {
|
||||
alert(data.msg || "Operation failed");
|
||||
} else {
|
||||
window.location.href = '<%= api.url("log") %>'
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function ManualSubscribeAll() {
|
||||
var sectionIds = [];
|
||||
var urls = [];
|
||||
|
||||
var table = document.getElementById("cbi-" + appname + "-subscribe_list");
|
||||
var editBtns = table ? table.getElementsByClassName("cbi-button cbi-button-edit") : [];
|
||||
|
||||
for (var i = 0; i < editBtns.length; i++) {
|
||||
var btn = editBtns[i];
|
||||
var onclickStr = btn.getAttribute("onclick");
|
||||
if (!onclickStr) continue;
|
||||
|
||||
var id = onclickStr.substring(onclickStr.lastIndexOf('/') + 1, onclickStr.length - 1);
|
||||
if (!id) continue;
|
||||
|
||||
var urlInput = document.querySelector("input[name='cbid." + appname + "." + id + ".url']");
|
||||
var currentUrl = urlInput ? urlInput.value.trim() : "";
|
||||
if (!currentUrl) {
|
||||
alert("<%:Subscribe URL cannot be empty.%>");
|
||||
return;
|
||||
}
|
||||
|
||||
sectionIds.push(id);
|
||||
urls.push(currentUrl);
|
||||
}
|
||||
|
||||
if (sectionIds.length === 0) {
|
||||
//alert("No subscriptions found.");
|
||||
return;
|
||||
}
|
||||
|
||||
var params = new URLSearchParams();
|
||||
params.append("sections", sectionIds.join(","));
|
||||
params.append("urls", urls.join(","));
|
||||
|
||||
fetch('<%= api.url("subscribe_manual_all") %>', {
|
||||
method: 'POST',
|
||||
body: params
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (!data.success) {
|
||||
alert(data.msg || "Operation failed");
|
||||
} else {
|
||||
window.location.href = '<%= api.url("log") %>'
|
||||
}
|
||||
});
|
||||
}
|
||||
//]]>
|
||||
</script>
|
||||
120
luci-app-passwall/luasrc/view/passwall/rule/rule_version.htm
Normal file
120
luci-app-passwall/luasrc/view/passwall/rule/rule_version.htm
Normal file
@@ -0,0 +1,120 @@
|
||||
<%
|
||||
local api = require "luci.passwall.api"
|
||||
-%>
|
||||
|
||||
<style>
|
||||
div.cbi-value[id$="-gfwlist_update"],
|
||||
div.cbi-value[id$="-chnroute_update"],
|
||||
div.cbi-value[id$="-chnroute6_update"],
|
||||
div.cbi-value[id$="-chnlist_update"],
|
||||
div.cbi-value[id$="-geoip_update"],
|
||||
div.cbi-value[id$="-geosite_update"] {
|
||||
display: none !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="cbi-value" id="_rule_div">
|
||||
<label class="cbi-value-title">
|
||||
<%:Update Options%>
|
||||
</label>
|
||||
<div class="cbi-value-field">
|
||||
<div>
|
||||
<label>
|
||||
<input class="cbi-input-checkbox" type="checkbox" name="gfwlist" value="1" />
|
||||
gfwlist
|
||||
</label>
|
||||
<label>
|
||||
<input class="cbi-input-checkbox" type="checkbox" name="chnroute" value="1" />
|
||||
chnroute
|
||||
</label>
|
||||
<label>
|
||||
<input class="cbi-input-checkbox" type="checkbox" name="chnroute6" value="1" />
|
||||
chnroute6
|
||||
</label>
|
||||
<label>
|
||||
<input class="cbi-input-checkbox" type="checkbox" name="chnlist" value="1" />
|
||||
chnlist
|
||||
</label>
|
||||
<label>
|
||||
<input class="cbi-input-checkbox" type="checkbox" name="geoip" value="1" />
|
||||
geoip
|
||||
</label>
|
||||
<label>
|
||||
<input class="cbi-input-checkbox" type="checkbox" name="geosite" value="1" />
|
||||
geosite
|
||||
</label>
|
||||
<br><br><input class="btn cbi-button cbi-button-apply" type="button" id="update_rules_btn" onclick="update_rules(this)" value="<%:Manually update%>" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script type="text/javascript">
|
||||
//<![CDATA[
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
const flags = [
|
||||
"gfwlist_update","chnroute_update","chnroute6_update",
|
||||
"chnlist_update","geoip_update","geosite_update"
|
||||
];
|
||||
const bindFlags = () => {
|
||||
let allBound = true;
|
||||
flags.forEach(flag => {
|
||||
const orig = Array.from(document.querySelectorAll(`input[name$=".${flag}"]`)).find(i => i.type === 'checkbox');
|
||||
if (!orig) { allBound = false; return; }
|
||||
// 隐藏最外层 div
|
||||
const wrapper = orig.closest('.cbi-value');
|
||||
if (wrapper && wrapper.style.display !== 'none') {
|
||||
wrapper.style.display = 'none';
|
||||
}
|
||||
const custom = document.querySelector(`.cbi-input-checkbox[name="${flag.replace('_update','')}"]`);
|
||||
if (!custom) { allBound = false; return; }
|
||||
custom.checked = orig.checked;
|
||||
// 自定义选择框与原生Flag双向绑定
|
||||
if (!custom._binded) {
|
||||
custom._binded = true;
|
||||
orig.addEventListener('change', () => {
|
||||
custom.checked = orig.checked;
|
||||
});
|
||||
custom.addEventListener('change', () => {
|
||||
orig.checked = custom.checked;
|
||||
orig.dispatchEvent(new Event('change', { bubbles: true }));
|
||||
});
|
||||
}
|
||||
});
|
||||
return allBound;
|
||||
};
|
||||
const target = document.querySelector('form') || document.body;
|
||||
const observer = new MutationObserver(() => bindFlags() ? observer.disconnect() : 0);
|
||||
observer.observe(target, { childList: true, subtree: true });
|
||||
const timer = setInterval(() => bindFlags() ? (clearInterval(timer), observer.disconnect()) : 0, 300);
|
||||
setTimeout(() => { clearInterval(timer); observer.disconnect(); }, 5000);
|
||||
});
|
||||
|
||||
function update_rules(btn) {
|
||||
btn.disabled = true;
|
||||
btn.value = '<%:Updating...%>';
|
||||
var div = document.getElementById('_rule_div');
|
||||
var domList = div.getElementsByTagName('input');
|
||||
var checkBoxList = [];
|
||||
var len = domList.length;
|
||||
while(len--) {
|
||||
var dom = domList[len];
|
||||
if(dom.type == 'checkbox' && dom.checked) {
|
||||
checkBoxList.push(dom.name);
|
||||
}
|
||||
}
|
||||
XHR.get('<%=api.url("update_rules")%>', {
|
||||
update: checkBoxList.join(",")
|
||||
},
|
||||
function(x, data) {
|
||||
if(x && x.status == 200) {
|
||||
window.location.href = '<%=api.url("log")%>';
|
||||
} else {
|
||||
alert("<%:Error%>");
|
||||
btn.disabled = false;
|
||||
btn.value = '<%:Manually update%>';
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
//]]>
|
||||
</script>
|
||||
96
luci-app-passwall/luasrc/view/passwall/rule_list/geoview.htm
Normal file
96
luci-app-passwall/luasrc/view/passwall/rule_list/geoview.htm
Normal file
@@ -0,0 +1,96 @@
|
||||
<%
|
||||
local api = require "luci.passwall.api"
|
||||
-%>
|
||||
|
||||
<style>
|
||||
.faq-title {
|
||||
color: var(--primary);
|
||||
font-weight: bolder;
|
||||
margin-bottom: 0.5rem;
|
||||
display: inline-block;
|
||||
}
|
||||
.faq-item {
|
||||
margin-bottom: 0.8rem;
|
||||
line-height:1.2rem;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="cbi-value">
|
||||
<ul>
|
||||
<b class="faq-title"><%:Tips:%></b>
|
||||
<li class="faq-item">1. <span><%:By entering a domain or IP, you can query the Geo rule list they belong to.%></span></li>
|
||||
<li class="faq-item">2. <span><%:By entering a GeoIP or Geosite, you can extract the domains/IPs they contain.%></span></li>
|
||||
<li class="faq-item">3. <span><%:Use the GeoIP/Geosite query function to verify if the entered Geo rules are correct.%></span></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="cbi-value" id="cbi-passwall-geoview-lookup"><label class="cbi-value-title" for="geoview.lookup"><%:Domain/IP Query%></label>
|
||||
<div class="cbi-value-field">
|
||||
<input type="text" class="cbi-textfield" id="geoview.lookup" name="geoview.lookup" />
|
||||
<input class="btn cbi-button cbi-button-apply" type="button" id="lookup-view_btn"
|
||||
onclick='do_geoview(this, "lookup", document.getElementById("geoview.lookup").value)'
|
||||
value="<%:Query%>" />
|
||||
<br />
|
||||
<div class="cbi-value-description">
|
||||
<%:Enter a domain or IP to query the Geo rule list they belong to.%>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="cbi-value" id="cbi-passwall-geoview-extract"><label class="cbi-value-title" for="geoview.extract"><%:GeoIP/Geosite Query%></label>
|
||||
<div class="cbi-value-field">
|
||||
<input type="text" class="cbi-textfield" id="geoview.extract" name="geoview.extract" />
|
||||
<input class="btn cbi-button cbi-button-apply" type="button" id="extract-view_btn"
|
||||
onclick='do_geoview(this, "extract", document.getElementById("geoview.extract").value)'
|
||||
value="<%:Query%>" />
|
||||
<br />
|
||||
<div class="cbi-value-description">
|
||||
<%:Enter a GeoIP or Geosite to extract the domains/IPs they contain. Format: geoip:cn or geosite:gfw%>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="cbi-value">
|
||||
<textarea id="geoview_textarea" class="cbi-input-textarea" style="width: 100%; margin-top: 10px;" rows="25" wrap="off" readonly="readonly"></textarea>
|
||||
</div>
|
||||
|
||||
<script type="text/javascript">
|
||||
//<![CDATA[
|
||||
var lookup_btn = document.getElementById("lookup-view_btn");
|
||||
var extract_btn = document.getElementById("extract-view_btn");
|
||||
var QueryText = '<%:Query%>';
|
||||
var QueryingText = '<%:Querying%>';
|
||||
|
||||
function do_geoview(btn,action,value) {
|
||||
value = value.trim();
|
||||
if (!value) {
|
||||
alert("<%:Please enter query content!%>");
|
||||
return;
|
||||
}
|
||||
lookup_btn.disabled = true;
|
||||
extract_btn.disabled = true;
|
||||
btn.value = QueryingText;
|
||||
var textarea = document.getElementById('geoview_textarea');
|
||||
textarea.textContent = "";
|
||||
fetch('<%= api.url("geo_view") %>?action=' + action + '&value=' + encodeURIComponent(value))
|
||||
.then(response => response.text())
|
||||
.then(data => {
|
||||
textarea.textContent = data;
|
||||
lookup_btn.disabled = false;
|
||||
extract_btn.disabled = false;
|
||||
btn.value = QueryText;
|
||||
})
|
||||
}
|
||||
|
||||
document.getElementById("geoview.lookup").addEventListener("keydown", function(event) {
|
||||
if (event.key === "Enter") {
|
||||
event.preventDefault();
|
||||
lookup_btn.click();
|
||||
}
|
||||
});
|
||||
|
||||
document.getElementById("geoview.extract").addEventListener("keydown", function(event) {
|
||||
if (event.key === "Enter") {
|
||||
event.preventDefault();
|
||||
extract_btn.click();
|
||||
}
|
||||
});
|
||||
//]]>
|
||||
</script>
|
||||
47
luci-app-passwall/luasrc/view/passwall/rule_list/js.htm
Normal file
47
luci-app-passwall/luasrc/view/passwall/rule_list/js.htm
Normal file
@@ -0,0 +1,47 @@
|
||||
<%
|
||||
local api = require "luci.passwall.api"
|
||||
local translate = luci.i18n.translate
|
||||
local total_lines_text = translate("Total Lines")
|
||||
-%>
|
||||
<script type="text/javascript">
|
||||
//<![CDATA[
|
||||
function read_gfw() {
|
||||
fetch('<%= api.url("read_rulelist") %>?type=gfw')
|
||||
.then(response => response.text())
|
||||
.then(data => {
|
||||
var total_lines = data.split("\n").filter(line => line.trim() !== "").length;
|
||||
var textarea = document.getElementById('gfw_textarea');
|
||||
textarea.innerHTML = data;
|
||||
//textarea.scrollTop = textarea.scrollHeight;
|
||||
var totalLinesLabel = document.getElementById('gfw_total_lines');
|
||||
totalLinesLabel.innerHTML = "<%= total_lines_text %> " + total_lines;
|
||||
})
|
||||
}
|
||||
|
||||
function read_chn() {
|
||||
fetch('<%= api.url("read_rulelist") %>?type=chn')
|
||||
.then(response => response.text())
|
||||
.then(data => {
|
||||
var total_lines = data.split("\n").filter(line => line.trim() !== "").length;
|
||||
var textarea = document.getElementById('chn_textarea');
|
||||
textarea.innerHTML = data;
|
||||
//textarea.scrollTop = textarea.scrollHeight;
|
||||
var totalLinesLabel = document.getElementById('chn_total_lines');
|
||||
totalLinesLabel.innerHTML = "<%= total_lines_text %> " + total_lines;
|
||||
})
|
||||
}
|
||||
|
||||
function read_chnroute() {
|
||||
fetch('<%= api.url("read_rulelist") %>?type=chnroute')
|
||||
.then(response => response.text())
|
||||
.then(data => {
|
||||
var total_lines = data.split("\n").filter(line => line.trim() !== "").length;
|
||||
var textarea = document.getElementById('chnroute_textarea');
|
||||
textarea.innerHTML = data;
|
||||
//textarea.scrollTop = textarea.scrollHeight;
|
||||
var totalLinesLabel = document.getElementById('chnroute_total_lines');
|
||||
totalLinesLabel.innerHTML = "<%= total_lines_text %> " + total_lines;
|
||||
})
|
||||
}
|
||||
//]]>
|
||||
</script>
|
||||
35
luci-app-passwall/luasrc/view/passwall/server/log.htm
Normal file
35
luci-app-passwall/luasrc/view/passwall/server/log.htm
Normal file
@@ -0,0 +1,35 @@
|
||||
<%
|
||||
local api = require "luci.passwall.api"
|
||||
-%>
|
||||
<script type="text/javascript">
|
||||
//<![CDATA[
|
||||
function clear_log(btn) {
|
||||
XHR.get('<%=api.url("server_clear_log")%>', null,
|
||||
function(x, data) {
|
||||
if(x && x.status == 200) {
|
||||
var log_textarea = document.getElementById('log_textarea');
|
||||
log_textarea.innerHTML = "";
|
||||
log_textarea.scrollTop = log_textarea.scrollHeight;
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
XHR.poll(3, '<%=api.url("server_get_log")%>', null,
|
||||
function(x, data) {
|
||||
if(x && x.status == 200) {
|
||||
var log_textarea = document.getElementById('log_textarea');
|
||||
log_textarea.innerHTML = x.responseText;
|
||||
log_textarea.scrollTop = log_textarea.scrollHeight;
|
||||
}
|
||||
}
|
||||
);
|
||||
//]]>
|
||||
</script>
|
||||
<fieldset class="cbi-section" id="_log_fieldset">
|
||||
<legend>
|
||||
<%:Logs%>
|
||||
</legend>
|
||||
<input class="btn cbi-button cbi-button-remove" type="button" onclick="clear_log()" value="<%:Clear logs%>" />
|
||||
<textarea id="log_textarea" class="cbi-input-textarea" style="width: 100%;margin-top: 10px;" data-update="change" rows="20" wrap="off" readonly="readonly"></textarea>
|
||||
</fieldset>
|
||||
@@ -0,0 +1,38 @@
|
||||
<%
|
||||
local api = require "luci.passwall.api"
|
||||
-%>
|
||||
<script type="text/javascript">
|
||||
//<![CDATA[
|
||||
var _users_status = document.getElementsByClassName('_users_status');
|
||||
for(var i = 0; i < _users_status.length; i++) {
|
||||
var id = _users_status[i].parentElement.parentElement.parentElement.id;
|
||||
id = id.substr(id.lastIndexOf("-") + 1);
|
||||
XHR.get('<%=api.url("server_user_status")%>', {
|
||||
index: i,
|
||||
id: id
|
||||
},
|
||||
function(x, result) {
|
||||
_users_status[result.index].setAttribute("style","font-weight:bold;");
|
||||
_users_status[result.index].setAttribute("color",result.status ? "green":"red");
|
||||
_users_status[result.index].innerHTML = (result.status ? '✓' : 'X');
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
var edit_btn = document.getElementById("cbi-passwall_server-user").getElementsByClassName("cbi-button cbi-button-edit");
|
||||
for (var i = 0; i < edit_btn.length; i++) {
|
||||
try {
|
||||
var onclick_str = edit_btn[i].getAttribute("onclick");
|
||||
var id = onclick_str.substring(onclick_str.lastIndexOf('/') + 1, onclick_str.length - 1);
|
||||
var td = edit_btn[i].parentNode;
|
||||
var new_div = "";
|
||||
//添加"日志"按钮
|
||||
new_div += '<input class="btn cbi-button cbi-button-add" type="button" value="<%:Log%>" onclick="window.open(\'' + '<%=api.url("server_user_log")%>' + '?id=' + id + '\', \'_blank\')"/> ';
|
||||
td.innerHTML = new_div + td.innerHTML;
|
||||
}
|
||||
catch(err) {
|
||||
console.error(err);
|
||||
}
|
||||
}
|
||||
//]]>
|
||||
</script>
|
||||
@@ -0,0 +1,28 @@
|
||||
<%
|
||||
local api = require "luci.passwall.api"
|
||||
-%>
|
||||
|
||||
<script type="text/javascript">
|
||||
//<![CDATA[
|
||||
let socks_id = window.location.pathname.substring(window.location.pathname.lastIndexOf("/") + 1)
|
||||
function add_node_by_key() {
|
||||
var key = prompt("<%:Please enter the node keyword, pay attention to distinguish between spaces, uppercase and lowercase.%>", "");
|
||||
if (key) {
|
||||
window.location.href = '<%=api.url("socks_autoswitch_add_node")%>' + "?id=" + socks_id + "&key=" + key;
|
||||
}
|
||||
}
|
||||
function remove_node_by_key() {
|
||||
var key = prompt("<%:Please enter the node keyword, pay attention to distinguish between spaces, uppercase and lowercase.%>", "");
|
||||
if (key) {
|
||||
window.location.href = '<%=api.url("socks_autoswitch_remove_node")%>' + "?id=" + socks_id + "&key=" + key;
|
||||
}
|
||||
}
|
||||
//]]>
|
||||
</script>
|
||||
<div id="cbi-<%=self.config.."-"..section.."-"..self.option%>" data-index="<%=self.index%>" data-depends="<%=pcdata(self:deplist2json(section))%>">
|
||||
<label class="cbi-value-title"></label>
|
||||
<div class="cbi-value-field">
|
||||
<input class="btn cbi-button cbi-button-add" type="button" onclick="add_node_by_key()" value="<%:Add nodes to the standby node list by keywords%>" />
|
||||
<input class="btn cbi-button cbi-button-remove" type="button" onclick="remove_node_by_key()" value="<%:Delete nodes in the standby node list by keywords%>" />
|
||||
</div>
|
||||
</div>
|
||||
Reference in New Issue
Block a user