<!-- 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; } .descheader{ display:none !important; } @media (min-width: 1367px) { .descheader{ display:auto !important; } } </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" tourstep="matchingkeyword"> <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" tourstep="targetdomain"> <label>Target IP Address or Domain Name with port</label> <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> </div> <div class="field" tourstep="requireTLS"> <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 advanceoptions"> <div id="advanceProxyRules" class="ui fluid accordion"> <div class="title"> <i class="dropdown icon"></i> Advance Settings </div> <div class="content"> <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"> <label>Tags</label> <input type="text" id="proxyTags" placeholder="e.g. mediaserver, management"> <small>Comma-separated list of tags for this proxy host.</small> </div> <div class="ui horizontal divider"> <i class="ui green lock icon"></i> Security </div> <div class="field" tourstep="skipTLSValidation"> <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="ui horizontal divider"> <i class="ui red ban icon"></i> Access Control </div> <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="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 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> </div> </div> <br> <div tourstep="newProxyRule" style="display: inline-block;"> <button class="ui basic button" onclick="newProxyEndpoint();"><i class="green add icon"></i> Create Endpoint</button> </div> <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>aroz.org</code> <br>Any acess requesting aroz.org 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.aroz.org</code> <br>Any request starting with s1.aroz.org 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>*.aroz.org</code> <br>Any request with a host name matching *.aroz.org will be proxy to the IP address below. Here are some examples.<br> <div class="ui list"> <div class="item"><code>www.aroz.org</code></div> <div class="item"><code>foo.bar.aroz.org</code></div> </div> <br> </div> </div> </div> </div> <script> //New Proxy Endpoint function newProxyEndpoint(){ let rootname = $("#rootname").val(); let proxyDomain = $("#proxyDomain").val(); let useTLS = $("#reqTls")[0].checked; let skipTLSValidation = $("#skipTLSValidation")[0].checked; let bypassGlobalTLS = $("#bypassGlobalTLS")[0].checked; let requireBasicAuth = $("#requireBasicAuth")[0].checked; let proxyRateLimit = $("#proxyRateLimit").val(); let requireRateLimit = $("#requireRateLimit")[0].checked; let skipWebSocketOriginCheck = $("#skipWebsocketOriginCheck")[0].checked; let accessRuleToUse = $("#newProxyRuleAccessFilter").val(); let useStickySessionLB = $("#useStickySessionLB")[0].checked; let tags = $("#proxyTags").val().trim(); 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 $.cjax({ url: "/api/proxy/add", method: "POST", 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, stickysess: useStickySessionLB, tags: tags, }, success: function(data){ if (data.error != undefined){ msgbox(data.error, false, 5000); }else{ //Clear old data $("#rootname").val(""); $("#proxyDomain").val(""); $("#proxyTags").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"); } } } }); } //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){ $.cjax({ url: "/api/proxy/tlscheck?selfsignchk=true", data: {url: targetDomain}, success: function(data){ if (data.error != undefined){ msgbox(data.error, false); }else{ //Check if the site require TLS if (data.protocol == "https"){ $("#reqTls").parent().checkbox("set checked"); }else if (data.protocol == "http"){ $("#reqTls").parent().checkbox("set unchecked"); } //Check if the site is using self-signed cert if (data.selfsign){ $("#skipTLSValidation").parent().checkbox("set checked"); }else{ $("#skipTLSValidation").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(); } /* Docker Optimizations */ function initDockerUXOptimizations(){ $.get("/api/docker/available", function(dockerAvailable){ if (dockerAvailable){ $(".dockerOptimizations").show(); }else{ $(".dockerOptimizations").hide(); } }); } initDockerUXOptimizations(); function openDockerContainersList(){ showSideWrapper('snippet/dockerContainersList.html'); } function addContainerItem(item) { $('#rootname').val(item.name); $('#proxyDomain').val(`${item.ip}:${item.port}`) hideSideWrapper(true); } /* UI Element Initialization */ 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>