mirror of
https://github.com/tobychui/zoraxy.git
synced 2025-06-04 22:57:20 +02:00

- Moved rate limiter scope into proxy router - Give IpTable a better name following clean code guideline - Optimized client IP retrieval method - Added stop channel for request counter ticker - Fixed #199 - Optimized UI for rate limit
435 lines
19 KiB
HTML
435 lines
19 KiB
HTML
<!-- Proxy Create Form-->
|
|
<style>
|
|
.rulesInstructions{
|
|
background: var(--theme_background) !important;
|
|
color: var(--theme_lgrey);
|
|
border-radius: 1em !important;
|
|
}
|
|
|
|
.ui.form .sub.field{
|
|
background-color: var(--theme_advance);
|
|
border-radius: 0.6em;
|
|
padding: 1em;
|
|
}
|
|
</style>
|
|
<div class="standardContainer">
|
|
<div class="ui stackable grid">
|
|
<div class="ten wide column">
|
|
<div class="ui basic segment" style="border-radius: 1em; padding: 1em !important;">
|
|
<h2>New Proxy Rule</h2>
|
|
<p>You can add more proxy rules to support more site via domain / subdomains</p>
|
|
<div class="ui form">
|
|
<div class="field">
|
|
<label>Matching Keyword / Domain</label>
|
|
<input type="text" id="rootname" placeholder="mydomain.com">
|
|
<small>Support subdomain and wildcard, e.g. s1.mydomain.com or *.test.mydomain.com. Use comma (,) for alias hostnames. </small>
|
|
</div>
|
|
<div class="field">
|
|
<label>Target IP Address or Domain Name with port</label>
|
|
<input type="text" id="proxyDomain" onchange="autoCheckTls(this.value);">
|
|
<small>E.g. 192.168.0.101:8000 or example.com</small>
|
|
</div>
|
|
<div class="field">
|
|
<div class="ui checkbox">
|
|
<input type="checkbox" id="reqTls">
|
|
<label>Proxy Target require TLS Connection <br><small>(i.e. Your proxy target starts with https://)</small></label>
|
|
</div>
|
|
</div>
|
|
<!-- Advance configs -->
|
|
<div class="ui basic segment" style="background-color: #f7f7f7; border-radius: 1em;">
|
|
<div id="advanceProxyRules" class="ui fluid accordion">
|
|
<div class="title">
|
|
<i class="dropdown icon"></i>
|
|
Advance Settings
|
|
</div>
|
|
<div class="content">
|
|
<div class="field">
|
|
<label>Access Rule</label>
|
|
<div class="ui selection dropdown">
|
|
<input type="hidden" id="newProxyRuleAccessFilter" value="default">
|
|
<i class="dropdown icon"></i>
|
|
<div class="default text">Default</div>
|
|
<div class="menu" id="newProxyRuleAccessList">
|
|
<div class="item" data-value="default"><i class="ui yellow star icon"></i> Default</div>
|
|
</div>
|
|
</div>
|
|
<small>Allow regional access control using blacklist or whitelist. Use "default" for "allow all".</small>
|
|
</div>
|
|
<div class="field">
|
|
<div class="ui checkbox">
|
|
<input type="checkbox" id="skipTLSValidation">
|
|
<label>Ignore TLS/SSL Verification Error<br><small>For targets that is using self-signed, expired certificate (Not Recommended)</small></label>
|
|
</div>
|
|
</div>
|
|
<div class="field">
|
|
<div class="ui checkbox">
|
|
<input type="checkbox" id="skipWebsocketOriginCheck" checked>
|
|
<label>Skip WebSocket Origin Check<br><small>Allow cross-origin websocket requests (Usually not a security concern)</small></label>
|
|
</div>
|
|
</div>
|
|
<div class="field">
|
|
<div class="ui checkbox">
|
|
<input type="checkbox" id="bypassGlobalTLS">
|
|
<label>Allow plain HTTP access<br><small>Allow this subdomain to be connected without TLS (Require HTTP server enabled on port 80)</small></label>
|
|
</div>
|
|
</div>
|
|
<div class="field">
|
|
<div class="ui checkbox">
|
|
<input type="checkbox" id="requireRateLimit">
|
|
<label>Require Rate Limit<br><small>This proxy endpoint will be rate limited.</small></label>
|
|
</div>
|
|
</div>
|
|
<div class="field">
|
|
<label>Rate Limit</label>
|
|
<div class="ui fluid right labeled input">
|
|
<input type="number" id="proxyRateLimit" placeholder="100" min="1" max="1000" value="100">
|
|
<div class="ui basic label">
|
|
req / sec / IP
|
|
</div>
|
|
</div>
|
|
<small>Return a 429 error code if request rate exceed the rate limit.</small>
|
|
</div>
|
|
<div class="field">
|
|
<div class="ui checkbox">
|
|
<input type="checkbox" id="requireBasicAuth">
|
|
<label>Require Basic Auth<br><small>Require client to login in order to view the page</small></label>
|
|
</div>
|
|
</div>
|
|
<div id="basicAuthCredentials" class="field">
|
|
<p>Enter the username and password for allowing them to access this proxy endpoint</p>
|
|
<table class="ui very basic celled table">
|
|
<thead>
|
|
<tr>
|
|
<th>Username</th>
|
|
<th>Password</th>
|
|
<th>Remove</th>
|
|
</tr></thead>
|
|
<tbody id="basicAuthCredentialTable">
|
|
<tr>
|
|
<td colspan="3"><i class="ui green circle check icon"></i> No Entered Credential</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
<div class="three small fields credentialEntry">
|
|
<div class="field">
|
|
<input id="basicAuthCredUsername" type="text" placeholder="Username" autocomplete="off">
|
|
</div>
|
|
<div class="field">
|
|
<input id="basicAuthCredPassword" type="password" placeholder="Password" autocomplete="off">
|
|
</div>
|
|
<div class="field">
|
|
<button class="ui basic button" onclick="addCredentials();"><i class="blue add icon"></i> Add Credential</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<br>
|
|
<button class="ui basic button" onclick="newProxyEndpoint();"><i class="green add icon"></i> Create Endpoint</button>
|
|
<br><br>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="six wide column">
|
|
<div class="ui basic segment rulesInstructions">
|
|
<span style="font-size: 1.2em; font-weight: 300;"><i class="ui yellow star icon"></i> Domain</span><br>
|
|
Example of domain matching keyword:<br>
|
|
<code>arozos.com</code> <br>Any acess requesting arozos.com will be proxy to the IP address below<br>
|
|
<div class="ui divider"></div>
|
|
<span style="font-size: 1.2em; font-weight: 300;"><i class="ui yellow star icon"></i> Subdomain</span><br>
|
|
Example of subdomain matching keyword:<br>
|
|
<code>s1.arozos.com</code> <br>Any request starting with s1.arozos.com will be proxy to the IP address below<br>
|
|
<div class="ui divider"></div>
|
|
<span style="font-size: 1.2em; font-weight: 300;"><i class="ui yellow star icon"></i> Wildcard</span><br>
|
|
Example of wildcard matching keyword:<br>
|
|
<code>*.arozos.com</code> <br>Any request with a host name matching *.arozos.com will be proxy to the IP address below. Here are some examples.<br>
|
|
<div class="ui list">
|
|
<div class="item"><code>www.arozos.com</code></div>
|
|
<div class="item"><code>foo.bar.arozos.com</code></div>
|
|
</div>
|
|
<br>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<script>
|
|
|
|
//New Proxy Endpoint
|
|
function newProxyEndpoint(){
|
|
var rootname = $("#rootname").val();
|
|
var proxyDomain = $("#proxyDomain").val();
|
|
var useTLS = $("#reqTls")[0].checked;
|
|
var skipTLSValidation = $("#skipTLSValidation")[0].checked;
|
|
var bypassGlobalTLS = $("#bypassGlobalTLS")[0].checked;
|
|
var requireBasicAuth = $("#requireBasicAuth")[0].checked;
|
|
var proxyRateLimit = $("#proxyRateLimit").val();
|
|
var requireRateLimit = $("#requireRateLimit")[0].checked;
|
|
var skipWebSocketOriginCheck = $("#skipWebsocketOriginCheck")[0].checked;
|
|
var accessRuleToUse = $("#newProxyRuleAccessFilter").val();
|
|
|
|
if (rootname.trim() == ""){
|
|
$("#rootname").parent().addClass("error");
|
|
return
|
|
}else{
|
|
$("#rootname").parent().removeClass("error");
|
|
}
|
|
|
|
if (proxyDomain.trim() == ""){
|
|
$("#proxyDomain").parent().addClass("error");
|
|
return
|
|
}else{
|
|
$("#proxyDomain").parent().removeClass("error");
|
|
}
|
|
|
|
//Create the endpoint by calling add
|
|
$.ajax({
|
|
url: "/api/proxy/add",
|
|
data: {
|
|
type: "host",
|
|
rootname: rootname,
|
|
tls: useTLS,
|
|
ep: proxyDomain,
|
|
tlsval: skipTLSValidation,
|
|
bpwsorg: skipWebSocketOriginCheck,
|
|
bypassGlobalTLS: bypassGlobalTLS,
|
|
bauth: requireBasicAuth,
|
|
rate: requireRateLimit,
|
|
ratenum: proxyRateLimit,
|
|
cred: JSON.stringify(credentials),
|
|
access: accessRuleToUse,
|
|
},
|
|
success: function(data){
|
|
if (data.error != undefined){
|
|
msgbox(data.error, false, 5000);
|
|
}else{
|
|
//Clear old data
|
|
$("#rootname").val("");
|
|
$("#proxyDomain").val("");
|
|
credentials = [];
|
|
updateTable();
|
|
reloadUptimeList();
|
|
//Check if it is a new subdomain and TLS enabled
|
|
if ($("#tls").checkbox("is checked")){
|
|
confirmBox("Request new SSL Cert for this subdomain?", function(choice){
|
|
if (choice == true){
|
|
//Load the prefer CA from TLS page
|
|
let defaultCA = $("#defaultCA").dropdown("get value");
|
|
if (defaultCA.trim() == ""){
|
|
defaultCA = "Let's Encrypt";
|
|
}
|
|
//Get a new cert using ACME
|
|
msgbox("Requesting certificate via " + defaultCA +"...");
|
|
console.log("Trying to get a new certificate via ACME");
|
|
|
|
//Request ACME for certificate, see cert.html component
|
|
obtainCertificate(rootname, defaultCA.trim(), function(){
|
|
// Renew the parent certificate list
|
|
initManagedDomainCertificateList();
|
|
});
|
|
}else{
|
|
msgbox("Proxy Endpoint Added");
|
|
}
|
|
});
|
|
}else{
|
|
msgbox("Proxy Endpoint Added");
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
}
|
|
|
|
//Generic functions for delete rp endpoints
|
|
function deleteEndpoint(epoint){
|
|
epoint = decodeURIComponent(epoint).hexDecode();
|
|
if (confirm("Confirm remove proxy for :" + epoint + "?")){
|
|
$.ajax({
|
|
url: "/api/proxy/del",
|
|
data: {ep: epoint, },
|
|
success: function(){
|
|
listProxyEndpoints();
|
|
msgbox("Proxy Rule Deleted", true);
|
|
reloadUptimeList();
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
|
|
function autoCheckTls(targetDomain){
|
|
$.ajax({
|
|
url: "/api/proxy/tlscheck",
|
|
data: {url: targetDomain},
|
|
success: function(data){
|
|
if (data.error != undefined){
|
|
|
|
}else if (data == "https"){
|
|
$("#reqTls").parent().checkbox("set checked");
|
|
}else if (data == "http"){
|
|
$("#reqTls").parent().checkbox("set unchecked");
|
|
}
|
|
}
|
|
})
|
|
}
|
|
|
|
|
|
function toggleBasicAuth() {
|
|
var basicAuthDiv = document.getElementById('basicAuthOnly');
|
|
if ($("#requireBasicAuth").parent().checkbox("is checked")) {
|
|
$("#basicAuthCredentials").removeClass("disabled");
|
|
} else {
|
|
$("#basicAuthCredentials").addClass("disabled");
|
|
}
|
|
}
|
|
$("#requireBasicAuth").on('change', toggleBasicAuth);
|
|
toggleBasicAuth();
|
|
|
|
function toggleRateLimit() {
|
|
if ($("#requireRateLimit").parent().checkbox("is checked")) {
|
|
$("#proxyRateLimit").parent().parent().removeClass("disabled");
|
|
} else {
|
|
$("#proxyRateLimit").parent().parent().addClass("disabled");
|
|
}
|
|
}
|
|
$("#requireRateLimit").on('change', toggleRateLimit);
|
|
toggleRateLimit();
|
|
|
|
|
|
/*
|
|
Credential Managements
|
|
|
|
*/
|
|
let credentials = []; // Global variable to store credentials
|
|
|
|
function addCredentials() {
|
|
// Retrieve the username and password input values
|
|
var username = $('#basicAuthCredUsername').val();
|
|
var password = $('#basicAuthCredPassword').val();
|
|
|
|
if(username == "" || password == ""){
|
|
msgbox("Username or password cannot be empty", false, 5000);
|
|
return;
|
|
}
|
|
|
|
// Create a new credential object
|
|
var credential = {
|
|
username: username,
|
|
password: password
|
|
};
|
|
|
|
// Add the credential to the global credentials array
|
|
credentials.push(credential);
|
|
|
|
// Clear the input fields
|
|
$('#basicAuthCredUsername').val('');
|
|
$('#basicAuthCredPassword').val('');
|
|
|
|
// Update the table body with the credentials
|
|
updateTable();
|
|
}
|
|
|
|
function updateTable() {
|
|
var tableBody = $('#basicAuthCredentialTable');
|
|
tableBody.empty();
|
|
|
|
if (credentials.length === 0) {
|
|
tableBody.append('<tr><td colspan="3"><i class="ui green circle check icon"></i> No Entered Credential</td></tr>');
|
|
} else {
|
|
for (var i = 0; i < credentials.length; i++) {
|
|
var credential = credentials[i];
|
|
var username = credential.username;
|
|
var password = credential.password.replace(/./g, '*'); // Replace each character with '*'
|
|
|
|
var row = '<tr>' +
|
|
'<td>' + username + '</td>' +
|
|
'<td>' + password + '</td>' +
|
|
'<td><button class="ui basic button" onclick="removeCredential(' + i + ');"><i class="red remove icon"></i> Remove</button></td>' +
|
|
'</tr>';
|
|
|
|
tableBody.append(row);
|
|
}
|
|
}
|
|
}
|
|
|
|
function removeCredential(index) {
|
|
// Remove the credential from the credentials array
|
|
credentials.splice(index, 1);
|
|
|
|
// Update the table body
|
|
updateTable();
|
|
}
|
|
|
|
|
|
|
|
//Update v3.0.0
|
|
//Since some proxy rules now contains wildcard characters
|
|
//all uuid are converted to hex code before use in DOM selector
|
|
|
|
String.prototype.hexEncode = function(){
|
|
var hex, i;
|
|
|
|
var result = "";
|
|
for (i=0; i<this.length; i++) {
|
|
hex = this.charCodeAt(i).toString(16);
|
|
result += ("000"+hex).slice(-4);
|
|
}
|
|
|
|
return result
|
|
}
|
|
|
|
String.prototype.hexDecode = function(){
|
|
var j;
|
|
var hexes = this.match(/.{1,4}/g) || [];
|
|
var back = "";
|
|
for(j = 0; j<hexes.length; j++) {
|
|
back += String.fromCharCode(parseInt(hexes[j], 16));
|
|
}
|
|
|
|
return back;
|
|
}
|
|
|
|
/*
|
|
Access Rule dropdown Initialization
|
|
*/
|
|
|
|
function initNewProxyRuleAccessDropdownList(callback=undefined){
|
|
$.get("/api/access/list", function(data){
|
|
if (data.error == undefined){
|
|
$("#newProxyRuleAccessList").html("");
|
|
data.forEach(function(rule){
|
|
let icon = `<i class="ui grey filter icon"></i>`;
|
|
if (rule.ID == "default"){
|
|
icon = `<i class="ui yellow star icon"></i>`;
|
|
}else if (rule.BlacklistEnabled && !rule.WhitelistEnabled){
|
|
//This is a blacklist filter
|
|
icon = `<i class="ui red filter icon"></i>`;
|
|
}else if (rule.WhitelistEnabled && !rule.BlacklistEnabled){
|
|
//This is a whitelist filter
|
|
icon = `<i class="ui green filter icon"></i>`;
|
|
}
|
|
$("#newProxyRuleAccessList").append(`<div class="item" data-value="${rule.ID}">${icon} ${rule.Name}</div>`);
|
|
});
|
|
$("#newProxyRuleAccessFilter").parent().dropdown();
|
|
if (callback != undefined){
|
|
callback();
|
|
}
|
|
}else{
|
|
msgbox("Access rule load failed: " + data.error, false);
|
|
}
|
|
})
|
|
}
|
|
initNewProxyRuleAccessDropdownList();
|
|
|
|
//Bind on tab switch events
|
|
tabSwitchEventBind["rules"] = function(){
|
|
//Update the access rule list
|
|
initNewProxyRuleAccessDropdownList();
|
|
}
|
|
|
|
|
|
$("#advanceProxyRules").accordion();
|
|
$("#newProxyRuleAccessFilter").parent().dropdown();
|
|
|
|
|
|
</script> |