upstreams.html 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420
  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. @media (max-width: 499px) {
  33. .upstreamActions{
  34. position: relative;
  35. margin-top: 1em;
  36. margin-left: 0.4em;
  37. margin-bottom: 0.4em;
  38. }
  39. }
  40. </style>
  41. </head>
  42. <body>
  43. <br>
  44. <div class="ui container">
  45. <div class="ui header">
  46. <div class="content">
  47. Upstreams / Load Balance
  48. <div class="sub header epname"></div>
  49. </div>
  50. </div>
  51. <div class="ui divider"></div>
  52. <div class="ui small pointing secondary menu">
  53. <a class="item active narrowpadding" data-tab="upstreamlist">Upstreams</a>
  54. <a class="item narrowpadding" data-tab="newupstream">Add Upstream</a>
  55. </div>
  56. <div class="ui tab basic segment active" data-tab="upstreamlist">
  57. <!-- A list of current existing upstream on this reverse proxy-->
  58. <div id="upstreamTable">
  59. <div class="ui segment">
  60. <a><i class="ui loading spinner icon"></i> Loading</a>
  61. </div>
  62. </div>
  63. <div class="ui message">
  64. <i class="ui blue info circle icon"></i> Round-robin load balancing algorithm will be used for upstreams with same priority. Lower priority origin server will be used as fallback when all the higher priority server are offline.
  65. </div>
  66. </div>
  67. <div class="ui tab basic segment" data-tab="newupstream">
  68. <!-- Web Form to create a new upstream -->
  69. <h4 class="ui header">
  70. <i class="green add icon"></i>
  71. <div class="content">
  72. Add Upstream Server
  73. <div class="sub header">Create new load balance or fallback upstream origin</div>
  74. </div>
  75. </h4>
  76. <p style="margin-bottom: 0.4em;">Target IP address with port</p>
  77. <div class="ui fluid small input">
  78. <input type="text" id="originURL" onchange="cleanProxyTargetValue(this);"><br>
  79. </div>
  80. <small>E.g. 192.168.0.101:8000 or example.com</small>
  81. <br><br>
  82. <div id="activateNewUpstream" class="ui toggle checkbox" style="display:inline-block;">
  83. <input type="checkbox" id="activateNewUpstreamCheckbox" style="margin-top: 0.4em;" checked>
  84. <label>Activate<br>
  85. <small>Enable this upstream for load balancing</small></label>
  86. </div><br>
  87. <div class="ui checkbox" style="margin-top: 1.2em;">
  88. <input type="checkbox" id="requireTLS">
  89. <label>Require TLS<br>
  90. <small>Proxy target require HTTPS connection</small></label>
  91. </div><br>
  92. <div class="ui checkbox" style="margin-top: 0.6em;">
  93. <input type="checkbox" id="skipTlsVerification">
  94. <label>Skip Verification<br>
  95. <small>Check this if proxy target is using self signed certificates</small></label>
  96. </div><br>
  97. <div class="ui checkbox" style="margin-top: 0.4em;">
  98. <input type="checkbox" id="SkipWebSocketOriginCheck" checked>
  99. <label>Skip WebSocket Origin Check<br>
  100. <small>Check this to allow cross-origin websocket requests</small></label>
  101. </div>
  102. <br><br>
  103. <button class="ui basic button" onclick="addNewUpstream();"><i class="ui green circle add icon"></i> Create</button>
  104. </div>
  105. <div class="ui divider"></div>
  106. <div class="field" >
  107. <button class="ui basic button" style="float: right;" onclick="closeThisWrapper();">Close</button>
  108. </div>
  109. </div>
  110. <br><br><br><br>
  111. </div>
  112. <script>
  113. let origins = [];
  114. let editingEndpoint = {};
  115. $('.menu .item').tab();
  116. function initOriginList(){
  117. $.ajax({
  118. url: "/api/proxy/upstream/list",
  119. method: "POST",
  120. data: {
  121. "type":"host",
  122. "ep": editingEndpoint.ep
  123. },
  124. success: function(data){
  125. if (data.error != undefined){
  126. //This endpoint not exists?
  127. alert(data.error);
  128. return;
  129. }else{
  130. $("#upstreamTable").html("");
  131. if (data != undefined){
  132. console.log(data);
  133. data.ActiveOrigins.forEach(upstream => {
  134. renderUpstreamEntryToTable(upstream, true);
  135. });
  136. data.InactiveOrigins.forEach(upstream => {
  137. renderUpstreamEntryToTable(upstream, false);
  138. });
  139. if (data.ActiveOrigins.length == 1){
  140. $(".lowPriorityButton").addClass('disabled');
  141. }
  142. $(".ui.checkbox").checkbox();
  143. }else{
  144. //Assume no origins
  145. $("#inlineEditTable").html(`<tr>
  146. <td colspan="2"><i class="ui red circle times icon"></i> Invalid Upstream Settings</td>
  147. </tr>`);
  148. }
  149. }
  150. }
  151. })
  152. }
  153. function renderUpstreamEntryToTable(upstream, isActive){
  154. function newUID(){return"00000000-0000-4000-8000-000000000000".replace(/0/g,function(){return(0|Math.random()*16).toString(16)})};
  155. let tlsIcon = "";
  156. if (upstream.RequireTLS){
  157. tlsIcon = `<i class="green lock icon" title="TLS Mode"></i>`;
  158. if (upstream.SkipCertValidations){
  159. tlsIcon = `<i class="yellow lock icon" title="TLS/SSL mode without verification"></i>`
  160. }
  161. }
  162. //Priority Arrows
  163. let upArrowClass = "";
  164. if (upstream.Priority == 0 ){
  165. //Cannot go any higher
  166. upArrowClass = "disabled";
  167. }
  168. let url = `${upstream.RequireTLS?"https://":"http://"}${upstream.OriginIpOrDomain}`
  169. let payload = encodeURIComponent(JSON.stringify(upstream));
  170. let domUID = newUID();
  171. $("#upstreamTable").append(`<div class="ui upstreamEntry ${isActive?"":"disabled"} segment" data-domid="${domUID}" data-payload="${payload}" data-priority="${upstream.Priority}">
  172. <h4 class="ui header">
  173. <div class="ui toggle checkbox" style="display:inline-block;">
  174. <input type="checkbox" class="enableState" name="enabled" style="margin-top: 0.4em;" onchange="saveUpstreamUpdate('${domUID}');" ${isActive?"checked":""}>
  175. <label></label>
  176. </div>
  177. <div class="content">
  178. <a href="${url}" target="_blank" class="upstreamLink">${upstream.OriginIpOrDomain} ${tlsIcon}</a>
  179. <div class="sub header">Priority: ${upstream.Priority}</div>
  180. </div>
  181. </h4>
  182. <div class="advanceOptions" style="display:none;">
  183. <div class="upstreamOriginField">
  184. <p>New upstream origin IP address or domain</p>
  185. <div class="ui small fluid input" style="margin-top: -0.6em;">
  186. <input type="text" class="newOrigin" value="${upstream.OriginIpOrDomain}" onchange="handleAutoOriginClean('${domUID}');">
  187. </div>
  188. <small>e.g. 192.168.0.101:8000 or example.com</small>
  189. </div>
  190. <div class="ui divider"></div>
  191. <div class="ui checkbox">
  192. <input type="checkbox" class="reqTLSCheckbox" ${upstream.RequireTLS?"checked":""}>
  193. <label>Require TLS<br>
  194. <small>Proxy target require HTTPS connection</small></label>
  195. </div><br>
  196. <div class="ui checkbox" style="margin-top: 0.6em;">
  197. <input type="checkbox" class="skipVerificationCheckbox" ${upstream.SkipCertValidations?"checked":""}>
  198. <label>Skip Verification<br>
  199. <small>Check this if proxy target is using self signed certificates</small></label>
  200. </div><br>
  201. <div class="ui checkbox" style="margin-top: 0.4em;">
  202. <input type="checkbox" class="SkipWebSocketOriginCheck" ${upstream.SkipWebSocketOriginCheck?"checked":""}>
  203. <label>Skip WebSocket Origin Check<br>
  204. <small>Check this to allow cross-origin websocket requests</small></label>
  205. </div><br>
  206. </div>
  207. <div class="upstreamActions">
  208. <!-- Change Priority -->
  209. <button class="ui basic circular icon button ${upArrowClass} highPriorityButton" title="Higher Priority"><i class="ui arrow up icon"></i></button>
  210. <button class="ui basic circular icon button lowPriorityButton" title="Lower Priority"><i class="ui arrow down icon"></i></button>
  211. <button class="ui basic circular icon editbtn button" onclick="handleUpstreamOriginEdit('${domUID}');" title="Edit Upstream Destination"><i class="ui grey edit icon"></i></button>
  212. <button style="display:none;" class="ui basic circular icon savebtn button" onclick="saveUpstreamUpdate('${domUID}');" title="Save New Destination"><i class="ui green save icon"></i></button>
  213. <button style="display:none;" class="ui basic circular icon cancelbtn button" onclick="initOriginList();" title="Cancel"><i class="ui grey times icon"></i></button>
  214. <button style="display:none;" class="ui basic circular icon trashbtn button" title="Remove Upstream"><i class="ui red trash icon"></i></button>
  215. </div>
  216. </div>`);
  217. }
  218. /* New Upstream Origin Functions */
  219. //Clearn the proxy target value, make sure user do not enter http:// or https://
  220. //and auto select TLS checkbox if https:// exists
  221. function cleanProxyTargetValue(input){
  222. let targetDomain = $(input).val().trim();
  223. if (targetDomain.startsWith("http://")){
  224. targetDomain = targetDomain.substr(7);
  225. $(input).val(targetDomain);
  226. $("#requireTLS").parent().checkbox("set unchecked");
  227. }else if (targetDomain.startsWith("https://")){
  228. targetDomain = targetDomain.substr(8);
  229. $(input).val(targetDomain);
  230. $("#requireTLS").parent().checkbox("set checked");
  231. }else{
  232. //URL does not contains https or http protocol tag
  233. //sniff header
  234. $.ajax({
  235. url: "/api/proxy/tlscheck",
  236. data: {url: targetDomain},
  237. success: function(data){
  238. if (data.error != undefined){
  239. }else if (data == "https"){
  240. $("#requireTLS").parent().checkbox("set checked");
  241. }else if (data == "http"){
  242. $("#requireTLS").parent().checkbox("set unchecked");
  243. }
  244. }
  245. })
  246. }
  247. }
  248. //Add a new upstream to this http proxy rule
  249. function addNewUpstream(){
  250. let origin = $("#originURL").val().trim();
  251. let requireTLS = $("#requireTLS")[0].checked;
  252. let skipVerification = $("#skipTlsVerification")[0].checked;
  253. let skipWebSocketOriginCheck = $("#SkipWebSocketOriginCheck")[0].checked;
  254. let activateLoadbalancer = $("#activateNewUpstreamCheckbox")[0].checked;
  255. if (origin == ""){
  256. parent.msgbox("Upstream origin cannot be empty", false);
  257. return;
  258. }
  259. $.ajax({
  260. url: "/api/proxy/upstream/add",
  261. method: "POST",
  262. data:{
  263. "ep": editingEndpoint.ep,
  264. "origin": origin,
  265. "tls": requireTLS,
  266. "tlsval": skipVerification,
  267. "bpwsorg":skipWebSocketOriginCheck,
  268. "active": activateLoadbalancer,
  269. },
  270. success: function(data){
  271. if (data.error != undefined){
  272. parent.msgbox(data.error, false);
  273. }else{
  274. parent.msgbox("New upstream origin added");
  275. initOriginList();
  276. $("#originURL").val("");
  277. }
  278. }
  279. })
  280. }
  281. //Get a upstream setting data from DOM element
  282. function getUpstreamSettingFromDOM(upstream){
  283. //Get the original setting from DOM payload
  284. let originalSettings = $(upstream).attr("data-payload");
  285. originalSettings = JSON.parse(decodeURIComponent(originalSettings));
  286. //Get the updated settings if any
  287. let requireTLS = $(upstream).find(".reqTLSCheckbox")[0].checked;
  288. let skipTLSVerification = $(upstream).find(".skipVerificationCheckbox")[0].checked;
  289. let skipWebSocketOriginCheck = $(upstream).find(".SkipWebSocketOriginCheck")[0].checked;
  290. //Update the original setting with new one just applied
  291. originalSettings.OriginIpOrDomain = $(upstream).find(".newOrigin").val();
  292. originalSettings.RequireTLS = requireTLS;
  293. originalSettings.SkipCertValidations = skipTLSVerification;
  294. originalSettings.SkipWebSocketOriginCheck = skipWebSocketOriginCheck;
  295. //console.log(originalSettings);
  296. return originalSettings;
  297. }
  298. //Handle setting change on upstream config
  299. function saveUpstreamUpdate(upstreamDomID){
  300. let targetDOM = $(`.upstreamEntry[data-domid=${upstreamDomID}]`);
  301. let originalSettings = $(targetDOM).attr("data-payload");
  302. originalSettings = JSON.parse(decodeURIComponent(originalSettings));
  303. let newConfig = getUpstreamSettingFromDOM(targetDOM);
  304. let isActive = $(targetDOM).find(".enableState")[0].checked;
  305. console.log(newConfig);
  306. $.ajax({
  307. url: "/api/proxy/upstream/update",
  308. method: "POST",
  309. data: {
  310. ep: editingEndpoint.ep,
  311. origin: originalSettings.OriginIpOrDomain, //Original ip or domain as key
  312. payload: JSON.stringify(newConfig),
  313. active: isActive,
  314. },
  315. success: function(data){
  316. if (data.error != undefined){
  317. parent.msgbox(data.error, false);
  318. }else{
  319. parent.msgbox("Upstream setting updated");
  320. initOriginList();
  321. }
  322. }
  323. })
  324. }
  325. //Edit the upstream origin of this upstream entry
  326. function handleUpstreamOriginEdit(upstreamDomID){
  327. let targetDOM = $(`.upstreamEntry[data-domid=${upstreamDomID}]`);
  328. let originalSettings = $(targetDOM).attr("data-payload");
  329. originalSettings = JSON.parse(decodeURIComponent(originalSettings));
  330. let originIP = originalSettings.OriginIpOrDomain;
  331. //Change the UI to edit mode
  332. $(".editbtn").hide();
  333. $(".lowPriorityButton").hide();
  334. $(".highPriorityButton").hide();
  335. $(targetDOM).find(".trashbtn").show();
  336. $(targetDOM).find(".savebtn").show();
  337. $(targetDOM).find(".cancelbtn").show();
  338. $(targetDOM).find(".advanceOptions").show();
  339. }
  340. //Check if the entered URL contains http or https
  341. function handleAutoOriginClean(domid){
  342. let targetDOM = $(`.upstreamEntry[data-domid=${domid}]`);
  343. let targetTLSCheckbox = $(targetDOM).find(".reqTLSCheckbox");
  344. let targetDomain = $(targetDOM).find(".newOrigin").val().trim();
  345. if (targetDomain.startsWith("http://")){
  346. targetDomain = targetDomain.substr(7);
  347. $(input).val(targetDomain);
  348. $(targetTLSCheckbox).parent().checkbox("set unchecked");
  349. }else if (targetDomain.startsWith("https://")){
  350. targetDomain = targetDomain.substr(8);
  351. $(input).val(targetDomain);
  352. $(targetTLSCheckbox).parent().checkbox("set checked");
  353. }else{
  354. //URL does not contains https or http protocol tag
  355. //sniff header
  356. $.ajax({
  357. url: "/api/proxy/tlscheck",
  358. data: {url: targetDomain},
  359. success: function(data){
  360. if (data.error != undefined){
  361. }else if (data == "https"){
  362. $(targetTLSCheckbox).parent().checkbox("set checked");
  363. }else if (data == "http"){
  364. $(targetTLSCheckbox).parent().checkbox("set unchecked");
  365. }
  366. }
  367. })
  368. }
  369. }
  370. if (window.location.hash.length > 1){
  371. let payloadHash = window.location.hash.substr(1);
  372. try{
  373. payloadHash = JSON.parse(decodeURIComponent(payloadHash));
  374. $(".epname").text(payloadHash.ep);
  375. editingEndpoint = payloadHash;
  376. initOriginList();
  377. }catch(ex){
  378. console.log("Unable to load endpoint data from hash")
  379. }
  380. }
  381. function closeThisWrapper(){
  382. parent.hideSideWrapper(true);
  383. }
  384. </script>
  385. </body>
  386. </html>