Added tour for basic operations
- added static website setup tour - added subdomain setup tour
@ -144,7 +144,7 @@ func (a *ACMEHandler) ObtainCert(domains []string, certificateName string, email
|
|||||||
} else {
|
} else {
|
||||||
// (caName == "" || caUrl == "") will use default acme
|
// (caName == "" || caUrl == "") will use default acme
|
||||||
config.CADirURL = a.DefaultAcmeServer
|
config.CADirURL = a.DefaultAcmeServer
|
||||||
a.Logf("[INFO] Using Default ACME "+a.DefaultAcmeServer+" for CA Directory URL", nil)
|
a.Logf("Using Default ACME "+a.DefaultAcmeServer+" for CA Directory URL", nil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -75,6 +75,15 @@ func HandleGuidedStepCheck(w http.ResponseWriter, r *http.Request) {
|
|||||||
httpServerReachable := isHTTPServerAvailable(domain)
|
httpServerReachable := isHTTPServerAvailable(domain)
|
||||||
js, _ := json.Marshal(httpServerReachable)
|
js, _ := json.Marshal(httpServerReachable)
|
||||||
utils.SendJSONResponse(w, string(js))
|
utils.SendJSONResponse(w, string(js))
|
||||||
|
} else if stepNo == 10 {
|
||||||
|
//Resolve public Ip address for tour
|
||||||
|
publicIp, err := getPublicIPAddress()
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
js, _ := json.Marshal(publicIp)
|
||||||
|
utils.SendJSONResponse(w, string(js))
|
||||||
} else {
|
} else {
|
||||||
utils.SendErrorResponse(w, "invalid step number")
|
utils.SendErrorResponse(w, "invalid step number")
|
||||||
}
|
}
|
||||||
|
@ -54,6 +54,9 @@ type ReverseProxy struct {
|
|||||||
Prepender string
|
Prepender string
|
||||||
|
|
||||||
Verbal bool
|
Verbal bool
|
||||||
|
|
||||||
|
//Appended by Zoraxy project
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type ResponseRewriteRuleSet struct {
|
type ResponseRewriteRuleSet struct {
|
||||||
|
@ -348,6 +348,20 @@
|
|||||||
`);
|
`);
|
||||||
}else if (datatype == "inbound"){
|
}else if (datatype == "inbound"){
|
||||||
let originalContent = $(column).html();
|
let originalContent = $(column).html();
|
||||||
|
|
||||||
|
//Check if this host is covered within one of the certificates. If not, show the icon
|
||||||
|
let domainIsCovered = 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);
|
||||||
|
}
|
||||||
|
if (true){
|
||||||
|
domainIsCovered = false;
|
||||||
|
}
|
||||||
|
//encode the domain to DOM
|
||||||
|
let certificateDomains = encodeURIComponent(JSON.stringify(domains));
|
||||||
|
|
||||||
column.empty().append(`${originalContent}
|
column.empty().append(`${originalContent}
|
||||||
<div class="ui divider"></div>
|
<div class="ui divider"></div>
|
||||||
<div class="ui checkbox" style="margin-top: 0.4em;">
|
<div class="ui checkbox" style="margin-top: 0.4em;">
|
||||||
@ -355,11 +369,12 @@
|
|||||||
<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><br>
|
</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="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 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 ${domainIsCovered?"disabled":""} button" style="margin-left: 0.4em; margin-top: 0.4em;" onclick="requestCertificateForExistingHost('${uuid}', '${certificateDomains}');"><i class="green lock icon"></i> Get Certificate</button>
|
||||||
`);
|
`);
|
||||||
|
|
||||||
|
|
||||||
$(".hostAccessRuleSelector").dropdown();
|
$(".hostAccessRuleSelector").dropdown();
|
||||||
}else{
|
}else{
|
||||||
//Unknown field. Leave it untouched
|
//Unknown field. Leave it untouched
|
||||||
@ -517,6 +532,15 @@
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Certificate Shortcut
|
||||||
|
*/
|
||||||
|
|
||||||
|
function requestCertificateForExistingHost(hostUUID, RootAndAliasDomains){
|
||||||
|
RootAndAliasDomains = JSON.parse(decodeURIComponent(RootAndAliasDomains))
|
||||||
|
alert(RootAndAliasDomains.join(", "))
|
||||||
|
}
|
||||||
|
|
||||||
//Bind on tab switch events
|
//Bind on tab switch events
|
||||||
tabSwitchEventBind["httprp"] = function(){
|
tabSwitchEventBind["httprp"] = function(){
|
||||||
listProxyEndpoints();
|
listProxyEndpoints();
|
||||||
|
77
src/web/components/quickstart.html
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
|
||||||
|
<div id="quickstart" class="standardContainer">
|
||||||
|
<div class="ui container">
|
||||||
|
<h1 class="ui header">
|
||||||
|
<img src="img/res/1F387.png">
|
||||||
|
<div class="content">
|
||||||
|
Welcome to Zoraxy!
|
||||||
|
<div class="sub header">What services are you planning to setup today?</div>
|
||||||
|
</div>
|
||||||
|
</h1>
|
||||||
|
<br>
|
||||||
|
<div class="ui stackable equal width grid">
|
||||||
|
<div class="column">
|
||||||
|
<div class="serviceOption homepage" name="homepage">
|
||||||
|
<div class="titleWrapper">
|
||||||
|
<p>Basic Homepage</p>
|
||||||
|
</div>
|
||||||
|
<div class="ui divider"></div>
|
||||||
|
<p>Host a static homepage with Zoraxy and point your domain name to your web server.</p>
|
||||||
|
<img class="themebackground ui small image" src="img/res/1F310.png">
|
||||||
|
<div class="activeOption">
|
||||||
|
<i class="ui white huge circle check icon"></i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="column">
|
||||||
|
<div class="serviceOption subdomain" name="subdomain">
|
||||||
|
<div class="titleWrapper">
|
||||||
|
<p>Sub-domains Routing</p>
|
||||||
|
</div>
|
||||||
|
<div class="ui divider"></div>
|
||||||
|
<p>Add and handle traffic from your subdomains and point them to a dedicated web services somewhere else.</p>
|
||||||
|
<img class="themebackground ui small image" src="img/res/1F500.png">
|
||||||
|
<div class="activeOption">
|
||||||
|
<i class="ui white huge circle check icon"></i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="column">
|
||||||
|
<div class="serviceOption tls" name="tls">
|
||||||
|
<div class="titleWrapper">
|
||||||
|
<p>HTTPS Green Lock(s)</p>
|
||||||
|
</div>
|
||||||
|
<div class="ui divider"></div>
|
||||||
|
<p>Turn your unsafe HTTP website into HTTPS using free certificate from public certificate authorities organizations.</p>
|
||||||
|
<img class="themebackground ui small image" src="img/res/1F512.png">
|
||||||
|
<div class="activeOption">
|
||||||
|
<i class="ui white huge circle check icon"></i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="ui divider"></div>
|
||||||
|
<div style="width: 100%;" align="center">
|
||||||
|
<button onclick="startQuickStartTour();" class="ui finished button quickstartControlButton">
|
||||||
|
Start Walkthrough
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<script>
|
||||||
|
var currentQuickSetupClass = "";
|
||||||
|
var currentQuickSetupTourStep = 0;
|
||||||
|
//For tour logic, see quicksetup.js
|
||||||
|
|
||||||
|
|
||||||
|
//Bind selecting events to serviceOption
|
||||||
|
$("#quickstart .serviceOption").on("click", function(data){
|
||||||
|
$(".serviceOption.active").removeClass("active");
|
||||||
|
$(this).addClass("active");
|
||||||
|
let tourType = $(this).attr("name");
|
||||||
|
currentQuickSetupClass = tourType;
|
||||||
|
});
|
||||||
|
|
||||||
|
</script>
|
||||||
|
<script src="script/quicksetup.js"></script>
|
@ -30,12 +30,12 @@
|
|||||||
<h2>New Proxy Rule</h2>
|
<h2>New Proxy Rule</h2>
|
||||||
<p>You can add more proxy rules to support more site via domain / subdomains</p>
|
<p>You can add more proxy rules to support more site via domain / subdomains</p>
|
||||||
<div class="ui form">
|
<div class="ui form">
|
||||||
<div class="field">
|
<div class="field" tourstep="matchingkeyword">
|
||||||
<label>Matching Keyword / Domain</label>
|
<label>Matching Keyword / Domain</label>
|
||||||
<input type="text" id="rootname" placeholder="mydomain.com">
|
<input type="text" id="rootname" placeholder="mydomain.com">
|
||||||
<small>Support subdomain and wildcard, e.g. s1.mydomain.com or *.test.mydomain.com. Use comma (,) for alias hostnames. </small>
|
<small>Support subdomain and wildcard, e.g. s1.mydomain.com or *.test.mydomain.com. Use comma (,) for alias hostnames. </small>
|
||||||
</div>
|
</div>
|
||||||
<div class="field">
|
<div class="field" tourstep="targetdomain">
|
||||||
<label>Target IP Address or Domain Name with port</label>
|
<label>Target IP Address or Domain Name with port</label>
|
||||||
<input type="text" id="proxyDomain" onchange="autoFillTargetTLS(this);">
|
<input type="text" id="proxyDomain" onchange="autoFillTargetTLS(this);">
|
||||||
<small>e.g. 192.168.0.101:8000 or example.com</small>
|
<small>e.g. 192.168.0.101:8000 or example.com</small>
|
||||||
@ -43,7 +43,7 @@
|
|||||||
<div class="field dockerOptimizations" style="display:none;">
|
<div class="field dockerOptimizations" style="display:none;">
|
||||||
<button style="margin-top: -2em;" class="ui basic small button" onclick="openDockerContainersList();"><i class="blue docker icon"></i> Pick from Docker Containers</button>
|
<button style="margin-top: -2em;" class="ui basic small button" onclick="openDockerContainersList();"><i class="blue docker icon"></i> Pick from Docker Containers</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="field">
|
<div class="field" tourstep="requireTLS">
|
||||||
<div class="ui checkbox">
|
<div class="ui checkbox">
|
||||||
<input type="checkbox" id="reqTls">
|
<input type="checkbox" id="reqTls">
|
||||||
<label>Proxy Target require TLS Connection <br><small>(i.e. Your proxy target starts with https://)</small></label>
|
<label>Proxy Target require TLS Connection <br><small>(i.e. Your proxy target starts with https://)</small></label>
|
||||||
@ -67,7 +67,7 @@
|
|||||||
<i class="ui green lock icon"></i>
|
<i class="ui green lock icon"></i>
|
||||||
Security
|
Security
|
||||||
</div>
|
</div>
|
||||||
<div class="field">
|
<div class="field" tourstep="skipTLSValidation">
|
||||||
<div class="ui checkbox">
|
<div class="ui checkbox">
|
||||||
<input type="checkbox" id="skipTLSValidation">
|
<input type="checkbox" id="skipTLSValidation">
|
||||||
<label>Ignore TLS/SSL Verification Error<br><small>For targets that is using self-signed, expired certificate (Not Recommended)</small></label>
|
<label>Ignore TLS/SSL Verification Error<br><small>For targets that is using self-signed, expired certificate (Not Recommended)</small></label>
|
||||||
@ -154,7 +154,9 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<br>
|
<br>
|
||||||
<button class="ui basic button" onclick="newProxyEndpoint();"><i class="green add icon"></i> Create Endpoint</button>
|
<div tourstep="newProxyRule" style="display: inline-block;">
|
||||||
|
<button class="ui basic button" onclick="newProxyEndpoint();"><i class="green add icon"></i> Create Endpoint</button>
|
||||||
|
</div>
|
||||||
<br><br>
|
<br><br>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -53,8 +53,10 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="standardContainer" style="padding-bottom: 0 !important;">
|
<div class="standardContainer" style="padding-bottom: 0 !important;">
|
||||||
<!-- Power Buttons-->
|
<!-- Power Buttons-->
|
||||||
<button id="startbtn" class="ui basic button" onclick="startService();"><i class="ui green arrow alternate circle up icon"></i> Start Service</button>
|
<div class="poweroptions" style="display:inline-block;">
|
||||||
<button id="stopbtn" class="ui basic notloopbackOnly disabled button" onclick="stopService();"><i class="ui red minus circle icon"></i> Stop Service</button>
|
<button id="startbtn" class="ui basic button" onclick="startService();"><i class="ui green arrow alternate circle up icon"></i> Start Service</button>
|
||||||
|
<button id="stopbtn" class="ui basic notloopbackOnly disabled button" onclick="stopService();"><i class="ui red minus circle icon"></i> Stop Service</button>
|
||||||
|
</div>
|
||||||
<div class="ui divider"></div>
|
<div class="ui divider"></div>
|
||||||
<h4>Network Status</h4>
|
<h4>Network Status</h4>
|
||||||
<p>Overall Network I/O in Current Host Server</p>
|
<p>Overall Network I/O in Current Host Server</p>
|
||||||
|
@ -13,34 +13,35 @@
|
|||||||
</div>
|
</div>
|
||||||
</h4>
|
</h4>
|
||||||
</div>
|
</div>
|
||||||
|
<div>
|
||||||
<h3>Web Server Settings</h3>
|
<h3>Web Server Settings</h3>
|
||||||
<div class="ui form">
|
<div class="ui form">
|
||||||
<div class="inline field">
|
<div class="inline field">
|
||||||
<div class="ui toggle checkbox webservRootDisabled">
|
<div class="ui toggle checkbox webservRootDisabled">
|
||||||
<input id="webserv_enable" type="checkbox" class="hidden">
|
<input id="webserv_enable" type="checkbox" class="hidden">
|
||||||
<label>Enable Static Web Server</label>
|
<label>Enable Static Web Server</label>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div class="inline field">
|
||||||
<div class="inline field">
|
<div class="ui toggle checkbox">
|
||||||
<div class="ui toggle checkbox">
|
<input id="webserv_enableDirList" type="checkbox" class="hidden">
|
||||||
<input id="webserv_enableDirList" type="checkbox" class="hidden">
|
<label>Enable Directory Listing</label>
|
||||||
<label>Enable Directory Listing</label>
|
<small>If this folder do not contains any index files, list the directory of this folder.</small>
|
||||||
<small>If this folder do not contains any index files, list the directory of this folder.</small>
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="field">
|
||||||
|
<label>Document Root Folder</label>
|
||||||
|
<input id="webserv_docRoot" type="text" readonly="true">
|
||||||
|
<small>
|
||||||
|
The web server root folder can only be changed via startup flags of zoraxy for security reasons.
|
||||||
|
See the -webserv flag for more details.
|
||||||
|
</small>
|
||||||
|
</div>
|
||||||
|
<div class="field webservRootDisabled">
|
||||||
|
<label>Port Number</label>
|
||||||
|
<input id="webserv_listenPort" type="number" step="1" min="0" max="65535" value="8081" onchange="updateWebServLinkExample(this.value);">
|
||||||
|
<small>Use <code>http://127.0.0.1:<span class="webserv_port">8081</span></code> in proxy rules to access the web server</small>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
<div class="field">
|
|
||||||
<label>Document Root Folder</label>
|
|
||||||
<input id="webserv_docRoot" type="text" readonly="true">
|
|
||||||
<small>
|
|
||||||
The web server root folder can only be changed via startup flags of zoraxy for security reasons.
|
|
||||||
See the -webserv flag for more details.
|
|
||||||
</small>
|
|
||||||
</div>
|
|
||||||
<div class="field webservRootDisabled">
|
|
||||||
<label>Port Number</label>
|
|
||||||
<input id="webserv_listenPort" type="number" step="1" min="0" max="65535" value="8081" onchange="updateWebServLinkExample(this.value);">
|
|
||||||
<small>Use <code>http://127.0.0.1:<span class="webserv_port">8081</span></code> in proxy rules to access the web server</small>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<small><i class="ui blue save icon"></i> Changes are saved automatically</small>
|
<small><i class="ui blue save icon"></i> Changes are saved automatically</small>
|
||||||
|
BIN
src/web/img/res/1F310.png
Normal file
After Width: | Height: | Size: 14 KiB |
BIN
src/web/img/res/1F387.png
Normal file
After Width: | Height: | Size: 10 KiB |
BIN
src/web/img/res/1F38A.png
Normal file
After Width: | Height: | Size: 11 KiB |
BIN
src/web/img/res/1F44B.png
Normal file
After Width: | Height: | Size: 14 KiB |
BIN
src/web/img/res/1F500.png
Normal file
After Width: | Height: | Size: 5.9 KiB |
BIN
src/web/img/res/1F512.png
Normal file
After Width: | Height: | Size: 15 KiB |
BIN
src/web/img/res/1F914.png
Normal file
After Width: | Height: | Size: 12 KiB |
BIN
src/web/img/res/2728.png
Normal file
After Width: | Height: | Size: 12 KiB |
BIN
src/web/img/res/2753.png
Normal file
After Width: | Height: | Size: 8.1 KiB |
BIN
src/web/img/res/E25E.png
Normal file
After Width: | Height: | Size: 4.4 KiB |
@ -36,6 +36,9 @@
|
|||||||
<div class="wrapper">
|
<div class="wrapper">
|
||||||
<div class="toolbar">
|
<div class="toolbar">
|
||||||
<div id="mainmenu" class="ui secondary vertical menu">
|
<div id="mainmenu" class="ui secondary vertical menu">
|
||||||
|
<a class="item" tag="qstart">
|
||||||
|
<i class="simplistic magic icon"></i>Quick Start
|
||||||
|
</a>
|
||||||
<a class="item active" tag="status">
|
<a class="item active" tag="status">
|
||||||
<i class="simplistic info circle icon"></i>Status
|
<i class="simplistic info circle icon"></i>Status
|
||||||
</a>
|
</a>
|
||||||
@ -92,6 +95,9 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="contentWindow">
|
<div class="contentWindow">
|
||||||
|
<!-- Quick Start -->
|
||||||
|
<div id="qstart" class="functiontab" target="quickstart.html"></div>
|
||||||
|
|
||||||
<!-- Status Tab -->
|
<!-- Status Tab -->
|
||||||
<div id="status" class="functiontab" target="status.html" style="display: block ;">
|
<div id="status" class="functiontab" target="status.html" style="display: block ;">
|
||||||
<br><br><div class="ui active centered inline loader"></div>
|
<br><br><div class="ui active centered inline loader"></div>
|
||||||
@ -171,6 +177,22 @@
|
|||||||
<div class="questionToConfirm">Confirm Exit?</div>
|
<div class="questionToConfirm">Confirm Exit?</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div id="tourModal" class="nofocus" position="center">
|
||||||
|
<h4 class="tourStepTitle">Welcome to Zoraxy Tour</h4>
|
||||||
|
<p class="tourStepContent">This is a simplified tour to show some of what it can do.
|
||||||
|
Use your keyboard or click the next button to get going.</p>
|
||||||
|
<div class="ui divider"></div>
|
||||||
|
<div class="ui equal width grid" align="center">
|
||||||
|
<div class="column"><button onclick="previousTourStep();" class="ui basic small disabled button tourStepButtonBack">Back</button></div>
|
||||||
|
<div class="column"><p style="margin-top: 0.4em">Steps <span class="tourStepCounter">1 / 9</span></p></div>
|
||||||
|
<div class="column nextStepAvaible"><button onclick="nextTourStep();" class="ui basic right floated small button tourStepButtonNext">Next</button></div>
|
||||||
|
<div class="column nextStepFinish"><button onclick="endTourFocus();" class="ui right floated small button tourStepButtonFinish">Finish</button></div>
|
||||||
|
</div>
|
||||||
|
<button onclick="endTourFocus();" class="ui circular small icon button tourCloseButton"><i class="ui times icon"></i></button>
|
||||||
|
</div>
|
||||||
|
<div id="tourModalOverlay" style="display:none;"></div>
|
||||||
|
|
||||||
<br><br>
|
<br><br>
|
||||||
<script>
|
<script>
|
||||||
$(".year").text(new Date().getFullYear());
|
$(".year").text(new Date().getFullYear());
|
||||||
|
164
src/web/main.css
@ -46,7 +46,7 @@ body.darkTheme{
|
|||||||
--button_border_color: #646464;
|
--button_border_color: #646464;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Theme Toggle Css */
|
/* Theme Toggle CSS */
|
||||||
#themeColorButton{
|
#themeColorButton{
|
||||||
background-color: black;
|
background-color: black;
|
||||||
color: var(--text_color_inverted);
|
color: var(--text_color_inverted);
|
||||||
@ -85,7 +85,6 @@ body{
|
|||||||
top: 0;
|
top: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
z-index: 10;
|
z-index: 10;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.menubar .logo{
|
.menubar .logo{
|
||||||
@ -154,7 +153,7 @@ body{
|
|||||||
right: 1em;
|
right: 1em;
|
||||||
display:none;
|
display:none;
|
||||||
max-width: 300px;
|
max-width: 300px;
|
||||||
z-index: 999;
|
z-index: 1000;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Confirm Box */
|
/* Confirm Box */
|
||||||
@ -519,6 +518,14 @@ body{
|
|||||||
display:none;
|
display:none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Default Site
|
||||||
|
*/
|
||||||
|
|
||||||
|
#setroot{
|
||||||
|
border-radius: 0.6em;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
HTTP Proxy & Virtual Directory
|
HTTP Proxy & Virtual Directory
|
||||||
*/
|
*/
|
||||||
@ -710,4 +717,153 @@ body{
|
|||||||
|
|
||||||
#traceroute_results::selection {
|
#traceroute_results::selection {
|
||||||
background: #a9d1f3;
|
background: #a9d1f3;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Quick Start Overview
|
||||||
|
*/
|
||||||
|
|
||||||
|
#quickstart .serviceOption{
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
padding: 1em;
|
||||||
|
background-color: rgb(240, 240, 240);
|
||||||
|
border-radius: 0.6em;
|
||||||
|
cursor: pointer;
|
||||||
|
min-height: 250px;
|
||||||
|
transition: opacity 0.1s ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#quickstart .serviceOption .activeOption{
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0.2em;
|
||||||
|
left: 0.2em;
|
||||||
|
display:none;
|
||||||
|
}
|
||||||
|
|
||||||
|
#quickstart .serviceOption.active .activeOption{
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#quickstart .serviceOption .titleWrapper{
|
||||||
|
text-align: center;
|
||||||
|
width: 100%;
|
||||||
|
font-weight: bolder;
|
||||||
|
font-size: 1.3em;
|
||||||
|
}
|
||||||
|
|
||||||
|
#quickstart .serviceOption :not(.titleWrapper){
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
#quickstart .serviceOption .themebackground{
|
||||||
|
opacity: 0.2;
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
margin-right: -1em;
|
||||||
|
margin-bottom: -2em;
|
||||||
|
pointer-events: none;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
#quickstart .serviceOption:not(.active):hover{
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
|
||||||
|
#quickstart .serviceOption.tls{
|
||||||
|
background: var(--theme_green);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
#quickstart .serviceOption.subdomain{
|
||||||
|
background: var(--theme_background);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
#quickstart .serviceOption.homepage{
|
||||||
|
background: var(--theme_background_inverted);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#quickstart .finished.ui.button{
|
||||||
|
background: var(--theme_green);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
#tourModal{
|
||||||
|
background-color: white;
|
||||||
|
border-radius: 0.6em;
|
||||||
|
padding: 1.4em;
|
||||||
|
position: fixed;
|
||||||
|
z-index: 999;
|
||||||
|
width: 380px;
|
||||||
|
display:none;
|
||||||
|
border: 1px solid rgb(230, 230, 230);
|
||||||
|
box-shadow: 3px 3px 11px -3px rgba(0,0,0,0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Locations of tourModal */
|
||||||
|
#tourModal[position="center"]{
|
||||||
|
top: 200px;
|
||||||
|
left: calc(50% - 190px);
|
||||||
|
}
|
||||||
|
|
||||||
|
#tourModal[position="topleft"]{
|
||||||
|
top: 4em;
|
||||||
|
left: 4em;
|
||||||
|
}
|
||||||
|
|
||||||
|
#tourModal[position="topright"]{
|
||||||
|
top: 4em;
|
||||||
|
right: 4em;
|
||||||
|
}
|
||||||
|
|
||||||
|
#tourModal[position="bottomleft"]{
|
||||||
|
bottom: 4em;
|
||||||
|
left: 4em;
|
||||||
|
}
|
||||||
|
|
||||||
|
#tourModal[position="bottomright"]{
|
||||||
|
bottom: 4em;
|
||||||
|
right: 4em;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#tourModal .tourStepButtonFinish{
|
||||||
|
background: var(--theme_green) !important;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#tourModal .tourCloseButton{
|
||||||
|
position: absolute;
|
||||||
|
top: 0em;
|
||||||
|
right: 0em;
|
||||||
|
margin-top: -0.6em;
|
||||||
|
margin-right: -0.6em;
|
||||||
|
}
|
||||||
|
|
||||||
|
#tourModal .nextStepFinish{
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
#tourModal.nofocus{
|
||||||
|
box-shadow: 0 0 0 max(100vh, 100vw) rgba(0, 0, 0, .3);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#tourModalOverlay{
|
||||||
|
position: fixed;
|
||||||
|
top: 10em;
|
||||||
|
left: 10em;
|
||||||
|
width: 300px;
|
||||||
|
height: 200px;
|
||||||
|
pointer-events: none;
|
||||||
|
z-index: 199;
|
||||||
|
border-radius: 0.6em;
|
||||||
|
box-shadow: 0 0 0 max(100vh, 100vw) rgba(0, 0, 0, .3);
|
||||||
|
}
|
387
src/web/script/quicksetup.js
Normal file
@ -0,0 +1,387 @@
|
|||||||
|
/*
|
||||||
|
Quick Setup Tour
|
||||||
|
|
||||||
|
This script file contains all the required script
|
||||||
|
for quick setup tour and walkthrough
|
||||||
|
*/
|
||||||
|
|
||||||
|
//tourStepFactory generate a function that renders the steps in tourModal
|
||||||
|
//Keys: {element, title, desc, tab, pos, scrollto, callback}
|
||||||
|
// elements -> Element (selector) to focus on
|
||||||
|
// tab -> Tab ID to switch pages
|
||||||
|
// pos -> Where to display the tour modal, {topleft, topright, bottomleft, bottomright, center}
|
||||||
|
// scrollto -> Element (selector) to scroll to, can be different from elements
|
||||||
|
function adjustTourModalOverlayToElement(element){;
|
||||||
|
if ($(element) == undefined || $(element).offset() == undefined){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let padding = 12;
|
||||||
|
$("#tourModalOverlay").css({
|
||||||
|
"top": $(element).offset().top - padding - $(document).scrollTop(),
|
||||||
|
"left": $(element).offset().left - padding,
|
||||||
|
"width": $(element).width() + 2 * padding,
|
||||||
|
"height": $(element).height() + 2 * padding,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
var tourOverlayUpdateTicker;
|
||||||
|
|
||||||
|
function tourStepFactory(config){
|
||||||
|
return function(){
|
||||||
|
//Check if this step require tab swap
|
||||||
|
if (config.tab != undefined && config.tab != ""){
|
||||||
|
//This tour require tab swap. call to openTabById
|
||||||
|
openTabById(config.tab);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config.element == undefined){
|
||||||
|
//No focused element in this step.
|
||||||
|
$(".tourFocusObject").removeClass("tourFocusObject");
|
||||||
|
$("#tourModal").addClass("nofocus");
|
||||||
|
$("#tourModalOverlay").hide();
|
||||||
|
|
||||||
|
//If there is a target element to scroll to
|
||||||
|
if (config.scrollto != undefined){
|
||||||
|
$('html, body').animate({
|
||||||
|
scrollTop: $(config.scrollto).offset().top - 100
|
||||||
|
}, 500);
|
||||||
|
}
|
||||||
|
|
||||||
|
}else{
|
||||||
|
|
||||||
|
let elementHighligher = function(){
|
||||||
|
//Match the overlay to element position and size
|
||||||
|
$(window).off("resize").on("resize", function(){
|
||||||
|
adjustTourModalOverlayToElement(config.element);
|
||||||
|
});
|
||||||
|
if (tourOverlayUpdateTicker != undefined){
|
||||||
|
clearInterval(tourOverlayUpdateTicker);
|
||||||
|
}
|
||||||
|
tourOverlayUpdateTicker = setInterval(function(){
|
||||||
|
adjustTourModalOverlayToElement(config.element);
|
||||||
|
}, 300);
|
||||||
|
adjustTourModalOverlayToElement(config.element);
|
||||||
|
$("#tourModalOverlay").fadeIn();
|
||||||
|
}
|
||||||
|
|
||||||
|
//Consists of focus element in this step
|
||||||
|
$(".tourFocusObject").removeClass("tourFocusObject");
|
||||||
|
$(config.element).addClass("tourFocusObject");
|
||||||
|
$("#tourModal").removeClass("nofocus");
|
||||||
|
$("#tourModalOverlay").hide();
|
||||||
|
//If there is a target element to scroll to
|
||||||
|
if (config.scrollto != undefined){
|
||||||
|
$('html, body').animate({
|
||||||
|
scrollTop: $(config.scrollto).offset().top - 100
|
||||||
|
}, 500, function(){
|
||||||
|
setTimeout(elementHighligher, 300);
|
||||||
|
});
|
||||||
|
}else{
|
||||||
|
setTimeout(elementHighligher, 300);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Get the modal location of this step
|
||||||
|
let showupZone = "center";
|
||||||
|
if (config.pos != undefined){
|
||||||
|
showupZone = config.pos
|
||||||
|
}
|
||||||
|
|
||||||
|
$("#tourModal").attr("position", showupZone);
|
||||||
|
|
||||||
|
$("#tourModal .tourStepTitle").html(config.title);
|
||||||
|
$("#tourModal .tourStepContent").html(config.desc);
|
||||||
|
if (config.callback != undefined){
|
||||||
|
config.callback();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function startQuickStartTour(){
|
||||||
|
if (currentQuickSetupClass == ""){
|
||||||
|
msgbox("No selected setup service tour", false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
//Show the tour modal
|
||||||
|
$("#tourModal").show();
|
||||||
|
//Load the tour steps
|
||||||
|
if (tourSteps[currentQuickSetupClass] == undefined || tourSteps[currentQuickSetupClass].length == 0){
|
||||||
|
//This tour is not defined or empty
|
||||||
|
let notFound = tourStepFactory({
|
||||||
|
title: "😭 Tour not found",
|
||||||
|
desc: "Seems you are requesting a tour that has not been developed yet. Check back on later!"
|
||||||
|
});
|
||||||
|
notFound();
|
||||||
|
|
||||||
|
//Enable the finish button
|
||||||
|
$("#tourModal .nextStepAvaible").hide();
|
||||||
|
$("#tourModal .nextStepFinish").show();
|
||||||
|
|
||||||
|
//Set step counter to 1
|
||||||
|
$("#tourModal .tourStepCounter").text("0 / 0");
|
||||||
|
return;
|
||||||
|
}else{
|
||||||
|
tourSteps[currentQuickSetupClass][0]();
|
||||||
|
}
|
||||||
|
|
||||||
|
updateTourStepCount();
|
||||||
|
|
||||||
|
//Disable the previous button
|
||||||
|
if (tourSteps[currentQuickSetupClass].length == 1){
|
||||||
|
//There are only 1 step in this tour
|
||||||
|
$("#tourModal .nextStepAvaible").hide();
|
||||||
|
$("#tourModal .nextStepFinish").show();
|
||||||
|
}else{
|
||||||
|
$("#tourModal .nextStepAvaible").show();
|
||||||
|
$("#tourModal .nextStepFinish").hide();
|
||||||
|
}
|
||||||
|
$("#tourModal .tourStepButtonBack").addClass("disabled");
|
||||||
|
|
||||||
|
//Disable body scroll and let tour steps to handle scrolling
|
||||||
|
$("body").css("overflow-y","hidden");
|
||||||
|
$("#mainmenu").css("pointer-events", "none");
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateTourStepCount(){
|
||||||
|
let tourlistLength = tourSteps[currentQuickSetupClass]==undefined?1:tourSteps[currentQuickSetupClass].length;
|
||||||
|
$("#tourModal .tourStepCounter").text((currentQuickSetupTourStep + 1) + " / " + tourlistLength);
|
||||||
|
}
|
||||||
|
|
||||||
|
function nextTourStep(){
|
||||||
|
//Add one to the tour steps
|
||||||
|
currentQuickSetupTourStep++;
|
||||||
|
if (currentQuickSetupTourStep == tourSteps[currentQuickSetupClass].length - 1){
|
||||||
|
//Already the last step
|
||||||
|
$("#tourModal .nextStepAvaible").hide();
|
||||||
|
$("#tourModal .nextStepFinish").show();
|
||||||
|
}
|
||||||
|
updateTourStepCount();
|
||||||
|
tourSteps[currentQuickSetupClass][currentQuickSetupTourStep]();
|
||||||
|
if (currentQuickSetupTourStep > 0){
|
||||||
|
$("#tourModal .tourStepButtonBack").removeClass("disabled");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function previousTourStep(){
|
||||||
|
if (currentQuickSetupTourStep > 0){
|
||||||
|
currentQuickSetupTourStep--;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentQuickSetupTourStep != tourSteps[currentQuickSetupClass].length - 1){
|
||||||
|
//Not at the last step
|
||||||
|
$("#tourModal .nextStepAvaible").show();
|
||||||
|
$("#tourModal .nextStepFinish").hide();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentQuickSetupTourStep == 0){
|
||||||
|
//Cant go back anymore
|
||||||
|
$("#tourModal .tourStepButtonBack").addClass("disabled");
|
||||||
|
}
|
||||||
|
updateTourStepCount();
|
||||||
|
tourSteps[currentQuickSetupClass][currentQuickSetupTourStep]();
|
||||||
|
}
|
||||||
|
|
||||||
|
//End tour and reset everything
|
||||||
|
function endTourFocus(){
|
||||||
|
$(".tourFocusObject").removeClass("tourFocusObject");
|
||||||
|
$(".serviceOption.active").removeClass("active");
|
||||||
|
currentQuickSetupClass = "";
|
||||||
|
currentQuickSetupTourStep = 0;
|
||||||
|
$("#tourModal").hide();
|
||||||
|
$("#tourModal .nextStepAvaible").show();
|
||||||
|
$("#tourModal .nextStepFinish").hide();
|
||||||
|
$("#tourModalOverlay").hide();
|
||||||
|
if (tourOverlayUpdateTicker != undefined){
|
||||||
|
clearInterval(tourOverlayUpdateTicker);
|
||||||
|
}
|
||||||
|
$("body").css("overflow-y","auto");
|
||||||
|
$("#mainmenu").css("pointer-events", "auto");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
var tourSteps = {
|
||||||
|
//Homepage steps
|
||||||
|
"homepage": [
|
||||||
|
tourStepFactory({
|
||||||
|
title: "🎉 Congratulation on your first site!",
|
||||||
|
desc: "In this tour, you will be guided through the steps required to setup a basic static website using your own domain name with Zoraxy."
|
||||||
|
}),
|
||||||
|
tourStepFactory({
|
||||||
|
title: "👉 Pointing domain DNS to Zoraxy's IP",
|
||||||
|
desc: `Setup a DNS A Record that points your domain name to this Zoraxy instances public IP address. <br>
|
||||||
|
Assume your public IP is 93.184.215.14, you should have an A record like this.
|
||||||
|
<table class="ui celled collapsing basic striped table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Name</th>
|
||||||
|
<th>Type</th>
|
||||||
|
<th>Value</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>yourdomain.com</td>
|
||||||
|
<td>A</td>
|
||||||
|
<td>93.184.215.14</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<br>If the IP of Zoraxy start from 192.168, you might want to use your router's public IP address and setup port forward for both port 80 and 443 as well.`,
|
||||||
|
callback: function(){
|
||||||
|
$.get("/api/acme/wizard?step=10", function(data){
|
||||||
|
if (data.error == undefined){
|
||||||
|
//Should return the public IP address from acme wizard
|
||||||
|
//Overwrite the sample IP address
|
||||||
|
let originalText = $("#tourModal .tourStepContent").html();
|
||||||
|
originalText = originalText.split("93.184.215.14").join(data);
|
||||||
|
$("#tourModal .tourStepContent").html(originalText);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
tourStepFactory({
|
||||||
|
title: "🏠 Setup Default Site",
|
||||||
|
desc: `If you already have an apache or nginx web server running, use "Reverse Proxy Target" and enter your current web server IP address. <br>Otherwise, pick "Internal Static Web Server" and click "Apply Change"`,
|
||||||
|
tab: "setroot",
|
||||||
|
element: "#setroot",
|
||||||
|
pos: "bottomright"
|
||||||
|
}),
|
||||||
|
tourStepFactory({
|
||||||
|
title: "🌐 Enable Static Web Server",
|
||||||
|
desc: `Enable the static web server if it is not already enabled. Skip this step if you are using external web servers like Apache or Nginx.`,
|
||||||
|
tab: "webserv",
|
||||||
|
element: "#webserv",
|
||||||
|
pos: "bottomright"
|
||||||
|
}),
|
||||||
|
tourStepFactory({
|
||||||
|
title: "📤 Upload Static Website",
|
||||||
|
desc: `Upload your static website files (e.g. HTML files) to the web directory. If remote access is not avaible, you can also upload it with the web server file manager here.`,
|
||||||
|
tab: "webserv",
|
||||||
|
element: "#webserv",
|
||||||
|
pos: "bottomright",
|
||||||
|
scrollto: "#webserv_dirManager"
|
||||||
|
}),
|
||||||
|
tourStepFactory({
|
||||||
|
title: "💡 Start Zoraxy HTTP listener",
|
||||||
|
desc: `Start Zoraxy (if it is not already running) by pressing the "Start Service" button.<br>You should now be able to visit your domain and see the static web server contents show up in your browser.`,
|
||||||
|
tab: "status",
|
||||||
|
element: "#status .poweroptions",
|
||||||
|
pos: "bottomright",
|
||||||
|
})
|
||||||
|
],
|
||||||
|
|
||||||
|
//Subdomains tour steps
|
||||||
|
"subdomain":[
|
||||||
|
tourStepFactory({
|
||||||
|
title: "🎉 Creating your first subdomain",
|
||||||
|
desc: "Seems you are now ready to expand your site with more services! To do so, you can create a new subdomain for your new web services. <br><br>In this tour, you will be guided through the steps to setup a new subdomain reverse proxy.",
|
||||||
|
pos: "center"
|
||||||
|
}),
|
||||||
|
tourStepFactory({
|
||||||
|
title: "👉 Pointing subdomain DNS to Zoraxy's IP",
|
||||||
|
desc: `Setup a DNS CNAME Record that points your subdomain to your root domain. <br>
|
||||||
|
Assume your public IP is 93.184.215.14, you should have an CNAME record like this.
|
||||||
|
<table class="ui celled collapsing basic striped table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Name</th>
|
||||||
|
<th>Type</th>
|
||||||
|
<th>Value</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>example.com</td>
|
||||||
|
<td>A</td>
|
||||||
|
<td>93.184.215.14</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>sub.example.com</td>
|
||||||
|
<td>CNAME</td>
|
||||||
|
<td>example.com</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>`,
|
||||||
|
callback: function(){
|
||||||
|
$.get("/api/acme/wizard?step=10", function(data){
|
||||||
|
if (data.error == undefined){
|
||||||
|
//Should return the public IP address from acme wizard
|
||||||
|
//Overwrite the sample IP address
|
||||||
|
let originalText = $("#tourModal .tourStepContent").html();
|
||||||
|
originalText = originalText.split("93.184.215.14").join(data);
|
||||||
|
$("#tourModal .tourStepContent").html(originalText);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
tourStepFactory({
|
||||||
|
title: "➕ Create New Proxy Rule",
|
||||||
|
desc: `Next, you can now move on to create a proxy rule that reverse proxy your new subdomain in Zoraxy. You can easily add new rules using the "New Proxy Rule" web form.`,
|
||||||
|
tab: "rules",
|
||||||
|
pos: "topright"
|
||||||
|
}),
|
||||||
|
tourStepFactory({
|
||||||
|
title: "🌐 Matching Keyword / Domain",
|
||||||
|
desc: `Fill in your new subdomain in the "Matching Keyword / Domain" field.<br> e.g. sub.example.com`,
|
||||||
|
element: "#rules .field[tourstep='matchingkeyword']",
|
||||||
|
pos: "bottomright"
|
||||||
|
}),
|
||||||
|
tourStepFactory({
|
||||||
|
title: "🖥️ Target IP Address or Domain Name with port",
|
||||||
|
desc: `Fill in the Reverse Proxy Destination. e.g. localhost:8080 or 192.168.1.100:9096. <br><br>Please make sure your web services is accessible by Zoraxy.`,
|
||||||
|
element: "#rules .field[tourstep='targetdomain']",
|
||||||
|
pos: "bottomright"
|
||||||
|
}),
|
||||||
|
tourStepFactory({
|
||||||
|
title: "🔐 Proxy Target require TLS Connection",
|
||||||
|
desc: `If your upstream service only accept https connection, select this option.`,
|
||||||
|
element: "#rules .field[tourstep='requireTLS']",
|
||||||
|
pos: "bottomright",
|
||||||
|
|
||||||
|
}),
|
||||||
|
tourStepFactory({
|
||||||
|
title: "🔓 Ignore TLS Validation Error",
|
||||||
|
desc: `Some open source projects like Proxmox or NextCloud use self-signed certificate to serve its web UI. If you are proxying services like that, enable this option. `,
|
||||||
|
element: "#rules #advanceProxyRules .field[tourstep='skipTLSValidation']",
|
||||||
|
scrollto: "#rules #advanceProxyRules",
|
||||||
|
pos: "bottomright",
|
||||||
|
callback: function(){
|
||||||
|
$("#advanceProxyRules").accordion();
|
||||||
|
if (!$("#rules #advanceProxyRules .content").is(":visible")){
|
||||||
|
//Open up the advance config menu
|
||||||
|
$("#rules #advanceProxyRules .title")[0].click()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
tourStepFactory({
|
||||||
|
title: "💾 Save New Proxy Rule",
|
||||||
|
desc: `Now, click "Create Endpoint" to add this reverse proxy rule to runtime.`,
|
||||||
|
element: "#rules div[tourstep='newProxyRule']",
|
||||||
|
scrollto: "#rules div[tourstep='newProxyRule']",
|
||||||
|
pos: "topright",
|
||||||
|
}),
|
||||||
|
tourStepFactory({
|
||||||
|
title: "🎉 New Proxy Rule Setup Completed!",
|
||||||
|
desc: `You can continue to add more subdomains or alias domain using this web form. To view the created reverse proxy rules, you can navigate to the HTTP Proxy tab.`,
|
||||||
|
element: "#rules",
|
||||||
|
tab: "rules",
|
||||||
|
pos: "bottomright",
|
||||||
|
}),
|
||||||
|
tourStepFactory({
|
||||||
|
title: "🌲 HTTP Proxy List",
|
||||||
|
desc: `In this tab, you will see all the created HTTP proxy rules and edit them if needed. You should see your newly created HTTP proxy rule in the above list. <Br><Br>
|
||||||
|
This is the end of this tour. If you want further documentation on how to setup access control filters or load balancer, check out our Github Wiki page.`,
|
||||||
|
element: "#httprp",
|
||||||
|
tab: "httprp",
|
||||||
|
pos: "bottomright",
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
|
||||||
|
//TLS and ACME tour steps
|
||||||
|
"tls":[
|
||||||
|
|
||||||
|
],
|
||||||
|
}
|