quicksetup.js 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387
  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. function adjustTourModalOverlayToElement(element){;
  13. if ($(element) == undefined || $(element).offset() == undefined){
  14. return;
  15. }
  16. let padding = 12;
  17. $("#tourModalOverlay").css({
  18. "top": $(element).offset().top - padding - $(document).scrollTop(),
  19. "left": $(element).offset().left - padding,
  20. "width": $(element).width() + 2 * padding,
  21. "height": $(element).height() + 2 * padding,
  22. });
  23. }
  24. var tourOverlayUpdateTicker;
  25. function tourStepFactory(config){
  26. return function(){
  27. //Check if this step require tab swap
  28. if (config.tab != undefined && config.tab != ""){
  29. //This tour require tab swap. call to openTabById
  30. openTabById(config.tab);
  31. }
  32. if (config.element == undefined){
  33. //No focused element in this step.
  34. $(".tourFocusObject").removeClass("tourFocusObject");
  35. $("#tourModal").addClass("nofocus");
  36. $("#tourModalOverlay").hide();
  37. //If there is a target element to scroll to
  38. if (config.scrollto != undefined){
  39. $('html, body').animate({
  40. scrollTop: $(config.scrollto).offset().top - 100
  41. }, 500);
  42. }
  43. }else{
  44. let elementHighligher = function(){
  45. //Match the overlay to element position and size
  46. $(window).off("resize").on("resize", function(){
  47. adjustTourModalOverlayToElement(config.element);
  48. });
  49. if (tourOverlayUpdateTicker != undefined){
  50. clearInterval(tourOverlayUpdateTicker);
  51. }
  52. tourOverlayUpdateTicker = setInterval(function(){
  53. adjustTourModalOverlayToElement(config.element);
  54. }, 300);
  55. adjustTourModalOverlayToElement(config.element);
  56. $("#tourModalOverlay").fadeIn();
  57. }
  58. //Consists of focus element in this step
  59. $(".tourFocusObject").removeClass("tourFocusObject");
  60. $(config.element).addClass("tourFocusObject");
  61. $("#tourModal").removeClass("nofocus");
  62. $("#tourModalOverlay").hide();
  63. //If there is a target element to scroll to
  64. if (config.scrollto != undefined){
  65. $('html, body').animate({
  66. scrollTop: $(config.scrollto).offset().top - 100
  67. }, 500, function(){
  68. setTimeout(elementHighligher, 300);
  69. });
  70. }else{
  71. setTimeout(elementHighligher, 300);
  72. }
  73. }
  74. //Get the modal location of this step
  75. let showupZone = "center";
  76. if (config.pos != undefined){
  77. showupZone = config.pos
  78. }
  79. $("#tourModal").attr("position", showupZone);
  80. $("#tourModal .tourStepTitle").html(config.title);
  81. $("#tourModal .tourStepContent").html(config.desc);
  82. if (config.callback != undefined){
  83. config.callback();
  84. }
  85. }
  86. }
  87. function startQuickStartTour(){
  88. if (currentQuickSetupClass == ""){
  89. msgbox("No selected setup service tour", false);
  90. return;
  91. }
  92. //Show the tour modal
  93. $("#tourModal").show();
  94. //Load the tour steps
  95. if (tourSteps[currentQuickSetupClass] == undefined || tourSteps[currentQuickSetupClass].length == 0){
  96. //This tour is not defined or empty
  97. let notFound = tourStepFactory({
  98. title: "😭 Tour not found",
  99. desc: "Seems you are requesting a tour that has not been developed yet. Check back on later!"
  100. });
  101. notFound();
  102. //Enable the finish button
  103. $("#tourModal .nextStepAvaible").hide();
  104. $("#tourModal .nextStepFinish").show();
  105. //Set step counter to 1
  106. $("#tourModal .tourStepCounter").text("0 / 0");
  107. return;
  108. }else{
  109. tourSteps[currentQuickSetupClass][0]();
  110. }
  111. updateTourStepCount();
  112. //Disable the previous button
  113. if (tourSteps[currentQuickSetupClass].length == 1){
  114. //There are only 1 step in this tour
  115. $("#tourModal .nextStepAvaible").hide();
  116. $("#tourModal .nextStepFinish").show();
  117. }else{
  118. $("#tourModal .nextStepAvaible").show();
  119. $("#tourModal .nextStepFinish").hide();
  120. }
  121. $("#tourModal .tourStepButtonBack").addClass("disabled");
  122. //Disable body scroll and let tour steps to handle scrolling
  123. $("body").css("overflow-y","hidden");
  124. $("#mainmenu").css("pointer-events", "none");
  125. }
  126. function updateTourStepCount(){
  127. let tourlistLength = tourSteps[currentQuickSetupClass]==undefined?1:tourSteps[currentQuickSetupClass].length;
  128. $("#tourModal .tourStepCounter").text((currentQuickSetupTourStep + 1) + " / " + tourlistLength);
  129. }
  130. function nextTourStep(){
  131. //Add one to the tour steps
  132. currentQuickSetupTourStep++;
  133. if (currentQuickSetupTourStep == tourSteps[currentQuickSetupClass].length - 1){
  134. //Already the last step
  135. $("#tourModal .nextStepAvaible").hide();
  136. $("#tourModal .nextStepFinish").show();
  137. }
  138. updateTourStepCount();
  139. tourSteps[currentQuickSetupClass][currentQuickSetupTourStep]();
  140. if (currentQuickSetupTourStep > 0){
  141. $("#tourModal .tourStepButtonBack").removeClass("disabled");
  142. }
  143. }
  144. function previousTourStep(){
  145. if (currentQuickSetupTourStep > 0){
  146. currentQuickSetupTourStep--;
  147. }
  148. if (currentQuickSetupTourStep != tourSteps[currentQuickSetupClass].length - 1){
  149. //Not at the last step
  150. $("#tourModal .nextStepAvaible").show();
  151. $("#tourModal .nextStepFinish").hide();
  152. }
  153. if (currentQuickSetupTourStep == 0){
  154. //Cant go back anymore
  155. $("#tourModal .tourStepButtonBack").addClass("disabled");
  156. }
  157. updateTourStepCount();
  158. tourSteps[currentQuickSetupClass][currentQuickSetupTourStep]();
  159. }
  160. //End tour and reset everything
  161. function endTourFocus(){
  162. $(".tourFocusObject").removeClass("tourFocusObject");
  163. $(".serviceOption.active").removeClass("active");
  164. currentQuickSetupClass = "";
  165. currentQuickSetupTourStep = 0;
  166. $("#tourModal").hide();
  167. $("#tourModal .nextStepAvaible").show();
  168. $("#tourModal .nextStepFinish").hide();
  169. $("#tourModalOverlay").hide();
  170. if (tourOverlayUpdateTicker != undefined){
  171. clearInterval(tourOverlayUpdateTicker);
  172. }
  173. $("body").css("overflow-y","auto");
  174. $("#mainmenu").css("pointer-events", "auto");
  175. }
  176. var tourSteps = {
  177. //Homepage steps
  178. "homepage": [
  179. tourStepFactory({
  180. title: "🎉 Congratulation on your first site!",
  181. 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."
  182. }),
  183. tourStepFactory({
  184. title: "👉 Pointing domain DNS to Zoraxy's IP",
  185. desc: `Setup a DNS A Record that points your domain name to this Zoraxy instances public IP address. <br>
  186. Assume your public IP is 93.184.215.14, you should have an A record like this.
  187. <table class="ui celled collapsing basic striped table">
  188. <thead>
  189. <tr>
  190. <th>Name</th>
  191. <th>Type</th>
  192. <th>Value</th>
  193. </tr>
  194. </thead>
  195. <tbody>
  196. <tr>
  197. <td>yourdomain.com</td>
  198. <td>A</td>
  199. <td>93.184.215.14</td>
  200. </tr>
  201. </tbody>
  202. </table>
  203. <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.`,
  204. callback: function(){
  205. $.get("/api/acme/wizard?step=10", function(data){
  206. if (data.error == undefined){
  207. //Should return the public IP address from acme wizard
  208. //Overwrite the sample IP address
  209. let originalText = $("#tourModal .tourStepContent").html();
  210. originalText = originalText.split("93.184.215.14").join(data);
  211. $("#tourModal .tourStepContent").html(originalText);
  212. }
  213. })
  214. }
  215. }),
  216. tourStepFactory({
  217. title: "🏠 Setup Default Site",
  218. 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"`,
  219. tab: "setroot",
  220. element: "#setroot",
  221. pos: "bottomright"
  222. }),
  223. tourStepFactory({
  224. title: "🌐 Enable Static Web Server",
  225. 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.`,
  226. tab: "webserv",
  227. element: "#webserv",
  228. pos: "bottomright"
  229. }),
  230. tourStepFactory({
  231. title: "📤 Upload Static Website",
  232. 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.`,
  233. tab: "webserv",
  234. element: "#webserv",
  235. pos: "bottomright",
  236. scrollto: "#webserv_dirManager"
  237. }),
  238. tourStepFactory({
  239. title: "💡 Start Zoraxy HTTP listener",
  240. 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.`,
  241. tab: "status",
  242. element: "#status .poweroptions",
  243. pos: "bottomright",
  244. })
  245. ],
  246. //Subdomains tour steps
  247. "subdomain":[
  248. tourStepFactory({
  249. title: "🎉 Creating your first subdomain",
  250. 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.",
  251. pos: "center"
  252. }),
  253. tourStepFactory({
  254. title: "👉 Pointing subdomain DNS to Zoraxy's IP",
  255. desc: `Setup a DNS CNAME Record that points your subdomain to your root domain. <br>
  256. Assume your public IP is 93.184.215.14, you should have an CNAME record like this.
  257. <table class="ui celled collapsing basic striped table">
  258. <thead>
  259. <tr>
  260. <th>Name</th>
  261. <th>Type</th>
  262. <th>Value</th>
  263. </tr>
  264. </thead>
  265. <tbody>
  266. <tr>
  267. <td>example.com</td>
  268. <td>A</td>
  269. <td>93.184.215.14</td>
  270. </tr>
  271. <tr>
  272. <td>sub.example.com</td>
  273. <td>CNAME</td>
  274. <td>example.com</td>
  275. </tr>
  276. </tbody>
  277. </table>`,
  278. callback: function(){
  279. $.get("/api/acme/wizard?step=10", function(data){
  280. if (data.error == undefined){
  281. //Should return the public IP address from acme wizard
  282. //Overwrite the sample IP address
  283. let originalText = $("#tourModal .tourStepContent").html();
  284. originalText = originalText.split("93.184.215.14").join(data);
  285. $("#tourModal .tourStepContent").html(originalText);
  286. }
  287. })
  288. }
  289. }),
  290. tourStepFactory({
  291. title: "➕ Create New Proxy Rule",
  292. 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.`,
  293. tab: "rules",
  294. pos: "topright"
  295. }),
  296. tourStepFactory({
  297. title: "🌐 Matching Keyword / Domain",
  298. desc: `Fill in your new subdomain in the "Matching Keyword / Domain" field.<br> e.g. sub.example.com`,
  299. element: "#rules .field[tourstep='matchingkeyword']",
  300. pos: "bottomright"
  301. }),
  302. tourStepFactory({
  303. title: "🖥️ Target IP Address or Domain Name with port",
  304. 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.`,
  305. element: "#rules .field[tourstep='targetdomain']",
  306. pos: "bottomright"
  307. }),
  308. tourStepFactory({
  309. title: "🔐 Proxy Target require TLS Connection",
  310. desc: `If your upstream service only accept https connection, select this option.`,
  311. element: "#rules .field[tourstep='requireTLS']",
  312. pos: "bottomright",
  313. }),
  314. tourStepFactory({
  315. title: "🔓 Ignore TLS Validation Error",
  316. 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. `,
  317. element: "#rules #advanceProxyRules .field[tourstep='skipTLSValidation']",
  318. scrollto: "#rules #advanceProxyRules",
  319. pos: "bottomright",
  320. callback: function(){
  321. $("#advanceProxyRules").accordion();
  322. if (!$("#rules #advanceProxyRules .content").is(":visible")){
  323. //Open up the advance config menu
  324. $("#rules #advanceProxyRules .title")[0].click()
  325. }
  326. }
  327. }),
  328. tourStepFactory({
  329. title: "💾 Save New Proxy Rule",
  330. desc: `Now, click "Create Endpoint" to add this reverse proxy rule to runtime.`,
  331. element: "#rules div[tourstep='newProxyRule']",
  332. scrollto: "#rules div[tourstep='newProxyRule']",
  333. pos: "topright",
  334. }),
  335. tourStepFactory({
  336. title: "🎉 New Proxy Rule Setup Completed!",
  337. 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.`,
  338. element: "#rules",
  339. tab: "rules",
  340. pos: "bottomright",
  341. }),
  342. tourStepFactory({
  343. title: "🌲 HTTP Proxy List",
  344. 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>
  345. 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.`,
  346. element: "#httprp",
  347. tab: "httprp",
  348. pos: "bottomright",
  349. }),
  350. ],
  351. //TLS and ACME tour steps
  352. "tls":[
  353. ],
  354. }