1
0

upstreams.html 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486
  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4. <!-- Notes: This should be open in its original path-->
  5. <meta charset="utf-8">
  6. <link rel="stylesheet" href="../script/semantic/semantic.min.css">
  7. <script src="../script/jquery-3.6.0.min.js"></script>
  8. <script src="../script/semantic/semantic.min.js"></script>
  9. <style>
  10. .upstreamActions{
  11. position: absolute;
  12. top: 0.6em;
  13. right: 0.6em;
  14. }
  15. .upstreamLink{
  16. max-width: 220px;
  17. display: inline-block;
  18. word-break: break-all;
  19. }
  20. .upstreamEntry .ui.toggle.checkbox input:checked ~ label::before{
  21. background-color: #00ca52 !important;
  22. }
  23. #activateNewUpstream.ui.toggle.checkbox input:checked ~ label::before{
  24. background-color: #00ca52 !important;
  25. }
  26. #upstreamTable{
  27. max-height: 480px;
  28. border-radius: 0.3em;
  29. padding: 0.3em;
  30. overflow-y: auto;
  31. }
  32. .upstreamEntry.inactive{
  33. background-color: #f3f3f3 !important;
  34. }
  35. .upstreamEntry{
  36. cursor: pointer;
  37. border-radius: 0.4em !important;
  38. border: 1px solid rgb(233, 233, 233) !important;
  39. }
  40. @media (max-width: 499px) {
  41. .upstreamActions{
  42. position: relative;
  43. margin-top: 1em;
  44. margin-left: 0.4em;
  45. margin-bottom: 0.4em;
  46. }
  47. }
  48. </style>
  49. </head>
  50. <body>
  51. <br>
  52. <div class="ui container">
  53. <div class="ui header">
  54. <div class="content">
  55. Upstreams / Load Balance
  56. <div class="sub header epname"></div>
  57. </div>
  58. </div>
  59. <div class="ui divider"></div>
  60. <div class="ui small pointing secondary menu">
  61. <a class="item active narrowpadding" data-tab="upstreamlist">Upstreams</a>
  62. <a class="item narrowpadding" data-tab="newupstream">Add Upstream</a>
  63. </div>
  64. <div class="ui tab basic segment active" data-tab="upstreamlist">
  65. <!-- A list of current existing upstream on this reverse proxy-->
  66. <div id="upstreamTable">
  67. <div class="ui segment">
  68. <a><i class="ui loading spinner icon"></i> Loading</a>
  69. </div>
  70. </div>
  71. <div class="ui message">
  72. <i class="ui blue info circle icon"></i> Round-robin load balancing algorithm will be used for upstreams with same weight. Set weight to 0 for fallback only.
  73. </div>
  74. </div>
  75. <div class="ui tab basic segment" data-tab="newupstream">
  76. <!-- Web Form to create a new upstream -->
  77. <h4 class="ui header">
  78. <i class="green add icon"></i>
  79. <div class="content">
  80. Add Upstream Server
  81. <div class="sub header">Create new load balance or fallback upstream origin</div>
  82. </div>
  83. </h4>
  84. <p style="margin-bottom: 0.4em;">Target IP address with port</p>
  85. <div class="ui fluid small input">
  86. <input type="text" id="originURL" onchange="cleanProxyTargetValue(this);"><br>
  87. </div>
  88. <small>E.g. 192.168.0.101:8000 or example.com</small>
  89. <br><br>
  90. <div id="activateNewUpstream" class="ui toggle checkbox" style="display:inline-block;">
  91. <input type="checkbox" id="activateNewUpstreamCheckbox" style="margin-top: 0.4em;" checked>
  92. <label>Activate<br>
  93. <small>Enable this upstream for load balancing</small></label>
  94. </div><br>
  95. <div class="ui checkbox" style="margin-top: 1.2em;">
  96. <input type="checkbox" id="requireTLS">
  97. <label>Require TLS<br>
  98. <small>Proxy target require HTTPS connection</small></label>
  99. </div><br>
  100. <div class="ui checkbox" style="margin-top: 0.6em;">
  101. <input type="checkbox" id="skipTlsVerification">
  102. <label>Skip Verification<br>
  103. <small>Check this if proxy target is using self signed certificates</small></label>
  104. </div><br>
  105. <div class="ui checkbox" style="margin-top: 0.4em;">
  106. <input type="checkbox" id="SkipWebSocketOriginCheck" checked>
  107. <label>Skip WebSocket Origin Check<br>
  108. <small>Check this to allow cross-origin websocket requests</small></label>
  109. </div>
  110. <br><br>
  111. <button class="ui basic button" onclick="addNewUpstream();"><i class="ui green circle add icon"></i> Create</button>
  112. </div>
  113. <div class="ui divider"></div>
  114. <div class="field" >
  115. <button class="ui basic button" style="float: right;" onclick="closeThisWrapper();">Close</button>
  116. </div>
  117. </div>
  118. <br><br><br><br>
  119. </div>
  120. <script>
  121. let origins = [];
  122. let editingEndpoint = {};
  123. $('.menu .item').tab();
  124. function initOriginList(){
  125. $.ajax({
  126. url: "/api/proxy/upstream/list",
  127. method: "POST",
  128. data: {
  129. "type":"host",
  130. "ep": editingEndpoint.ep
  131. },
  132. success: function(data){
  133. if (data.error != undefined){
  134. //This endpoint not exists?
  135. alert(data.error);
  136. return;
  137. }else{
  138. $("#upstreamTable").html("");
  139. if (data.ActiveOrigins.length == 0){
  140. //There is no upstream for this proxy rule
  141. $("#upstreamTable").append(`<tr>
  142. <td colspan="2"><b><i class="ui yellow exclamation triangle icon"></i> No Active Upstream Origin</b><br>
  143. This HTTP proxy rule will always return Error 521 when requested. To fix this, add or enable a upstream origin to this proxy endpoint.
  144. <div class="ui divider"></div>
  145. </td>
  146. </tr>`);
  147. }
  148. data.ActiveOrigins.forEach(upstream => {
  149. renderUpstreamEntryToTable(upstream, true);
  150. });
  151. data.InactiveOrigins.forEach(upstream => {
  152. renderUpstreamEntryToTable(upstream, false);
  153. });
  154. let totalUpstreams = data.ActiveOrigins.length + data.InactiveOrigins.length;
  155. if (totalUpstreams == 1){
  156. $(".lowPriorityButton").addClass('disabled');
  157. }
  158. if (parent && parent.document.getElementById("httpProxyList") != null){
  159. //Also update the parent display
  160. let element = $(parent.document.getElementById("httpProxyList")).find(".upstreamList.editing");
  161. let upstreams = "";
  162. data.ActiveOrigins.forEach(upstream => {
  163. //Check if the upstreams require TLS connections
  164. let tlsIcon = "";
  165. if (upstream.RequireTLS){
  166. tlsIcon = `<i class="green lock icon" title="TLS Mode"></i>`;
  167. if (upstream.SkipCertValidations){
  168. tlsIcon = `<i class="yellow lock icon" title="TLS/SSL mode without verification"></i>`
  169. }
  170. }
  171. let upstreamLink = `${upstream.RequireTLS?"https://":"http://"}${upstream.OriginIpOrDomain}`;
  172. upstreams += `<a href="${upstreamLink}" target="_blank">${upstream.OriginIpOrDomain} ${tlsIcon}</a><br>`;
  173. });
  174. if (data.ActiveOrigins.length == 0){
  175. upstreams = `<i class="ui yellow exclamation triangle icon"></i> No Active Upstream Origin<br>`
  176. }
  177. $(element).html(upstreams);
  178. }
  179. $(".ui.checkbox").checkbox();
  180. }
  181. }
  182. })
  183. }
  184. function renderUpstreamEntryToTable(upstream, isActive){
  185. function newUID(){return"00000000-0000-4000-8000-000000000000".replace(/0/g,function(){return(0|Math.random()*16).toString(16)})};
  186. let tlsIcon = "";
  187. if (upstream.RequireTLS){
  188. tlsIcon = `<i class="green lock icon" title="TLS Mode"></i>`;
  189. if (upstream.SkipCertValidations){
  190. tlsIcon = `<i class="yellow lock icon" title="TLS/SSL mode without verification"></i>`
  191. }
  192. }
  193. //Priority Arrows
  194. let downArrowClass = "";
  195. if (upstream.Weight == 0 ){
  196. //Cannot go any lower
  197. downArrowClass = "disabled";
  198. }
  199. let url = `${upstream.RequireTLS?"https://":"http://"}${upstream.OriginIpOrDomain}`
  200. let payload = encodeURIComponent(JSON.stringify(upstream));
  201. let domUID = newUID();
  202. $("#upstreamTable").append(`<div class="ui upstreamEntry ${isActive?"":"inactive"} basic segment" data-domid="${domUID}" data-payload="${payload}" data-priority="${upstream.Priority}">
  203. <h4 class="ui header">
  204. <div class="ui toggle checkbox" style="display:inline-block;">
  205. <input type="checkbox" class="enableState" name="enabled" style="margin-top: 0.4em;" onchange="saveUpstreamUpdate('${domUID}');" ${isActive?"checked":""}>
  206. <label></label>
  207. </div>
  208. <div class="content">
  209. <a href="${url}" target="_blank" class="upstreamLink">${upstream.OriginIpOrDomain} ${tlsIcon}</a>
  210. <div class="sub header">${isActive?"Active":"Inactive"} | Weight: ${upstream.Weight}x</div>
  211. </div>
  212. </h4>
  213. <div class="advanceOptions" style="display:none;">
  214. <div class="upstreamOriginField">
  215. <p>New upstream origin IP address or domain</p>
  216. <div class="ui small fluid input" style="margin-top: -0.6em;">
  217. <input type="text" class="newOrigin" value="${upstream.OriginIpOrDomain}" onchange="handleAutoOriginClean('${domUID}');">
  218. </div>
  219. <small>e.g. 192.168.0.101:8000 or example.com</small>
  220. </div>
  221. <div class="ui divider"></div>
  222. <div class="ui checkbox">
  223. <input type="checkbox" class="reqTLSCheckbox" ${upstream.RequireTLS?"checked":""}>
  224. <label>Require TLS<br>
  225. <small>Proxy target require HTTPS connection</small></label>
  226. </div><br>
  227. <div class="ui checkbox" style="margin-top: 0.6em;">
  228. <input type="checkbox" class="skipVerificationCheckbox" ${upstream.SkipCertValidations?"checked":""}>
  229. <label>Skip Verification<br>
  230. <small>Check this if proxy target is using self signed certificates</small></label>
  231. </div><br>
  232. <div class="ui checkbox" style="margin-top: 0.4em;">
  233. <input type="checkbox" class="SkipWebSocketOriginCheck" ${upstream.SkipWebSocketOriginCheck?"checked":""}>
  234. <label>Skip WebSocket Origin Check<br>
  235. <small>Check this to allow cross-origin websocket requests</small></label>
  236. </div><br>
  237. </div>
  238. <div class="upstreamActions">
  239. <!-- Change Priority -->
  240. <button class="ui basic circular icon button highPriorityButton" title="Increase Weight"><i class="ui arrow up icon"></i></button>
  241. <button class="ui basic circular icon button lowPriorityButton ${downArrowClass}" title="Reduce Weight"><i class="ui arrow down icon"></i></button>
  242. <button class="ui basic circular icon editbtn button" onclick="handleUpstreamOriginEdit('${domUID}');" title="Edit Upstream Destination"><i class="ui grey edit icon"></i></button>
  243. <button style="display:none;" class="ui basic circular icon trashbtn button" onclick="removeUpstream('${domUID}');" title="Remove Upstream"><i class="ui red trash icon"></i></button>
  244. <button style="display:none;" class="ui basic circular icon savebtn button" onclick="saveUpstreamUpdate('${domUID}');" title="Save Changes"><i class="ui green save icon"></i></button>
  245. <button style="display:none;" class="ui basic circular icon cancelbtn button" onclick="initOriginList();" title="Cancel"><i class="ui grey times icon"></i></button>
  246. </div>
  247. </div>`);
  248. }
  249. /* New Upstream Origin Functions */
  250. //Clearn the proxy target value, make sure user do not enter http:// or https://
  251. //and auto select TLS checkbox if https:// exists
  252. function cleanProxyTargetValue(input){
  253. let targetDomain = $(input).val().trim();
  254. if (targetDomain.startsWith("http://")){
  255. targetDomain = targetDomain.substr(7);
  256. $(input).val(targetDomain);
  257. $("#requireTLS").parent().checkbox("set unchecked");
  258. }else if (targetDomain.startsWith("https://")){
  259. targetDomain = targetDomain.substr(8);
  260. $(input).val(targetDomain);
  261. $("#requireTLS").parent().checkbox("set checked");
  262. }else{
  263. //URL does not contains https or http protocol tag
  264. //sniff header
  265. $.ajax({
  266. url: "/api/proxy/tlscheck",
  267. data: {url: targetDomain},
  268. success: function(data){
  269. if (data.error != undefined){
  270. }else if (data == "https"){
  271. $("#requireTLS").parent().checkbox("set checked");
  272. }else if (data == "http"){
  273. $("#requireTLS").parent().checkbox("set unchecked");
  274. }
  275. }
  276. })
  277. }
  278. }
  279. //Add a new upstream to this http proxy rule
  280. function addNewUpstream(){
  281. let origin = $("#originURL").val().trim();
  282. let requireTLS = $("#requireTLS")[0].checked;
  283. let skipVerification = $("#skipTlsVerification")[0].checked;
  284. let skipWebSocketOriginCheck = $("#SkipWebSocketOriginCheck")[0].checked;
  285. let activateLoadbalancer = $("#activateNewUpstreamCheckbox")[0].checked;
  286. if (origin == ""){
  287. parent.msgbox("Upstream origin cannot be empty", false);
  288. return;
  289. }
  290. $.ajax({
  291. url: "/api/proxy/upstream/add",
  292. method: "POST",
  293. data:{
  294. "ep": editingEndpoint.ep,
  295. "origin": origin,
  296. "tls": requireTLS,
  297. "tlsval": skipVerification,
  298. "bpwsorg":skipWebSocketOriginCheck,
  299. "active": activateLoadbalancer,
  300. },
  301. success: function(data){
  302. if (data.error != undefined){
  303. parent.msgbox(data.error, false);
  304. }else{
  305. parent.msgbox("New upstream origin added");
  306. initOriginList();
  307. $("#originURL").val("");
  308. }
  309. }
  310. })
  311. }
  312. //Get a upstream setting data from DOM element
  313. function getUpstreamSettingFromDOM(upstream){
  314. //Get the original setting from DOM payload
  315. let originalSettings = $(upstream).attr("data-payload");
  316. originalSettings = JSON.parse(decodeURIComponent(originalSettings));
  317. //Get the updated settings if any
  318. let requireTLS = $(upstream).find(".reqTLSCheckbox")[0].checked;
  319. let skipTLSVerification = $(upstream).find(".skipVerificationCheckbox")[0].checked;
  320. let skipWebSocketOriginCheck = $(upstream).find(".SkipWebSocketOriginCheck")[0].checked;
  321. //Update the original setting with new one just applied
  322. originalSettings.OriginIpOrDomain = $(upstream).find(".newOrigin").val();
  323. originalSettings.RequireTLS = requireTLS;
  324. originalSettings.SkipCertValidations = skipTLSVerification;
  325. originalSettings.SkipWebSocketOriginCheck = skipWebSocketOriginCheck;
  326. //console.log(originalSettings);
  327. return originalSettings;
  328. }
  329. //Handle setting change on upstream config
  330. function saveUpstreamUpdate(upstreamDomID){
  331. let targetDOM = $(`.upstreamEntry[data-domid=${upstreamDomID}]`);
  332. let originalSettings = $(targetDOM).attr("data-payload");
  333. originalSettings = JSON.parse(decodeURIComponent(originalSettings));
  334. let newConfig = getUpstreamSettingFromDOM(targetDOM);
  335. let isActive = $(targetDOM).find(".enableState")[0].checked;
  336. console.log(newConfig);
  337. $.ajax({
  338. url: "/api/proxy/upstream/update",
  339. method: "POST",
  340. data: {
  341. ep: editingEndpoint.ep,
  342. origin: originalSettings.OriginIpOrDomain, //Original ip or domain as key
  343. payload: JSON.stringify(newConfig),
  344. active: isActive,
  345. },
  346. success: function(data){
  347. if (data.error != undefined){
  348. parent.msgbox(data.error, false);
  349. }else{
  350. parent.msgbox("Upstream setting updated");
  351. initOriginList();
  352. }
  353. }
  354. })
  355. }
  356. //Edit the upstream origin of this upstream entry
  357. function handleUpstreamOriginEdit(upstreamDomID){
  358. let targetDOM = $(`.upstreamEntry[data-domid=${upstreamDomID}]`);
  359. let originalSettings = $(targetDOM).attr("data-payload");
  360. originalSettings = JSON.parse(decodeURIComponent(originalSettings));
  361. let originIP = originalSettings.OriginIpOrDomain;
  362. //Change the UI to edit mode
  363. $(".editbtn").hide();
  364. $(".lowPriorityButton").hide();
  365. $(".highPriorityButton").hide();
  366. $(targetDOM).find(".trashbtn").show();
  367. $(targetDOM).find(".savebtn").show();
  368. $(targetDOM).find(".cancelbtn").show();
  369. $(targetDOM).find(".advanceOptions").show();
  370. }
  371. //Check if the entered URL contains http or https
  372. function handleAutoOriginClean(domid){
  373. let targetDOM = $(`.upstreamEntry[data-domid=${domid}]`);
  374. let targetTLSCheckbox = $(targetDOM).find(".reqTLSCheckbox");
  375. let targetDomain = $(targetDOM).find(".newOrigin").val().trim();
  376. if (targetDomain.startsWith("http://")){
  377. targetDomain = targetDomain.substr(7);
  378. $(input).val(targetDomain);
  379. $(targetTLSCheckbox).parent().checkbox("set unchecked");
  380. }else if (targetDomain.startsWith("https://")){
  381. targetDomain = targetDomain.substr(8);
  382. $(input).val(targetDomain);
  383. $(targetTLSCheckbox).parent().checkbox("set checked");
  384. }else{
  385. //URL does not contains https or http protocol tag
  386. //sniff header
  387. $.ajax({
  388. url: "/api/proxy/tlscheck",
  389. data: {url: targetDomain},
  390. success: function(data){
  391. if (data.error != undefined){
  392. }else if (data == "https"){
  393. $(targetTLSCheckbox).parent().checkbox("set checked");
  394. }else if (data == "http"){
  395. $(targetTLSCheckbox).parent().checkbox("set unchecked");
  396. }
  397. }
  398. })
  399. }
  400. }
  401. //Handle removal of an upstream
  402. function removeUpstream(domid){
  403. let targetDOM = $(`.upstreamEntry[data-domid=${domid}]`);
  404. let originalSettings = $(targetDOM).attr("data-payload");
  405. originalSettings = JSON.parse(decodeURIComponent(originalSettings));
  406. let UpstreamKey = originalSettings.OriginIpOrDomain;
  407. if (!confirm("Confirm removing " + UpstreamKey + " from upstream?")){
  408. return;
  409. }
  410. //Remove the upstream
  411. $.ajax({
  412. url: "/api/proxy/upstream/remove",
  413. method: "POST",
  414. data: {
  415. ep: editingEndpoint.ep,
  416. origin: originalSettings.OriginIpOrDomain, //Original ip or domain as key
  417. },
  418. success: function(data){
  419. if (data.error != undefined){
  420. parent.msgbox(data.error, false);
  421. }else{
  422. parent.msgbox("Upstream deleted");
  423. initOriginList();
  424. }
  425. }
  426. })
  427. }
  428. if (window.location.hash.length > 1){
  429. let payloadHash = window.location.hash.substr(1);
  430. try{
  431. payloadHash = JSON.parse(decodeURIComponent(payloadHash));
  432. $(".epname").text(payloadHash.ep);
  433. editingEndpoint = payloadHash;
  434. initOriginList();
  435. }catch(ex){
  436. console.log("Unable to load endpoint data from hash")
  437. }
  438. }
  439. function closeThisWrapper(){
  440. parent.hideSideWrapper(true);
  441. }
  442. </script>
  443. </body>
  444. </html>