Optimized docker detection structure

- Merged #202 and optimized UI elements
- Added HSTS headers toggle
- Added permission policy injector in dynamicproxy
- Fixed slow search LAN ip detection
- Optimized UI for HTTP reverse proxy rules
- Added wip permission policy and load balancer
This commit is contained in:
Toby Chui
2024-06-16 12:46:29 +08:00
parent b604c66a2f
commit dfb81513b1
13 changed files with 598 additions and 164 deletions

View File

@@ -19,7 +19,7 @@
<th>Host</th>
<th>Destination</th>
<th>Virtual Directory</th>
<th>Advanced Settings</th>
<th style="max-width: 300px;">Advanced Settings</th>
<th class="no-sort" style="min-width:150px;">Actions</th>
</tr>
</thead>
@@ -78,7 +78,7 @@
vdList += `</div>`;
if (subd.VirtualDirectories.length == 0){
vdList = `<small style="opacity: 0.3; pointer-events: none; user-select: none;"><i class="check icon"></i> No Virtual Directory</small>`;
vdList = `<small style="opacity: 0.3; pointer-events: none; user-select: none;">No Virtual Directory</small>`;
}
let enableChecked = "checked";
@@ -104,9 +104,11 @@
</td>
<td data-label="" editable="true" datatype="domain">${subd.Domain} ${tlsIcon}</td>
<td data-label="" editable="true" datatype="vdir">${vdList}</td>
<td data-label="" editable="true" datatype="advanced">
${subd.RequireBasicAuth?`<i class="ui green check icon"></i> Basic Auth`:`<i class="ui grey remove icon"></i> Basic Auth`}<br>
${subd.RequireRateLimit?`<i class="ui green check icon"></i> Rate Limit @ ${subd.RateLimit} req/s`:`<i class="ui grey remove icon"></i> Rate Limit`}
<td data-label="" editable="true" datatype="advanced" style="width: 350px;">
${subd.RequireBasicAuth?`<i class="ui green check icon"></i> Basic Auth`:``}
${subd.RequireBasicAuth && subd.RequireRateLimit?"<br>":""}
${subd.RequireRateLimit?`<i class="ui green check icon"></i> Rate Limit @ ${subd.RateLimit} req/s`:``}
${!subd.RequireBasicAuth && !subd.RequireRateLimit?`<small style="opacity: 0.3; pointer-events: none; user-select: none;">No Special Settings</small>`:""}
</td>
<td class="center aligned ignoremw" editable="true" datatype="action" data-label="">
<div class="ui toggle tiny fitted checkbox" style="margin-bottom: -0.5em; margin-right: 0.4em;" title="Enable / Disable Rule">
@@ -246,7 +248,7 @@
<div class="ui mini fluid input">
<input type="text" class="Domain" value="${domain}">
</div>
<div class="ui checkbox" style="margin-top: 0.4em;">
<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>
@@ -255,7 +257,8 @@
<input type="checkbox" class="SkipCertValidations" ${checkstate}>
<label>Skip Verification<br>
<small>Check this if proxy target is using self signed certificates</small></label>
</div>
</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);
}else if (datatype == "vdir"){
@@ -300,7 +303,6 @@
<button class="ui basic compact tiny button" style="margin-left: 0.4em; margin-top: 0.4em;" onclick="editBasicAuthCredentials('${uuid}');"><i class="ui blue user circle icon"></i> Edit Credentials</button>
<br>
<button class="ui basic compact tiny button" style="margin-left: 0.4em; margin-top: 0.4em;" onclick="editCustomHeaders('${uuid}');"><i class="heading icon"></i> Custom Headers</button>
<!-- <button class="ui basic compact tiny button" style="margin-left: 0.4em; margin-top: 0.4em;" onclick="editLoadBalanceOptions('${uuid}');"><i class="blue server icon"></i> Load Balance</button> -->
<div class="ui basic advance segment" style="padding: 0.4em !important; border-radius: 0.4em;">
<div class="ui endpointAdvanceConfig accordion" style="padding-right: 0.6em;">
@@ -317,7 +319,8 @@
<br>
<div class="ui checkbox" style="margin-top: 0.4em;">
<input type="checkbox" onchange="handleToggleRateLimitInput();" class="RequireRateLimit" ${rateLimitCheckState}>
<label>Require Rate Limit</label>
<label>Require Rate Limit<br>
<small>Check this to enable rate limit on this inbound hostname</small></label>
</div><br>
<div class="ui mini right labeled fluid input ${rateLimitDisableState}" style="margin-top: 0.4em;">
<input type="number" class="RateLimit" value="${rateLimit}" min="1" >
@@ -462,6 +465,7 @@
$("#vdirBaseRoutingRule").parent().dropdown("set selected", uuid);
}
//Open the custom header editor
function editCustomHeaders(uuid){
let payload = encodeURIComponent(JSON.stringify({
ept: "host",
@@ -470,6 +474,15 @@
showSideWrapper("snippet/customHeaders.html?t=" + Date.now() + "#" + payload);
}
//Open the load balance option
function editLoadBalanceOptions(uuid){
let payload = encodeURIComponent(JSON.stringify({
ept: "host",
ep: uuid
}));
showSideWrapper("snippet/loadBalancer.html?t=" + Date.now() + "#" + payload);
}
function handleProxyRuleToggle(object){
let endpointUUID = $(object).attr("eptuuid");
let isChecked = object.checked;

View File

@@ -26,12 +26,14 @@
</div>
<div class="field">
<label>Target IP Address or Domain Name with port</label>
<div class="ui action input">
<input type="text" id="proxyDomain" onchange="autoCheckTls(this.value);">
<button class="ui icon button" onclick="openDockerContainersList();"><i class="blue docker icon"></i></button>
</div>
<small>E.g. 192.168.0.101:8000 or example.com</small>
</div>
<div class="field" class="dockerOptimizations" style="display:none;">
<button class="ui basic button" onclick="openDockerContainersList();"><i class="blue docker icon"></i> Pick from Docker Containers</button>
</div>
<div class="field">
<div class="ui checkbox">
<input type="checkbox" id="reqTls">
@@ -430,6 +432,15 @@
initNewProxyRuleAccessDropdownList();
}
/* Docker Optimizations */
$("/api/docker/available", function(dockerAvailable){
if (dockerAvailable){
$(".dockerOptimizations").show();
}else{
$(".dockerOptimizations").hide();
}
})
function openDockerContainersList(){
showSideWrapper('snippet/dockerContainersList.html');
}
@@ -439,9 +450,9 @@
$('#proxyDomain').val(`${item.ip}:${item.port}`)
hideSideWrapper(true);
}
/* UI Element Initialization */
$("#advanceProxyRules").accordion();
$("#newProxyRuleAccessFilter").parent().dropdown();
</script>

View File

@@ -5,6 +5,18 @@
<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>
.ui.tabular.menu .item.narrowpadding{
padding: 0.6em !important;
margin: 0.15em !important;
}
#permissionPolicyEditor.disabled{
opacity: 0.4;
pointer-events: none;
user-select: none;
}
</style>
</head>
<body>
<br>
@@ -16,52 +28,95 @@
</div>
</div>
<div class="ui divider"></div>
<p>You can define custom headers to be sent
together with the client request to the backend server in
this reverse proxy endpoint / host.</p>
<table class="ui very basic compacted unstackable celled table">
<thead>
<tr>
<th>Key</th>
<th>Value</th>
<th>Remove</th>
</tr></thead>
<tbody id="headerTable">
<tr>
<td colspan="3"><i class="ui green circle check icon"></i> No Additonal Header</td>
</tr>
</tbody>
</table>
<div class="ui divider"></div>
<h4>Edit Custom Header</h4>
<p>Add or remove custom header(s) over this proxy target</p>
<div class="scrolling content ui form">
<div class="five small fields credentialEntry">
<div class="field" align="center">
<button id="toOriginButton" title="Downstream to Upstream" class="ui circular basic active button">Zoraxy <i class="angle double right blue icon" style="margin-right: 0.4em;"></i> Origin</button>
<button id="toClientButton" title="Upstream to Downstream" class="ui circular basic button">Client <i class="angle double left orange icon" style="margin-left: 0.4em;"></i> Zoraxy</button>
<div class="ui small pointing secondary menu">
<a class="item active narrowpadding" data-tab="customheaders">Custom Headers</a>
<a class="item narrowpadding" data-tab="security">Security Headers</a>
</div>
<div class="ui tab basic segment active" data-tab="customheaders">
<table class="ui very basic compacted unstackable celled table">
<thead>
<tr>
<th>Key</th>
<th>Value</th>
<th>Remove</th>
</tr></thead>
<tbody id="headerTable">
<tr>
<td colspan="3"><i class="ui green circle check icon"></i> No Additonal Header</td>
</tr>
</tbody>
</table>
<p>
<i class="angle double right blue icon"></i> Sent additional custom headers to origin server <br>
<i class="angle double left orange icon"></i> Inject custom headers into origin server responses
</p>
<div class="ui divider"></div>
<h4>Edit Custom Header</h4>
<p>Add or remove custom header(s) over this proxy target</p>
<div class="scrolling content ui form">
<div class="five small fields credentialEntry">
<div class="field" align="center">
<button id="toOriginButton" style="margin-top: 0.6em;" title="Downstream to Upstream" class="ui circular basic active button">Zoraxy <i class="angle double right blue icon" style="margin-right: 0.4em;"></i> Origin</button>
<button id="toClientButton" style="margin-top: 0.6em;" title="Upstream to Downstream" class="ui circular basic button">Client <i class="angle double left orange icon" style="margin-left: 0.4em;"></i> Zoraxy</button>
</div>
<div class="field" align="center">
<button id="headerModeAdd" style="margin-top: 0.6em;" class="ui circular basic active button"><i class="ui green circle add icon"></i> Add Header</button>
<button id="headerModeRemove" style="margin-top: 0.6em;" class="ui circular basic button"><i class="ui red circle times icon"></i> Remove Header</button>
</div>
<div class="field">
<label>Header Key</label>
<input id="headerName" type="text" placeholder="X-Custom-Header" autocomplete="off">
<small>The header key is <b>NOT</b> case sensitive</small>
</div>
<div class="field">
<label>Header Value</label>
<input id="headerValue" type="text" placeholder="value1,value2,value3" autocomplete="off">
</div>
<div class="field" >
<button class="ui basic button" onclick="addCustomHeader();"><i class="green add icon"></i> Add Header Rewrite Rule</button>
</div>
<div class="ui divider"></div>
</div>
<div class="field" align="center">
<button id="headerModeAdd" class="ui circular basic active button"><i class="ui green circle add icon"></i> Add Header</button>
<button id="headerModeRemove" class="ui circular basic button"><i class="ui red circle times icon"></i> Remove Header</button>
</div>
<div class="field">
<label>Header Key</label>
<input id="headerName" type="text" placeholder="X-Custom-Header" autocomplete="off">
<small>The header key is <b>NOT</b> case sensitive</small>
</div>
<div class="field">
<label>Header Value</label>
<input id="headerValue" type="text" placeholder="value1,value2,value3" autocomplete="off">
</div>
<div class="field" >
<button class="ui basic button" onclick="addCustomHeader();"><i class="green add icon"></i> Add Header Rewrite Rule</button>
</div>
<div class="ui divider"></div>
</div>
</div>
<div class="ui divider"></div>
<div class="ui tab basic segment" data-tab="security">
<h4>HTTP Strict Transport Security</h4>
<p>Force future attempts to access this site to only use HTTPS</p>
<div class="ui toggle checkbox">
<input type="checkbox" id="enableHSTS" name="enableHSTS">
<label>Enable HSTS<br>
<small>HSTS header will be automatically ignored if the site is accessed using HTTP</small></label>
</div>
<div class="ui divider"></div>
<h4>Permission Policy</h4>
<p>Explicitly declare what functionality can and cannot be used on this website. </p>
<div class="ui toggle checkbox" style="margin-top: 0.6em;">
<input type="checkbox" id="enablePP" name="enablePP">
<label>Enable Permission Policy<br>
<small>Enable Permission-Policy header with all allowed state.</small></label>
</div>
<div style="margin-top: 1em;" id="permissionPolicyEditor">
<table class="ui celled unstackable very compact table">
<thead>
<tr><th>Feature</th>
<th>Enabled</th>
<th>Allow All (*)</th>
<th>Self Only (self)</th>
</tr></thead>
<tbody id="permissionPolicyEditTable">
<tr>
<td>James</td>
<td>24</td>
<td>Engineer</td>
<td>Engineer</td>
</tr>
</tbody>
</table>
</div>
<br>
<button class="ui basic button"><i class="green save icon"></i> Save</button>
</div>
<div class="field" >
<button class="ui basic button" style="float: right;" onclick="closeThisWrapper();">Close</button>
</div>
@@ -70,6 +125,8 @@
<br><br><br><br>
<script>
$('.menu .item').tab();
let editingEndpoint = {};
if (window.location.hash.length > 1){
let payloadHash = window.location.hash.substr(1);
@@ -239,6 +296,99 @@
});
}
listCustomHeaders();
//Start HSTS state
function initHSTSState(){
$.get("/api/proxy/header/handleHSTS?domain=" + editingEndpoint.ep, function(data){
if (data == 0){
//HSTS disabled
$("#enableHSTS").parent().checkbox("set unchecked");
}else{
//HSTS enabled
$("#enableHSTS").parent().checkbox("set checked");
}
/* Bind events to toggles */
$("#enableHSTS").on("change", function(){
let HSTSEnabled = $("#enableHSTS")[0].checked;
$.ajax({
url: "/api/proxy/header/handleHSTS",
method: "POST",
data: {
"domain": editingEndpoint.ep,
"maxage": 31536000
},
success: function(data){
if (data.error != undefined){
parent.msgbox(data.error, false);
}else{
parent.msgbox(`HSTS ${HSTSEnabled?"Enabled":"Disabled"}`);
}
}
})
});
});
}
initHSTSState();
/* List permission policy header from server */
function initPermissionPolicy(){
$.get("/api/proxy/header/handlePermissionPolicy?domain=" + editingEndpoint.ep, function(data){
if (data.error != undefined){
console.log(data.error);
$("#enablePP").parent().addClass('disabled');
return;
}
//Set checkbox state
if (data.PPEnabled){
$("#enablePP").parent().checkbox("set checked");
$("#permissionPolicyEditor").removeClass("disabled");
}else{
$("#enablePP").parent().checkbox("set unchecked");
$("#permissionPolicyEditor").addClass("disabled");
}
//Render the table to list
$("#permissionPolicyEditTable").html("");
for (const [key, value] of Object.entries(data.CurrentPolicy)) {
let allowall = "";
let allowself = "";
let enabled = "checked";
if (value.length == 1 && value[0] == "*"){
allowall = "checked";
}else if (value.length == 1 && value[0] == "self"){
allowself = "checked";
}
if (value.length == 0){
enabled = ""
}
$("#permissionPolicyEditTable").append(`<tr>
<td>${key}</td>
<td>
<div class="ui checkbox">
<input class="enabled" type="checkbox" name="${key}" ${enabled}>
<label></label>
</div>
</td>
<td>
<div class="ui radio checkbox">
<input type="radio" value="all" name="${key}-target" ${allowall}>
<label></label>
</div>
</td>
<td>
<div class="ui radio checkbox">
<input type="radio" value="self" name="${key}-target" ${allowself}>
<label></label>
</div>
</td>
</tr>`);
}
});
}
initPermissionPolicy();
</script>
</body>
</html>

View File

@@ -0,0 +1,49 @@
<!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>