Flattened HTTP proxy rule edit menu

This commit is contained in:
Toby Chui 2025-06-10 22:04:04 +08:00
parent c7b5e0994e
commit 809e1fa815
7 changed files with 194 additions and 124 deletions

View File

@ -46,7 +46,7 @@
left: 50%; left: 50%;
transform: translate(-50%, -50%); transform: translate(-50%, -50%);
width: 68vw; width: 68vw;
height: 70vh; height: 75vh;
background-color: var(--theme_bg_primary); background-color: var(--theme_bg_primary);
padding: 1.4em; padding: 1.4em;
border-radius: .6em; border-radius: .6em;
@ -204,12 +204,18 @@
<a class="item hrpedit_menu_item" cfgpage="vdirs"> <a class="item hrpedit_menu_item" cfgpage="vdirs">
<i class="angle folder icon"></i> Virtual Directory <i class="angle folder icon"></i> Virtual Directory
</a> </a>
<a class="item hrpedit_menu_item" cfgpage="alias">
<i class="at icon"></i> Alias
</a>
<a class="item hrpedit_menu_item" cfgpage="ssl"> <a class="item hrpedit_menu_item" cfgpage="ssl">
<i class="lock icon"></i> TLS / SSL <i class="lock icon"></i> TLS / SSL
</a> </a>
<a class="item hrpedit_menu_item" cfgpage="headers"> <a class="item hrpedit_menu_item" cfgpage="headers">
<i class="heading icon"></i> Headers <i class="heading icon"></i> Headers
</a> </a>
<a class="item hrpedit_menu_item" cfgpage="accessrule">
<i class="star icon"></i> Access Rules
</a>
<a class="item hrpedit_menu_item" cfgpage="security"> <a class="item hrpedit_menu_item" cfgpage="security">
<i class="key icon"></i> Security <i class="key icon"></i> Security
</a> </a>
@ -238,9 +244,6 @@
<label>Allow plain HTTP access<br> <label>Allow plain HTTP access<br>
<small>Allow inbound connections without TLS/SSL</small></label> <small>Allow inbound connections without TLS/SSL</small></label>
</div> </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>
</div> </div>
</div> </div>
@ -292,64 +295,81 @@
</button> </button>
</div> </div>
</div> </div>
<!-- Alias -->
<div class="rpconfig_content" rpcfg="alias">
<iframe src="" class="wrapper_frame">
</iframe>
</div>
<!-- TLS / SSL -->
<div class="rpconfig_content" rpcfg="ssl">
<div class="ui segment">
<p>Work In Progress </p>
<br>
<button class="ui basic small button getCertificateBtn" style="margin-left: 0.4em; margin-top: 0.4em;"><i class="green lock icon"></i> Get Certificate</button>
</div>
</div>
<!-- Custom Headers --> <!-- Custom Headers -->
<div class="rpconfig_content" rpcfg="headers"> <div class="rpconfig_content" rpcfg="headers">
<iframe src="" class="wrapper_frame"> <iframe src="" class="wrapper_frame">
</iframe> </iframe>
</div> </div>
<!-- Access Rule -->
<div class="rpconfig_content" rpcfg="accessrule">
<iframe src="" class="wrapper_frame">
</iframe>
</div>
<!-- Security --> <!-- Security -->
<div class="rpconfig_content" rpcfg="security"> <div class="rpconfig_content" rpcfg="security">
<div class="grouped fields authProviderPicker"> <div class="ui segment">
<!-- Auth Providers --> <div class="grouped fields authProviderPicker">
<label><b>Authentication Provider</b></label> <!-- Auth Providers -->
<div class="field"> <label><b>Authentication Provider</b></label>
<div class="ui radio checkbox"> <div class="field">
<input type="radio" value="0" name="authProviderType"> <div class="ui radio checkbox">
<label>None (Anyone can access)</label> <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>
</div> </div>
<div class="field"> <br>
<div class="ui radio checkbox"> <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>
<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> <div class="ui divider"></div>
<!-- Rate Limits--> <!-- Rate Limits-->
<div class="ui checkbox" style="margin-top: 0.4em;"> <div class="ui checkbox" style="margin-top: 0.4em;">
<input type="checkbox" onchange="handleToggleRateLimitInput();" class="RequireRateLimit" ${rateLimitCheckState}> <input type="checkbox" onchange="handleToggleRateLimitInput();" class="RequireRateLimit" ${rateLimitCheckState}>
<label>Require Rate Limit<br> <label>Require Rate Limit<br>
<small>Check this to enable rate limit on this inbound hostname</small></label> <small>Check this to enable rate limit on this inbound hostname</small></label>
</div><br> </div><br>
<div class="ui small right labeled fluid input" style="margin-top: 0.4em;"> <div class="ui small right labeled fluid input" style="margin-top: 0.4em;">
<input type="number" class="RateLimit" value="0" min="1" > <input type="number" class="RateLimit" value="0" min="1" >
<label class="ui basic label"> <label class="ui basic label">
req / sec / IP req / sec / IP
</label> </label>
</div>
</div> </div>
</div> </div>
<!-- TLS / SSL -->
<div class="rpconfig_content" rpcfg="ssl">
SSL
<button class="ui basic small button getCertificateBtn" style="margin-left: 0.4em; margin-top: 0.4em;"><i class="green lock icon"></i> Get Certificate</button>
</div>
<!-- Tags --> <!-- Tags -->
<div class="rpconfig_content" rpcfg="tags"> <div class="rpconfig_content" rpcfg="tags">
<iframe src="" class="wrapper_frame"> <iframe src="" class="wrapper_frame">
@ -882,8 +902,7 @@
"ratenum" :rateLimit, "ratenum" :rateLimit,
"tags": tags, "tags": tags,
}); });
alert("save function wip, see console.log");
return;
$.cjax({ $.cjax({
url: "/api/proxy/edit", url: "/api/proxy/edit",
method: "POST", method: "POST",
@ -940,21 +959,6 @@
showEditorSideWrapper("snippet/basicAuthEditor.html?t=" + Date.now() + "#" + payload); showEditorSideWrapper("snippet/basicAuthEditor.html?t=" + Date.now() + "#" + payload);
} }
function editAccessRule(uuid){
let payload = encodeURIComponent(JSON.stringify({
ept: "host",
ep: uuid
}));
showEditorSideWrapper("snippet/hostAccessEditor.html?t=" + Date.now() + "#" + payload);
}
function editAliasHostnames(uuid){
let payload = encodeURIComponent(JSON.stringify({
ept: "host",
ep: uuid
}));
showEditorSideWrapper("snippet/aliasEditor.html?t=" + Date.now() + "#" + payload);
}
function quickEditVdir(uuid){ function quickEditVdir(uuid){
openTabById("vdir"); openTabById("vdir");
@ -1113,9 +1117,8 @@
function getTagsArrayFromEndpoint(endpoint){ function getTagsArrayFromEndpoint(endpoint){
let targetProxyRuleEle = $(".subdEntry[eptuuid='" + endpoint + "'] td[data-label='tags']"); let subd = getEditingHttpProxyCachedSubd();
let tags = $(targetProxyRuleEle).attr("payload"); return tags = subd.Tags || [];
return JSON.parse(decodeURIComponent(tags));
} }
/* Modal Events */ /* Modal Events */
@ -1138,10 +1141,18 @@
if (hostname == ""){ if (hostname == ""){
return null; return null;
} }
return decodeURIComponent(hostname); return decodeURIComponent(hostname);
} }
function getEditingHttpProxyCachedSubd(){
let hostname = getEditingHttpProxyHostname();
if (hostname == null){
return null;
}
let subd = httpProxyList.find(subd => subd.RootOrMatchingDomain === hostname);
return subd;
}
//Initialize the http proxy rule editor //Initialize the http proxy rule editor
function initHttpProxyRuleEditorModal(rulepayload){ function initHttpProxyRuleEditorModal(rulepayload){
let subd = JSON.parse(JSON.stringify(rulepayload)); let subd = JSON.parse(JSON.stringify(rulepayload));
@ -1200,7 +1211,7 @@
}); });
editor.find(".downstream_alias_hostname").html(aliasHTML); editor.find(".downstream_alias_hostname").html(aliasHTML);
//TODO: Move this to SSL TLS section
let enableQuickRequestButton = true; let enableQuickRequestButton = true;
let domains = [subd.RootOrMatchingDomain]; //Domain for getting certificate if needed let domains = [subd.RootOrMatchingDomain]; //Domain for getting certificate if needed
for (var i = 0; i < subd.MatchingDomainAlias.length; i++){ for (var i = 0; i < subd.MatchingDomainAlias.length; i++){
@ -1229,13 +1240,6 @@
editor.find(".getCertificateBtn").addClass("disabled"); 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(){ editor.find(".getCertificateBtn").off("click").on("click", function(){
requestCertificateForExistingHost(uuid, certificateDomains, this); requestCertificateForExistingHost(uuid, certificateDomains, this);
}); });
@ -1244,7 +1248,19 @@
editor.find(".upstream_list").html(renderUpstreamList(subd)); editor.find(".upstream_list").html(renderUpstreamList(subd));
editor.find(".editUpstreamButton").off("click").on("click", function(){ editor.find(".editUpstreamButton").off("click").on("click", function(){
editUpstreams(uuid); editUpstreams(uuid);
}) });
editor.find(".EnableUptimeMonitor").off("change");
editor.find(".EnableUptimeMonitor").prop("checked", !subd.DisableUptimeMonitor);
editor.find(".EnableUptimeMonitor").on("change", function() {
saveProxyInlineEdit(uuid);
});
editor.find(".UseStickySession").off("change");
editor.find(".UseStickySession").prop("checked", subd.UseStickySession);
editor.find(".UseStickySession").on("change", function() {
saveProxyInlineEdit(uuid);
});
/* ------------ Vdirs ------------ */ /* ------------ Vdirs ------------ */
editor.find(".vdir_list").html(renderVirtualDirectoryList(subd)); editor.find(".vdir_list").html(renderVirtualDirectoryList(subd));
@ -1252,7 +1268,17 @@
quickEditVdir(uuid); quickEditVdir(uuid);
}); });
/* Headers */ /* ------------ Alias ------------ */
(() => {
let payload = encodeURIComponent(JSON.stringify({
ept: "host",
ep: uuid
}));
let frameURL = "snippet/aliasEditor.html?t=" + Date.now() + "#" + payload;
editor.find(".rpconfig_content[rpcfg='alias'] .wrapper_frame").attr('src', frameURL);
})();
/* ------------ Headers ------------ */
(() => { (() => {
let payload = encodeURIComponent(JSON.stringify({ let payload = encodeURIComponent(JSON.stringify({
ept: "host", ept: "host",
@ -1262,6 +1288,16 @@
editor.find(".rpconfig_content[rpcfg='headers'] .wrapper_frame").attr('src', frameURL); editor.find(".rpconfig_content[rpcfg='headers'] .wrapper_frame").attr('src', frameURL);
})(); })();
/* ------------ Access Rule ------------ */
(()=>{
let payload = encodeURIComponent(JSON.stringify({
ept: "host",
ep: uuid
}));
let frameURL = "snippet/hostAccessEditor.html?t=" + Date.now() + "#" + payload;
editor.find(".rpconfig_content[rpcfg='accessrule'] .wrapper_frame").attr('src', frameURL);
})();
/* ------------ Security ------------ */ /* ------------ Security ------------ */
let authMethodContent = ""; let authMethodContent = "";
@ -1293,14 +1329,34 @@
editor.find(".RateLimit").parent().addClass("disabled"); editor.find(".RateLimit").parent().addClass("disabled");
} }
function rateLimitChangeEvent(){
let rateLimitValue = $(this).val();
if (rateLimitValue < 0 || isNaN(rateLimitValue)) {
msgbox("Rate limit must be >= 0", false);
$(this).val(subd.RateLimit); // Reset to previous valid value
return;
}
saveProxyInlineEdit(uuid);
}
editor.find(".RequireRateLimit").off("change").on("change", function() { editor.find(".RequireRateLimit").off("change").on("change", function() {
if ($(this).is(":checked")) { if ($(this).is(":checked")) {
editor.find(".RateLimit").parent().removeClass("disabled"); editor.find(".RateLimit").parent().removeClass("disabled");
} else { } else {
editor.find(".RateLimit").parent().addClass("disabled"); editor.find(".RateLimit").parent().addClass("disabled");
} }
if (subd.RateLimit === 0) {
subd.RateLimit = 100; // Set default rate limit to 100 if uninitialized
$(this).val(subd.RateLimit);
editor.find(".RateLimit").off("change"); // Temporarily disable the change event handler
editor.find(".RateLimit").val(100); // Set the value to 100
editor.find(".RateLimit").on("change", rateLimitChangeEvent); // Re-enable the change event handler
}
saveProxyInlineEdit(uuid);
}); });
editor.find(".RateLimit").attr("value", subd.RateLimit); editor.find(".RateLimit").attr("value", subd.RateLimit);
editor.find(".RateLimit").off("change").on("change", rateLimitChangeEvent);
/* ------------ TLS ------------ */ /* ------------ TLS ------------ */
@ -1317,11 +1373,9 @@
console.log(subd); console.log(subd);
} }
function updateVdirInProxyEditor(){ // Pull the latest proxy config from server side again and populate the editor
// When page switch from Vdir and back to this page // with latest settings
// there is a chance where the user has modified the Vdir function resyncProxyEditorConfig(){
// we need to get the latest setting from server side and
// render it again
let currentEditingHostname = getEditingHttpProxyHostname(); let currentEditingHostname = getEditingHttpProxyHostname();
$.get("/api/proxy/list?type=host", function(data){ $.get("/api/proxy/list?type=host", function(data){
data.sort((a,b) => (a.RootOrMatchingDomain > b.RootOrMatchingDomain) ? 1 : ((b.RootOrMatchingDomain > a.RootOrMatchingDomain) ? -1 : 0)); data.sort((a,b) => (a.RootOrMatchingDomain > b.RootOrMatchingDomain) ? 1 : ((b.RootOrMatchingDomain > a.RootOrMatchingDomain) ? -1 : 0));
@ -1341,13 +1395,14 @@
let cfgPageId = $(this).attr("cfgpage"); let cfgPageId = $(this).attr("cfgpage");
$("#httprpEditModal .rpconfig_content").hide(); $("#httprpEditModal .rpconfig_content").hide();
$(`#httprpEditModal .rpconfig_content[rpcfg='${cfgPageId}']`).show(); $(`#httprpEditModal .rpconfig_content[rpcfg='${cfgPageId}']`).show();
$("#httprpEditModal .wrapper_frame").contents().scrollTop(0);
hideEditorSideWrapper(); //Always close the side wrapper on tab change hideEditorSideWrapper(); //Always close the side wrapper on tab change
}); });
$("#httprpEditModal .editor_back_button").on("click", function(event) { $("#httprpEditModal .editor_back_button").on("click", function(event) {
// Prevent click event from propagating to the modal background // Prevent click event from propagating to the modal background
event.stopPropagation(); event.stopPropagation();
hideEditorSideWrapper(); hideEditorSideWrapperViaBtn();
}); });
function showEditorSideWrapper(url){ function showEditorSideWrapper(url){
@ -1355,9 +1410,13 @@
$("#httprpEditModal .editor_side_wrapper").fadeIn("fast"); $("#httprpEditModal .editor_side_wrapper").fadeIn("fast");
} }
function hideEditorSideWrapperViaBtn(){
hideEditorSideWrapper();
resyncProxyEditorConfig();
}
function hideEditorSideWrapper(){ function hideEditorSideWrapper(){
$("#httprpEditModal .editor_side_wrapper").fadeOut("fast"); $("#httprpEditModal .editor_side_wrapper").fadeOut("fast");
$("#httprpEditModal .editor_side_wrapper .wrapper_frame").attr('src', "snippet/placeholder.html");
} }
@ -1381,7 +1440,10 @@
tabSwitchEventBind["httprp"] = function(){ tabSwitchEventBind["httprp"] = function(){
//Check if the proxy editor is opened //Check if the proxy editor is opened
if ($("#httprpEditModalWrapper").is(":visible")) { if ($("#httprpEditModalWrapper").is(":visible")) {
//Update the information in the modal // 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
updateVdirInProxyEditor(); updateVdirInProxyEditor();
} else { } else {
listProxyEndpoints(); listProxyEndpoints();

View File

@ -342,9 +342,9 @@
$(".sideWrapper iframe")[0].contentWindow.setDarkTheme(false); $(".sideWrapper iframe")[0].contentWindow.setDarkTheme(false);
} }
if (editorSideWrapper.is(":visible")){ $(editorSideWrapper).each(function(){
editorSideWrapper[0].contentWindow.setDarkTheme(false); $(this)[0].contentWindow.setDarkTheme(false);
} })
if ($("#pluginContextLoader").is(":visible")){ if ($("#pluginContextLoader").is(":visible")){
$("#pluginContextLoader")[0].contentWindow.setDarkTheme(false); $("#pluginContextLoader")[0].contentWindow.setDarkTheme(false);
@ -355,9 +355,9 @@
if ($(".sideWrapper").is(":visible")){ if ($(".sideWrapper").is(":visible")){
$(".sideWrapper iframe")[0].contentWindow.setDarkTheme(true); $(".sideWrapper iframe")[0].contentWindow.setDarkTheme(true);
} }
if (editorSideWrapper.is(":visible")){ $(editorSideWrapper).each(function(){
editorSideWrapper[0].contentWindow.setDarkTheme(true); $(this)[0].contentWindow.setDarkTheme(true);
} })
if ($("#pluginContextLoader").is(":visible")){ if ($("#pluginContextLoader").is(":visible")){
$("#pluginContextLoader")[0].contentWindow.setDarkTheme(true); $("#pluginContextLoader")[0].contentWindow.setDarkTheme(true);
} }

View File

@ -264,7 +264,6 @@
} }
} }
document.getElementById('accessRuleSelector').addEventListener('change', handleSelectEditingAccessRule); document.getElementById('accessRuleSelector').addEventListener('change', handleSelectEditingAccessRule);
document.getElementById('accessRuleForm').addEventListener('submit', handleCreateNewAccessRule); document.getElementById('accessRuleForm').addEventListener('submit', handleCreateNewAccessRule);

View File

@ -14,13 +14,14 @@
<script src="../script/darktheme.js"></script> <script src="../script/darktheme.js"></script>
<br> <br>
<div class="ui container"> <div class="ui container">
<!--
<div class="ui header"> <div class="ui header">
<div class="content"> <div class="content">
Alias Hostname Alias Hostname
<div class="sub header epname"></div> <div class="sub header epname"></div>
</div> </div>
</div> </div>
<div class="ui divider"></div> <div class="ui divider"></div>-->
<div class="scrolling content ui form"> <div class="scrolling content ui form">
<div id="inlineEditBasicAuthCredentials" class="field"> <div id="inlineEditBasicAuthCredentials" class="field">
<p>Enter alias hostname or wildcard matching keywords for <code class="epname"></code></p> <p>Enter alias hostname or wildcard matching keywords for <code class="epname"></code></p>
@ -50,10 +51,6 @@
</div> </div>
</div> </div>
</div> </div>
<div class="ui divider"></div>
<div class="field" >
<button class="ui basic button" style="float: right;" onclick="closeThisWrapper();">Close</button>
</div>
</div> </div>
<br><br><br><br> <br><br><br><br>

View File

@ -27,6 +27,11 @@
body.darkTheme #permissionPolicyEditor .experimental{ body.darkTheme #permissionPolicyEditor .experimental{
background-color: rgb(41, 41, 41); background-color: rgb(41, 41, 41);
} }
.advanceoptions{
background: var(--theme_advance) !important;
border-radius: 0.4em !important;
}
</style> </style>
</head> </head>
<body> <body>

View File

@ -35,7 +35,7 @@
#accessRuleList{ #accessRuleList{
padding: 0.6em; padding: 0.6em;
border: 1px solid rgb(228, 228, 228); /* border: 1px solid rgb(228, 228, 228); */
border-radius: 0.4em !important; border-radius: 0.4em !important;
max-height: calc(100vh - 15em); max-height: calc(100vh - 15em);
min-height: 300px; min-height: 300px;
@ -65,15 +65,6 @@
<script src="../script/darktheme.js"></script> <script src="../script/darktheme.js"></script>
<br> <br>
<div class="ui container"> <div class="ui container">
<div class="ui header">
<div class="content">
<div class="content">
Host Access Settings
<div class="sub header" id="epname"></div>
</div>
</div>
</div>
<div class="ui divider"></div>
<p>Select an access rule to apply blacklist / whitelist filtering</p> <p>Select an access rule to apply blacklist / whitelist filtering</p>
<div id="accessRuleList"> <div id="accessRuleList">
<div class="ui segment accessRule"> <div class="ui segment accessRule">
@ -87,9 +78,7 @@
</div> </div>
</div> </div>
<br> <br>
<button class="ui basic button" onclick="applyChangeAndClose()"><i class="ui green check icon"></i> Apply Change</button> <!-- <button class="ui basic button" onclick="applyChange()"><i class="ui green check icon"></i> Apply Change</button> -->
<button class="ui basic button" style="float: right;" onclick="parent.hideSideWrapper();"><i class="remove icon"></i> Close</button>
<br><br><br> <br><br><br>
</div> </div>
@ -176,6 +165,35 @@
let accessRuleID = $(accessRuleObject).attr("ruleid"); let accessRuleID = $(accessRuleObject).attr("ruleid");
$(".accessRule").removeClass('active'); $(".accessRule").removeClass('active');
$(accessRuleObject).addClass('active'); $(accessRuleObject).addClass('active');
//Updates 2025-06-10: Added auto save on change feature
applyChange();
}
function applyChange(){
let newAccessRuleID = $(".accessRule.active").attr("ruleid");
let targetEndpoint = editingEndpoint.ep;
$.cjax({
url: "/api/access/attach",
method: "POST",
data: {
id: newAccessRuleID,
host: targetEndpoint
},
success: function(data){
if (data.error != undefined){
parent.msgbox(data.error, false);
}else{
parent.msgbox("Access Rule Updated");
//Modify the parent list if exists
if (parent != undefined && parent.updateAccessRuleNameUnderHost){
parent.updateAccessRuleNameUnderHost(targetEndpoint, newAccessRuleID);
}
}
}
});
} }
function applyChangeAndClose(){ function applyChangeAndClose(){

View File

@ -75,13 +75,6 @@
<script src="../script/darktheme.js"></script> <script src="../script/darktheme.js"></script>
<br> <br>
<div class="ui container"> <div class="ui container">
<div class="ui header">
<div class="content">
Upstreams / Load Balance
<div class="sub header epname"></div>
</div>
</div>
<div class="ui divider"></div>
<div class="ui small pointing secondary menu"> <div class="ui small pointing secondary menu">
<a class="item active narrowpadding" data-tab="upstreamlist">Upstreams</a> <a class="item active narrowpadding" data-tab="upstreamlist">Upstreams</a>
<a class="item narrowpadding" data-tab="newupstream">Add Upstream</a> <a class="item narrowpadding" data-tab="newupstream">Add Upstream</a>
@ -159,10 +152,6 @@
<br><br> <br><br>
<button class="ui basic button" onclick="addNewUpstream();"><i class="ui green circle add icon"></i> Create</button> <button class="ui basic button" onclick="addNewUpstream();"><i class="ui green circle add icon"></i> Create</button>
</div> </div>
<div class="ui divider"></div>
<div class="field" >
<button class="ui basic button" style="float: right;" onclick="closeThisWrapper();">Close</button>
</div>
</div> </div>
<br><br><br><br> <br><br><br><br>