1
0

httprp.html 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500
  1. <div class="standardContainer">
  2. <div class="ui basic segment">
  3. <h2>HTTP Proxy</h2>
  4. <p>Proxy HTTP server with HTTP or HTTPS for multiple hosts. If you are only proxying for one host / domain, use Default Site instead.</p>
  5. </div>
  6. <style>
  7. #httpProxyList .ui.toggle.checkbox input:checked ~ label::before{
  8. background-color: #00ca52 !important;
  9. }
  10. .subdEntry td:not(.ignoremw){
  11. min-width: 200px;
  12. }
  13. </style>
  14. <div style="width: 100%; overflow-x: auto; margin-bottom: 1em; min-height: 300px;">
  15. <table class="ui celled sortable unstackable compact table">
  16. <thead>
  17. <tr>
  18. <th>Host</th>
  19. <th>Destination</th>
  20. <th>Virtual Directory</th>
  21. <th style="max-width: 300px;">Advanced Settings</th>
  22. <th class="no-sort" style="min-width:150px;">Actions</th>
  23. </tr>
  24. </thead>
  25. <tbody id="httpProxyList">
  26. </tbody>
  27. </table>
  28. </div>
  29. <button class="ui icon right floated basic button" onclick="listProxyEndpoints();"><i class="green refresh icon"></i> Refresh</button>
  30. <br><br>
  31. </div>
  32. <script>
  33. /* List all proxy endpoints */
  34. function listProxyEndpoints(){
  35. $.get("/api/proxy/list?type=host", function(data){
  36. $("#httpProxyList").html(``);
  37. if (data.error !== undefined){
  38. $("#httpProxyList").append(`<tr>
  39. <td data-label="" colspan="5"><i class="remove icon"></i> ${data.error}</td>
  40. </tr>`);
  41. }else if (data.length == 0){
  42. $("#httpProxyList").append(`<tr>
  43. <td data-label="" colspan="5"><i class="green check circle icon"></i> No HTTP Proxy Record</td>
  44. </tr>`);
  45. }else{
  46. //Sort by RootOrMatchingDomain field
  47. data.sort((a,b) => (a.RootOrMatchingDomain > b.RootOrMatchingDomain) ? 1 : ((b.RootOrMatchingDomain > a.RootOrMatchingDomain) ? -1 : 0))
  48. data.forEach(subd => {
  49. let subdData = encodeURIComponent(JSON.stringify(subd));
  50. //Build the upstream list
  51. let upstreams = "";
  52. if (subd.Origins.length == 0){
  53. //Invalid config
  54. upstreams = `<i class="ui red times icon"></i> No upstream configured`;
  55. }else{
  56. subd.Origins.forEach(upstream => {
  57. console.log(upstream);
  58. //Check if the upstreams require TLS connections
  59. let tlsIcon = "";
  60. if (upstream.RequireTLS){
  61. tlsIcon = `<i class="green lock icon" title="TLS Mode"></i>`;
  62. if (upstream.SkipCertValidations){
  63. tlsIcon = `<i class="yellow lock icon" title="TLS/SSL mode without verification"></i>`
  64. }
  65. }
  66. let upstreamLink = `${upstream.RequireTLS?"https://":"http://"}${upstream.OriginIpOrDomain}`;
  67. upstreams += `<a href="${upstreamLink}" target="_blank">${upstream.OriginIpOrDomain} ${tlsIcon}</a><br>`;
  68. })
  69. }
  70. let inboundTlsIcon = "";
  71. if ($("#tls").checkbox("is checked")){
  72. inboundTlsIcon = `<i class="green lock icon" title="TLS Mode"></i>`;
  73. if (subd.BypassGlobalTLS){
  74. inboundTlsIcon = `<i class="grey lock icon" title="TLS Bypass Enabled"></i>`;
  75. }
  76. }else{
  77. inboundTlsIcon = `<i class="yellow lock open icon" title="Plain Text Mode"></i>`;
  78. }
  79. //Build the virtual directory list
  80. var vdList = `<div class="ui list">`;
  81. subd.VirtualDirectories.forEach(vdir => {
  82. vdList += `<div class="item">${vdir.MatchingPath} <i class="green angle double right icon"></i> ${vdir.Domain}</div>`;
  83. });
  84. vdList += `</div>`;
  85. if (subd.VirtualDirectories.length == 0){
  86. vdList = `<small style="opacity: 0.3; pointer-events: none; user-select: none;">No Virtual Directory</small>`;
  87. }
  88. let enableChecked = "checked";
  89. if (subd.Disabled){
  90. enableChecked = "";
  91. }
  92. let aliasDomains = ``;
  93. if (subd.MatchingDomainAlias != undefined && subd.MatchingDomainAlias.length > 0){
  94. aliasDomains = `<small class="aliasDomains" eptuuid="${subd.RootOrMatchingDomain}" style="color: #636363;">Alias: `;
  95. subd.MatchingDomainAlias.forEach(alias => {
  96. aliasDomains += `<a href="//${alias}" target="_blank">${alias}</a>, `;
  97. });
  98. aliasDomains = aliasDomains.substr(0, aliasDomains.length - 2); //Remove the last tailing seperator
  99. aliasDomains += `</small><br>`;
  100. }
  101. $("#httpProxyList").append(`<tr eptuuid="${subd.RootOrMatchingDomain}" payload="${subdData}" class="subdEntry">
  102. <td data-label="" editable="true" datatype="inbound">
  103. <a href="//${subd.RootOrMatchingDomain}" target="_blank">${subd.RootOrMatchingDomain}</a> ${inboundTlsIcon}<br>
  104. ${aliasDomains}
  105. <small class="accessRuleNameUnderHost" ruleid="${subd.AccessFilterUUID}"></small>
  106. </td>
  107. <td data-label="" editable="true" datatype="domain">${upstreams}</td>
  108. <td data-label="" editable="true" datatype="vdir">${vdList}</td>
  109. <td data-label="" editable="true" datatype="advanced" style="width: 350px;">
  110. ${subd.RequireBasicAuth?`<i class="ui green check icon"></i> Basic Auth`:``}
  111. ${subd.RequireBasicAuth && subd.RequireRateLimit?"<br>":""}
  112. ${subd.RequireRateLimit?`<i class="ui green check icon"></i> Rate Limit @ ${subd.RateLimit} req/s`:``}
  113. ${!subd.RequireBasicAuth && !subd.RequireRateLimit?`<small style="opacity: 0.3; pointer-events: none; user-select: none;">No Special Settings</small>`:""}
  114. </td>
  115. <td class="center aligned ignoremw" editable="true" datatype="action" data-label="">
  116. <div class="ui toggle tiny fitted checkbox" style="margin-bottom: -0.5em; margin-right: 0.4em;" title="Enable / Disable Rule">
  117. <input type="checkbox" class="enableToggle" name="active" ${enableChecked} eptuuid="${subd.RootOrMatchingDomain}" onchange="handleProxyRuleToggle(this);">
  118. <label></label>
  119. </div>
  120. <button title="Edit Proxy Rule" class="ui circular mini basic icon button editBtn inlineEditActionBtn" onclick='editEndpoint("${(subd.RootOrMatchingDomain).hexEncode()}")'><i class="edit icon"></i></button>
  121. <button title="Remove Proxy Rule" class="ui circular mini red basic icon button inlineEditActionBtn" onclick='deleteEndpoint("${(subd.RootOrMatchingDomain).hexEncode()}")'><i class="trash icon"></i></button>
  122. </td>
  123. </tr>`);
  124. });
  125. }
  126. resolveAccessRuleNameOnHostRPlist();
  127. });
  128. }
  129. //Perform realtime alias update without refreshing the whole page
  130. function updateAliasListForEndpoint(endpointName, newAliasDomainList){
  131. let targetEle = $(`.aliasDomains[eptuuid='${endpointName}']`);
  132. console.log(targetEle);
  133. if (targetEle.length == 0){
  134. return;
  135. }
  136. let aliasDomains = ``;
  137. if (newAliasDomainList != undefined && newAliasDomainList.length > 0){
  138. aliasDomains = `Alias: `;
  139. newAliasDomainList.forEach(alias => {
  140. aliasDomains += `<a href="//${alias}" target="_blank">${alias}</a>, `;
  141. });
  142. aliasDomains = aliasDomains.substr(0, aliasDomains.length - 2); //Remove the last tailing seperator
  143. $(targetEle).html(aliasDomains);
  144. $(targetEle).show();
  145. }else{
  146. $(targetEle).hide();
  147. }
  148. }
  149. //Resolve & Update all rule names on host PR list
  150. function resolveAccessRuleNameOnHostRPlist(){
  151. //Resolve the access filters
  152. $.get("/api/access/list", function(data){
  153. console.log(data);
  154. if (data.error == undefined){
  155. //Build a map base on the data
  156. let accessRuleMap = {};
  157. for (var i = 0; i < data.length; i++){
  158. accessRuleMap[data[i].ID] = data[i];
  159. }
  160. $(".accessRuleNameUnderHost").each(function(){
  161. let thisAccessRuleID = $(this).attr("ruleid");
  162. if (thisAccessRuleID== ""){
  163. thisAccessRuleID = "default"
  164. }
  165. if (thisAccessRuleID == "default"){
  166. //No need to label default access rules
  167. $(this).html("");
  168. return;
  169. }
  170. let rule = accessRuleMap[thisAccessRuleID];
  171. let icon = `<i class="ui grey filter icon"></i>`;
  172. if (rule.ID == "default"){
  173. icon = `<i class="ui yellow star icon"></i>`;
  174. }else if (rule.BlacklistEnabled && !rule.WhitelistEnabled){
  175. //This is a blacklist filter
  176. icon = `<i class="ui red filter icon"></i>`;
  177. }else if (rule.WhitelistEnabled && !rule.BlacklistEnabled){
  178. //This is a whitelist filter
  179. icon = `<i class="ui green filter icon"></i>`;
  180. }else if (rule.WhitelistEnabled && rule.BlacklistEnabled){
  181. //Whitelist and blacklist filter
  182. icon = `<i class="ui yellow filter icon"></i>`;
  183. }
  184. if (rule != undefined){
  185. $(this).html(`${icon} ${rule.Name}`);
  186. }
  187. });
  188. }
  189. })
  190. }
  191. //Update the access rule name on given epuuid, call by hostAccessEditor.html
  192. function updateAccessRuleNameUnderHost(epuuid, newruleUID){
  193. $(`tr[eptuuid='${epuuid}'].subdEntry`).find(".accessRuleNameUnderHost").attr("ruleid", newruleUID);
  194. resolveAccessRuleNameOnHostRPlist();
  195. }
  196. /*
  197. Inline editor for httprp.html
  198. */
  199. function editEndpoint(uuid) {
  200. uuid = uuid.hexDecode();
  201. var row = $('tr[eptuuid="' + uuid + '"]');
  202. var columns = row.find('td[data-label]');
  203. var payload = $(row).attr("payload");
  204. payload = JSON.parse(decodeURIComponent(payload));
  205. console.log(payload);
  206. //console.log(payload);
  207. columns.each(function(index) {
  208. var column = $(this);
  209. var oldValue = column.text().trim();
  210. if ($(this).attr("editable") == "false"){
  211. //This col do not allow edit. Skip
  212. return;
  213. }
  214. // Create an input element based on the column content
  215. var input;
  216. var datatype = $(this).attr("datatype");
  217. if (datatype == "domain"){
  218. let useStickySessionChecked = "";
  219. if (payload.UseStickySession){
  220. useStickySessionChecked = "checked";
  221. }
  222. input = `<button class="ui basic compact tiny button" style="margin-left: 0.4em; margin-top: 1em;" onclick="editUpstreams('${uuid}');"><i class="grey server icon"></i> Edit Upstreams</button>
  223. <div class="ui divider"></div>
  224. <div class="ui checkbox" style="margin-top: 0.4em;">
  225. <input type="checkbox" class="UseStickySession" ${useStickySessionChecked}>
  226. <label>Use Sticky Session<br>
  227. <small>Enable stick session on load balancing</small></label>
  228. </div>
  229. `;
  230. column.append(input);
  231. }else if (datatype == "vdir"){
  232. //Append a quick access button for vdir page
  233. column.append(`<button class="ui basic tiny button" style="margin-left: 0.4em; margin-top: 0.4em;" onclick="quickEditVdir('${uuid}');">
  234. <i class="ui yellow folder icon"></i> Edit Virtual Directories
  235. </button>`);
  236. }else if (datatype == "advanced"){
  237. let requireBasicAuth = payload.RequireBasicAuth;
  238. let basicAuthCheckstate = "";
  239. if (requireBasicAuth){
  240. basicAuthCheckstate = "checked";
  241. }
  242. let skipWebSocketOriginCheck = payload.SkipWebSocketOriginCheck;
  243. let wsCheckstate = "";
  244. if (skipWebSocketOriginCheck){
  245. wsCheckstate = "checked";
  246. }
  247. let requireRateLimit = payload.RequireRateLimit;
  248. let rateLimitCheckState = "";
  249. if (requireRateLimit){
  250. rateLimitCheckState = "checked";
  251. }
  252. let rateLimit = payload.RateLimit;
  253. if (rateLimit == 0){
  254. //This value is not set. Make it default to 100
  255. rateLimit = 100;
  256. }
  257. let rateLimitDisableState = "";
  258. if (!payload.RequireRateLimit){
  259. rateLimitDisableState = "disabled";
  260. }
  261. column.empty().append(`<div class="ui checkbox" style="margin-top: 0.4em;">
  262. <input type="checkbox" class="RequireBasicAuth" ${basicAuthCheckstate}>
  263. <label>Require Basic Auth</label>
  264. </div>
  265. <br>
  266. <button class="ui basic compact tiny button" style="margin-left: 0.4em; margin-top: 0.4em;" onclick="editBasicAuthCredentials('${uuid}');"><i class="ui blue user circle icon"></i> Edit Credentials</button>
  267. <br>
  268. <button class="ui basic compact tiny button" style="margin-left: 0.4em; margin-top: 0.4em;" onclick="editCustomHeaders('${uuid}');"><i class="heading icon"></i> Custom Headers</button>
  269. <div class="ui basic advance segment" style="padding: 0.4em !important; border-radius: 0.4em;">
  270. <div class="ui endpointAdvanceConfig accordion" style="padding-right: 0.6em;">
  271. <div class="title">
  272. <i class="dropdown icon"></i>
  273. Security Options
  274. </div>
  275. <div class="content">
  276. <div class="ui checkbox" style="margin-top: 0.4em;">
  277. <input type="checkbox" onchange="handleToggleRateLimitInput();" class="RequireRateLimit" ${rateLimitCheckState}>
  278. <label>Require Rate Limit<br>
  279. <small>Check this to enable rate limit on this inbound hostname</small></label>
  280. </div><br>
  281. <div class="ui mini right labeled fluid input ${rateLimitDisableState}" style="margin-top: 0.4em;">
  282. <input type="number" class="RateLimit" value="${rateLimit}" min="1" >
  283. <label class="ui basic label">
  284. req / sec / IP
  285. </label>
  286. </div>
  287. </div>
  288. </div>
  289. <div>
  290. `);
  291. } else if (datatype == "ratelimit"){
  292. column.empty().append(`
  293. <div class="ui checkbox" style="margin-top: 0.4em;">
  294. <input type="checkbox" class="RequireRateLimit" ${checkstate}>
  295. <label>Require Rate Limit</label>
  296. </div>
  297. <div class="ui mini fluid input">
  298. <input type="number" class="RateLimit" value="${rateLimit}" placeholder="100" min="1" max="1000" >
  299. </div>
  300. `);
  301. }else if (datatype == 'action'){
  302. column.empty().append(`
  303. <button title="Save" onclick="saveProxyInlineEdit('${uuid.hexEncode()}');" class="ui basic small icon circular button inlineEditActionBtn"><i class="ui green save icon"></i></button>
  304. <button title="Cancel" onclick="exitProxyInlineEdit();" class="ui basic small icon circular button inlineEditActionBtn"><i class="ui remove icon"></i></button>
  305. `);
  306. }else if (datatype == "inbound"){
  307. let originalContent = $(column).html();
  308. column.empty().append(`${originalContent}
  309. <div class="ui divider"></div>
  310. <div class="ui checkbox" style="margin-top: 0.4em;">
  311. <input type="checkbox" class="BypassGlobalTLS" ${payload.BypassGlobalTLS?"checked":""}>
  312. <label>Allow plain HTTP access<br>
  313. <small>Allow inbound connections without TLS/SSL</small></label>
  314. </div><br>
  315. <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>
  316. <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>
  317. `);
  318. $(".hostAccessRuleSelector").dropdown();
  319. }else{
  320. //Unknown field. Leave it untouched
  321. }
  322. });
  323. $(".endpointAdvanceConfig").accordion();
  324. $("#httpProxyList").find(".editBtn").addClass("disabled");
  325. }
  326. //handleToggleRateLimitInput will get trigger if the "require rate limit" checkbox
  327. // is changed and toggle the disable state of the rate limit input field
  328. function handleToggleRateLimitInput(){
  329. let isRateLimitEnabled = $("#httpProxyList input.RequireRateLimit")[0].checked;
  330. if (isRateLimitEnabled){
  331. $("#httpProxyList input.RateLimit").parent().removeClass("disabled");
  332. }else{
  333. $("#httpProxyList input.RateLimit").parent().addClass("disabled");
  334. }
  335. }
  336. function exitProxyInlineEdit(){
  337. listProxyEndpoints();
  338. $("#httpProxyList").find(".editBtn").removeClass("disabled");
  339. }
  340. function saveProxyInlineEdit(uuid){
  341. uuid = uuid.hexDecode();
  342. var row = $('tr[eptuuid="' + uuid + '"]');
  343. if (row.length == 0){
  344. return;
  345. }
  346. var epttype = "host";
  347. let useStickySession = $(row).find(".UseStickySession")[0].checked;
  348. let requireBasicAuth = $(row).find(".RequireBasicAuth")[0].checked;
  349. let requireRateLimit = $(row).find(".RequireRateLimit")[0].checked;
  350. let rateLimit = $(row).find(".RateLimit").val();
  351. let bypassGlobalTLS = $(row).find(".BypassGlobalTLS")[0].checked;
  352. let bypassWebsocketOrigin = $(row).find(".SkipWebSocketOriginCheck")[0].checked;
  353. $.ajax({
  354. url: "/api/proxy/edit",
  355. method: "POST",
  356. data: {
  357. "type": epttype,
  358. "rootname": uuid,
  359. "ss":useStickySession,
  360. "bpgtls": bypassGlobalTLS,
  361. "bpwsorg" : bypassWebsocketOrigin,
  362. "bauth" :requireBasicAuth,
  363. "rate" :requireRateLimit,
  364. "ratenum" :rateLimit,
  365. },
  366. success: function(data){
  367. if (data.error !== undefined){
  368. msgbox(data.error, false, 6000);
  369. }else{
  370. msgbox("Proxy endpoint updated");
  371. listProxyEndpoints();
  372. }
  373. }
  374. })
  375. }
  376. /* button events */
  377. function editBasicAuthCredentials(uuid){
  378. let payload = encodeURIComponent(JSON.stringify({
  379. ept: "host",
  380. ep: uuid
  381. }));
  382. showSideWrapper("snippet/basicAuthEditor.html?t=" + Date.now() + "#" + payload);
  383. }
  384. function editAccessRule(uuid){
  385. let payload = encodeURIComponent(JSON.stringify({
  386. ept: "host",
  387. ep: uuid
  388. }));
  389. showSideWrapper("snippet/hostAccessEditor.html?t=" + Date.now() + "#" + payload);
  390. }
  391. function editAliasHostnames(uuid){
  392. let payload = encodeURIComponent(JSON.stringify({
  393. ept: "host",
  394. ep: uuid
  395. }));
  396. showSideWrapper("snippet/aliasEditor.html?t=" + Date.now() + "#" + payload);
  397. }
  398. function quickEditVdir(uuid){
  399. openTabById("vdir");
  400. $("#vdirBaseRoutingRule").parent().dropdown("set selected", uuid);
  401. }
  402. //Open the custom header editor
  403. function editCustomHeaders(uuid){
  404. let payload = encodeURIComponent(JSON.stringify({
  405. ept: "host",
  406. ep: uuid
  407. }));
  408. showSideWrapper("snippet/customHeaders.html?t=" + Date.now() + "#" + payload);
  409. }
  410. //Open the load balance option
  411. function editUpstreams(uuid){
  412. let payload = encodeURIComponent(JSON.stringify({
  413. ept: "host",
  414. ep: uuid
  415. }));
  416. showSideWrapper("snippet/upstreams.html?t=" + Date.now() + "#" + payload);
  417. }
  418. function handleProxyRuleToggle(object){
  419. let endpointUUID = $(object).attr("eptuuid");
  420. let isChecked = object.checked;
  421. $.ajax({
  422. url: "/api/proxy/toggle",
  423. data: {
  424. "ep": endpointUUID,
  425. "enable": isChecked
  426. },
  427. success: function(data){
  428. if (data.error != undefined){
  429. msgbox(data.error, false);
  430. }else{
  431. if (isChecked){
  432. msgbox("Proxy Rule Enabled");
  433. }else{
  434. msgbox("Proxy Rule Disabled");
  435. }
  436. }
  437. }
  438. })
  439. }
  440. //Bind on tab switch events
  441. tabSwitchEventBind["httprp"] = function(){
  442. listProxyEndpoints();
  443. }
  444. </script>