mirror of
https://github.com/tobychui/zoraxy.git
synced 2025-08-11 23:55:24 +02:00
Added load balancer (wip)
+ Added support for multiple upstreams + Added load balancer + Added upstream abstraction in endpoint + Added load balancer structure + Added breaking change auto-updater + Added uptime monitor proxy type definitions + Added upstream editor UI + Fixed charset bug in many snippets HTML files
This commit is contained in:
@@ -51,13 +51,29 @@
|
||||
//Sort by RootOrMatchingDomain field
|
||||
data.sort((a,b) => (a.RootOrMatchingDomain > b.RootOrMatchingDomain) ? 1 : ((b.RootOrMatchingDomain > a.RootOrMatchingDomain) ? -1 : 0))
|
||||
data.forEach(subd => {
|
||||
let tlsIcon = "";
|
||||
let subdData = encodeURIComponent(JSON.stringify(subd));
|
||||
if (subd.RequireTLS){
|
||||
tlsIcon = `<i class="green lock icon" title="TLS Mode"></i>`;
|
||||
if (subd.SkipCertValidations){
|
||||
tlsIcon = `<i class="yellow lock icon" title="TLS/SSL mode without verification"></i>`
|
||||
}
|
||||
|
||||
//Build the upstream list
|
||||
let upstreams = "";
|
||||
if (subd.ActiveOrigins.length == 0){
|
||||
//Invalid config
|
||||
upstreams = `<i class="ui yellow exclamation triangle icon"></i> No Active Upstream Origin<br>`;
|
||||
}else{
|
||||
subd.ActiveOrigins.forEach(upstream => {
|
||||
console.log(upstream);
|
||||
//Check if the upstreams require TLS connections
|
||||
let tlsIcon = "";
|
||||
if (upstream.RequireTLS){
|
||||
tlsIcon = `<i class="green lock icon" title="TLS Mode"></i>`;
|
||||
if (upstream.SkipCertValidations){
|
||||
tlsIcon = `<i class="yellow lock icon" title="TLS/SSL mode without verification"></i>`
|
||||
}
|
||||
}
|
||||
|
||||
let upstreamLink = `${upstream.RequireTLS?"https://":"http://"}${upstream.OriginIpOrDomain}`;
|
||||
|
||||
upstreams += `<a href="${upstreamLink}" target="_blank">${upstream.OriginIpOrDomain} ${tlsIcon}</a><br>`;
|
||||
})
|
||||
}
|
||||
|
||||
let inboundTlsIcon = "";
|
||||
@@ -102,7 +118,11 @@
|
||||
${aliasDomains}
|
||||
<small class="accessRuleNameUnderHost" ruleid="${subd.AccessFilterUUID}"></small>
|
||||
</td>
|
||||
<td data-label="" editable="true" datatype="domain">${subd.Domain} ${tlsIcon}</td>
|
||||
<td data-label="" editable="true" datatype="domain">
|
||||
<div class="upstreamList">
|
||||
${upstreams}
|
||||
</div>
|
||||
</td>
|
||||
<td data-label="" editable="true" datatype="vdir">${vdList}</td>
|
||||
<td data-label="" editable="true" datatype="advanced" style="width: 350px;">
|
||||
${subd.RequireBasicAuth?`<i class="ui green check icon"></i> Basic Auth`:``}
|
||||
@@ -228,39 +248,21 @@
|
||||
var input;
|
||||
var datatype = $(this).attr("datatype");
|
||||
if (datatype == "domain"){
|
||||
let domain = payload.Domain;
|
||||
//Target require TLS for proxying
|
||||
let tls = payload.RequireTLS;
|
||||
if (tls){
|
||||
tls = "checked";
|
||||
}else{
|
||||
tls = "";
|
||||
let useStickySessionChecked = "";
|
||||
if (payload.UseStickySession){
|
||||
useStickySessionChecked = "checked";
|
||||
}
|
||||
input = `<button class="ui basic compact tiny button" style="margin-left: 0.4em; margin-top: 1em;" onclick="editUpstreams('${uuid}');"><i class="grey server icon"></i> Edit Upstreams</button>
|
||||
<div class="ui divider"></div>
|
||||
<div class="ui checkbox" style="margin-top: 0.4em;">
|
||||
<input type="checkbox" class="UseStickySession" ${useStickySessionChecked}>
|
||||
<label>Use Sticky Session<br>
|
||||
<small>Enable stick session on load balancing</small></label>
|
||||
</div>
|
||||
|
||||
//Require TLS validation
|
||||
let skipTLSValidation = payload.SkipCertValidations;
|
||||
let checkstate = "";
|
||||
if (skipTLSValidation){
|
||||
checkstate = "checked";
|
||||
}
|
||||
|
||||
input = `
|
||||
<div class="ui mini fluid input">
|
||||
<input type="text" class="Domain" onchange="cleanProxyTargetValue(this)" value="${domain}">
|
||||
</div>
|
||||
<div class="ui checkbox" style="margin-top: 0.6em;">
|
||||
<input type="checkbox" class="RequireTLS" ${tls}>
|
||||
<label>Require TLS<br>
|
||||
<small>Proxy target require HTTPS connection</small></label>
|
||||
</div><br>
|
||||
<div class="ui checkbox" style="margin-top: 0.4em;">
|
||||
<input type="checkbox" class="SkipCertValidations" ${checkstate}>
|
||||
<label>Skip Verification<br>
|
||||
<small>Check this if proxy target is using self signed certificates</small></label>
|
||||
</div><br>
|
||||
<!-- <button class="ui basic compact tiny button" style="margin-left: 0.4em; margin-top: 0.4em;" onclick="editLoadBalanceOptions('${uuid}');"><i class="purple server icon"></i> Load Balance</button> -->
|
||||
`;
|
||||
column.empty().append(input);
|
||||
column.append(input);
|
||||
$(column).find(".upstreamList").addClass("editing");
|
||||
}else if (datatype == "vdir"){
|
||||
//Append a quick access button for vdir page
|
||||
column.append(`<button class="ui basic tiny button" style="margin-left: 0.4em; margin-top: 0.4em;" onclick="quickEditVdir('${uuid}');">
|
||||
@@ -311,12 +313,6 @@
|
||||
Security Options
|
||||
</div>
|
||||
<div class="content">
|
||||
<div class="ui checkbox" style="margin-top: 0.4em;">
|
||||
<input type="checkbox" class="SkipWebSocketOriginCheck" ${wsCheckstate}>
|
||||
<label>Skip WebSocket Origin Check<br>
|
||||
<small>Check this to allow cross-origin websocket requests</small></label>
|
||||
</div>
|
||||
<br>
|
||||
<div class="ui checkbox" style="margin-top: 0.4em;">
|
||||
<input type="checkbox" onchange="handleToggleRateLimitInput();" class="RequireRateLimit" ${rateLimitCheckState}>
|
||||
<label>Require Rate Limit<br>
|
||||
@@ -399,15 +395,11 @@
|
||||
}
|
||||
|
||||
var epttype = "host";
|
||||
let newDomain = $(row).find(".Domain").val();
|
||||
let requireTLS = $(row).find(".RequireTLS")[0].checked;
|
||||
let skipCertValidations = $(row).find(".SkipCertValidations")[0].checked;
|
||||
let useStickySession = $(row).find(".UseStickySession")[0].checked;
|
||||
let requireBasicAuth = $(row).find(".RequireBasicAuth")[0].checked;
|
||||
let requireRateLimit = $(row).find(".RequireRateLimit")[0].checked;
|
||||
let rateLimit = $(row).find(".RateLimit").val();
|
||||
let bypassGlobalTLS = $(row).find(".BypassGlobalTLS")[0].checked;
|
||||
let bypassWebsocketOrigin = $(row).find(".SkipWebSocketOriginCheck")[0].checked;
|
||||
console.log(newDomain, requireTLS, skipCertValidations, requireBasicAuth)
|
||||
|
||||
$.ajax({
|
||||
url: "/api/proxy/edit",
|
||||
@@ -415,11 +407,8 @@
|
||||
data: {
|
||||
"type": epttype,
|
||||
"rootname": uuid,
|
||||
"ep":newDomain,
|
||||
"ss":useStickySession,
|
||||
"bpgtls": bypassGlobalTLS,
|
||||
"tls" :requireTLS,
|
||||
"tlsval": skipCertValidations,
|
||||
"bpwsorg" : bypassWebsocketOrigin,
|
||||
"bauth" :requireBasicAuth,
|
||||
"rate" :requireRateLimit,
|
||||
"ratenum" :rateLimit,
|
||||
@@ -434,21 +423,6 @@
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
//Clearn the proxy target value, make sure user do not enter http:// or https://
|
||||
//and auto select TLS checkbox if https:// exists
|
||||
function cleanProxyTargetValue(input){
|
||||
let targetDomain = $(input).val().trim();
|
||||
if (targetDomain.startsWith("http://")){
|
||||
targetDomain = targetDomain.substr(7);
|
||||
$(input).val(targetDomain);
|
||||
$("#httpProxyList input.RequireTLS").parent().checkbox("set unchecked");
|
||||
}else if (targetDomain.startsWith("https://")){
|
||||
targetDomain = targetDomain.substr(8);
|
||||
$(input).val(targetDomain);
|
||||
$("#httpProxyList input.RequireTLS").parent().checkbox("set checked");
|
||||
}
|
||||
}
|
||||
|
||||
/* button events */
|
||||
function editBasicAuthCredentials(uuid){
|
||||
@@ -490,12 +464,12 @@
|
||||
}
|
||||
|
||||
//Open the load balance option
|
||||
function editLoadBalanceOptions(uuid){
|
||||
function editUpstreams(uuid){
|
||||
let payload = encodeURIComponent(JSON.stringify({
|
||||
ept: "host",
|
||||
ep: uuid
|
||||
}));
|
||||
showSideWrapper("snippet/loadBalancer.html?t=" + Date.now() + "#" + payload);
|
||||
showSideWrapper("snippet/upstreams.html?t=" + Date.now() + "#" + payload);
|
||||
}
|
||||
|
||||
function handleProxyRuleToggle(object){
|
||||
|
@@ -122,7 +122,7 @@
|
||||
function initRootInfo(callback=undefined){
|
||||
$.get("/api/proxy/list?type=root", function(data){
|
||||
if (data == null){
|
||||
|
||||
msgbox("Default site load failed", false);
|
||||
}else{
|
||||
var $radios = $('input:radio[name=defaultsiteOption]');
|
||||
let proxyType = data.DefaultSiteOption;
|
||||
@@ -140,8 +140,8 @@
|
||||
}
|
||||
updateAvaibleDefaultSiteOptions();
|
||||
|
||||
$("#proxyRoot").val(data.Domain);
|
||||
checkRootRequireTLS(data.Domain);
|
||||
$("#proxyRoot").val(data.ActiveOrigins[0].OriginIpOrDomain);
|
||||
checkRootRequireTLS(data.ActiveOrigins[0].OriginIpOrDomain);
|
||||
}
|
||||
|
||||
if (callback != undefined){
|
||||
@@ -247,7 +247,9 @@
|
||||
msgbox(data.error, false, 5000);
|
||||
}else{
|
||||
//OK
|
||||
|
||||
initRootInfo(function(){
|
||||
|
||||
//Check if WebServ is enabled
|
||||
isUsingStaticWebServerAsRoot(function(isUsingWebServ){
|
||||
if (isUsingWebServ){
|
||||
@@ -256,11 +258,7 @@
|
||||
setWebServerRunningState(true);
|
||||
}
|
||||
|
||||
setTimeout(function(){
|
||||
//Update the checkbox
|
||||
msgbox("Default Site Updated");
|
||||
}, 100);
|
||||
|
||||
msgbox("Default Site Updated");
|
||||
})
|
||||
});
|
||||
|
||||
@@ -269,6 +267,9 @@
|
||||
if (btn != undefined){
|
||||
$(btn).removeClass("disabled");
|
||||
}
|
||||
},
|
||||
error: function(){
|
||||
msgbox("Unknown error occured", false);
|
||||
}
|
||||
});
|
||||
|
||||
|
@@ -26,8 +26,8 @@
|
||||
</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>
|
||||
<input type="text" id="proxyDomain" onchange="autoFillTargetTLS(this);">
|
||||
<small>e.g. 192.168.0.101:8000 or example.com</small>
|
||||
</div>
|
||||
<div class="field dockerOptimizations" style="display:none;">
|
||||
<button style="margin-top: -2em;" class="ui basic small button" onclick="openDockerContainersList();"><i class="blue docker icon"></i> Pick from Docker Containers</button>
|
||||
@@ -76,6 +76,12 @@
|
||||
<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="useStickySessionLB">
|
||||
<label>Sticky Session<br><small>Enable stick session on upstream load balancing</small></label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<div class="ui checkbox">
|
||||
<input type="checkbox" id="requireRateLimit">
|
||||
@@ -259,7 +265,26 @@
|
||||
}
|
||||
}
|
||||
|
||||
//Clearn the proxy target value, make sure user do not enter http:// or https://
|
||||
//and auto select TLS checkbox if https:// exists
|
||||
function autoFillTargetTLS(input){
|
||||
let targetDomain = $(input).val().trim();
|
||||
if (targetDomain.startsWith("http://")){
|
||||
targetDomain = targetDomain.substr(7);
|
||||
$(input).val(targetDomain);
|
||||
$("#reqTls").parent().checkbox("set unchecked");
|
||||
}else if (targetDomain.startsWith("https://")){
|
||||
targetDomain = targetDomain.substr(8);
|
||||
$(input).val(targetDomain);
|
||||
$("#reqTls").parent().checkbox("set checked");
|
||||
}else{
|
||||
//No http or https was given. Sniff it
|
||||
autoCheckTls(targetDomain);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//Automatic check if the site require TLS and check the checkbox if needed
|
||||
function autoCheckTls(targetDomain){
|
||||
$.ajax({
|
||||
url: "/api/proxy/tlscheck",
|
||||
@@ -453,7 +478,25 @@
|
||||
}
|
||||
|
||||
/* UI Element Initialization */
|
||||
$("#advanceProxyRules").accordion();
|
||||
$("#newProxyRuleAccessFilter").parent().dropdown();
|
||||
function initAdvanceSettingsAccordion(){
|
||||
function hasClickEvent(element) {
|
||||
var events = $._data(element, "events");
|
||||
return events && events.click && events.click.length > 0;
|
||||
}
|
||||
|
||||
if (!hasClickEvent($("#advanceProxyRules"))){
|
||||
// Not sure why sometime the accordion events are not binding
|
||||
// to the DOM element. This makes sure the element is binded
|
||||
// correctly by checking it again after 300ms
|
||||
$("#advanceProxyRules").accordion();
|
||||
$("#newProxyRuleAccessFilter").parent().dropdown();
|
||||
setTimeout(function(){
|
||||
initAdvanceSettingsAccordion();
|
||||
}, 300);
|
||||
}
|
||||
}
|
||||
initAdvanceSettingsAccordion();
|
||||
|
||||
|
||||
|
||||
</script>
|
@@ -9,7 +9,6 @@
|
||||
<title>Control Panel | Zoraxy</title>
|
||||
<link rel="stylesheet" href="script/semantic/semantic.min.css">
|
||||
<script src="script/jquery-3.6.0.min.js"></script>
|
||||
<script src="../script/ao_module.js"></script>
|
||||
<script src="script/semantic/semantic.min.js"></script>
|
||||
<script src="script/tablesort.js"></script>
|
||||
<script src="script/countryCode.js"></script>
|
||||
|
@@ -2,6 +2,7 @@
|
||||
<html>
|
||||
<head>
|
||||
<!-- Notes: This should be open in its original path-->
|
||||
<meta charset="utf-8">
|
||||
<link rel="stylesheet" href="../script/semantic/semantic.min.css">
|
||||
<script src="../script/jquery-3.6.0.min.js"></script>
|
||||
<script src="../script/semantic/semantic.min.js"></script>
|
||||
|
@@ -2,6 +2,7 @@
|
||||
<html>
|
||||
<head>
|
||||
<!-- Notes: This should be open in its original path-->
|
||||
<meta charset="utf-8">
|
||||
<link rel="stylesheet" href="../script/semantic/semantic.min.css">
|
||||
<script src="../script/jquery-3.6.0.min.js"></script>
|
||||
<script src="../script/semantic/semantic.min.js"></script>
|
||||
@@ -133,11 +134,17 @@
|
||||
</div>
|
||||
<div class="field dnsChallengeOnly" style="display:none;">
|
||||
<div class="ui divider"></div>
|
||||
<p>DNS Credentials (Leave all fields empty to use previous settings)<br>
|
||||
<small><i class="yellow exclamation triangle icon"></i> Note that domain DNS credentials are stored separately. For each new subdomain, you will need to enter a new DNS credentials.</small></p>
|
||||
<p>DNS Credentials</p>
|
||||
<div id="dnsProviderAPIFields">
|
||||
|
||||
<p><i class="ui loading circle notch icon"></i> Generating WebForm</p>
|
||||
</div>
|
||||
<h4><i class="yellow exclamation triangle icon"></i> Notes & FAQ</h4>
|
||||
<div class="ui bulleted list">
|
||||
<div class="item">Domain DNS credentials are stored separately. For each new subdomain, you will need to enter a new DNS credentials.</div>
|
||||
<div class="item">For some DNS providers like CloudFlare, you do not need to fill in all fields.</div>
|
||||
<div class="item">If you are not sure what to fill in, check out the documentation from <a href="https://go-acme.github.io/lego/dns/" target="_blank">lego (DNS challenge library)</a></div>
|
||||
</div>
|
||||
|
||||
<!--
|
||||
<label>Credentials File Content</label>
|
||||
<textarea id="dnsCredentials" placeholder=""></textarea>
|
||||
@@ -740,6 +747,9 @@
|
||||
function toggleDnsChallenge(){
|
||||
if ( $("#useDnsChallenge")[0].checked){
|
||||
$(".dnsChallengeOnly").show();
|
||||
setTimeout(function(){
|
||||
$("#dnsProvider").dropdown("set text", "Cloudflare");
|
||||
}, 500);
|
||||
}else{
|
||||
$(".dnsChallengeOnly").hide();
|
||||
}
|
||||
|
@@ -2,6 +2,7 @@
|
||||
<html>
|
||||
<head>
|
||||
<!-- Notes: This should be open in its original path-->
|
||||
<meta charset="utf-8">
|
||||
<link rel="stylesheet" href="../script/semantic/semantic.min.css">
|
||||
<script src="../script/jquery-3.6.0.min.js"></script>
|
||||
<script src="../script/semantic/semantic.min.js"></script>
|
||||
|
@@ -2,6 +2,7 @@
|
||||
<html>
|
||||
<head>
|
||||
<!-- Notes: This should be open in its original path-->
|
||||
<meta charset="utf-8">
|
||||
<link rel="stylesheet" href="../script/semantic/semantic.min.css">
|
||||
<script src="../script/jquery-3.6.0.min.js"></script>
|
||||
<script src="../script/semantic/semantic.min.js"></script>
|
||||
|
@@ -2,6 +2,7 @@
|
||||
<html>
|
||||
<head>
|
||||
<!-- Notes: This should be open in its original path-->
|
||||
<meta charset="utf-8">
|
||||
<link rel="stylesheet" href="../script/semantic/semantic.min.css">
|
||||
<script src="../script/jquery-3.6.0.min.js"></script>
|
||||
<script src="../script/semantic/semantic.min.js"></script>
|
||||
|
@@ -2,6 +2,7 @@
|
||||
<html>
|
||||
<head>
|
||||
<!-- Notes: This should be open in its original path-->
|
||||
<meta charset="utf-8">
|
||||
<link rel="stylesheet" href="../script/semantic/semantic.min.css">
|
||||
<script src="../script/jquery-3.6.0.min.js"></script>
|
||||
<script src="../script/semantic/semantic.min.js"></script>
|
||||
|
@@ -2,6 +2,7 @@
|
||||
<html>
|
||||
<head>
|
||||
<!-- Notes: This should be open in its original path-->
|
||||
<meta charset="utf-8">
|
||||
<link rel="stylesheet" href="../script/semantic/semantic.min.css">
|
||||
<script src="../script/jquery-3.6.0.min.js"></script>
|
||||
<script src="../script/semantic/semantic.min.js"></script>
|
||||
|
@@ -2,6 +2,7 @@
|
||||
<html>
|
||||
<head>
|
||||
<!-- Notes: This should be open in its original path-->
|
||||
<meta charset="utf-8">
|
||||
<link rel="stylesheet" href="../script/semantic/semantic.min.css" />
|
||||
<script src="../script/jquery-3.6.0.min.js"></script>
|
||||
<script src="../script/semantic/semantic.min.js"></script>
|
||||
|
@@ -2,6 +2,7 @@
|
||||
<html>
|
||||
<head>
|
||||
<!-- Notes: This should be open in its original path-->
|
||||
<meta charset="utf-8">
|
||||
<link rel="stylesheet" href="../script/semantic/semantic.min.css">
|
||||
<script src="../script/jquery-3.6.0.min.js"></script>
|
||||
<script src="../script/semantic/semantic.min.js"></script>
|
||||
|
@@ -2,6 +2,7 @@
|
||||
<html>
|
||||
<head>
|
||||
<!-- Notes: This should be open in its original path-->
|
||||
<meta charset="utf-8">
|
||||
<link rel="stylesheet" href="../script/semantic/semantic.min.css">
|
||||
<script src="../script/jquery-3.6.0.min.js"></script>
|
||||
<script src="../script/semantic/semantic.min.js"></script>
|
||||
|
@@ -1,49 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<!-- Notes: This should be open in its original path-->
|
||||
<link rel="stylesheet" href="../script/semantic/semantic.min.css">
|
||||
<script src="../script/jquery-3.6.0.min.js"></script>
|
||||
<script src="../script/semantic/semantic.min.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<br>
|
||||
<div class="ui container">
|
||||
<div class="ui header">
|
||||
<div class="content">
|
||||
Load Balance
|
||||
<div class="sub header epname"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ui divider"></div>
|
||||
|
||||
<div class="ui divider"></div>
|
||||
<div class="field" >
|
||||
<button class="ui basic button" style="float: right;" onclick="closeThisWrapper();">Close</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<br><br><br><br>
|
||||
|
||||
</div>
|
||||
<script>
|
||||
let aliasList = [];
|
||||
let editingEndpoint = {};
|
||||
|
||||
if (window.location.hash.length > 1){
|
||||
let payloadHash = window.location.hash.substr(1);
|
||||
try{
|
||||
payloadHash = JSON.parse(decodeURIComponent(payloadHash));
|
||||
$(".epname").text(payloadHash.ep);
|
||||
editingEndpoint = payloadHash;
|
||||
}catch(ex){
|
||||
console.log("Unable to load endpoint data from hash")
|
||||
}
|
||||
}
|
||||
|
||||
function closeThisWrapper(){
|
||||
parent.hideSideWrapper(true);
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
@@ -1,6 +1,7 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<link rel="stylesheet" href="../script/semantic/semantic.min.css">
|
||||
<script src="../script/jquery-3.6.0.min.js"></script>
|
||||
<script src="../script/semantic/semantic.min.js"></script>
|
||||
|
528
src/web/snippet/upstreams.html
Normal file
528
src/web/snippet/upstreams.html
Normal file
@@ -0,0 +1,528 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<!-- Notes: This should be open in its original path-->
|
||||
<meta charset="utf-8">
|
||||
<link rel="stylesheet" href="../script/semantic/semantic.min.css">
|
||||
<script src="../script/jquery-3.6.0.min.js"></script>
|
||||
<script src="../script/semantic/semantic.min.js"></script>
|
||||
<style>
|
||||
.upstreamActions{
|
||||
position: absolute;
|
||||
top: 0.6em;
|
||||
right: 0.6em;
|
||||
}
|
||||
|
||||
.upstreamLink{
|
||||
max-width: 220px;
|
||||
display: inline-block;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.upstreamEntry .ui.toggle.checkbox input:checked ~ label::before{
|
||||
background-color: #00ca52 !important;
|
||||
}
|
||||
|
||||
#activateNewUpstream.ui.toggle.checkbox input:checked ~ label::before{
|
||||
background-color: #00ca52 !important;
|
||||
}
|
||||
|
||||
#upstreamTable{
|
||||
max-height: 480px;
|
||||
border-radius: 0.3em;
|
||||
padding: 0.3em;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.upstreamEntry.inactive{
|
||||
background-color: #f3f3f3 !important;
|
||||
}
|
||||
|
||||
.upstreamEntry{
|
||||
border-radius: 0.4em !important;
|
||||
border: 1px solid rgb(233, 233, 233) !important;
|
||||
}
|
||||
|
||||
@media (max-width: 499px) {
|
||||
.upstreamActions{
|
||||
position: relative;
|
||||
margin-top: 1em;
|
||||
margin-left: 0.4em;
|
||||
margin-bottom: 0.4em;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<br>
|
||||
<div class="ui container">
|
||||
<div class="ui header">
|
||||
<div class="content">
|
||||
Upstreams / Load Balance
|
||||
<div class="sub header epname"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ui divider"></div>
|
||||
<div class="ui small pointing secondary menu">
|
||||
<a class="item active narrowpadding" data-tab="upstreamlist">Upstreams</a>
|
||||
<a class="item narrowpadding" data-tab="newupstream">Add Upstream</a>
|
||||
</div>
|
||||
<div class="ui tab basic segment active" data-tab="upstreamlist">
|
||||
<!-- A list of current existing upstream on this reverse proxy-->
|
||||
<div id="upstreamTable">
|
||||
<div class="ui segment">
|
||||
<a><i class="ui loading spinner icon"></i> Loading</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ui message">
|
||||
<i class="ui blue info circle icon"></i> Round-robin load balancing algorithm will be used for upstreams with same weight. Set weight to 0 for fallback only.
|
||||
</div>
|
||||
</div>
|
||||
<div class="ui tab basic segment" data-tab="newupstream">
|
||||
<!-- Web Form to create a new upstream -->
|
||||
<h4 class="ui header">
|
||||
<i class="green add icon"></i>
|
||||
<div class="content">
|
||||
Add Upstream Server
|
||||
<div class="sub header">Create new load balance or fallback upstream origin</div>
|
||||
</div>
|
||||
</h4>
|
||||
<p style="margin-bottom: 0.4em;">Target IP address with port</p>
|
||||
<div class="ui fluid small input">
|
||||
<input type="text" id="originURL" onchange="cleanProxyTargetValue(this);"><br>
|
||||
</div>
|
||||
<small>E.g. 192.168.0.101:8000 or example.com</small>
|
||||
<br><br>
|
||||
<div id="activateNewUpstream" class="ui toggle checkbox" style="display:inline-block;">
|
||||
<input type="checkbox" id="activateNewUpstreamCheckbox" style="margin-top: 0.4em;" checked>
|
||||
<label>Activate<br>
|
||||
<small>Enable this upstream for load balancing</small></label>
|
||||
</div><br>
|
||||
<div class="ui checkbox" style="margin-top: 1.2em;">
|
||||
<input type="checkbox" id="requireTLS">
|
||||
<label>Require TLS<br>
|
||||
<small>Proxy target require HTTPS connection</small></label>
|
||||
</div><br>
|
||||
<div class="ui checkbox" style="margin-top: 0.6em;">
|
||||
<input type="checkbox" id="skipTlsVerification">
|
||||
<label>Skip Verification<br>
|
||||
<small>Check this if proxy target is using self signed certificates</small></label>
|
||||
</div><br>
|
||||
<div class="ui checkbox" style="margin-top: 0.4em;">
|
||||
<input type="checkbox" id="SkipWebSocketOriginCheck" checked>
|
||||
<label>Skip WebSocket Origin Check<br>
|
||||
<small>Check this to allow cross-origin websocket requests</small></label>
|
||||
</div>
|
||||
<br><br>
|
||||
<button class="ui basic button" onclick="addNewUpstream();"><i class="ui green circle add icon"></i> Create</button>
|
||||
</div>
|
||||
<div class="ui divider"></div>
|
||||
<div class="field" >
|
||||
<button class="ui basic button" style="float: right;" onclick="closeThisWrapper();">Close</button>
|
||||
</div>
|
||||
</div>
|
||||
<br><br><br><br>
|
||||
|
||||
</div>
|
||||
<script>
|
||||
let origins = [];
|
||||
let editingEndpoint = {};
|
||||
$('.menu .item').tab();
|
||||
|
||||
|
||||
function initOriginList(){
|
||||
$.ajax({
|
||||
url: "/api/proxy/upstream/list",
|
||||
method: "POST",
|
||||
data: {
|
||||
"type":"host",
|
||||
"ep": editingEndpoint.ep
|
||||
},
|
||||
success: function(data){
|
||||
if (data.error != undefined){
|
||||
//This endpoint not exists?
|
||||
alert(data.error);
|
||||
return;
|
||||
}else{
|
||||
$("#upstreamTable").html("");
|
||||
|
||||
if (data.ActiveOrigins.length == 0){
|
||||
//There is no upstream for this proxy rule
|
||||
$("#upstreamTable").append(`<tr>
|
||||
<td colspan="2"><b><i class="ui yellow exclamation triangle icon"></i> No Active Upstream Origin</b><br>
|
||||
This HTTP proxy rule will always return Error 521 when requested. To fix this, add or enable a upstream origin to this proxy endpoint.
|
||||
<div class="ui divider"></div>
|
||||
</td>
|
||||
</tr>`);
|
||||
}
|
||||
|
||||
data.ActiveOrigins.forEach(upstream => {
|
||||
renderUpstreamEntryToTable(upstream, true);
|
||||
});
|
||||
|
||||
data.InactiveOrigins.forEach(upstream => {
|
||||
renderUpstreamEntryToTable(upstream, false);
|
||||
});
|
||||
|
||||
let totalUpstreams = data.ActiveOrigins.length + data.InactiveOrigins.length;
|
||||
if (totalUpstreams == 1){
|
||||
$(".lowPriorityButton").addClass('disabled');
|
||||
}
|
||||
|
||||
if (parent && parent.document.getElementById("httpProxyList") != null){
|
||||
//Also update the parent display
|
||||
let element = $(parent.document.getElementById("httpProxyList")).find(".upstreamList.editing");
|
||||
let upstreams = "";
|
||||
data.ActiveOrigins.forEach(upstream => {
|
||||
//Check if the upstreams require TLS connections
|
||||
let tlsIcon = "";
|
||||
if (upstream.RequireTLS){
|
||||
tlsIcon = `<i class="green lock icon" title="TLS Mode"></i>`;
|
||||
if (upstream.SkipCertValidations){
|
||||
tlsIcon = `<i class="yellow lock icon" title="TLS/SSL mode without verification"></i>`
|
||||
}
|
||||
}
|
||||
|
||||
let upstreamLink = `${upstream.RequireTLS?"https://":"http://"}${upstream.OriginIpOrDomain}`;
|
||||
|
||||
upstreams += `<a href="${upstreamLink}" target="_blank">${upstream.OriginIpOrDomain} ${tlsIcon}</a><br>`;
|
||||
});
|
||||
|
||||
if (data.ActiveOrigins.length == 0){
|
||||
upstreams = `<i class="ui yellow exclamation triangle icon"></i> No Active Upstream Origin<br>`
|
||||
}
|
||||
$(element).html(upstreams);
|
||||
}
|
||||
|
||||
$(".ui.checkbox").checkbox();
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function renderUpstreamEntryToTable(upstream, isActive){
|
||||
function newUID(){return"00000000-0000-4000-8000-000000000000".replace(/0/g,function(){return(0|Math.random()*16).toString(16)})};
|
||||
let tlsIcon = "";
|
||||
if (upstream.RequireTLS){
|
||||
tlsIcon = `<i class="green lock icon" title="TLS Mode"></i>`;
|
||||
if (upstream.SkipCertValidations){
|
||||
tlsIcon = `<i class="yellow lock icon" title="TLS/SSL mode without verification"></i>`
|
||||
}
|
||||
}
|
||||
|
||||
//Priority Arrows
|
||||
let downArrowClass = "";
|
||||
if (upstream.Weight == 0 ){
|
||||
//Cannot go any lower
|
||||
downArrowClass = "disabled";
|
||||
}
|
||||
let url = `${upstream.RequireTLS?"https://":"http://"}${upstream.OriginIpOrDomain}`
|
||||
let payload = encodeURIComponent(JSON.stringify(upstream));
|
||||
let domUID = newUID();
|
||||
$("#upstreamTable").append(`<div class="ui upstreamEntry ${isActive?"":"inactive"} basic segment" data-domid="${domUID}" data-payload="${payload}" data-priority="${upstream.Priority}">
|
||||
<h4 class="ui header">
|
||||
<div class="ui toggle checkbox" style="display:inline-block;">
|
||||
<input type="checkbox" class="enableState" name="enabled" style="margin-top: 0.4em;" onchange="saveUpstreamUpdate('${domUID}');" ${isActive?"checked":""}>
|
||||
<label></label>
|
||||
</div>
|
||||
<div class="content">
|
||||
<a href="${url}" target="_blank" class="upstreamLink">${upstream.OriginIpOrDomain} ${tlsIcon}</a>
|
||||
<div class="sub header">${isActive?(upstream.Weight==0?"Fallback Only":"Active"):"Inactive"} | Weight: ${upstream.Weight}x </div>
|
||||
</div>
|
||||
</h4>
|
||||
<div class="advanceOptions" style="display:none;">
|
||||
<div class="upstreamOriginField">
|
||||
<p>New upstream origin IP address or domain</p>
|
||||
<div class="ui small fluid input" style="margin-top: -0.6em;">
|
||||
<input type="text" class="newOrigin" value="${upstream.OriginIpOrDomain}" onchange="handleAutoOriginClean('${domUID}');">
|
||||
</div>
|
||||
<small>e.g. 192.168.0.101:8000 or example.com</small>
|
||||
</div>
|
||||
<div class="ui divider"></div>
|
||||
<div class="ui checkbox">
|
||||
<input type="checkbox" class="reqTLSCheckbox" ${upstream.RequireTLS?"checked":""}>
|
||||
<label>Require TLS<br>
|
||||
<small>Proxy target require HTTPS connection</small></label>
|
||||
</div><br>
|
||||
<div class="ui checkbox" style="margin-top: 0.6em;">
|
||||
<input type="checkbox" class="skipVerificationCheckbox" ${upstream.SkipCertValidations?"checked":""}>
|
||||
<label>Skip Verification<br>
|
||||
<small>Check this if proxy target is using self signed certificates</small></label>
|
||||
</div><br>
|
||||
<div class="ui checkbox" style="margin-top: 0.4em;">
|
||||
<input type="checkbox" class="SkipWebSocketOriginCheck" ${upstream.SkipWebSocketOriginCheck?"checked":""}>
|
||||
<label>Skip WebSocket Origin Check<br>
|
||||
<small>Check this to allow cross-origin websocket requests</small></label>
|
||||
</div><br>
|
||||
</div>
|
||||
<div class="upstreamActions">
|
||||
<!-- Change Priority -->
|
||||
<button class="ui basic circular icon button highPriorityButton" title="Increase Weight" onclick="increaseUpstreamWeight('${domUID}');"><i class="ui arrow up icon"></i></button>
|
||||
<button class="ui basic circular icon button lowPriorityButton ${downArrowClass}" title="Reduce Weight" onclick="decreaseUpstreamWeight('${domUID}');"><i class="ui arrow down icon"></i></button>
|
||||
<button class="ui basic circular icon editbtn button" onclick="handleUpstreamOriginEdit('${domUID}');" title="Edit Upstream Destination"><i class="ui grey edit icon"></i></button>
|
||||
<button style="display:none;" class="ui basic circular icon trashbtn button" onclick="removeUpstream('${domUID}');" title="Remove Upstream"><i class="ui red trash icon"></i></button>
|
||||
<button style="display:none;" class="ui basic circular icon savebtn button" onclick="saveUpstreamUpdate('${domUID}');" title="Save Changes"><i class="ui green save icon"></i></button>
|
||||
<button style="display:none;" class="ui basic circular icon cancelbtn button" onclick="initOriginList();" title="Cancel"><i class="ui grey times icon"></i></button>
|
||||
</div>
|
||||
</div>`);
|
||||
}
|
||||
|
||||
/* New Upstream Origin Functions */
|
||||
|
||||
//Clearn the proxy target value, make sure user do not enter http:// or https://
|
||||
//and auto select TLS checkbox if https:// exists
|
||||
function cleanProxyTargetValue(input){
|
||||
let targetDomain = $(input).val().trim();
|
||||
if (targetDomain.startsWith("http://")){
|
||||
targetDomain = targetDomain.substr(7);
|
||||
$(input).val(targetDomain);
|
||||
$("#requireTLS").parent().checkbox("set unchecked");
|
||||
}else if (targetDomain.startsWith("https://")){
|
||||
targetDomain = targetDomain.substr(8);
|
||||
$(input).val(targetDomain);
|
||||
$("#requireTLS").parent().checkbox("set checked");
|
||||
}else{
|
||||
//URL does not contains https or http protocol tag
|
||||
//sniff header
|
||||
$.ajax({
|
||||
url: "/api/proxy/tlscheck",
|
||||
data: {url: targetDomain},
|
||||
success: function(data){
|
||||
if (data.error != undefined){
|
||||
|
||||
}else if (data == "https"){
|
||||
$("#requireTLS").parent().checkbox("set checked");
|
||||
}else if (data == "http"){
|
||||
$("#requireTLS").parent().checkbox("set unchecked");
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
//Add a new upstream to this http proxy rule
|
||||
function addNewUpstream(){
|
||||
let origin = $("#originURL").val().trim();
|
||||
let requireTLS = $("#requireTLS")[0].checked;
|
||||
let skipVerification = $("#skipTlsVerification")[0].checked;
|
||||
let skipWebSocketOriginCheck = $("#SkipWebSocketOriginCheck")[0].checked;
|
||||
let activateLoadbalancer = $("#activateNewUpstreamCheckbox")[0].checked;
|
||||
|
||||
if (origin == ""){
|
||||
parent.msgbox("Upstream origin cannot be empty", false);
|
||||
return;
|
||||
}
|
||||
|
||||
$.ajax({
|
||||
url: "/api/proxy/upstream/add",
|
||||
method: "POST",
|
||||
data:{
|
||||
"ep": editingEndpoint.ep,
|
||||
"origin": origin,
|
||||
"tls": requireTLS,
|
||||
"tlsval": skipVerification,
|
||||
"bpwsorg":skipWebSocketOriginCheck,
|
||||
"active": activateLoadbalancer,
|
||||
},
|
||||
success: function(data){
|
||||
if (data.error != undefined){
|
||||
parent.msgbox(data.error, false);
|
||||
}else{
|
||||
parent.msgbox("New upstream origin added");
|
||||
initOriginList();
|
||||
$("#originURL").val("");
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
//Get a upstream setting data from DOM element
|
||||
function getUpstreamSettingFromDOM(upstream){
|
||||
//Get the original setting from DOM payload
|
||||
let originalSettings = $(upstream).attr("data-payload");
|
||||
originalSettings = JSON.parse(decodeURIComponent(originalSettings));
|
||||
|
||||
//Get the updated settings if any
|
||||
let requireTLS = $(upstream).find(".reqTLSCheckbox")[0].checked;
|
||||
let skipTLSVerification = $(upstream).find(".skipVerificationCheckbox")[0].checked;
|
||||
let skipWebSocketOriginCheck = $(upstream).find(".SkipWebSocketOriginCheck")[0].checked;
|
||||
|
||||
//Update the original setting with new one just applied
|
||||
originalSettings.OriginIpOrDomain = $(upstream).find(".newOrigin").val();
|
||||
originalSettings.RequireTLS = requireTLS;
|
||||
originalSettings.SkipCertValidations = skipTLSVerification;
|
||||
originalSettings.SkipWebSocketOriginCheck = skipWebSocketOriginCheck;
|
||||
|
||||
//console.log(originalSettings);
|
||||
return originalSettings;
|
||||
}
|
||||
|
||||
//Handle setting change on upstream config
|
||||
function saveUpstreamUpdate(upstreamDomID){
|
||||
let targetDOM = $(`.upstreamEntry[data-domid=${upstreamDomID}]`);
|
||||
let originalSettings = $(targetDOM).attr("data-payload");
|
||||
originalSettings = JSON.parse(decodeURIComponent(originalSettings));
|
||||
let newConfig = getUpstreamSettingFromDOM(targetDOM);
|
||||
let isActive = $(targetDOM).find(".enableState")[0].checked;
|
||||
console.log(newConfig);
|
||||
$.ajax({
|
||||
url: "/api/proxy/upstream/update",
|
||||
method: "POST",
|
||||
data: {
|
||||
ep: editingEndpoint.ep,
|
||||
origin: originalSettings.OriginIpOrDomain, //Original ip or domain as key
|
||||
payload: JSON.stringify(newConfig),
|
||||
active: isActive,
|
||||
},
|
||||
success: function(data){
|
||||
if (data.error != undefined){
|
||||
parent.msgbox(data.error, false);
|
||||
|
||||
}else{
|
||||
parent.msgbox("Upstream setting updated");
|
||||
initOriginList();
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
//Edit the upstream origin of this upstream entry
|
||||
function handleUpstreamOriginEdit(upstreamDomID){
|
||||
let targetDOM = $(`.upstreamEntry[data-domid=${upstreamDomID}]`);
|
||||
let originalSettings = getUpstreamSettingsFromDomID(upstreamDomID);
|
||||
let originIP = originalSettings.OriginIpOrDomain;
|
||||
|
||||
//Change the UI to edit mode
|
||||
$(".editbtn").hide();
|
||||
$(".lowPriorityButton").hide();
|
||||
$(".highPriorityButton").hide();
|
||||
$(targetDOM).find(".trashbtn").show();
|
||||
$(targetDOM).find(".savebtn").show();
|
||||
$(targetDOM).find(".cancelbtn").show();
|
||||
$(targetDOM).find(".advanceOptions").show();
|
||||
}
|
||||
|
||||
//Check if the entered URL contains http or https
|
||||
function handleAutoOriginClean(domid){
|
||||
let targetDOM = $(`.upstreamEntry[data-domid=${domid}]`);
|
||||
let targetTLSCheckbox = $(targetDOM).find(".reqTLSCheckbox");
|
||||
let targetDomain = $(targetDOM).find(".newOrigin").val().trim();
|
||||
if (targetDomain.startsWith("http://")){
|
||||
targetDomain = targetDomain.substr(7);
|
||||
$(input).val(targetDomain);
|
||||
$(targetTLSCheckbox).parent().checkbox("set unchecked");
|
||||
}else if (targetDomain.startsWith("https://")){
|
||||
targetDomain = targetDomain.substr(8);
|
||||
$(input).val(targetDomain);
|
||||
$(targetTLSCheckbox).parent().checkbox("set checked");
|
||||
}else{
|
||||
//URL does not contains https or http protocol tag
|
||||
//sniff header
|
||||
$.ajax({
|
||||
url: "/api/proxy/tlscheck",
|
||||
data: {url: targetDomain},
|
||||
success: function(data){
|
||||
if (data.error != undefined){
|
||||
|
||||
}else if (data == "https"){
|
||||
$(targetTLSCheckbox).parent().checkbox("set checked");
|
||||
}else if (data == "http"){
|
||||
$(targetTLSCheckbox).parent().checkbox("set unchecked");
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function getUpstreamSettingsFromDomID(domid){
|
||||
let targetDOM = $(`.upstreamEntry[data-domid=${domid}]`);
|
||||
if (targetDOM.length == 0){
|
||||
return undefined;
|
||||
}
|
||||
let upstreamSettings = $(targetDOM).attr("data-payload");
|
||||
upstreamSettings = JSON.parse(decodeURIComponent(upstreamSettings));
|
||||
return upstreamSettings;
|
||||
}
|
||||
|
||||
function increaseUpstreamWeight(domid){
|
||||
let upstreamSetting = getUpstreamSettingsFromDomID(domid);
|
||||
let originIP = upstreamSetting.OriginIpOrDomain;
|
||||
let currentWeight = upstreamSetting.Weight;
|
||||
setUpstreamWeight(originIP, currentWeight+1);
|
||||
}
|
||||
|
||||
function decreaseUpstreamWeight(domid){
|
||||
let upstreamSetting = getUpstreamSettingsFromDomID(domid);
|
||||
let originIP = upstreamSetting.OriginIpOrDomain;
|
||||
let currentWeight = upstreamSetting.Weight;
|
||||
setUpstreamWeight(originIP, currentWeight-1);
|
||||
}
|
||||
|
||||
//Set a weight of a upstream
|
||||
function setUpstreamWeight(originIP, newWeight){
|
||||
$.ajax({
|
||||
url: "/api/proxy/upstream/setPriority",
|
||||
method: "POST",
|
||||
data: {
|
||||
ep: editingEndpoint.ep,
|
||||
origin: originIP,
|
||||
weight: newWeight,
|
||||
},
|
||||
success: function(data){
|
||||
if (data.error != undefined){
|
||||
parent.msgbox(data.error, false);
|
||||
}else{
|
||||
parent.msgbox("Upstream Weight Updated");
|
||||
initOriginList();
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
//Handle removal of an upstream
|
||||
function removeUpstream(domid){
|
||||
let targetDOM = $(`.upstreamEntry[data-domid=${domid}]`);
|
||||
let originalSettings = $(targetDOM).attr("data-payload");
|
||||
originalSettings = JSON.parse(decodeURIComponent(originalSettings));
|
||||
let UpstreamKey = originalSettings.OriginIpOrDomain;
|
||||
if (!confirm("Confirm removing " + UpstreamKey + " from upstream?")){
|
||||
return;
|
||||
}
|
||||
//Remove the upstream
|
||||
$.ajax({
|
||||
url: "/api/proxy/upstream/remove",
|
||||
method: "POST",
|
||||
data: {
|
||||
ep: editingEndpoint.ep,
|
||||
origin: originalSettings.OriginIpOrDomain, //Original ip or domain as key
|
||||
},
|
||||
success: function(data){
|
||||
if (data.error != undefined){
|
||||
parent.msgbox(data.error, false);
|
||||
|
||||
}else{
|
||||
parent.msgbox("Upstream deleted");
|
||||
initOriginList();
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
if (window.location.hash.length > 1){
|
||||
let payloadHash = window.location.hash.substr(1);
|
||||
try{
|
||||
payloadHash = JSON.parse(decodeURIComponent(payloadHash));
|
||||
$(".epname").text(payloadHash.ep);
|
||||
editingEndpoint = payloadHash;
|
||||
initOriginList();
|
||||
}catch(ex){
|
||||
console.log("Unable to load endpoint data from hash")
|
||||
}
|
||||
}
|
||||
|
||||
function closeThisWrapper(){
|
||||
parent.hideSideWrapper(true);
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
Reference in New Issue
Block a user