mirror of
https://github.com/tobychui/zoraxy.git
synced 2025-06-21 06:53:04 +02:00
New UI for proxy editor
This commit is contained in:
parent
0e74ff69c3
commit
1f8684481a
@ -9,7 +9,7 @@
|
||||
}
|
||||
|
||||
.subdEntry td:not(.ignoremw){
|
||||
min-width: 200px;
|
||||
min-width: 100px;
|
||||
}
|
||||
|
||||
.httpProxyListTools{
|
||||
@ -20,10 +20,86 @@
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
th.no-sort{
|
||||
cursor: default !important;
|
||||
}
|
||||
|
||||
.tag-select:hover{
|
||||
text-decoration: underline;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.inlineEditActionBtn{
|
||||
border: 0px solid transparent !important;
|
||||
box-shadow: none !important;
|
||||
background-color: transparent !important;
|
||||
}
|
||||
|
||||
body.darkTheme .ui.basic.small.icon.circular.button.inlineEditActionBtn{
|
||||
border: 0px solid transparent !important;
|
||||
}
|
||||
|
||||
/* Custom, non overlaying modal for proxy rule editing */
|
||||
#httprpEditModal{
|
||||
position: fixed;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
width: 68vw;
|
||||
height: 50vh;
|
||||
background-color: var(--theme_bg_primary);
|
||||
padding: 1.4em;
|
||||
border-radius: .6em;
|
||||
box-shadow: 0px 4px 8px rgba(0, 0, 0, 0.2), 0px 8px 16px rgba(0, 0, 0, 0.2);
|
||||
z-index: 9;
|
||||
max-width: 840px;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 1024px) {
|
||||
#httprpEditModal {
|
||||
width: 85vw;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 768px) {
|
||||
#httprpEditModal {
|
||||
width: 95vw;
|
||||
height: 80vh;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.httpProxyEditClosePC{
|
||||
display:none !important;
|
||||
}
|
||||
.httpProxyEditCloseMobile{
|
||||
display:block !important;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (min-width: 769px) {
|
||||
.httpProxyEditClosePC{
|
||||
display:block !important;
|
||||
}
|
||||
.httpProxyEditCloseMobile{
|
||||
display:none !important;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
#httprpEditDarkenLayer {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
z-index: 8;
|
||||
}
|
||||
|
||||
#httprpEditModalWrapper {
|
||||
display: none; /* Hidden by default */
|
||||
}
|
||||
</style>
|
||||
<div class="httpProxyListTools" style="margin-bottom: 1em;">
|
||||
<div id="tagFilterDropdown" class="ui floating basic dropdown labeled icon button" style="min-width: 150px;">
|
||||
@ -55,15 +131,15 @@
|
||||
</div>
|
||||
|
||||
<div style="width: 100%; overflow-x: auto; margin-bottom: 1em; min-height: 300px;">
|
||||
<table class="ui celled sortable unstackable compact table">
|
||||
<table class="ui unstackable sortable compact celled definition table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th></th>
|
||||
<th>Host</th>
|
||||
<th>Destination</th>
|
||||
<th>Virtual Directory</th>
|
||||
<th>Tags</th>
|
||||
<th style="max-width: 300px;">Advanced Settings</th>
|
||||
<th class="no-sort" style="min-width:150px;">Actions</th>
|
||||
<th class="no-sort">Tags</th>
|
||||
<th class="no-sort" style="width:50px; cursor: default !important;"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="httpProxyList">
|
||||
@ -76,7 +152,219 @@
|
||||
<br><br>
|
||||
</div>
|
||||
|
||||
<!-- Modal for editing a HTTP Proxy Rule -->
|
||||
<div id="httprpEditModalWrapper">
|
||||
<div id="httprpEditModal" editing-host="">
|
||||
<div class="ui stackable grid">
|
||||
<div class="four wide column">
|
||||
<div class="ui secondary fluid vertical menu">
|
||||
<a class="active item hrpedit_menu_item" cfgpage="downstream">
|
||||
<i class="angle double white right icon"></i> Downstream
|
||||
</a>
|
||||
<a class="item hrpedit_menu_item" cfgpage="upstream">
|
||||
<i class="angle double left icon"></i> Upstream
|
||||
</a>
|
||||
<a class="item hrpedit_menu_item" cfgpage="vdirs">
|
||||
<i class="angle folder icon"></i> Virtual Directory
|
||||
</a>
|
||||
<a class="item hrpedit_menu_item" cfgpage="ssl">
|
||||
<i class="lock icon"></i> TLS / SSL
|
||||
</a>
|
||||
<a class="item hrpedit_menu_item" cfgpage="headers">
|
||||
<i class="heading icon"></i> Headers
|
||||
</a>
|
||||
<a class="item hrpedit_menu_item" cfgpage="security">
|
||||
<i class="key icon"></i> Security
|
||||
</a>
|
||||
<a class="item hrpedit_menu_item" cfgpage="tags">
|
||||
<i class="tags icon"></i> Tags
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<button class="ui basic fluid button httpProxyEditClosePC" onclick="closeHttpRuleEditor();">Close</button>
|
||||
</div>
|
||||
<div class="twelve wide column">
|
||||
<div class="ui segment">
|
||||
<!-- Downstream -->
|
||||
<div class="rpconfig_content" rpcfg="downstream">
|
||||
<h3 class="downstream_primary_hostname">
|
||||
|
||||
</h3>
|
||||
<div class="downstream_alias_hostname">
|
||||
|
||||
</div>
|
||||
<div class="ui divider"></div>
|
||||
<div class="downstream_action_list">
|
||||
<div class="ui checkbox" style="margin-top: 0.4em;">
|
||||
<input type="checkbox" class="BypassGlobalTLS" ${subd.BypassGlobalTLS?"checked":""}>
|
||||
<label>Allow plain HTTP access<br>
|
||||
<small>Allow inbound connections without TLS/SSL</small></label>
|
||||
</div>
|
||||
<br>
|
||||
<button class="ui basic compact tiny button editAliasHostnameBtn" style="margin-left: 0.4em; margin-top: 0.4em;"><i class="blue at icon"></i> Alias Hostnames</button>
|
||||
<button class="ui basic compact tiny button editAccessRuleBtn" style="margin-left: 0.4em; margin-top: 0.4em;"><i class="ui filter icon"></i> Access Rule</button>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Upstream -->
|
||||
<div class="rpconfig_content" rpcfg="upstream">
|
||||
<div class="upstream_list">
|
||||
|
||||
</div>
|
||||
<button class="ui basic compact button editUpstreamButton" style="margin-left: 0.4em; margin-top: 1em;"><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="EnableUptimeMonitor">
|
||||
<label>Monitor Uptime<br>
|
||||
<small>Enable active uptime monitor and auto disable upstreams that are offline</small></label>
|
||||
</div>
|
||||
<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;">
|
||||
<div class="title">
|
||||
<i class="dropdown icon"></i>
|
||||
Advanced Settings
|
||||
</div>
|
||||
<div class="content">
|
||||
<div class="ui checkbox" style="margin-top: 0.4em;">
|
||||
<input type="checkbox" class="UseStickySession">
|
||||
<label>Use Sticky Session<br>
|
||||
<small>Enable stick session on load balancing</small></label>
|
||||
</div>
|
||||
<br>
|
||||
<div class="ui disabled checkbox" style="margin-top: 0.4em;">
|
||||
<input type="checkbox" class="DisableChunkedTransferEncoding">
|
||||
<label>Disable Chunked Transfer Encoding<br>
|
||||
<small>Enable this option if your upstream uses a legacy HTTP server implementation</small></label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Virtual Directories-->
|
||||
<div class="rpconfig_content" rpcfg="vdirs">
|
||||
<div class="vdir_list">
|
||||
|
||||
</div>
|
||||
<div class="ui divider"></div>
|
||||
<button class="ui basic tiny button editVdirBtn" style="margin-left: 0.4em; margin-top: 0.4em;">
|
||||
<i class="ui yellow folder icon"></i> Edit Virtual Directories
|
||||
</button>
|
||||
</div>
|
||||
<div class="rpconfig_content" rpcfg="headers">
|
||||
Headers
|
||||
<button class="ui basic compact tiny button editHeaderBtn" style="margin-left: 0.4em; margin-top: 0.4em;"><i class="heading icon"></i> Custom Headers</button>
|
||||
</div>
|
||||
<!-- Security -->
|
||||
<div class="rpconfig_content" rpcfg="security">
|
||||
<div class="grouped fields authProviderPicker">
|
||||
<!-- Auth Providers -->
|
||||
<label><b>Authentication Provider</b></label>
|
||||
<div class="field">
|
||||
<div class="ui radio checkbox">
|
||||
<input type="radio" value="0" name="authProviderType">
|
||||
<label>None (Anyone can access)</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<div class="ui radio checkbox">
|
||||
<input type="radio" value="1" name="authProviderType">
|
||||
<label>Basic Auth</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<div class="ui radio checkbox">
|
||||
<input type="radio" value="2" name="authProviderType">
|
||||
<label>Forward Auth</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<div class="ui radio checkbox">
|
||||
<input type="radio" value="3" name="authProviderType">
|
||||
<label>OAuth2</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<br>
|
||||
<button class="ui basic compact tiny button editBasicAuthCredentialsBtn" style="margin-left: 0.4em; margin-top: 0.4em;"><i class="ui blue user circle icon"></i> Basic Auth Credentials</button>
|
||||
|
||||
<div class="ui divider"></div>
|
||||
<!-- Rate Limits-->
|
||||
<div class="ui checkbox" style="margin-top: 0.4em;">
|
||||
<input type="checkbox" onchange="handleToggleRateLimitInput();" class="RequireRateLimit" ${rateLimitCheckState}>
|
||||
<label>Require Rate Limit<br>
|
||||
<small>Check this to enable rate limit on this inbound hostname</small></label>
|
||||
</div><br>
|
||||
<div class="ui small right labeled fluid input" style="margin-top: 0.4em;">
|
||||
<input type="number" class="RateLimit" value="0" min="1" >
|
||||
<label class="ui basic label">
|
||||
req / sec / IP
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<!-- TLS / SSL -->
|
||||
<div class="rpconfig_content" rpcfg="ssl">
|
||||
SSL
|
||||
<button class="ui basic compact small button getCertificateBtn" style="margin-left: 0.4em; margin-top: 0.4em;"><i class="green lock icon"></i> Get Certificate</button>
|
||||
</div>
|
||||
<!-- Tags -->
|
||||
<div class="rpconfig_content" rpcfg="tags">
|
||||
Tags
|
||||
<button class="ui basic compact small button editTagsBtn" style="margin-left: 0.4em; margin-top: 0.4em;"><i class="ui purple tag icon"></i> Edit tags</button>
|
||||
</div>
|
||||
</div>
|
||||
<p><em>Note: All changes will be automatically saved.</em></p>
|
||||
<br><br>
|
||||
<button class="ui basic fluid button httpProxyEditCloseMobile" onclick="closeHttpRuleEditor();">Close</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="httprpEditDarkenLayer"></div>
|
||||
</div>
|
||||
|
||||
|
||||
<script>
|
||||
var httpProxyList = []; //Proxy list of the current node
|
||||
|
||||
$('#httprpEditModal .tabular.menu .item').tab();
|
||||
|
||||
/* Renderers */
|
||||
function renderUpstreamList(subd){
|
||||
//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>`;
|
||||
})
|
||||
}
|
||||
return upstreams;
|
||||
}
|
||||
|
||||
function renderVirtualDirectoryList(subd){
|
||||
let vdList = `<div class="ui list">`;
|
||||
subd.VirtualDirectories.forEach(vdir => {
|
||||
vdList += `<div class="item">${vdir.MatchingPath} <i class="green angle double right icon"></i> ${vdir.Domain}</div>`;
|
||||
});
|
||||
vdList += `</div>`
|
||||
if (subd.VirtualDirectories.length == 0){
|
||||
vdList = `<small style="opacity: 0.3; pointer-events: none; user-select: none;">No Virtual Directory</small>`;
|
||||
}
|
||||
|
||||
return vdList;
|
||||
}
|
||||
|
||||
/* List all proxy endpoints */
|
||||
function listProxyEndpoints(){
|
||||
@ -93,31 +381,12 @@
|
||||
}else{
|
||||
//Sort by RootOrMatchingDomain field
|
||||
data.sort((a,b) => (a.RootOrMatchingDomain > b.RootOrMatchingDomain) ? 1 : ((b.RootOrMatchingDomain > a.RootOrMatchingDomain) ? -1 : 0))
|
||||
httpProxyList = data;
|
||||
data.forEach(subd => {
|
||||
let subdData = encodeURIComponent(JSON.stringify(subd));
|
||||
|
||||
//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 upstreams = renderUpstreamList(subd);
|
||||
|
||||
let inboundTlsIcon = "";
|
||||
if ($("#tls").checkbox("is checked")){
|
||||
@ -130,20 +399,16 @@
|
||||
}
|
||||
|
||||
//Build the virtual directory list
|
||||
var vdList = `<div class="ui list">`;
|
||||
subd.VirtualDirectories.forEach(vdir => {
|
||||
vdList += `<div class="item">${vdir.MatchingPath} <i class="green angle double right icon"></i> ${vdir.Domain}</div>`;
|
||||
});
|
||||
vdList += `</div>`;
|
||||
|
||||
if (subd.VirtualDirectories.length == 0){
|
||||
vdList = `<small style="opacity: 0.3; pointer-events: none; user-select: none;">No Virtual Directory</small>`;
|
||||
}
|
||||
let vdList = renderVirtualDirectoryList(subd);
|
||||
let vdIsEmpty = (subd.VirtualDirectories.length == 0);
|
||||
|
||||
//Build the Subdomain enable checkbox
|
||||
let enableChecked = "checked";
|
||||
if (subd.Disabled){
|
||||
enableChecked = "";
|
||||
}
|
||||
|
||||
//Generate a the TLS lock icons
|
||||
let httpProto = "http://";
|
||||
if ($("#tls").checkbox("is checked")) {
|
||||
httpProto = "https://";
|
||||
@ -156,6 +421,8 @@
|
||||
}else{
|
||||
hostnameRedirectPort = ":" + hostnameRedirectPort;
|
||||
}
|
||||
|
||||
//Generate alias domain list
|
||||
let aliasDomains = ``;
|
||||
if (subd.MatchingDomainAlias != undefined && subd.MatchingDomainAlias.length > 0){
|
||||
aliasDomains = `<small class="aliasDomains" eptuuid="${subd.RootOrMatchingDomain}" style="color: #636363;">Alias: `;
|
||||
@ -166,7 +433,23 @@
|
||||
aliasDomains += `</small><br>`;
|
||||
}
|
||||
|
||||
//Build tag list
|
||||
let tagList = "";
|
||||
let tagListEmpty = false;
|
||||
if (subd.Tags.length > 0){
|
||||
tagList = subd.Tags.map(tag => `<span class="ui tiny label tag-select" style="background-color: ${getTagColorByName(tag)}; color: ${getTagTextColor(tag)}">${tag}</span>`).join("");
|
||||
}else{
|
||||
tagList = "<small style='opacity: 0.3; pointer-events: none; user-select: none;'>No Tags</small>";
|
||||
tagListEmpty = true;
|
||||
}
|
||||
|
||||
$("#httpProxyList").append(`<tr eptuuid="${subd.RootOrMatchingDomain}" payload="${subdData}" class="subdEntry">
|
||||
<td class="collapsing ignoremw" datatype="enable">
|
||||
<div class="ui toggle tiny fitted checkbox" style="margin-bottom: -0.5em; margin-right: 0.4em;" title="Enable / Disable Rule">
|
||||
<input type="checkbox" class="enableToggle" name="active" ${enableChecked} eptuuid="${subd.RootOrMatchingDomain}" onchange="handleProxyRuleToggle(this);">
|
||||
<label></label>
|
||||
</div>
|
||||
</td>
|
||||
<td data-label="" editable="true" datatype="inbound">
|
||||
<a href="${httpProto}${subd.RootOrMatchingDomain}${hostnameRedirectPort}" target="_blank">${subd.RootOrMatchingDomain}</a> ${inboundTlsIcon}<br>
|
||||
${aliasDomains}
|
||||
@ -177,27 +460,23 @@
|
||||
${upstreams}
|
||||
</div>
|
||||
</td>
|
||||
<td data-label="" editable="true" datatype="vdir">${vdList}</td>
|
||||
<td data-label="tags" payload="${encodeURIComponent(JSON.stringify(subd.Tags))}" datatype="tags">
|
||||
<td data-label="" editable="true" datatype="vdir" style="${vdIsEmpty?"text-align: center;":""}">${vdList}</td>
|
||||
<td data-label="tags" payload="${encodeURIComponent(JSON.stringify(subd.Tags))}" datatype="tags" style="${tagListEmpty?"text-align: center;":""}">
|
||||
<div class="tags-list">
|
||||
${subd.Tags.length >0 ? subd.Tags.map(tag => `<span class="ui tiny label tag-select" style="background-color: ${getTagColorByName(tag)}; color: ${getTagTextColor(tag)}">${tag}</span>`).join(""):"<small style='opacity: 0.3; pointer-events: none; user-select: none;'>No Tags</small>"}
|
||||
${tagList}
|
||||
</div>
|
||||
</td>
|
||||
<td data-label="" editable="true" datatype="advanced" style="width: 350px;">
|
||||
<!-- <td data-label="" editable="true" datatype="advanced" style="width: 350px;">
|
||||
${subd.AuthenticationProvider.AuthMethod == 0x1?`<i class="ui grey key icon"></i> Basic Auth`:``}
|
||||
${subd.AuthenticationProvider.AuthMethod == 0x2?`<i class="ui blue key icon"></i> Forward Auth`:``}
|
||||
${subd.AuthenticationProvider.AuthMethod == 0x3?`<i class="ui yellow key icon"></i> OAuth2`:``}
|
||||
${subd.AuthenticationProvider.AuthMethod != 0x0 && subd.RequireRateLimit?"<br>":""}
|
||||
${subd.RequireRateLimit?`<i class="ui green check icon"></i> Rate Limit @ ${subd.RateLimit} req/s`:``}
|
||||
${subd.AuthenticationProvider.AuthMethod == 0x0 && !subd.RequireRateLimit?`<small style="opacity: 0.3; pointer-events: none; user-select: none;">No Special Settings</small>`:""}
|
||||
</td>
|
||||
</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">
|
||||
<input type="checkbox" class="enableToggle" name="active" ${enableChecked} eptuuid="${subd.RootOrMatchingDomain}" onchange="handleProxyRuleToggle(this);">
|
||||
<label></label>
|
||||
</div>
|
||||
<button title="Edit Proxy Rule" class="ui circular mini basic icon button editBtn inlineEditActionBtn" onclick='editEndpoint("${(subd.RootOrMatchingDomain).hexEncode()}")'><i class="edit icon"></i></button>
|
||||
<button title="Remove Proxy Rule" class="ui circular mini red basic icon button inlineEditActionBtn" onclick='deleteEndpoint("${(subd.RootOrMatchingDomain).hexEncode()}")'><i class="trash icon"></i></button>
|
||||
<button title="Edit Proxy Rule" class="ui circular small basic icon button editBtn inlineEditActionBtn" onclick='editEndpoint("${(subd.RootOrMatchingDomain).hexEncode()}")'><i class="ellipsis vertical icon"></i></button>
|
||||
<!-- <button title="Remove Proxy Rule" class="ui circular mini red basic icon button inlineEditActionBtn" onclick='deleteEndpoint("${(subd.RootOrMatchingDomain).hexEncode()}")'><i class="trash icon"></i></button> -->
|
||||
</td>
|
||||
</tr>`);
|
||||
});
|
||||
@ -297,9 +576,12 @@
|
||||
function editEndpoint(uuid) {
|
||||
uuid = uuid.hexDecode();
|
||||
var row = $('tr[eptuuid="' + uuid + '"]');
|
||||
var columns = row.find('td[data-label]');
|
||||
var payload = $(row).attr("payload");
|
||||
payload = JSON.parse(decodeURIComponent(payload));
|
||||
initHttpProxyRuleEditorModal(payload);
|
||||
return;
|
||||
/*
|
||||
var columns = row.find('td[data-label]');
|
||||
console.log(payload);
|
||||
columns.each(function(index) {
|
||||
var column = $(this);
|
||||
@ -495,6 +777,7 @@
|
||||
|
||||
$(".endpointAdvanceConfig").accordion();
|
||||
$("#httpProxyList").find(".editBtn").addClass("disabled");
|
||||
*/
|
||||
}
|
||||
|
||||
//handleToggleRateLimitInput will get trigger if the "require rate limit" checkbox
|
||||
@ -514,25 +797,35 @@
|
||||
}
|
||||
|
||||
function saveProxyInlineEdit(uuid){
|
||||
uuid = uuid.hexDecode();
|
||||
var row = $('tr[eptuuid="' + uuid + '"]');
|
||||
if (row.length == 0){
|
||||
return;
|
||||
}
|
||||
let editor = $("#httprpEditModal");
|
||||
|
||||
var epttype = "host";
|
||||
let useStickySession = $(row).find(".UseStickySession")[0].checked;
|
||||
let DisableUptimeMonitor = !$(row).find(".EnableUptimeMonitor")[0].checked;
|
||||
let authProviderType = $(row).find(".authProviderPicker input[type='radio']:checked").val();
|
||||
let requireRateLimit = $(row).find(".RequireRateLimit")[0].checked;
|
||||
let rateLimit = $(row).find(".RateLimit").val();
|
||||
let bypassGlobalTLS = $(row).find(".BypassGlobalTLS")[0].checked;
|
||||
let epttype = "host";
|
||||
let useStickySession = $(editor).find(".UseStickySession")[0].checked;
|
||||
let DisableUptimeMonitor = !$(editor).find(".EnableUptimeMonitor")[0].checked;
|
||||
let authProviderType = $(editor).find(".authProviderPicker input[type='radio']:checked").val();
|
||||
let requireRateLimit = $(editor).find(".RequireRateLimit")[0].checked;
|
||||
let rateLimit = $(editor).find(".RateLimit").val();
|
||||
let bypassGlobalTLS = $(editor).find(".BypassGlobalTLS")[0].checked;
|
||||
let tags = getTagsArrayFromEndpoint(uuid);
|
||||
if (tags.length > 0){
|
||||
tags = tags.join(",");
|
||||
}else{
|
||||
tags = "";
|
||||
}
|
||||
|
||||
console.log({
|
||||
"type": epttype,
|
||||
"rootname": uuid,
|
||||
"ss":useStickySession,
|
||||
"dutm": DisableUptimeMonitor,
|
||||
"bpgtls": bypassGlobalTLS,
|
||||
"authprovider" :authProviderType,
|
||||
"rate" :requireRateLimit,
|
||||
"ratenum" :rateLimit,
|
||||
"tags": tags,
|
||||
});
|
||||
alert("save function wip, see console.log");
|
||||
return;
|
||||
$.cjax({
|
||||
url: "/api/proxy/edit",
|
||||
method: "POST",
|
||||
@ -681,14 +974,6 @@
|
||||
renewCertificate(renewDomainKey, false, btn);
|
||||
}
|
||||
|
||||
//Bind on tab switch events
|
||||
tabSwitchEventBind["httprp"] = function(){
|
||||
listProxyEndpoints();
|
||||
|
||||
//Reset the tag filter
|
||||
$("#tagFilterDropdown").dropdown('set selected', "");
|
||||
}
|
||||
|
||||
/* Tags & Search */
|
||||
function handleSearchInput(event){
|
||||
if (event.key == "Escape"){
|
||||
@ -778,11 +1063,218 @@
|
||||
}
|
||||
|
||||
function getTagsArrayFromEndpoint(endpoint){
|
||||
console.log("wip");
|
||||
return [];
|
||||
let targetProxyRuleEle = $(".subdEntry[eptuuid='" + endpoint + "'] td[data-label='tags']");
|
||||
let tags = $(targetProxyRuleEle).attr("payload");
|
||||
return JSON.parse(decodeURIComponent(tags));
|
||||
}
|
||||
|
||||
/* Modal Events */
|
||||
$("#httprpEditDarkenLayer").on("click", function() {
|
||||
closeHttpRuleEditor();
|
||||
});
|
||||
|
||||
function closeHttpRuleEditor(){
|
||||
$("#httprpEditModalWrapper").hide();
|
||||
exitProxyInlineEdit();
|
||||
}
|
||||
|
||||
//Get the current editing http hostname, return null if errors
|
||||
function getEditingHttpProxyHostname(){
|
||||
let hostname = $("#httprpEditModal").attr("editing-host");
|
||||
if (hostname == ""){
|
||||
return null;
|
||||
}
|
||||
|
||||
return decodeURIComponent(hostname);
|
||||
}
|
||||
|
||||
//Initialize the http proxy rule editor
|
||||
function initHttpProxyRuleEditorModal(rulepayload){
|
||||
let subd = JSON.parse(JSON.stringify(rulepayload));
|
||||
|
||||
//Populate all the information in the proxy editor
|
||||
populateAndBindEventsToHTTPProxyEditor(subd);
|
||||
|
||||
//Show the first rpconfig
|
||||
$("#httprpEditModal .rpconfig_content").hide();
|
||||
$("#httprpEditModal .rpconfig_content[rpcfg='downstream']").show();
|
||||
$("#httprpEditModal .hrpedit_menu_item.active").removeClass("active");
|
||||
$("#httprpEditModal .hrpedit_menu_item[cfgpage='downstream']").addClass("active");
|
||||
$("#httprpEditModalWrapper").show();
|
||||
}
|
||||
|
||||
// This function populate the bind all the events required for the proxy editor
|
||||
// pass a copy of the data structure of subd to prevent modification to the original one
|
||||
function populateAndBindEventsToHTTPProxyEditor(subd){
|
||||
//Assign the editing host to DOM element
|
||||
let uuid = subd.RootOrMatchingDomain; //Each matching hostname as uuid
|
||||
$("#httprpEditModal").attr("editing-host", encodeURIComponent(uuid));
|
||||
|
||||
/* ------------ Downstream ------------ */
|
||||
let editor = $("#httprpEditModalWrapper");
|
||||
|
||||
//Check if the domain is wildcard domain, if yes do not render as a link
|
||||
if (subd.RootOrMatchingDomain.indexOf("*") > -1){
|
||||
editor.find(".downstream_primary_hostname").html(subd.RootOrMatchingDomain);
|
||||
}else{
|
||||
editor.find(".downstream_primary_hostname").html(`<a href="//${subd.RootOrMatchingDomain}" target="_blank">${subd.RootOrMatchingDomain}</a>`);
|
||||
}
|
||||
editor.find(".BypassGlobalTLS").off('change');
|
||||
editor.find(".BypassGlobalTLS").prop("checked", subd.BypassGlobalTLS);
|
||||
editor.find(".BypassGlobalTLS").on("change", function() {
|
||||
saveProxyInlineEdit(uuid);
|
||||
});
|
||||
|
||||
|
||||
//Build the alias hostname list
|
||||
let aliasHTML = "";
|
||||
subd.MatchingDomainAlias.forEach(alias => {
|
||||
if (alias.indexOf("*") > -1){
|
||||
//Wildcard alias
|
||||
aliasHTML += `<span class="ui tiny label">${alias}</span> `;
|
||||
}else{
|
||||
//Normal alias
|
||||
aliasHTML += `<span class="ui tiny label"><a href="//${alias}" target="_blank">${alias}</a></span> `;
|
||||
}
|
||||
|
||||
});
|
||||
editor.find(".downstream_alias_hostname").html(aliasHTML);
|
||||
|
||||
|
||||
let enableQuickRequestButton = true;
|
||||
let domains = [subd.RootOrMatchingDomain]; //Domain for getting certificate if needed
|
||||
for (var i = 0; i < subd.MatchingDomainAlias.length; i++){
|
||||
let thisAliasName = subd.MatchingDomainAlias[i];
|
||||
domains.push(thisAliasName);
|
||||
}
|
||||
|
||||
//Check if the domain or alias contains wildcard, if yes, disabled the get certificate button
|
||||
if (subd.RootOrMatchingDomain.indexOf("*") > -1){
|
||||
enableQuickRequestButton = false;
|
||||
}
|
||||
|
||||
if (subd.MatchingDomainAlias != undefined){
|
||||
for (var i = 0; i < subd.MatchingDomainAlias.length; i++){
|
||||
if (subd.MatchingDomainAlias[i].indexOf("*") > -1){
|
||||
enableQuickRequestButton = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let certificateDomains = encodeURIComponent(JSON.stringify(domains));
|
||||
if (enableQuickRequestButton){
|
||||
editor.find(".getCertificateBtn").removeClass("disabled");
|
||||
}else{
|
||||
editor.find(".getCertificateBtn").addClass("disabled");
|
||||
}
|
||||
|
||||
//Bind events for action buttons
|
||||
editor.find(".editAliasHostnameBtn").off("click").on("click", function(){
|
||||
editAliasHostnames(uuid);
|
||||
});
|
||||
editor.find(".editAccessRuleBtn").off("click").on("click", function(){
|
||||
editAccessRule(uuid);
|
||||
});
|
||||
editor.find(".getCertificateBtn").off("click").on("click", function(){
|
||||
requestCertificateForExistingHost(uuid, certificateDomains, this);
|
||||
});
|
||||
|
||||
/* ------------ Upstreams ------------ */
|
||||
editor.find(".upstream_list").html(renderUpstreamList(subd));
|
||||
editor.find(".editUpstreamButton").off("click").on("click", function(){
|
||||
editUpstreams(uuid);
|
||||
})
|
||||
|
||||
/* ------------ Vdirs ------------ */
|
||||
editor.find(".vdir_list").html(renderVirtualDirectoryList(subd));
|
||||
editor.find(".editVdirBtn").off("click").on("click", function(){
|
||||
quickEditVdir(uuid);
|
||||
});
|
||||
|
||||
/* Headers */
|
||||
editor.find(".editHeaderBtn").off("click").on("click", function(){
|
||||
editCustomHeaders(uuid);
|
||||
});
|
||||
|
||||
/* ------------ Security ------------ */
|
||||
|
||||
let authMethodContent = "";
|
||||
switch (subd.AuthenticationProvider.AuthMethod) {
|
||||
case 0x1:
|
||||
editor.find(".authProviderPicker input[value='1']").prop("checked", true);
|
||||
break;
|
||||
case 0x2:
|
||||
editor.find(".authProviderPicker input[value='2']").prop("checked", true);
|
||||
break;
|
||||
case 0x3:
|
||||
editor.find(".authProviderPicker input[value='3']").prop("checked", true);
|
||||
break;
|
||||
default:
|
||||
editor.find(".authProviderPicker input[value='0']").prop("checked", true);
|
||||
break;
|
||||
}
|
||||
|
||||
editor.find(".editBasicAuthCredentialsBtn").off("click").on("click", function(){
|
||||
editBasicAuthCredentials(uuid);
|
||||
});
|
||||
|
||||
//Rate limit
|
||||
if (subd.RequireRateLimit) {
|
||||
editor.find(".RequireRateLimit").prop("checked", true);
|
||||
editor.find(".RateLimit").parent().removeClass("disabled");
|
||||
} else {
|
||||
editor.find(".RequireRateLimit").prop("checked", false);
|
||||
editor.find(".RateLimit").parent().addClass("disabled");
|
||||
}
|
||||
|
||||
editor.find(".RequireRateLimit").off("change").on("change", function() {
|
||||
if ($(this).is(":checked")) {
|
||||
editor.find(".RateLimit").parent().removeClass("disabled");
|
||||
} else {
|
||||
editor.find(".RateLimit").parent().addClass("disabled");
|
||||
}
|
||||
});
|
||||
editor.find(".RateLimit").attr("value", subd.RateLimit);
|
||||
|
||||
/* ------------ TLS ------------ */
|
||||
|
||||
/* ------------ Tags ------------ */
|
||||
editor.find(".editTagsBtn").off("click").on("click", function(){
|
||||
editTags(uuid);
|
||||
});
|
||||
|
||||
console.log(subd);
|
||||
}
|
||||
|
||||
function updateVdirInProxyEditor(){
|
||||
// When page switch from Vdir and back to this page
|
||||
// there is a chance where the user has modified the Vdir
|
||||
// we need to get the latest setting from server side and
|
||||
// render it again
|
||||
let currentEditingHostname = getEditingHttpProxyHostname();
|
||||
$.get("/api/proxy/list?type=host", function(data){
|
||||
data.sort((a,b) => (a.RootOrMatchingDomain > b.RootOrMatchingDomain) ? 1 : ((b.RootOrMatchingDomain > a.RootOrMatchingDomain) ? -1 : 0));
|
||||
let editingHost = data.find(subd => subd.RootOrMatchingDomain === currentEditingHostname);
|
||||
if (editingHost) {
|
||||
populateAndBindEventsToHTTPProxyEditor(editingHost);
|
||||
} else {
|
||||
closeHttpRuleEditor();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
//bind events to hrpedit_menu_item
|
||||
$(".hrpedit_menu_item").on("click", function() {
|
||||
$(".hrpedit_menu_item.active").removeClass("active");
|
||||
$(this).addClass("active");
|
||||
let cfgPageId = $(this).attr("cfgpage");
|
||||
$("#httprpEditModal .rpconfig_content").hide();
|
||||
$(`#httprpEditModal .rpconfig_content[rpcfg='${cfgPageId}']`).show();
|
||||
});
|
||||
|
||||
// Initialize the proxy list on page load
|
||||
$(document).ready(function() {
|
||||
listProxyEndpoints();
|
||||
@ -794,4 +1286,18 @@
|
||||
filterProxyList();
|
||||
});
|
||||
});
|
||||
|
||||
//Bind on tab switch events
|
||||
tabSwitchEventBind["httprp"] = function(){
|
||||
//Check if the proxy editor is opened
|
||||
if ($("#httprpEditModalWrapper").is(":visible")) {
|
||||
//Update the information in the modal
|
||||
updateVdirInProxyEditor();
|
||||
} else {
|
||||
listProxyEndpoints();
|
||||
//Reset the tag filter
|
||||
$("#tagFilterDropdown").dropdown('set selected', "");
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
797
src/web/components/httprp.html.bak
Normal file
797
src/web/components/httprp.html.bak
Normal file
@ -0,0 +1,797 @@
|
||||
<div class="standardContainer">
|
||||
<div class="ui basic segment">
|
||||
<h2>HTTP Proxy</h2>
|
||||
<p>Proxy HTTP server with HTTP or HTTPS for multiple hosts. If you are only proxying for one host / domain, use Default Site instead.</p>
|
||||
</div>
|
||||
<style>
|
||||
#httpProxyList .ui.toggle.checkbox input:checked ~ label::before{
|
||||
background-color: #00ca52 !important;
|
||||
}
|
||||
|
||||
.subdEntry td:not(.ignoremw){
|
||||
min-width: 200px;
|
||||
}
|
||||
|
||||
.httpProxyListTools{
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.tag-select{
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.tag-select:hover{
|
||||
text-decoration: underline;
|
||||
opacity: 0.8;
|
||||
}
|
||||
</style>
|
||||
<div class="httpProxyListTools" style="margin-bottom: 1em;">
|
||||
<div id="tagFilterDropdown" class="ui floating basic dropdown labeled icon button" style="min-width: 150px;">
|
||||
<i class="filter icon"></i>
|
||||
<span class="text">Filter by tags</span>
|
||||
<div class="menu">
|
||||
<div class="ui icon search input">
|
||||
<i class="search icon"></i>
|
||||
<input type="text" placeholder="Search tags...">
|
||||
</div>
|
||||
<div class="divider"></div>
|
||||
<div class="scrolling menu tagList">
|
||||
<!--
|
||||
Example:
|
||||
<div class="item">
|
||||
<div class="ui red empty circular label"></div>
|
||||
Important
|
||||
</div>
|
||||
-->
|
||||
<!-- Add more tag options dynamically -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ui small input" style="width: 300px; height: 38px;">
|
||||
<!-- Prevent the browser from filling the saved Zoraxy login account into the input searchInput below -->
|
||||
<input type="password" autocomplete="off" hidden/>
|
||||
<input type="text" id="searchInput" placeholder="Quick Search" onkeydown="handleSearchInput(event);" onchange="handleSearchInput(event);" onblur="handleSearchInput(event);">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="width: 100%; overflow-x: auto; margin-bottom: 1em; min-height: 300px;">
|
||||
<table class="ui celled sortable unstackable compact table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Host</th>
|
||||
<th>Destination</th>
|
||||
<th>Virtual Directory</th>
|
||||
<th>Tags</th>
|
||||
<th style="max-width: 300px;">Advanced Settings</th>
|
||||
<th class="no-sort" style="min-width:150px;">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="httpProxyList">
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<button class="ui icon right floated basic button" onclick="listProxyEndpoints();"><i class="green refresh icon"></i> Refresh</button>
|
||||
<br><br>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
|
||||
/* List all proxy endpoints */
|
||||
function listProxyEndpoints(){
|
||||
$.get("/api/proxy/list?type=host", function(data){
|
||||
$("#httpProxyList").html(``);
|
||||
if (data.error !== undefined){
|
||||
$("#httpProxyList").append(`<tr>
|
||||
<td data-label="" colspan="5"><i class="remove icon"></i> ${data.error}</td>
|
||||
</tr>`);
|
||||
}else if (data.length == 0){
|
||||
$("#httpProxyList").append(`<tr>
|
||||
<td data-label="" colspan="5"><i class="green check circle icon"></i> No HTTP Proxy Record</td>
|
||||
</tr>`);
|
||||
}else{
|
||||
//Sort by RootOrMatchingDomain field
|
||||
data.sort((a,b) => (a.RootOrMatchingDomain > b.RootOrMatchingDomain) ? 1 : ((b.RootOrMatchingDomain > a.RootOrMatchingDomain) ? -1 : 0))
|
||||
data.forEach(subd => {
|
||||
let subdData = encodeURIComponent(JSON.stringify(subd));
|
||||
|
||||
//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 = "";
|
||||
if ($("#tls").checkbox("is checked")){
|
||||
inboundTlsIcon = `<i class="green lock icon" title="TLS Mode"></i>`;
|
||||
if (subd.BypassGlobalTLS){
|
||||
inboundTlsIcon = `<i class="grey lock icon" title="TLS Bypass Enabled"></i>`;
|
||||
}
|
||||
}else{
|
||||
inboundTlsIcon = `<i class="yellow lock open icon" title="Plain Text Mode"></i>`;
|
||||
}
|
||||
|
||||
//Build the virtual directory list
|
||||
var vdList = `<div class="ui list">`;
|
||||
subd.VirtualDirectories.forEach(vdir => {
|
||||
vdList += `<div class="item">${vdir.MatchingPath} <i class="green angle double right icon"></i> ${vdir.Domain}</div>`;
|
||||
});
|
||||
vdList += `</div>`;
|
||||
|
||||
if (subd.VirtualDirectories.length == 0){
|
||||
vdList = `<small style="opacity: 0.3; pointer-events: none; user-select: none;">No Virtual Directory</small>`;
|
||||
}
|
||||
|
||||
let enableChecked = "checked";
|
||||
if (subd.Disabled){
|
||||
enableChecked = "";
|
||||
}
|
||||
let httpProto = "http://";
|
||||
if ($("#tls").checkbox("is checked")) {
|
||||
httpProto = "https://";
|
||||
} else {
|
||||
httpProto = "http://";
|
||||
}
|
||||
let hostnameRedirectPort = currentListeningPort;
|
||||
if (hostnameRedirectPort == 80 || hostnameRedirectPort == 443){
|
||||
hostnameRedirectPort = "";
|
||||
}else{
|
||||
hostnameRedirectPort = ":" + hostnameRedirectPort;
|
||||
}
|
||||
let aliasDomains = ``;
|
||||
if (subd.MatchingDomainAlias != undefined && subd.MatchingDomainAlias.length > 0){
|
||||
aliasDomains = `<small class="aliasDomains" eptuuid="${subd.RootOrMatchingDomain}" style="color: #636363;">Alias: `;
|
||||
subd.MatchingDomainAlias.forEach(alias => {
|
||||
aliasDomains += `<a href="${httpProto}${alias}${hostnameRedirectPort}" target="_blank">${alias}</a>, `;
|
||||
});
|
||||
aliasDomains = aliasDomains.substr(0, aliasDomains.length - 2); //Remove the last tailing seperator
|
||||
aliasDomains += `</small><br>`;
|
||||
}
|
||||
|
||||
$("#httpProxyList").append(`<tr eptuuid="${subd.RootOrMatchingDomain}" payload="${subdData}" class="subdEntry">
|
||||
<td data-label="" editable="true" datatype="inbound">
|
||||
<a href="${httpProto}${subd.RootOrMatchingDomain}${hostnameRedirectPort}" target="_blank">${subd.RootOrMatchingDomain}</a> ${inboundTlsIcon}<br>
|
||||
${aliasDomains}
|
||||
<small class="accessRuleNameUnderHost" ruleid="${subd.AccessFilterUUID}"></small>
|
||||
</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="tags" payload="${encodeURIComponent(JSON.stringify(subd.Tags))}" datatype="tags">
|
||||
<div class="tags-list">
|
||||
${subd.Tags.length >0 ? subd.Tags.map(tag => `<span class="ui tiny label tag-select" style="background-color: ${getTagColorByName(tag)}; color: ${getTagTextColor(tag)}">${tag}</span>`).join(""):"<small style='opacity: 0.3; pointer-events: none; user-select: none;'>No Tags</small>"}
|
||||
</div>
|
||||
</td>
|
||||
<td data-label="" editable="true" datatype="advanced" style="width: 350px;">
|
||||
${subd.AuthenticationProvider.AuthMethod == 0x1?`<i class="ui grey key icon"></i> Basic Auth`:``}
|
||||
${subd.AuthenticationProvider.AuthMethod == 0x2?`<i class="ui blue key icon"></i> Forward Auth`:``}
|
||||
${subd.AuthenticationProvider.AuthMethod == 0x3?`<i class="ui yellow key icon"></i> OAuth2`:``}
|
||||
${subd.AuthenticationProvider.AuthMethod != 0x0 && subd.RequireRateLimit?"<br>":""}
|
||||
${subd.RequireRateLimit?`<i class="ui green check icon"></i> Rate Limit @ ${subd.RateLimit} req/s`:``}
|
||||
${subd.AuthenticationProvider.AuthMethod == 0x0 && !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">
|
||||
<input type="checkbox" class="enableToggle" name="active" ${enableChecked} eptuuid="${subd.RootOrMatchingDomain}" onchange="handleProxyRuleToggle(this);">
|
||||
<label></label>
|
||||
</div>
|
||||
<button title="Edit Proxy Rule" class="ui circular mini basic icon button editBtn inlineEditActionBtn" onclick='editEndpoint("${(subd.RootOrMatchingDomain).hexEncode()}")'><i class="edit icon"></i></button>
|
||||
<button title="Remove Proxy Rule" class="ui circular mini red basic icon button inlineEditActionBtn" onclick='deleteEndpoint("${(subd.RootOrMatchingDomain).hexEncode()}")'><i class="trash icon"></i></button>
|
||||
</td>
|
||||
</tr>`);
|
||||
});
|
||||
populateTagFilterDropdown(data);
|
||||
}
|
||||
|
||||
resolveAccessRuleNameOnHostRPlist();
|
||||
});
|
||||
}
|
||||
|
||||
//Perform realtime alias update without refreshing the whole page
|
||||
function updateAliasListForEndpoint(endpointName, newAliasDomainList){
|
||||
let targetEle = $(`.aliasDomains[eptuuid='${endpointName}']`);
|
||||
console.log(targetEle);
|
||||
if (targetEle.length == 0){
|
||||
return;
|
||||
}
|
||||
|
||||
let aliasDomains = ``;
|
||||
if (newAliasDomainList != undefined && newAliasDomainList.length > 0){
|
||||
aliasDomains = `Alias: `;
|
||||
newAliasDomainList.forEach(alias => {
|
||||
aliasDomains += `<a href="//${alias}" target="_blank">${alias}</a>, `;
|
||||
});
|
||||
aliasDomains = aliasDomains.substr(0, aliasDomains.length - 2); //Remove the last tailing seperator
|
||||
$(targetEle).html(aliasDomains);
|
||||
$(targetEle).show();
|
||||
}else{
|
||||
$(targetEle).hide();
|
||||
}
|
||||
}
|
||||
|
||||
//Resolve & Update all rule names on host PR list
|
||||
function resolveAccessRuleNameOnHostRPlist(){
|
||||
//Resolve the access filters
|
||||
$.get("/api/access/list", function(data){
|
||||
console.log(data);
|
||||
if (data.error == undefined){
|
||||
//Build a map base on the data
|
||||
let accessRuleMap = {};
|
||||
for (var i = 0; i < data.length; i++){
|
||||
accessRuleMap[data[i].ID] = data[i];
|
||||
}
|
||||
|
||||
|
||||
$(".accessRuleNameUnderHost").each(function(){
|
||||
let thisAccessRuleID = $(this).attr("ruleid");
|
||||
if (thisAccessRuleID== ""){
|
||||
thisAccessRuleID = "default"
|
||||
}
|
||||
|
||||
if (thisAccessRuleID == "default"){
|
||||
//No need to label default access rules
|
||||
$(this).html("");
|
||||
return;
|
||||
}
|
||||
|
||||
let rule = accessRuleMap[thisAccessRuleID];
|
||||
if (rule == undefined){
|
||||
//Missing config or config too old
|
||||
$(this).html(`<i class="ui red exclamation triangle icon"></i> <b style="color: #db2828;">Access Rule Error</b>`);
|
||||
return;
|
||||
}
|
||||
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>`;
|
||||
}else if (rule.WhitelistEnabled && rule.BlacklistEnabled){
|
||||
//Whitelist and blacklist filter
|
||||
icon = `<i class="ui yellow filter icon"></i>`;
|
||||
}
|
||||
|
||||
if (rule != undefined){
|
||||
$(this).html(`${icon} ${rule.Name}`);
|
||||
}
|
||||
});
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
//Update the access rule name on given epuuid, call by hostAccessEditor.html
|
||||
function updateAccessRuleNameUnderHost(epuuid, newruleUID){
|
||||
$(`tr[eptuuid='${epuuid}'].subdEntry`).find(".accessRuleNameUnderHost").attr("ruleid", newruleUID);
|
||||
resolveAccessRuleNameOnHostRPlist();
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
Inline editor for httprp.html
|
||||
*/
|
||||
|
||||
function editEndpoint(uuid) {
|
||||
uuid = uuid.hexDecode();
|
||||
var row = $('tr[eptuuid="' + uuid + '"]');
|
||||
var columns = row.find('td[data-label]');
|
||||
var payload = $(row).attr("payload");
|
||||
payload = JSON.parse(decodeURIComponent(payload));
|
||||
console.log(payload);
|
||||
columns.each(function(index) {
|
||||
var column = $(this);
|
||||
var oldValue = column.text().trim();
|
||||
|
||||
if ($(this).attr("editable") == "false"){
|
||||
//This col do not allow edit. Skip
|
||||
return;
|
||||
}
|
||||
|
||||
// Create an input element based on the column content
|
||||
var input;
|
||||
var datatype = $(this).attr("datatype");
|
||||
if (datatype == "domain"){
|
||||
let useStickySessionChecked = "";
|
||||
if (payload.UseStickySession){
|
||||
useStickySessionChecked = "checked";
|
||||
}
|
||||
|
||||
let enableUptimeMonitor = "";
|
||||
//Note the config file store the uptime monitor as disable, so we need to reverse the logic
|
||||
if (!payload.DisableUptimeMonitor){
|
||||
enableUptimeMonitor = "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>
|
||||
<div class="ui checkbox" style="margin-top: 0.4em;">
|
||||
<input type="checkbox" class="EnableUptimeMonitor" ${enableUptimeMonitor}>
|
||||
<label>Monitor Uptime<br>
|
||||
<small>Enable active uptime monitor</small></label>
|
||||
</div>
|
||||
`;
|
||||
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}');">
|
||||
<i class="ui yellow folder icon"></i> Edit Virtual Directories
|
||||
</button>`);
|
||||
}else if (datatype == "tags"){
|
||||
column.append(`
|
||||
<div class="ui divider"></div>
|
||||
<button class="ui basic compact fluid tiny button" style="margin-left: 0.4em; margin-top: 0.4em;" onclick="editTags('${uuid}');"><i class="ui purple tag icon"></i> Edit tags</button>
|
||||
`);
|
||||
}else if (datatype == "advanced"){
|
||||
let authProvider = payload.AuthenticationProvider.AuthMethod;
|
||||
|
||||
let skipWebSocketOriginCheck = payload.SkipWebSocketOriginCheck;
|
||||
let wsCheckstate = "";
|
||||
if (skipWebSocketOriginCheck){
|
||||
wsCheckstate = "checked";
|
||||
}
|
||||
|
||||
let requireRateLimit = payload.RequireRateLimit;
|
||||
let rateLimitCheckState = "";
|
||||
if (requireRateLimit){
|
||||
rateLimitCheckState = "checked";
|
||||
}
|
||||
let rateLimit = payload.RateLimit;
|
||||
if (rateLimit == 0){
|
||||
//This value is not set. Make it default to 100
|
||||
rateLimit = 100;
|
||||
}
|
||||
let rateLimitDisableState = "";
|
||||
if (!payload.RequireRateLimit){
|
||||
rateLimitDisableState = "disabled";
|
||||
}
|
||||
|
||||
column.empty().append(`
|
||||
<div class="grouped fields authProviderPicker">
|
||||
<label><b>Authentication Provider</b></label>
|
||||
<div class="field">
|
||||
<div class="ui radio checkbox">
|
||||
<input type="radio" value="0" name="authProviderType" ${authProvider==0x0?"checked":""}>
|
||||
<label>None (Anyone can access)</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<div class="ui radio checkbox">
|
||||
<input type="radio" value="1" name="authProviderType" ${authProvider==0x1?"checked":""}>
|
||||
<label>Basic Auth</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<div class="ui radio checkbox">
|
||||
<input type="radio" value="2" name="authProviderType" ${authProvider==0x2?"checked":""}>
|
||||
<label>Forward Auth</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<div class="ui radio checkbox">
|
||||
<input type="radio" value="3" name="authProviderType" ${authProvider==0x3?"checked":""}>
|
||||
<label>OAuth2</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<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>
|
||||
<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>
|
||||
|
||||
<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;">
|
||||
<div class="title">
|
||||
<i class="dropdown icon"></i>
|
||||
Security Options
|
||||
</div>
|
||||
<div class="content">
|
||||
<div class="ui checkbox" style="margin-top: 0.4em;">
|
||||
<input type="checkbox" onchange="handleToggleRateLimitInput();" class="RequireRateLimit" ${rateLimitCheckState}>
|
||||
<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" >
|
||||
<label class="ui basic label">
|
||||
req / sec / IP
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
`);
|
||||
|
||||
$('.authProviderPicker .ui.checkbox').checkbox();
|
||||
} else if (datatype == "ratelimit"){
|
||||
|
||||
column.empty().append(`
|
||||
<div class="ui checkbox" style="margin-top: 0.4em;">
|
||||
<input type="checkbox" class="RequireRateLimit" ${checkstate}>
|
||||
<label>Require Rate Limit</label>
|
||||
</div>
|
||||
<div class="ui mini fluid input">
|
||||
<input type="number" class="RateLimit" value="${rateLimit}" placeholder="100" min="1" max="1000" >
|
||||
</div>
|
||||
`);
|
||||
|
||||
}else if (datatype == 'action'){
|
||||
column.empty().append(`
|
||||
<button title="Save" onclick="saveProxyInlineEdit('${uuid.hexEncode()}');" class="ui basic small icon circular button inlineEditActionBtn"><i class="ui green save icon"></i></button>
|
||||
<button title="Cancel" onclick="exitProxyInlineEdit();" class="ui basic small icon circular button inlineEditActionBtn"><i class="ui remove icon"></i></button>
|
||||
|
||||
`);
|
||||
}else if (datatype == "inbound"){
|
||||
let originalContent = $(column).html();
|
||||
|
||||
//Check if this host is covered within one of the certificates. If not, show the icon
|
||||
let enableQuickRequestButton = true;
|
||||
let domains = [payload.RootOrMatchingDomain]; //Domain for getting certificate if needed
|
||||
for (var i = 0; i < payload.MatchingDomainAlias.length; i++){
|
||||
let thisAliasName = payload.MatchingDomainAlias[i];
|
||||
domains.push(thisAliasName);
|
||||
}
|
||||
|
||||
//Check if the domain or alias contains wildcard, if yes, disabled the get certificate button
|
||||
if (payload.RootOrMatchingDomain.indexOf("*") > -1){
|
||||
enableQuickRequestButton = false;
|
||||
}
|
||||
|
||||
if (payload.MatchingDomainAlias != undefined){
|
||||
for (var i = 0; i < payload.MatchingDomainAlias.length; i++){
|
||||
if (payload.MatchingDomainAlias[i].indexOf("*") > -1){
|
||||
enableQuickRequestButton = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//encode the domain to DOM
|
||||
let certificateDomains = encodeURIComponent(JSON.stringify(domains));
|
||||
|
||||
column.empty().append(`${originalContent}
|
||||
<div class="ui divider"></div>
|
||||
<div class="ui checkbox" style="margin-top: 0.4em;">
|
||||
<input type="checkbox" class="BypassGlobalTLS" ${payload.BypassGlobalTLS?"checked":""}>
|
||||
<label>Allow plain HTTP access<br>
|
||||
<small>Allow inbound connections without TLS/SSL</small></label>
|
||||
</div><br>
|
||||
<button class="ui basic compact tiny button" style="margin-left: 0.4em; margin-top: 0.4em;" onclick="editAliasHostnames('${uuid}');"><i class=" blue at icon"></i> Alias</button>
|
||||
<button class="ui basic compact tiny button" style="margin-left: 0.4em; margin-top: 0.4em;" onclick="editAccessRule('${uuid}');"><i class="ui filter icon"></i> Access Rule</button>
|
||||
<button class="ui basic compact tiny ${enableQuickRequestButton?"":"disabled"} button" style="margin-left: 0.4em; margin-top: 0.4em;" onclick="requestCertificateForExistingHost('${uuid}', '${certificateDomains}', this);"><i class="green lock icon"></i> Get Certificate</button>
|
||||
`);
|
||||
|
||||
$(".hostAccessRuleSelector").dropdown();
|
||||
}else{
|
||||
//Unknown field. Leave it untouched
|
||||
}
|
||||
});
|
||||
|
||||
$(".endpointAdvanceConfig").accordion();
|
||||
$("#httpProxyList").find(".editBtn").addClass("disabled");
|
||||
}
|
||||
|
||||
//handleToggleRateLimitInput will get trigger if the "require rate limit" checkbox
|
||||
// is changed and toggle the disable state of the rate limit input field
|
||||
function handleToggleRateLimitInput(){
|
||||
let isRateLimitEnabled = $("#httpProxyList input.RequireRateLimit")[0].checked;
|
||||
if (isRateLimitEnabled){
|
||||
$("#httpProxyList input.RateLimit").parent().removeClass("disabled");
|
||||
}else{
|
||||
$("#httpProxyList input.RateLimit").parent().addClass("disabled");
|
||||
}
|
||||
}
|
||||
|
||||
function exitProxyInlineEdit(){
|
||||
listProxyEndpoints();
|
||||
$("#httpProxyList").find(".editBtn").removeClass("disabled");
|
||||
}
|
||||
|
||||
function saveProxyInlineEdit(uuid){
|
||||
uuid = uuid.hexDecode();
|
||||
var row = $('tr[eptuuid="' + uuid + '"]');
|
||||
if (row.length == 0){
|
||||
return;
|
||||
}
|
||||
|
||||
var epttype = "host";
|
||||
let useStickySession = $(row).find(".UseStickySession")[0].checked;
|
||||
let DisableUptimeMonitor = !$(row).find(".EnableUptimeMonitor")[0].checked;
|
||||
let authProviderType = $(row).find(".authProviderPicker input[type='radio']:checked").val();
|
||||
let requireRateLimit = $(row).find(".RequireRateLimit")[0].checked;
|
||||
let rateLimit = $(row).find(".RateLimit").val();
|
||||
let bypassGlobalTLS = $(row).find(".BypassGlobalTLS")[0].checked;
|
||||
let tags = getTagsArrayFromEndpoint(uuid);
|
||||
if (tags.length > 0){
|
||||
tags = tags.join(",");
|
||||
}else{
|
||||
tags = "";
|
||||
}
|
||||
$.cjax({
|
||||
url: "/api/proxy/edit",
|
||||
method: "POST",
|
||||
data: {
|
||||
"type": epttype,
|
||||
"rootname": uuid,
|
||||
"ss":useStickySession,
|
||||
"dutm": DisableUptimeMonitor,
|
||||
"bpgtls": bypassGlobalTLS,
|
||||
"authprovider" :authProviderType,
|
||||
"rate" :requireRateLimit,
|
||||
"ratenum" :rateLimit,
|
||||
"tags": tags,
|
||||
},
|
||||
success: function(data){
|
||||
if (data.error !== undefined){
|
||||
msgbox(data.error, false, 6000);
|
||||
}else{
|
||||
msgbox("Proxy endpoint updated");
|
||||
listProxyEndpoints();
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
//Generic functions for delete rp endpoints
|
||||
function deleteEndpoint(epoint){
|
||||
epoint = decodeURIComponent(epoint).hexDecode();
|
||||
if (confirm("Confirm remove proxy for :" + epoint + "?")){
|
||||
$.cjax({
|
||||
url: "/api/proxy/del",
|
||||
method: "POST",
|
||||
data: {ep: epoint},
|
||||
success: function(data){
|
||||
if (data.error == undefined){
|
||||
listProxyEndpoints();
|
||||
msgbox("Proxy Rule Deleted", true);
|
||||
reloadUptimeList();
|
||||
}else{
|
||||
msgbox(data.error, false);
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* button events */
|
||||
function editBasicAuthCredentials(uuid){
|
||||
let payload = encodeURIComponent(JSON.stringify({
|
||||
ept: "host",
|
||||
ep: uuid
|
||||
}));
|
||||
showSideWrapper("snippet/basicAuthEditor.html?t=" + Date.now() + "#" + payload);
|
||||
}
|
||||
|
||||
function editAccessRule(uuid){
|
||||
let payload = encodeURIComponent(JSON.stringify({
|
||||
ept: "host",
|
||||
ep: uuid
|
||||
}));
|
||||
showSideWrapper("snippet/hostAccessEditor.html?t=" + Date.now() + "#" + payload);
|
||||
}
|
||||
|
||||
function editAliasHostnames(uuid){
|
||||
let payload = encodeURIComponent(JSON.stringify({
|
||||
ept: "host",
|
||||
ep: uuid
|
||||
}));
|
||||
showSideWrapper("snippet/aliasEditor.html?t=" + Date.now() + "#" + payload);
|
||||
}
|
||||
|
||||
function quickEditVdir(uuid){
|
||||
openTabById("vdir");
|
||||
$("#vdirBaseRoutingRule").parent().dropdown("set selected", uuid);
|
||||
}
|
||||
|
||||
//Open the custom header editor
|
||||
function editCustomHeaders(uuid){
|
||||
let payload = encodeURIComponent(JSON.stringify({
|
||||
ept: "host",
|
||||
ep: uuid
|
||||
}));
|
||||
showSideWrapper("snippet/customHeaders.html?t=" + Date.now() + "#" + payload);
|
||||
}
|
||||
|
||||
//Open the load balance option
|
||||
function editUpstreams(uuid){
|
||||
let payload = encodeURIComponent(JSON.stringify({
|
||||
ept: "host",
|
||||
ep: uuid
|
||||
}));
|
||||
showSideWrapper("snippet/upstreams.html?t=" + Date.now() + "#" + payload);
|
||||
}
|
||||
|
||||
function handleProxyRuleToggle(object){
|
||||
let endpointUUID = $(object).attr("eptuuid");
|
||||
let isChecked = object.checked;
|
||||
$.cjax({
|
||||
url: "/api/proxy/toggle",
|
||||
data: {
|
||||
"ep": endpointUUID,
|
||||
"enable": isChecked
|
||||
},
|
||||
success: function(data){
|
||||
if (data.error != undefined){
|
||||
msgbox(data.error, false);
|
||||
}else{
|
||||
if (isChecked){
|
||||
msgbox("Proxy Rule Enabled");
|
||||
}else{
|
||||
msgbox("Proxy Rule Disabled");
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/*
|
||||
Certificate Shortcut
|
||||
*/
|
||||
|
||||
function requestCertificateForExistingHost(hostUUID, RootAndAliasDomains, btn=undefined){
|
||||
RootAndAliasDomains = JSON.parse(decodeURIComponent(RootAndAliasDomains))
|
||||
let renewDomainKey = RootAndAliasDomains.join(",");
|
||||
let preferedACMEEmail = $("#prefACMEEmail").val();
|
||||
if (preferedACMEEmail == ""){
|
||||
msgbox("Preferred email for ACME registration not set", false);
|
||||
return;
|
||||
}
|
||||
let defaultCA = $("#defaultCA").dropdown("get value");
|
||||
if (defaultCA == ""){
|
||||
defaultCA = "Let's Encrypt";
|
||||
}
|
||||
|
||||
//Check if the root or the alias domain contain wildcard character, if yes, return error
|
||||
for (var i = 0; i < RootAndAliasDomains.length; i++){
|
||||
if (RootAndAliasDomains[i].indexOf("*") != -1){
|
||||
msgbox("Wildcard domain can only be setup via ACME tool", false);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
//Renew the certificate
|
||||
renewCertificate(renewDomainKey, false, btn);
|
||||
}
|
||||
|
||||
//Bind on tab switch events
|
||||
tabSwitchEventBind["httprp"] = function(){
|
||||
listProxyEndpoints();
|
||||
|
||||
//Reset the tag filter
|
||||
$("#tagFilterDropdown").dropdown('set selected', "");
|
||||
}
|
||||
|
||||
/* Tags & Search */
|
||||
function handleSearchInput(event){
|
||||
if (event.key == "Escape"){
|
||||
$("#searchInput").val("");
|
||||
}
|
||||
filterProxyList();
|
||||
}
|
||||
|
||||
// Function to filter the proxy list
|
||||
function filterProxyList() {
|
||||
let searchInput = $("#searchInput").val().toLowerCase();
|
||||
let selectedTag = $("#tagFilterDropdown").dropdown('get value');
|
||||
$("#httpProxyList tr").each(function() {
|
||||
let host = $(this).find("td[data-label='']").text().toLowerCase();
|
||||
let tagElements = $(this).find("td[data-label='tags']");
|
||||
let tags = tagElements.attr("payload");
|
||||
tags = JSON.parse(decodeURIComponent(tags));
|
||||
if ((host.includes(searchInput) || searchInput === "") && (tags.includes(selectedTag) || selectedTag === "")) {
|
||||
$(this).show();
|
||||
} else {
|
||||
$(this).hide();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Function to generate a color based on a tag name
|
||||
function getTagColorByName(tagName) {
|
||||
function hashCode(str) {
|
||||
return str.split('').reduce((prevHash, currVal) =>
|
||||
((prevHash << 5) - prevHash) + currVal.charCodeAt(0), 0);
|
||||
}
|
||||
let hash = hashCode(tagName);
|
||||
let color = '#' + ((hash >> 24) & 0xFF).toString(16).padStart(2, '0') +
|
||||
((hash >> 16) & 0xFF).toString(16).padStart(2, '0') +
|
||||
((hash >> 8) & 0xFF).toString(16).padStart(2, '0');
|
||||
return color;
|
||||
}
|
||||
|
||||
function getTagTextColor(tagName){
|
||||
let color = getTagColorByName(tagName);
|
||||
let r = parseInt(color.substr(1, 2), 16);
|
||||
let g = parseInt(color.substr(3, 2), 16);
|
||||
let b = parseInt(color.substr(5, 2), 16);
|
||||
let brightness = Math.round(((r * 299) + (g * 587) + (b * 114)) / 1000);
|
||||
return brightness > 125 ? "#000000" : "#ffffff";
|
||||
}
|
||||
|
||||
// Populate the tag filter dropdown
|
||||
function populateTagFilterDropdown(data) {
|
||||
let tags = new Set();
|
||||
data.forEach(subd => {
|
||||
subd.Tags.forEach(tag => tags.add(tag));
|
||||
});
|
||||
tags = Array.from(tags).sort((a, b) => a.localeCompare(b));
|
||||
let dropdownMenu = $("#tagFilterDropdown .tagList");
|
||||
dropdownMenu.html(`<div class="item tag-select" data-value="">
|
||||
<div class="ui grey empty circular label"></div>
|
||||
Show all
|
||||
</div>`);
|
||||
tags.forEach(tag => {
|
||||
let thisTagColor = getTagColorByName(tag);
|
||||
dropdownMenu.append(`<div class="item tag-select" data-value="${tag}">
|
||||
<div class="ui empty circular label" style="background-color: ${thisTagColor}; border-color: ${thisTagColor};" ></div>
|
||||
${tag}
|
||||
</div>`);
|
||||
});
|
||||
}
|
||||
|
||||
// Edit tags for a specific endpoint
|
||||
function editTags(uuid){
|
||||
let payload = encodeURIComponent(JSON.stringify({
|
||||
ept: "host",
|
||||
ep: uuid
|
||||
}));
|
||||
showSideWrapper("snippet/tagEditor.html?t=" + Date.now() + "#" + payload);
|
||||
}
|
||||
|
||||
// Render the tags preview from tag editing snippet
|
||||
function renderTagsPreview(endpoint, tags){
|
||||
let targetProxyRuleEle = $(".subdEntry[eptuuid='" + endpoint + "'] td[data-label='tags']");
|
||||
//Update the tag DOM
|
||||
let newTagDOM = tags.map(tag => `<span class="ui tiny label tag-select" style="background-color: ${getTagColorByName(tag)}; color: ${getTagTextColor(tag)}">${tag}</span>`).join("");
|
||||
$(targetProxyRuleEle).find(".tags-list").html(newTagDOM);
|
||||
|
||||
//Update the tag payload
|
||||
$(targetProxyRuleEle).attr("payload", encodeURIComponent(JSON.stringify(tags)));
|
||||
}
|
||||
|
||||
function getTagsArrayFromEndpoint(endpoint){
|
||||
let targetProxyRuleEle = $(".subdEntry[eptuuid='" + endpoint + "'] td[data-label='tags']");
|
||||
let tags = $(targetProxyRuleEle).attr("payload");
|
||||
return JSON.parse(decodeURIComponent(tags));
|
||||
}
|
||||
|
||||
// Initialize the proxy list on page load
|
||||
$(document).ready(function() {
|
||||
listProxyEndpoints();
|
||||
|
||||
// Event listener for clicking on tags
|
||||
$(document).on('click', '.tag-select', function() {
|
||||
let tag = $(this).text().trim();
|
||||
$('#tagFilterDropdown').dropdown('set selected', tag);
|
||||
filterProxyList();
|
||||
});
|
||||
});
|
||||
</script>
|
Loading…
x
Reference in New Issue
Block a user