Browse Source

Finalized alias implementation

Toby Chui 11 months ago
parent
commit
818b631d50
5 changed files with 293 additions and 5 deletions
  1. 1 0
      api.go
  2. 74 1
      reverseproxy.go
  3. 39 3
      web/components/httprp.html
  4. 1 1
      web/components/rules.html
  5. 178 0
      web/snippet/aliasEditor.html

+ 1 - 0
api.go

@@ -51,6 +51,7 @@ func initAPIs() {
 	authRouter.HandleFunc("/api/proxy/list", ReverseProxyList)
 	authRouter.HandleFunc("/api/proxy/detail", ReverseProxyListDetail)
 	authRouter.HandleFunc("/api/proxy/edit", ReverseProxyHandleEditEndpoint)
+	authRouter.HandleFunc("/api/proxy/setAlias", ReverseProxyHandleAlias)
 	authRouter.HandleFunc("/api/proxy/del", DeleteProxyEndpoint)
 	authRouter.HandleFunc("/api/proxy/updateCredentials", UpdateProxyBasicAuthCredentials)
 	authRouter.HandleFunc("/api/proxy/tlscheck", HandleCheckSiteSupportTLS)

+ 74 - 1
reverseproxy.go

@@ -268,13 +268,30 @@ func ReverseProxyHandleAddEndpoint(w http.ResponseWriter, r *http.Request) {
 	if eptype == "host" {
 		rootOrMatchingDomain, err := utils.PostPara(r, "rootname")
 		if err != nil {
-			utils.SendErrorResponse(w, "subdomain not defined")
+			utils.SendErrorResponse(w, "hostname not defined")
 			return
 		}
+		rootOrMatchingDomain = strings.TrimSpace(rootOrMatchingDomain)
+
+		//Check if it contains ",", if yes, split the remainings as alias
+		aliasHostnames := []string{}
+		if strings.Contains(rootOrMatchingDomain, ",") {
+			matchingDomains := strings.Split(rootOrMatchingDomain, ",")
+			if len(matchingDomains) > 1 {
+				rootOrMatchingDomain = matchingDomains[0]
+				for _, aliasHostname := range matchingDomains[1:] {
+					//Filter out any space
+					aliasHostnames = append(aliasHostnames, strings.TrimSpace(aliasHostname))
+				}
+			}
+		}
+
+		//Generate a proxy endpoint object
 		thisProxyEndpoint := dynamicproxy.ProxyEndpoint{
 			//I/O
 			ProxyType:            dynamicproxy.ProxyType_Host,
 			RootOrMatchingDomain: rootOrMatchingDomain,
+			MatchingDomainAlias:  aliasHostnames,
 			Domain:               endpoint,
 			//TLS
 			RequireTLS:               useTLS,
@@ -454,6 +471,62 @@ func ReverseProxyHandleEditEndpoint(w http.ResponseWriter, r *http.Request) {
 	utils.SendOK(w)
 }
 
+func ReverseProxyHandleAlias(w http.ResponseWriter, r *http.Request) {
+	rootNameOrMatchingDomain, err := utils.PostPara(r, "ep")
+	if err != nil {
+		utils.SendErrorResponse(w, "Invalid ep given")
+		return
+	}
+
+	//No need to check for type as root (/) can be set to default route
+	//and hence, you will not need alias
+
+	//Load the previous alias from current proxy rules
+	targetProxyEntry, err := dynamicProxyRouter.LoadProxy(rootNameOrMatchingDomain)
+	if err != nil {
+		utils.SendErrorResponse(w, "Target proxy config not found or could not be loaded")
+		return
+	}
+
+	newAliasJSON, err := utils.PostPara(r, "alias")
+	if err != nil {
+		//No new set of alias given
+		utils.SendErrorResponse(w, "new alias not given")
+		return
+	}
+
+	//Write new alias to runtime and file
+	newAlias := []string{}
+	err = json.Unmarshal([]byte(newAliasJSON), &newAlias)
+	if err != nil {
+		SystemWideLogger.PrintAndLog("Proxy", "Unable to parse new alias list", err)
+		utils.SendErrorResponse(w, "Invalid alias list given")
+		return
+	}
+
+	//Set the current alias
+	newProxyEndpoint := dynamicproxy.CopyEndpoint(targetProxyEntry)
+	newProxyEndpoint.MatchingDomainAlias = newAlias
+
+	// Prepare to replace the current routing rule
+	readyRoutingRule, err := dynamicProxyRouter.PrepareProxyRoute(newProxyEndpoint)
+	if err != nil {
+		utils.SendErrorResponse(w, err.Error())
+		return
+	}
+	targetProxyEntry.Remove()
+	dynamicProxyRouter.AddProxyRouteToRuntime(readyRoutingRule)
+
+	// Save it to file
+	err = SaveReverseProxyConfig(newProxyEndpoint)
+	if err != nil {
+		utils.SendErrorResponse(w, "Alias update failed")
+		SystemWideLogger.PrintAndLog("Proxy", "Unable to save alias update", err)
+	}
+
+	utils.SendOK(w)
+}
+
 func DeleteProxyEndpoint(w http.ResponseWriter, r *http.Request) {
 	ep, err := utils.GetPara(r, "ep")
 	if err != nil {

+ 39 - 3
web/components/httprp.html

@@ -7,6 +7,10 @@
         #httpProxyList .ui.toggle.checkbox input:checked ~ label::before{
             background-color: #00ca52 !important;
         }
+
+        .subdEntry td:not(.ignoremw){
+            min-width: 200px;
+        }
     </style>
     <div style="width: 100%; overflow-x: auto; margin-bottom: 1em; min-height: 300px;">
         <table class="ui celled sortable unstackable compact table">
@@ -84,7 +88,7 @@
 
                     let aliasDomains = ``;
                     if (subd.MatchingDomainAlias != undefined && subd.MatchingDomainAlias.length > 0){
-                        aliasDomains = `<small class="aliasDomains" style="color: #636363;">Alias: `;
+                        aliasDomains = `<small class="aliasDomains" eptuuid="${subd.RootOrMatchingDomain}" style="color: #636363;">Alias: `;
                         subd.MatchingDomainAlias.forEach(alias => {
                             aliasDomains += `<a href="//${alias}" target="_blank">${alias}</a>, `;
                         });
@@ -103,7 +107,7 @@
                         <td data-label="" editable="true" datatype="basicauth">
                             ${subd.RequireBasicAuth?`<i class="ui green check icon"></i>`:`<i class="ui grey remove icon"></i>`}
                         </td>
-                        <td class="center aligned" editable="true" datatype="action" data-label="">
+                        <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>
@@ -119,6 +123,28 @@
         });
     }
 
+    //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
@@ -290,7 +316,9 @@
                         <label>Allow plain HTTP access<br>
                             <small>Allow inbound connections without TLS/SSL</small></label>
                     </div><br>
-                    <button class="ui basic tiny button" style="margin-left: 0.4em; margin-top: 0.4em;" onclick="editAccessRule('${uuid}');"><i class="ui filter icon"></i> Edit Access Rule</button>
+                    <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>
+                    
                 `);
 
                 $(".hostAccessRuleSelector").dropdown();
@@ -365,6 +393,14 @@
         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);

+ 1 - 1
web/components/rules.html

@@ -22,7 +22,7 @@
                     <div class="field">
                         <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</small>
+                        <small>Support subdomain and wildcard, e.g. s1.mydomain.com or *.test.mydomain.com. Use comma (,) for alias hostnames. </small>
                     </div>
                     <div class="field">
                         <label>Target IP Address or Domain Name with port</label>

+ 178 - 0
web/snippet/aliasEditor.html

@@ -0,0 +1,178 @@
+<!DOCTYPE html>
+<html>
+    <head>
+        <!-- Notes: This should be open in its original path-->
+        <link rel="stylesheet" href="../script/semantic/semantic.min.css">
+        <script src="../script/jquery-3.6.0.min.js"></script>
+        <script src="../script/semantic/semantic.min.js"></script>
+    </head>
+    <body>
+        <br>
+        <div class="ui container">
+            <div class="ui header">
+                <div class="content">
+                    Alias Hostname
+                    <div class="sub header epname"></div>
+                </div>
+            </div>
+            <div class="ui divider"></div>
+            <div class="scrolling content ui form">
+                <div id="inlineEditBasicAuthCredentials" class="field">
+                    <p>Enter alias hostname or wildcard matching keywords for <code class="epname"></code></p>
+                    <table class="ui very basic compacted unstackable celled table">
+                        <thead>
+                        <tr>
+                            <th>Alias Hostname</th>
+                            <th>Remove</th>
+                        </tr></thead>
+                        <tbody id="inlineEditTable">
+                        <tr>
+                            <td colspan="2"><i class="ui green circle check icon"></i> No Alias Hostname</td>
+                        </tr>
+                        </tbody>
+                    </table>
+                    <div class="ui divider"></div>
+                    <div class="three small fields">
+                        <div class="field">
+                            <label>Alias Hostname</label>
+                            <input id="aliasHostname" type="text" placeholder="alias.mydomain.com" autocomplete="off">
+                            <small>Support wildcards e.g. alias.mydomain.com or *.alias.mydomain.com</small>
+                        </div>
+                        <div class="field" >
+                            <button class="ui basic button" onclick="addAliasToRoutingRule();"><i class="green add icon"></i> Add Alias</button>
+                        </div>
+                        <div class="ui divider"></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>
+            
+        <br><br><br><br>
+
+        </div>
+        <script>
+            let aliasList = [];
+            let editingEndpoint = {};
+
+            if (window.location.hash.length > 1){
+                let payloadHash = window.location.hash.substr(1);
+                try{
+                    payloadHash = JSON.parse(decodeURIComponent(payloadHash));
+                    $(".epname").text(payloadHash.ep);
+                    editingEndpoint = payloadHash;
+                }catch(ex){
+                    console.log("Unable to load endpoint data from hash")
+                }
+            }
+
+            function initAliasNames(){
+                $.ajax({
+                    url: "/api/proxy/detail",
+                    method: "POST",
+                    data: {
+                        "type":"host",
+                        "epname": editingEndpoint.ep
+                    },
+                    success: function(data){
+                        if (data.error != undefined){
+                            //This endpoint not exists?
+                            alert(data.error);
+                            return;
+                        }else{
+                            $("#inlineEditTable").html("");
+                            if (data.MatchingDomainAlias != undefined){
+                                aliasList = data.MatchingDomainAlias;
+                                renderAliasList();
+                            }else{
+                                //Assume no alias
+                                $("#inlineEditTable").html(`<tr>
+                                    <td colspan="2"><i class="ui green circle check icon"></i> No Alias Hostname</td>
+                                </tr>`);
+                            }
+
+                            
+                        }
+                    }
+                })
+            }
+            initAliasNames();
+
+            function removeAliasDomain(targetDomain){
+                aliasList.splice(aliasList.indexOf(targetDomain), 1);
+                saveCurrentAliasList(function(data){
+                    if (data.error != undefined){
+                        parent.msgbox(data.error);
+                    }else{
+                        initAliasNames();
+                        parent.msgbox("Alias removed")
+                    }
+                });
+            }
+
+            function addAliasToRoutingRule(){
+                let newAliasHostname = $("#aliasHostname").val().trim();
+                aliasList.push(newAliasHostname);
+                $("#aliasHostname").val("");
+                saveCurrentAliasList(function(data){
+                    if (data.error != undefined){
+                        parent.msgbox(data.error);
+                    }else{
+                        parent.msgbox("New alias added")
+                        initAliasNames();
+                    }
+                    
+                });
+            }
+
+            function saveCurrentAliasList(callback=undefined){
+                $.ajax({
+                    url: "/api/proxy/setAlias",
+                    method: "POST",
+                    data:{
+                        "ep":editingEndpoint.ep,
+                        "alias": JSON.stringify(aliasList)
+                    },
+                    success: function(data){
+                        if (callback != undefined){
+                            callback(data);
+                        }
+
+                        if (data.error == undefined && parent != undefined && parent.document != undefined){
+                            //Try to update the parent object's rules if exists
+                            parent.updateAliasListForEndpoint(editingEndpoint.ep, aliasList);
+                        }
+                    }
+                })
+            }
+
+            function renderAliasList(){
+                $("#inlineEditTable").html("");
+                aliasList.forEach(aliasDomain => {
+                    let domainLink = `<a href="//${aliasDomain}" target="_blank">${aliasDomain}</a>`
+                    if (aliasDomain.includes("*")){
+                        //This is a wildcard hostname
+                        domainLink = aliasDomain;
+                    }
+                    $("#inlineEditTable").append(`<tr>
+                        <td>${domainLink}</td>
+                        <td><button class="ui basic button" onclick="removeAliasDomain('${aliasDomain}');"><i class="red remove icon"></i> Remove</button></td>
+                    </tr>`);
+                });
+
+                if (aliasList.length == 0){
+                    $("#inlineEditTable").html(`<tr>
+                        <td colspan="2"><i class="ui green circle check icon"></i> No Alias Hostname</td>
+                    </tr>`);
+                }
+            }
+
+            function closeThisWrapper(){
+                parent.hideSideWrapper(true);
+            }
+        </script>
+    </body>
+</html>