quicksetup.js 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506
  1. /*
  2. Quick Setup Tour
  3. This script file contains all the required script
  4. for quick setup tour and walkthrough
  5. */
  6. //tourStepFactory generate a function that renders the steps in tourModal
  7. //Keys: {element, title, desc, tab, pos, scrollto, callback}
  8. // elements -> Element (selector) to focus on
  9. // tab -> Tab ID to switch pages
  10. // pos -> Where to display the tour modal, {topleft, topright, bottomleft, bottomright, center}
  11. // scrollto -> Element (selector) to scroll to, can be different from elements
  12. // ignoreVisiableCheck -> Force highlight even if element is currently not visable
  13. function adjustTourModalOverlayToElement(element){;
  14. if ($(element) == undefined || $(element).offset() == undefined){
  15. return;
  16. }
  17. let padding = 12;
  18. $("#tourModalOverlay").css({
  19. "top": $(element).offset().top - padding - $(document).scrollTop(),
  20. "left": $(element).offset().left - padding,
  21. "width": $(element).width() + 2 * padding,
  22. "height": $(element).height() + 2 * padding,
  23. });
  24. }
  25. var tourOverlayUpdateTicker;
  26. function tourStepFactory(config){
  27. return function(){
  28. //Check if this step require tab swap
  29. if (config.tab != undefined && config.tab != ""){
  30. //This tour require tab swap. call to openTabById
  31. openTabById(config.tab);
  32. }
  33. if (config.ignoreVisiableCheck == undefined){
  34. config.ignoreVisiableCheck = false;
  35. }
  36. if (config.element == undefined || (!$(config.element).is(":visible") && !config.ignoreVisiableCheck)){
  37. //No focused element in this step.
  38. $(".tourFocusObject").removeClass("tourFocusObject");
  39. $("#tourModal").addClass("nofocus");
  40. $("#tourModalOverlay").hide();
  41. //If there is a target element to scroll to
  42. if (config.scrollto != undefined){
  43. $('html, body').animate({
  44. scrollTop: $(config.scrollto).offset().top - 100
  45. }, 500);
  46. }
  47. }else{
  48. let elementHighligher = function(){
  49. //Match the overlay to element position and size
  50. $(window).off("resize").on("resize", function(){
  51. adjustTourModalOverlayToElement(config.element);
  52. });
  53. if (tourOverlayUpdateTicker != undefined){
  54. clearInterval(tourOverlayUpdateTicker);
  55. }
  56. tourOverlayUpdateTicker = setInterval(function(){
  57. adjustTourModalOverlayToElement(config.element);
  58. }, 500);
  59. adjustTourModalOverlayToElement(config.element);
  60. $("#tourModalOverlay").fadeIn();
  61. }
  62. //Consists of focus element in this step
  63. $(".tourFocusObject").removeClass("tourFocusObject");
  64. $(config.element).addClass("tourFocusObject");
  65. $("#tourModal").removeClass("nofocus");
  66. $("#tourModalOverlay").hide();
  67. //If there is a target element to scroll to
  68. if (config.scrollto != undefined){
  69. $('html, body').animate({
  70. scrollTop: $(config.scrollto).offset().top - 100
  71. }, 300, function(){
  72. setTimeout(elementHighligher, 300);
  73. });
  74. }else{
  75. setTimeout(elementHighligher, 300);
  76. }
  77. }
  78. //Get the modal location of this step
  79. let showupZone = "center";
  80. if (config.pos != undefined){
  81. showupZone = config.pos
  82. }
  83. $("#tourModal").attr("position", showupZone);
  84. $("#tourModal .tourStepTitle").html(config.title);
  85. $("#tourModal .tourStepContent").html(config.desc);
  86. if (config.callback != undefined){
  87. config.callback();
  88. }
  89. }
  90. }
  91. //Hide the side warpper in tour mode and prevent body from restoring to
  92. //overflow scroll mode
  93. function hideSideWrapperInTourMode(){
  94. hideSideWrapper(); //Call to index.html hide side wrapper function
  95. $("body").css("overflow", "hidden"); //Restore overflow state
  96. }
  97. function startQuickStartTour(){
  98. if (currentQuickSetupClass == ""){
  99. msgbox("No selected setup service tour", false);
  100. return;
  101. }
  102. //Show the tour modal
  103. $("#tourModal").show();
  104. //Load the tour steps
  105. if (tourSteps[currentQuickSetupClass] == undefined || tourSteps[currentQuickSetupClass].length == 0){
  106. //This tour is not defined or empty
  107. let notFound = tourStepFactory({
  108. title: "😭 Tour not found",
  109. desc: "Seems you are requesting a tour that has not been developed yet. Check back on later!"
  110. });
  111. notFound();
  112. //Enable the finish button
  113. $("#tourModal .nextStepAvaible").hide();
  114. $("#tourModal .nextStepFinish").show();
  115. //Set step counter to 1
  116. $("#tourModal .tourStepCounter").text("0 / 0");
  117. return;
  118. }else{
  119. tourSteps[currentQuickSetupClass][0]();
  120. }
  121. updateTourStepCount();
  122. //Disable the previous button
  123. if (tourSteps[currentQuickSetupClass].length == 1){
  124. //There are only 1 step in this tour
  125. $("#tourModal .nextStepAvaible").hide();
  126. $("#tourModal .nextStepFinish").show();
  127. }else{
  128. $("#tourModal .nextStepAvaible").show();
  129. $("#tourModal .nextStepFinish").hide();
  130. }
  131. $("#tourModal .tourStepButtonBack").addClass("disabled");
  132. //Disable body scroll and let tour steps to handle scrolling
  133. $("body").css("overflow-y","hidden");
  134. $("#mainmenu").css("pointer-events", "none");
  135. }
  136. function updateTourStepCount(){
  137. let tourlistLength = tourSteps[currentQuickSetupClass]==undefined?1:tourSteps[currentQuickSetupClass].length;
  138. $("#tourModal .tourStepCounter").text((currentQuickSetupTourStep + 1) + " / " + tourlistLength);
  139. }
  140. function nextTourStep(){
  141. //Add one to the tour steps
  142. currentQuickSetupTourStep++;
  143. if (currentQuickSetupTourStep == tourSteps[currentQuickSetupClass].length - 1){
  144. //Already the last step
  145. $("#tourModal .nextStepAvaible").hide();
  146. $("#tourModal .nextStepFinish").show();
  147. }
  148. updateTourStepCount();
  149. tourSteps[currentQuickSetupClass][currentQuickSetupTourStep]();
  150. if (currentQuickSetupTourStep > 0){
  151. $("#tourModal .tourStepButtonBack").removeClass("disabled");
  152. }
  153. }
  154. function previousTourStep(){
  155. if (currentQuickSetupTourStep > 0){
  156. currentQuickSetupTourStep--;
  157. }
  158. if (currentQuickSetupTourStep != tourSteps[currentQuickSetupClass].length - 1){
  159. //Not at the last step
  160. $("#tourModal .nextStepAvaible").show();
  161. $("#tourModal .nextStepFinish").hide();
  162. }
  163. if (currentQuickSetupTourStep == 0){
  164. //Cant go back anymore
  165. $("#tourModal .tourStepButtonBack").addClass("disabled");
  166. }
  167. updateTourStepCount();
  168. tourSteps[currentQuickSetupClass][currentQuickSetupTourStep]();
  169. }
  170. //End tour and reset everything
  171. function endTourFocus(){
  172. $(".tourFocusObject").removeClass("tourFocusObject");
  173. $(".serviceOption.active").removeClass("active");
  174. currentQuickSetupClass = "";
  175. currentQuickSetupTourStep = 0;
  176. $("#tourModal").hide();
  177. $("#tourModal .nextStepAvaible").show();
  178. $("#tourModal .nextStepFinish").hide();
  179. $("#tourModalOverlay").hide();
  180. if (tourOverlayUpdateTicker != undefined){
  181. clearInterval(tourOverlayUpdateTicker);
  182. }
  183. $("body").css("overflow-y","auto");
  184. $("#mainmenu").css("pointer-events", "auto");
  185. }
  186. var tourSteps = {
  187. //Homepage steps
  188. "homepage": [
  189. tourStepFactory({
  190. title: "🎉 Congratulation on your first site!",
  191. 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."
  192. }),
  193. tourStepFactory({
  194. title: "👉 Pointing domain DNS to Zoraxy's IP",
  195. desc: `Setup a DNS A Record that points your domain name to this Zoraxy instances public IP address. <br>
  196. Assume your public IP is 93.184.215.14, you should have an A record like this.
  197. <table class="ui celled collapsing basic striped table">
  198. <thead>
  199. <tr>
  200. <th>Name</th>
  201. <th>Type</th>
  202. <th>Value</th>
  203. </tr>
  204. </thead>
  205. <tbody>
  206. <tr>
  207. <td>yourdomain.com</td>
  208. <td>A</td>
  209. <td>93.184.215.14</td>
  210. </tr>
  211. </tbody>
  212. </table>
  213. <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.`,
  214. callback: function(){
  215. $.get("/api/acme/wizard?step=10", function(data){
  216. if (data.error == undefined){
  217. //Should return the public IP address from acme wizard
  218. //Overwrite the sample IP address
  219. let originalText = $("#tourModal .tourStepContent").html();
  220. originalText = originalText.split("93.184.215.14").join(data);
  221. $("#tourModal .tourStepContent").html(originalText);
  222. }
  223. })
  224. }
  225. }),
  226. tourStepFactory({
  227. title: "🏠 Setup Default Site",
  228. 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"`,
  229. tab: "setroot",
  230. element: "#setroot",
  231. pos: "bottomright"
  232. }),
  233. tourStepFactory({
  234. title: "🌐 Enable Static Web Server",
  235. 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.`,
  236. tab: "webserv",
  237. element: "#webserv",
  238. pos: "bottomright"
  239. }),
  240. tourStepFactory({
  241. title: "📤 Upload Static Website",
  242. 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.`,
  243. tab: "webserv",
  244. element: "#webserv_dirManager",
  245. pos: "bottomright",
  246. scrollto: "#webserv_dirManager"
  247. }),
  248. tourStepFactory({
  249. title: "💡 Start Zoraxy HTTP listener",
  250. 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.`,
  251. tab: "status",
  252. element: "#status .poweroptions",
  253. pos: "bottomright",
  254. })
  255. ],
  256. //Subdomains tour steps
  257. "subdomain":[
  258. tourStepFactory({
  259. title: "🎉 Creating your first subdomain",
  260. 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.",
  261. pos: "center"
  262. }),
  263. tourStepFactory({
  264. title: "👉 Pointing subdomain DNS to Zoraxy's IP",
  265. desc: `Setup a DNS CNAME Record that points your subdomain to your root domain. <br>
  266. Assume your public IP is 93.184.215.14, you should have an CNAME record like this.
  267. <table class="ui celled collapsing basic striped table">
  268. <thead>
  269. <tr>
  270. <th>Name</th>
  271. <th>Type</th>
  272. <th>Value</th>
  273. </tr>
  274. </thead>
  275. <tbody>
  276. <tr>
  277. <td>example.com</td>
  278. <td>A</td>
  279. <td>93.184.215.14</td>
  280. </tr>
  281. <tr>
  282. <td>sub.example.com</td>
  283. <td>CNAME</td>
  284. <td>example.com</td>
  285. </tr>
  286. </tbody>
  287. </table>`,
  288. callback: function(){
  289. $.get("/api/acme/wizard?step=10", function(data){
  290. if (data.error == undefined){
  291. //Should return the public IP address from acme wizard
  292. //Overwrite the sample IP address
  293. let originalText = $("#tourModal .tourStepContent").html();
  294. originalText = originalText.split("93.184.215.14").join(data);
  295. $("#tourModal .tourStepContent").html(originalText);
  296. }
  297. })
  298. }
  299. }),
  300. tourStepFactory({
  301. title: "➕ Create New Proxy Rule",
  302. 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.`,
  303. tab: "rules",
  304. pos: "topright"
  305. }),
  306. tourStepFactory({
  307. title: "🌐 Matching Keyword / Domain",
  308. desc: `Fill in your new subdomain in the "Matching Keyword / Domain" field.<br> e.g. sub.example.com`,
  309. element: "#rules .field[tourstep='matchingkeyword']",
  310. pos: "bottomright"
  311. }),
  312. tourStepFactory({
  313. title: "🖥️ Target IP Address or Domain Name with port",
  314. 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.`,
  315. element: "#rules .field[tourstep='targetdomain']",
  316. pos: "bottomright"
  317. }),
  318. tourStepFactory({
  319. title: "🔐 Proxy Target require TLS Connection",
  320. desc: `If your upstream service only accept https connection, select this option.`,
  321. element: "#rules .field[tourstep='requireTLS']",
  322. pos: "bottomright",
  323. }),
  324. tourStepFactory({
  325. title: "🔓 Ignore TLS Validation Error",
  326. 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. `,
  327. element: "#rules #advanceProxyRules .field[tourstep='skipTLSValidation']",
  328. scrollto: "#rules #advanceProxyRules",
  329. pos: "bottomright",
  330. ignoreVisiableCheck: true,
  331. callback: function(){
  332. $("#advanceProxyRules").accordion();
  333. if (!$("#rules #advanceProxyRules .content").is(":visible")){
  334. //Open up the advance config menu
  335. $("#rules #advanceProxyRules .title")[0].click()
  336. }
  337. }
  338. }),
  339. tourStepFactory({
  340. title: "💾 Save New Proxy Rule",
  341. desc: `Now, click "Create Endpoint" to add this reverse proxy rule to runtime.`,
  342. element: "#rules div[tourstep='newProxyRule']",
  343. scrollto: "#rules div[tourstep='newProxyRule']",
  344. pos: "topright",
  345. }),
  346. tourStepFactory({
  347. title: "🎉 New Proxy Rule Setup Completed!",
  348. 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.`,
  349. element: "#rules",
  350. tab: "rules",
  351. pos: "bottomright",
  352. }),
  353. tourStepFactory({
  354. title: "🌲 HTTP Proxy List",
  355. 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>
  356. 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.`,
  357. element: "#httprp",
  358. tab: "httprp",
  359. pos: "bottomright",
  360. }),
  361. ],
  362. //TLS and ACME tour steps
  363. "tls":[
  364. tourStepFactory({
  365. title: "🔐 Enable HTTPS (TLS) for your site",
  366. desc: `Some technologies only work with HTTPS for security reasons. In this tour, you will be guided through the steps to enable HTTPS in Zoraxy.`,
  367. pos: "center",
  368. }),
  369. tourStepFactory({
  370. title: "➡️ Change Listening Port",
  371. desc: `HTTPS listen on port 443 instead of 80. If your Zoraxy is currently listening to ports other than 443, change it to 443 in incoming port option and click "Apply"`,
  372. tab: "status",
  373. element: "#status div[tourstep='incomingPort']",
  374. scrollto: "#status div[tourstep='incomingPort']",
  375. pos: "bottomright",
  376. }),
  377. tourStepFactory({
  378. title: "🔑 Enable TLS Serving",
  379. desc: `Next, you can enable TLS by checking the "Use TLS to serve proxy request"`,
  380. element: "#tls",
  381. scrollto: "#tls",
  382. pos: "bottomright",
  383. }),
  384. tourStepFactory({
  385. title: "💻 Enable HTTP Server on Port 80",
  386. desc: `As we might want some proxy rules to be accessible by HTTP, turn on the HTTP server listener on port 80 as well.`,
  387. element: "#listenP80",
  388. scrollto: "#tls",
  389. pos: "bottomright",
  390. }),
  391. tourStepFactory({
  392. title: "↩️ Force redirect HTTP request to HTTPS",
  393. desc: `By default, if a HTTP host-name is not found, 404 error page will be returned. However, in common scenerio for self-hosting, you might want to redirect that request to your HTTPS server instead. <br><br>Enabling this option allows such redirection to be done automatically.`,
  394. element: "#status div[tourstep='forceHttpsRedirect']",
  395. scrollto: "#tls",
  396. pos: "bottomright",
  397. }),
  398. tourStepFactory({
  399. title: "🎉 HTTPS Enabled!",
  400. desc: `Now, your Zoraxy instance is ready to serve HTTPS requests.
  401. <br><br>By default, Zoraxy serve all your host-names by its internal self-signed certificate which is not a proper setup. That is why you will need to request a proper certificate for your site from your ISP or CA. `,
  402. tab: "status",
  403. pos: "center",
  404. }),
  405. tourStepFactory({
  406. title: "🔐 TLS / SSL Certificates",
  407. desc: `Zoraxy come with a simple and handy TLS management interface, where you can upload or request your certificates with a web form. You can click "TLS / SSL Certificate" from the side menu to open this page.`,
  408. tab: "cert",
  409. element: "#mainmenu",
  410. pos: "center",
  411. }),
  412. tourStepFactory({
  413. title: "📤 Uploading Fallback (Default) Certificate",
  414. desc: `If you are using Cloudflare, you can upload the Cloudflare (full) strict mode certificate in the "Fallback Certificate" section and let Cloudflare handle all the remaining certificate dispatch. <br><br>
  415. Public key usually use a file extension of .pub or .pem, and private key usually ends with .key.`,
  416. element: "#cert div[tourstep='defaultCertificate']",
  417. scrollto: "#cert div[tourstep='defaultCertificate']",
  418. pos: "bottomright",
  419. }),
  420. tourStepFactory({
  421. title: "⚙️ Setup ACME",
  422. desc: `If you didn't want to pay for a certificate, there are free CA where you can use to obtain a certificate. By default, Let's Encrypt is used and in order to use their service, you will need to fill in your webmin contact email in the "ACME EMAIL" field.
  423. <br><br> After you are done, click "Save Settings" and continue.`,
  424. element: "#cert div[tourstep='acmeSettings']",
  425. scrollto: "#cert div[tourstep='acmeSettings']",
  426. pos: "bottomright",
  427. }),
  428. tourStepFactory({
  429. title: "👉 Open ACME Tool",
  430. desc: `Open the ACME Tool by pressing the button below the ACME settings. You will see a tool window popup from the side.`,
  431. element: ".sideWrapper",
  432. pos: "center",
  433. callback: function(){
  434. //Call to function in cert.html
  435. openACMEManager();
  436. }
  437. }),
  438. tourStepFactory({
  439. title: "📃 Obtain Certificate with ACME",
  440. desc: `Now, we can finally start requesting a free certificate from the selected CA. Fill in the "Generate New Certificate" web-form and click <b>"Get Certificate"</b>.
  441. This usually will takes a few minutes. Wait until the spinning icon disappear before moving on the next step.
  442. <br><br>Tips: You can check the "Use DNS Challenge" if you are trying to request a certificate containing wildcard character (*).`,
  443. element: ".sideWrapper",
  444. pos: "topleft",
  445. }),
  446. tourStepFactory({
  447. title: "🔄 Enable Auto Renew",
  448. desc:`Free certificate only last for a few months. If you want Zoraxy to help you automate the certificate renew process, enable "Auto Renew" by clicking the <b>"Enable Certificate Auto Renew"</b> toggle switch.
  449. <br><br>You can fine tune which certificate to renew in the "Advance Renew Policy" dropdown.`,
  450. element: ".sideWrapper",
  451. pos: "bottomleft",
  452. callback: function(){
  453. //If the user arrive this step from "Back"
  454. if (!$(".sideWrapper").is(":visible")){
  455. openACMEManager();
  456. }
  457. }
  458. }),
  459. tourStepFactory({
  460. title: "🎉 Certificate Installed!",
  461. desc:`Now, your certificate is loaded into the database and it is ready to use! In Zoraxy, you do not need to manually assign the certificate to a domain. Zoraxy will do that automatically for you.
  462. <br><br>Now, you can try to visit your website with https:// and see your green lock shows up next to your domain name!`,
  463. element: "#cert div[tourstep='certTable']",
  464. scrollto: "#cert div[tourstep='certTable']",
  465. pos: "bottomright",
  466. callback: function(){
  467. hideSideWrapperInTourMode();
  468. }
  469. }),
  470. ],
  471. }