Browse Source

Added basic proxy editing function for multi-upstream

Toby Chui 8 months ago
parent
commit
a2cd775c58

+ 0 - 1
config.go

@@ -138,7 +138,6 @@ func GetDefaultRootConfig() (*dynamicproxy.ProxyEndpoint, error) {
 				OriginIpOrDomain:    "127.0.0.1:" + staticWebServer.GetListeningPort(),
 				RequireTLS:          false,
 				SkipCertValidations: false,
-				PreferedCountryCode: []string{},
 				Priority:            0,
 			},
 		},

+ 10 - 3
mod/dynamicproxy/loadbalance/loadbalance.go

@@ -3,6 +3,7 @@ package loadbalance
 import (
 	"strings"
 	"sync"
+	"sync/atomic"
 
 	"imuslab.com/zoraxy/mod/dynamicproxy/dpcore"
 	"imuslab.com/zoraxy/mod/geodb"
@@ -37,10 +38,12 @@ type Upstream struct {
 	SkipWebSocketOriginCheck bool   //Skip origin check on websocket upgrade connections
 
 	//Load balancing configs
-	PreferedCountryCode []string //Prefered country codes, default to empty string slice (not assigned)
-	Priority            int      //Prirotiy of fallback, set all to 0 for round robin
+	Priority int  //Prirotiy of fallback, set all to 0 for round robin
+	MaxConn  int  //Maxmium connection to this server, 0 for unlimited
+	Enabled  bool //If this upstream is enabled
 
-	proxy *dpcore.ReverseProxy
+	currentConnectionCounts atomic.Uint64 //Counter for number of client currently connected
+	proxy                   *dpcore.ReverseProxy
 }
 
 // Create a new load balancer
@@ -59,6 +62,10 @@ func NewLoadBalancer(options *Options) *RouteManager {
 // origin server that is ready
 func (m *RouteManager) UpstreamsReady(upstreams []*Upstream) bool {
 	for _, upstream := range upstreams {
+		if !upstream.Enabled {
+			//This upstream is disabled. Assume offline
+			continue
+		}
 		if upstream.IsReady() {
 			return true
 		}

+ 1 - 0
mod/dynamicproxy/typedef.go

@@ -118,6 +118,7 @@ type ProxyEndpoint struct {
 	MatchingDomainAlias  []string                //A list of domains that alias to this rule
 	Origins              []*loadbalance.Upstream //Upstream or origin servers IP or domain to proxy to
 	UseStickySession     bool                    //Use stick session for load balancing
+	UseActiveLoadBalance bool                    //Use active loadbalancing, default passive
 	Disabled             bool                    //If the rule is disabled
 
 	//Inbound TLS/SSL Related

+ 3 - 2
mod/update/v308/typedef308.go

@@ -19,8 +19,9 @@ type v308Upstream struct {
 	SkipWebSocketOriginCheck bool   //Skip origin check on websocket upgrade connections
 
 	//Load balancing configs
-	PreferedCountryCode []string //Prefered country codes, default to empty string slice (not assigned)
-	Priority            int      //Prirotiy of fallback, set all to 0 for round robin
+	Priority int  //Prirotiy of fallback, set all to 0 for round robin
+	MaxConn  int  //Maxmium connection to this server
+	Enabled  bool //If this upstream is enabled
 }
 
 // A proxy endpoint record, a general interface for handling inbound routing

+ 2 - 1
mod/update/v308/v308.go

@@ -88,8 +88,9 @@ func convertV307ToV308(old v307ProxyEndpoint) v308ProxyEndpoint {
 			RequireTLS:               old.RequireTLS,
 			SkipCertValidations:      old.SkipCertValidations,
 			SkipWebSocketOriginCheck: old.SkipWebSocketOriginCheck,
-			PreferedCountryCode:      []string{},
 			Priority:                 0,
+			MaxConn:                  0,
+			Enabled:                  true,
 		}},
 		UseStickySession:             false,
 		Disabled:                     old.Disabled,

+ 4 - 21
reverseproxy.go

@@ -324,7 +324,6 @@ func ReverseProxyHandleAddEndpoint(w http.ResponseWriter, r *http.Request) {
 					RequireTLS:               useTLS,
 					SkipCertValidations:      skipTlsValidation,
 					SkipWebSocketOriginCheck: bypassWebsocketOriginCheck,
-					PreferedCountryCode:      []string{},
 					Priority:                 0,
 				},
 			},
@@ -390,7 +389,6 @@ func ReverseProxyHandleAddEndpoint(w http.ResponseWriter, r *http.Request) {
 					RequireTLS:               useTLS,
 					SkipCertValidations:      false,
 					SkipWebSocketOriginCheck: true,
-					PreferedCountryCode:      []string{},
 					Priority:                 0,
 				},
 			},
@@ -438,28 +436,12 @@ func ReverseProxyHandleEditEndpoint(w http.ResponseWriter, r *http.Request) {
 		return
 	}
 
-	/*
-		endpoint, err := utils.PostPara(r, "ep")
-		if err != nil {
-			utils.SendErrorResponse(w, "endpoint not defined")
-			return
-		}
-	*/
-
 	tls, _ := utils.PostPara(r, "tls")
 	if tls == "" {
 		tls = "false"
 	}
 
-	/*
-		useTLS := (tls == "true")
-
-		stv, _ := utils.PostPara(r, "tlsval")
-		if stv == "" {
-			stv = "false"
-		}
-		skipTlsValidation := (stv == "true")
-	*/
+	useStickySession, _ := utils.PostBool(r, "ss")
 
 	//Load bypass TLS option
 	bpgtls, _ := utils.PostPara(r, "bpgtls")
@@ -520,12 +502,13 @@ func ReverseProxyHandleEditEndpoint(w http.ResponseWriter, r *http.Request) {
 	//TODO: Move these into dedicated module
 	//newProxyEndpoint.Domain = endpoint
 	//newProxyEndpoint.RequireTLS = useTLS
-	newProxyEndpoint.BypassGlobalTLS = bypassGlobalTLS
 	//newProxyEndpoint.SkipCertValidations = skipTlsValidation
+	//newProxyEndpoint.SkipWebSocketOriginCheck = bypassWebsocketOriginCheck
+	newProxyEndpoint.BypassGlobalTLS = bypassGlobalTLS
 	newProxyEndpoint.RequireBasicAuth = requireBasicAuth
 	newProxyEndpoint.RequireRateLimit = requireRateLimit
 	newProxyEndpoint.RateLimit = proxyRateLimit
-	//newProxyEndpoint.SkipWebSocketOriginCheck = bypassWebsocketOriginCheck
+	newProxyEndpoint.UseStickySession = useStickySession
 
 	//Prepare to replace the current routing rule
 	readyRoutingRule, err := dynamicProxyRouter.PrepareProxyRoute(newProxyEndpoint)

+ 38 - 46
web/components/httprp.html

@@ -51,13 +51,29 @@
                 //Sort by RootOrMatchingDomain field
                 data.sort((a,b) => (a.RootOrMatchingDomain > b.RootOrMatchingDomain) ? 1 : ((b.RootOrMatchingDomain > a.RootOrMatchingDomain) ? -1 : 0))
                 data.forEach(subd => {
-                    let tlsIcon = "";
                     let subdData = encodeURIComponent(JSON.stringify(subd));
-                    if (subd.RequireTLS){
-                        tlsIcon = `<i class="green lock icon" title="TLS Mode"></i>`;
-                        if (subd.SkipCertValidations){
-                            tlsIcon = `<i class="yellow lock icon" title="TLS/SSL mode without verification"></i>`
-                        }
+                    
+                    //Build the upstream list
+                    let upstreams = "";
+                    if (subd.Origins.length == 0){
+                        //Invalid config
+                        upstreams = `<i class="ui red times icon"></i> No upstream configured`;
+                    }else{
+                        subd.Origins.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 = "";
@@ -102,7 +118,7 @@
                             ${aliasDomains}
                             <small class="accessRuleNameUnderHost" ruleid="${subd.AccessFilterUUID}"></small>
                         </td>
-                        <td data-label="" editable="true" datatype="domain">${subd.Domain} ${tlsIcon}</td>
+                        <td data-label="" editable="true" datatype="domain">${upstreams}</td>
                         <td data-label="" editable="true" datatype="vdir">${vdList}</td>
                         <td data-label="" editable="true" datatype="advanced" style="width: 350px;">
                             ${subd.RequireBasicAuth?`<i class="ui green check icon"></i> Basic Auth`:``}
@@ -228,39 +244,20 @@
             var input;
             var datatype = $(this).attr("datatype");
             if (datatype == "domain"){
-                let domain = payload.Domain;
-                //Target require TLS for proxying
-                let tls = payload.RequireTLS;
-                if (tls){
-                    tls = "checked";
-                }else{
-                    tls = "";
-                }
-
-                //Require TLS validation
-                let skipTLSValidation = payload.SkipCertValidations;
-                let checkstate = "";
-                if (skipTLSValidation){
-                    checkstate = "checked";
+                let useStickySessionChecked = "";
+                if (payload.UseStickySession){
+                    useStickySessionChecked = "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>
 
-                input = `
-                    <div class="ui mini fluid input">
-                        <input type="text" class="Domain" onchange="cleanProxyTargetValue(this)" value="${domain}">
-                    </div>
-                    <div class="ui checkbox" style="margin-top: 0.6em;">
-                        <input type="checkbox" class="RequireTLS" ${tls}>
-                        <label>Require TLS<br>
-                            <small>Proxy target require HTTPS connection</small></label>
-                    </div><br>
-                    <div class="ui checkbox" style="margin-top: 0.4em;">
-                        <input type="checkbox" class="SkipCertValidations" ${checkstate}>
-                        <label>Skip Verification<br>
-                        <small>Check this if proxy target is using self signed certificates</small></label>
-                    </div><br>
-                    <!-- <button class="ui basic compact tiny button" style="margin-left: 0.4em; margin-top: 0.4em;" onclick="editLoadBalanceOptions('${uuid}');"><i class="purple server icon"></i> Load Balance</button> -->
                 `;
-                column.empty().append(input);
+                column.append(input);
             }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}');">
@@ -399,15 +396,12 @@
         }
         
         var epttype = "host";
-        let newDomain =  $(row).find(".Domain").val();
-        let requireTLS = $(row).find(".RequireTLS")[0].checked;
-        let skipCertValidations = $(row).find(".SkipCertValidations")[0].checked;
+        let useStickySession =  $(row).find(".UseStickySession")[0].checked;
         let requireBasicAuth = $(row).find(".RequireBasicAuth")[0].checked;
         let requireRateLimit = $(row).find(".RequireRateLimit")[0].checked;
         let rateLimit = $(row).find(".RateLimit").val();
         let bypassGlobalTLS = $(row).find(".BypassGlobalTLS")[0].checked;
         let bypassWebsocketOrigin = $(row).find(".SkipWebSocketOriginCheck")[0].checked;
-        console.log(newDomain, requireTLS, skipCertValidations, requireBasicAuth)
 
         $.ajax({
             url: "/api/proxy/edit",
@@ -415,10 +409,8 @@
             data: {
                 "type": epttype,
                 "rootname": uuid,
-                "ep":newDomain,
+                "ss":useStickySession,
                 "bpgtls": bypassGlobalTLS,
-                "tls" :requireTLS,
-                "tlsval": skipCertValidations,
                 "bpwsorg" : bypassWebsocketOrigin,
                 "bauth" :requireBasicAuth,
                 "rate" :requireRateLimit,
@@ -490,12 +482,12 @@
     }
 
     //Open the load balance option
-    function editLoadBalanceOptions(uuid){
+    function editUpstreams(uuid){
         let payload = encodeURIComponent(JSON.stringify({
             ept: "host",
             ep: uuid
         }));
-        showSideWrapper("snippet/loadBalancer.html?t=" + Date.now() + "#" + payload);
+        showSideWrapper("snippet/upstreams.html?t=" + Date.now() + "#" + payload);
     }
 
     function handleProxyRuleToggle(object){

+ 18 - 2
web/components/rules.html

@@ -76,6 +76,12 @@
                                         <label>Allow plain HTTP access<br><small>Allow this subdomain to be connected without TLS (Require HTTP server enabled on port 80)</small></label>
                                     </div>
                                 </div>
+                                <div class="field">
+                                    <div class="ui checkbox">
+                                        <input type="checkbox" id="useStickySessionLB">
+                                        <label>Sticky Session<br><small>Enable stick session on upstream load balancing</small></label>
+                                    </div>
+                                </div>
                                 <div class="field">
                                     <div class="ui checkbox">
                                         <input type="checkbox" id="requireRateLimit">
@@ -453,7 +459,17 @@
     }
     
     /* UI Element Initialization */
-    $("#advanceProxyRules").accordion();
-    $("#newProxyRuleAccessFilter").parent().dropdown();
+    function initAdvanceSettingsAccordion(){
+        if ($("#advanceProxyRules").length > 0){
+            $("#advanceProxyRules").accordion();
+            $("#newProxyRuleAccessFilter").parent().dropdown();
+        }else{
+            setTimeout(function(){
+                initAdvanceSettingsAccordion();
+            }, 300);
+        }
+    }
+   
+ 
    
 </script>

+ 0 - 1
web/index.html

@@ -9,7 +9,6 @@
         <title>Control Panel | Zoraxy</title>
         <link rel="stylesheet" href="script/semantic/semantic.min.css">
         <script src="script/jquery-3.6.0.min.js"></script>
-        <script src="../script/ao_module.js"></script>
         <script src="script/semantic/semantic.min.js"></script>
         <script src="script/tablesort.js"></script>
         <script src="script/countryCode.js"></script>

+ 0 - 49
web/snippet/loadBalancer.html

@@ -1,49 +0,0 @@
-<!DOCTYPE html>
-<html>
-    <head>
-        <!-- Notes: This should be open in its original path-->
-        <link rel="stylesheet" href="../script/semantic/semantic.min.css">
-        <script src="../script/jquery-3.6.0.min.js"></script>
-        <script src="../script/semantic/semantic.min.js"></script>
-    </head>
-    <body>
-        <br>
-        <div class="ui container">
-            <div class="ui header">
-                <div class="content">
-                    Load Balance
-                    <div class="sub header epname"></div>
-                </div>
-            </div>
-            <div class="ui divider"></div>
-            
-            <div class="ui divider"></div>
-            <div class="field" >
-                <button class="ui basic button"  style="float: right;" onclick="closeThisWrapper();">Close</button>
-            </div>
-        </div>
-            
-        <br><br><br><br>
-
-        </div>
-        <script>
-            let aliasList = [];
-            let editingEndpoint = {};
-
-            if (window.location.hash.length > 1){
-                let payloadHash = window.location.hash.substr(1);
-                try{
-                    payloadHash = JSON.parse(decodeURIComponent(payloadHash));
-                    $(".epname").text(payloadHash.ep);
-                    editingEndpoint = payloadHash;
-                }catch(ex){
-                    console.log("Unable to load endpoint data from hash")
-                }
-            }
-
-            function closeThisWrapper(){
-                parent.hideSideWrapper(true);
-            }
-        </script>
-    </body>
-</html>

+ 95 - 0
web/snippet/upstreams.html

@@ -0,0 +1,95 @@
+<!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">
+                    Upstreams / Origins
+                    <div class="sub header epname"></div>
+                </div>
+            </div>
+            <div class="ui divider"></div>
+            <table class="ui very basic compacted unstackable celled table">
+                <thead>
+                <tr>
+                    <th>Upstream</th>
+                    <th>Remove</th>
+                </tr></thead>
+                <tbody id="inlineEditTable">
+                <tr>
+                    <td colspan="2"><i class="ui green circle check icon"></i> No Upstreams</td>
+                </tr>
+                </tbody>
+            </table>
+            <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 origins = [];
+            let editingEndpoint = {};
+
+           
+
+            function initOriginList(){
+                $.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.Origins != undefined){
+                                origins = data.Origins;
+                                console.log(origins);
+                            }else{
+                                //Assume no origins
+                                $("#inlineEditTable").html(`<tr>
+                                    <td colspan="2"><i class="ui red circle times icon"></i> Invalid Upstream Settings</td>
+                                </tr>`);
+                            }
+
+                            
+                        }
+                    }
+                })
+            }
+            
+
+            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;
+                    initOriginList();
+                }catch(ex){
+                    console.log("Unable to load endpoint data from hash")
+                }
+            }
+
+            function closeThisWrapper(){
+                parent.hideSideWrapper(true);
+            }
+        </script>
+    </body>
+</html>

+ 0 - 1
wrappers.go

@@ -200,7 +200,6 @@ func HandleStaticWebServerPortChange(w http.ResponseWriter, r *http.Request) {
 				RequireTLS:               false,
 				SkipCertValidations:      false,
 				SkipWebSocketOriginCheck: true,
-				PreferedCountryCode:      []string{},
 				Priority:                 0,
 			},
 		}