httprp.html 36 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775
  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. .httpProxyListTools{
  14. width: 100%;
  15. }
  16. .tag-select{
  17. cursor: pointer;
  18. }
  19. .tag-select:hover{
  20. text-decoration: underline;
  21. opacity: 0.8;
  22. }
  23. </style>
  24. <div class="httpProxyListTools" style="margin-bottom: 1em;">
  25. <div id="tagFilterDropdown" class="ui floating basic dropdown labeled icon button" style="min-width: 150px;">
  26. <i class="filter icon"></i>
  27. <span class="text">Filter by tags</span>
  28. <div class="menu">
  29. <div class="ui icon search input">
  30. <i class="search icon"></i>
  31. <input type="text" placeholder="Search tags...">
  32. </div>
  33. <div class="divider"></div>
  34. <div class="scrolling menu tagList">
  35. <!--
  36. Example:
  37. <div class="item">
  38. <div class="ui red empty circular label"></div>
  39. Important
  40. </div>
  41. -->
  42. <!-- Add more tag options dynamically -->
  43. </div>
  44. </div>
  45. </div>
  46. <div class="ui small input" style="width: 300px; height: 38px;">
  47. <input type="text" id="searchInput" placeholder="Quick Search" onkeydown="handleSearchInput(event);" onchange="handleSearchInput(event);" onblur="handleSearchInput(event);">
  48. </div>
  49. </div>
  50. <div style="width: 100%; overflow-x: auto; margin-bottom: 1em; min-height: 300px;">
  51. <table class="ui celled sortable unstackable compact table">
  52. <thead>
  53. <tr>
  54. <th>Host</th>
  55. <th>Destination</th>
  56. <th>Virtual Directory</th>
  57. <th>Tags</th>
  58. <th style="max-width: 300px;">Advanced Settings</th>
  59. <th class="no-sort" style="min-width:150px;">Actions</th>
  60. </tr>
  61. </thead>
  62. <tbody id="httpProxyList">
  63. </tbody>
  64. </table>
  65. </div>
  66. <button class="ui icon right floated basic button" onclick="listProxyEndpoints();"><i class="green refresh icon"></i> Refresh</button>
  67. <br><br>
  68. </div>
  69. <script>
  70. /* List all proxy endpoints */
  71. function listProxyEndpoints(){
  72. $.get("/api/proxy/list?type=host", function(data){
  73. $("#httpProxyList").html(``);
  74. if (data.error !== undefined){
  75. $("#httpProxyList").append(`<tr>
  76. <td data-label="" colspan="5"><i class="remove icon"></i> ${data.error}</td>
  77. </tr>`);
  78. }else if (data.length == 0){
  79. $("#httpProxyList").append(`<tr>
  80. <td data-label="" colspan="5"><i class="green check circle icon"></i> No HTTP Proxy Record</td>
  81. </tr>`);
  82. }else{
  83. //Sort by RootOrMatchingDomain field
  84. data.sort((a,b) => (a.RootOrMatchingDomain > b.RootOrMatchingDomain) ? 1 : ((b.RootOrMatchingDomain > a.RootOrMatchingDomain) ? -1 : 0))
  85. data.forEach(subd => {
  86. let subdData = encodeURIComponent(JSON.stringify(subd));
  87. //Build the upstream list
  88. let upstreams = "";
  89. if (subd.ActiveOrigins.length == 0){
  90. //Invalid config
  91. upstreams = `<i class="ui yellow exclamation triangle icon"></i> No Active Upstream Origin<br>`;
  92. }else{
  93. subd.ActiveOrigins.forEach(upstream => {
  94. console.log(upstream);
  95. //Check if the upstreams require TLS connections
  96. let tlsIcon = "";
  97. if (upstream.RequireTLS){
  98. tlsIcon = `<i class="green lock icon" title="TLS Mode"></i>`;
  99. if (upstream.SkipCertValidations){
  100. tlsIcon = `<i class="yellow lock icon" title="TLS/SSL mode without verification"></i>`
  101. }
  102. }
  103. let upstreamLink = `${upstream.RequireTLS?"https://":"http://"}${upstream.OriginIpOrDomain}`;
  104. upstreams += `<a href="${upstreamLink}" target="_blank">${upstream.OriginIpOrDomain} ${tlsIcon}</a><br>`;
  105. })
  106. }
  107. let inboundTlsIcon = "";
  108. if ($("#tls").checkbox("is checked")){
  109. inboundTlsIcon = `<i class="green lock icon" title="TLS Mode"></i>`;
  110. if (subd.BypassGlobalTLS){
  111. inboundTlsIcon = `<i class="grey lock icon" title="TLS Bypass Enabled"></i>`;
  112. }
  113. }else{
  114. inboundTlsIcon = `<i class="yellow lock open icon" title="Plain Text Mode"></i>`;
  115. }
  116. //Build the virtual directory list
  117. var vdList = `<div class="ui list">`;
  118. subd.VirtualDirectories.forEach(vdir => {
  119. vdList += `<div class="item">${vdir.MatchingPath} <i class="green angle double right icon"></i> ${vdir.Domain}</div>`;
  120. });
  121. vdList += `</div>`;
  122. if (subd.VirtualDirectories.length == 0){
  123. vdList = `<small style="opacity: 0.3; pointer-events: none; user-select: none;">No Virtual Directory</small>`;
  124. }
  125. let enableChecked = "checked";
  126. if (subd.Disabled){
  127. enableChecked = "";
  128. }
  129. let aliasDomains = ``;
  130. if (subd.MatchingDomainAlias != undefined && subd.MatchingDomainAlias.length > 0){
  131. aliasDomains = `<small class="aliasDomains" eptuuid="${subd.RootOrMatchingDomain}" style="color: #636363;">Alias: `;
  132. subd.MatchingDomainAlias.forEach(alias => {
  133. aliasDomains += `<a href="//${alias}" target="_blank">${alias}</a>, `;
  134. });
  135. aliasDomains = aliasDomains.substr(0, aliasDomains.length - 2); //Remove the last tailing seperator
  136. aliasDomains += `</small><br>`;
  137. }
  138. $("#httpProxyList").append(`<tr eptuuid="${subd.RootOrMatchingDomain}" payload="${subdData}" class="subdEntry">
  139. <td data-label="" editable="true" datatype="inbound">
  140. <a href="//${subd.RootOrMatchingDomain}" target="_blank">${subd.RootOrMatchingDomain}</a> ${inboundTlsIcon}<br>
  141. ${aliasDomains}
  142. <small class="accessRuleNameUnderHost" ruleid="${subd.AccessFilterUUID}"></small>
  143. </td>
  144. <td data-label="" editable="true" datatype="domain">
  145. <div class="upstreamList">
  146. ${upstreams}
  147. </div>
  148. </td>
  149. <td data-label="" editable="true" datatype="vdir">${vdList}</td>
  150. <td data-label="tags" payload="${encodeURIComponent(JSON.stringify(subd.Tags))}" datatype="tags">
  151. <div class="tags-list">
  152. ${subd.Tags.length >0 ? subd.Tags.map(tag => `<span class="ui tiny label tag-select" style="background-color: ${getTagColorByName(tag)}; color: ${getTagTextColor(tag)}">${tag}</span>`).join(""):"<small style='opacity: 0.3; pointer-events: none; user-select: none;'>No Tags</small>"}
  153. </div>
  154. </td>
  155. <td data-label="" editable="true" datatype="advanced" style="width: 350px;">
  156. ${subd.AuthenticationProvider.AuthMethod == 0x1?`<i class="ui grey key icon"></i> Basic Auth`:``}
  157. ${subd.AuthenticationProvider.AuthMethod == 0x2?`<i class="ui blue key icon"></i> Authelia`:``}
  158. ${subd.AuthenticationProvider.AuthMethod == 0x3?`<i class="ui yellow key icon"></i> Oauth2`:``}
  159. ${subd.AuthenticationProvider.AuthMethod != 0x0 && subd.RequireRateLimit?"<br>":""}
  160. ${subd.RequireRateLimit?`<i class="ui green check icon"></i> Rate Limit @ ${subd.RateLimit} req/s`:``}
  161. ${subd.AuthenticationProvider.AuthMethod == 0x0 && !subd.RequireRateLimit?`<small style="opacity: 0.3; pointer-events: none; user-select: none;">No Special Settings</small>`:""}
  162. </td>
  163. <td class="center aligned ignoremw" editable="true" datatype="action" data-label="">
  164. <div class="ui toggle tiny fitted checkbox" style="margin-bottom: -0.5em; margin-right: 0.4em;" title="Enable / Disable Rule">
  165. <input type="checkbox" class="enableToggle" name="active" ${enableChecked} eptuuid="${subd.RootOrMatchingDomain}" onchange="handleProxyRuleToggle(this);">
  166. <label></label>
  167. </div>
  168. <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>
  169. <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>
  170. </td>
  171. </tr>`);
  172. });
  173. populateTagFilterDropdown(data);
  174. }
  175. resolveAccessRuleNameOnHostRPlist();
  176. });
  177. }
  178. //Perform realtime alias update without refreshing the whole page
  179. function updateAliasListForEndpoint(endpointName, newAliasDomainList){
  180. let targetEle = $(`.aliasDomains[eptuuid='${endpointName}']`);
  181. console.log(targetEle);
  182. if (targetEle.length == 0){
  183. return;
  184. }
  185. let aliasDomains = ``;
  186. if (newAliasDomainList != undefined && newAliasDomainList.length > 0){
  187. aliasDomains = `Alias: `;
  188. newAliasDomainList.forEach(alias => {
  189. aliasDomains += `<a href="//${alias}" target="_blank">${alias}</a>, `;
  190. });
  191. aliasDomains = aliasDomains.substr(0, aliasDomains.length - 2); //Remove the last tailing seperator
  192. $(targetEle).html(aliasDomains);
  193. $(targetEle).show();
  194. }else{
  195. $(targetEle).hide();
  196. }
  197. }
  198. //Resolve & Update all rule names on host PR list
  199. function resolveAccessRuleNameOnHostRPlist(){
  200. //Resolve the access filters
  201. $.get("/api/access/list", function(data){
  202. console.log(data);
  203. if (data.error == undefined){
  204. //Build a map base on the data
  205. let accessRuleMap = {};
  206. for (var i = 0; i < data.length; i++){
  207. accessRuleMap[data[i].ID] = data[i];
  208. }
  209. $(".accessRuleNameUnderHost").each(function(){
  210. let thisAccessRuleID = $(this).attr("ruleid");
  211. if (thisAccessRuleID== ""){
  212. thisAccessRuleID = "default"
  213. }
  214. if (thisAccessRuleID == "default"){
  215. //No need to label default access rules
  216. $(this).html("");
  217. return;
  218. }
  219. let rule = accessRuleMap[thisAccessRuleID];
  220. if (rule == undefined){
  221. //Missing config or config too old
  222. $(this).html(`<i class="ui red exclamation triangle icon"></i> <b style="color: #db2828;">Access Rule Error</b>`);
  223. return;
  224. }
  225. let icon = `<i class="ui grey filter icon"></i>`;
  226. if (rule.ID == "default"){
  227. icon = `<i class="ui yellow star icon"></i>`;
  228. }else if (rule.BlacklistEnabled && !rule.WhitelistEnabled){
  229. //This is a blacklist filter
  230. icon = `<i class="ui red filter icon"></i>`;
  231. }else if (rule.WhitelistEnabled && !rule.BlacklistEnabled){
  232. //This is a whitelist filter
  233. icon = `<i class="ui green filter icon"></i>`;
  234. }else if (rule.WhitelistEnabled && rule.BlacklistEnabled){
  235. //Whitelist and blacklist filter
  236. icon = `<i class="ui yellow filter icon"></i>`;
  237. }
  238. if (rule != undefined){
  239. $(this).html(`${icon} ${rule.Name}`);
  240. }
  241. });
  242. }
  243. })
  244. }
  245. //Update the access rule name on given epuuid, call by hostAccessEditor.html
  246. function updateAccessRuleNameUnderHost(epuuid, newruleUID){
  247. $(`tr[eptuuid='${epuuid}'].subdEntry`).find(".accessRuleNameUnderHost").attr("ruleid", newruleUID);
  248. resolveAccessRuleNameOnHostRPlist();
  249. }
  250. /*
  251. Inline editor for httprp.html
  252. */
  253. function editEndpoint(uuid) {
  254. uuid = uuid.hexDecode();
  255. var row = $('tr[eptuuid="' + uuid + '"]');
  256. var columns = row.find('td[data-label]');
  257. var payload = $(row).attr("payload");
  258. payload = JSON.parse(decodeURIComponent(payload));
  259. console.log(payload);
  260. columns.each(function(index) {
  261. var column = $(this);
  262. var oldValue = column.text().trim();
  263. if ($(this).attr("editable") == "false"){
  264. //This col do not allow edit. Skip
  265. return;
  266. }
  267. // Create an input element based on the column content
  268. var input;
  269. var datatype = $(this).attr("datatype");
  270. if (datatype == "domain"){
  271. let useStickySessionChecked = "";
  272. if (payload.UseStickySession){
  273. useStickySessionChecked = "checked";
  274. }
  275. let enableUptimeMonitor = "";
  276. //Note the config file store the uptime monitor as disable, so we need to reverse the logic
  277. if (!payload.DisableUptimeMonitor){
  278. enableUptimeMonitor = "checked";
  279. }
  280. 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>
  281. <div class="ui divider"></div>
  282. <div class="ui checkbox" style="margin-top: 0.4em;">
  283. <input type="checkbox" class="UseStickySession" ${useStickySessionChecked}>
  284. <label>Use Sticky Session<br>
  285. <small>Enable stick session on load balancing</small></label>
  286. </div>
  287. <div class="ui checkbox" style="margin-top: 0.4em;">
  288. <input type="checkbox" class="EnableUptimeMonitor" ${enableUptimeMonitor}>
  289. <label>Monitor Uptime<br>
  290. <small>Enable active uptime monitor</small></label>
  291. </div>
  292. `;
  293. column.append(input);
  294. $(column).find(".upstreamList").addClass("editing");
  295. }else if (datatype == "vdir"){
  296. //Append a quick access button for vdir page
  297. column.append(`<button class="ui basic tiny button" style="margin-left: 0.4em; margin-top: 0.4em;" onclick="quickEditVdir('${uuid}');">
  298. <i class="ui yellow folder icon"></i> Edit Virtual Directories
  299. </button>`);
  300. }else if (datatype == "tags"){
  301. column.append(`
  302. <div class="ui divider"></div>
  303. <button class="ui basic compact fluid tiny button" style="margin-left: 0.4em; margin-top: 0.4em;" onclick="editTags('${uuid}');"><i class="ui purple tag icon"></i> Edit tags</button>
  304. `);
  305. }else if (datatype == "advanced"){
  306. let authProvider = payload.AuthenticationProvider.AuthMethod;
  307. let skipWebSocketOriginCheck = payload.SkipWebSocketOriginCheck;
  308. let wsCheckstate = "";
  309. if (skipWebSocketOriginCheck){
  310. wsCheckstate = "checked";
  311. }
  312. let requireRateLimit = payload.RequireRateLimit;
  313. let rateLimitCheckState = "";
  314. if (requireRateLimit){
  315. rateLimitCheckState = "checked";
  316. }
  317. let rateLimit = payload.RateLimit;
  318. if (rateLimit == 0){
  319. //This value is not set. Make it default to 100
  320. rateLimit = 100;
  321. }
  322. let rateLimitDisableState = "";
  323. if (!payload.RequireRateLimit){
  324. rateLimitDisableState = "disabled";
  325. }
  326. column.empty().append(`
  327. <div class="grouped fields authProviderPicker">
  328. <label><b>Authentication Provider</b></label>
  329. <div class="field">
  330. <div class="ui radio checkbox">
  331. <input type="radio" value="0" name="authProviderType" ${authProvider==0x0?"checked":""}>
  332. <label>None (Anyone can access)</label>
  333. </div>
  334. </div>
  335. <div class="field">
  336. <div class="ui radio checkbox">
  337. <input type="radio" value="1" name="authProviderType" ${authProvider==0x1?"checked":""}>
  338. <label>Basic Auth</label>
  339. </div>
  340. </div>
  341. <div class="field">
  342. <div class="ui radio checkbox">
  343. <input type="radio" value="2" name="authProviderType" ${authProvider==0x2?"checked":""}>
  344. <label>Authelia</label>
  345. </div>
  346. </div>
  347. </div>
  348. <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>
  349. <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>
  350. <div class="ui basic advance segment" style="padding: 0.4em !important; border-radius: 0.4em;">
  351. <div class="ui endpointAdvanceConfig accordion" style="padding-right: 0.6em;">
  352. <div class="title">
  353. <i class="dropdown icon"></i>
  354. Security Options
  355. </div>
  356. <div class="content">
  357. <div class="ui checkbox" style="margin-top: 0.4em;">
  358. <input type="checkbox" onchange="handleToggleRateLimitInput();" class="RequireRateLimit" ${rateLimitCheckState}>
  359. <label>Require Rate Limit<br>
  360. <small>Check this to enable rate limit on this inbound hostname</small></label>
  361. </div><br>
  362. <div class="ui mini right labeled fluid input ${rateLimitDisableState}" style="margin-top: 0.4em;">
  363. <input type="number" class="RateLimit" value="${rateLimit}" min="1" >
  364. <label class="ui basic label">
  365. req / sec / IP
  366. </label>
  367. </div>
  368. </div>
  369. </div>
  370. <div>
  371. `);
  372. $('.authProviderPicker .ui.checkbox').checkbox();
  373. } else if (datatype == "ratelimit"){
  374. column.empty().append(`
  375. <div class="ui checkbox" style="margin-top: 0.4em;">
  376. <input type="checkbox" class="RequireRateLimit" ${checkstate}>
  377. <label>Require Rate Limit</label>
  378. </div>
  379. <div class="ui mini fluid input">
  380. <input type="number" class="RateLimit" value="${rateLimit}" placeholder="100" min="1" max="1000" >
  381. </div>
  382. `);
  383. }else if (datatype == 'action'){
  384. column.empty().append(`
  385. <button title="Save" onclick="saveProxyInlineEdit('${uuid.hexEncode()}');" class="ui basic small icon circular button inlineEditActionBtn"><i class="ui green save icon"></i></button>
  386. <button title="Cancel" onclick="exitProxyInlineEdit();" class="ui basic small icon circular button inlineEditActionBtn"><i class="ui remove icon"></i></button>
  387. `);
  388. }else if (datatype == "inbound"){
  389. let originalContent = $(column).html();
  390. //Check if this host is covered within one of the certificates. If not, show the icon
  391. let enableQuickRequestButton = true;
  392. let domains = [payload.RootOrMatchingDomain]; //Domain for getting certificate if needed
  393. for (var i = 0; i < payload.MatchingDomainAlias.length; i++){
  394. let thisAliasName = payload.MatchingDomainAlias[i];
  395. domains.push(thisAliasName);
  396. }
  397. //Check if the domain or alias contains wildcard, if yes, disabled the get certificate button
  398. if (payload.RootOrMatchingDomain.indexOf("*") > -1){
  399. enableQuickRequestButton = false;
  400. }
  401. if (payload.MatchingDomainAlias != undefined){
  402. for (var i = 0; i < payload.MatchingDomainAlias.length; i++){
  403. if (payload.MatchingDomainAlias[i].indexOf("*") > -1){
  404. enableQuickRequestButton = false;
  405. break;
  406. }
  407. }
  408. }
  409. //encode the domain to DOM
  410. let certificateDomains = encodeURIComponent(JSON.stringify(domains));
  411. column.empty().append(`${originalContent}
  412. <div class="ui divider"></div>
  413. <div class="ui checkbox" style="margin-top: 0.4em;">
  414. <input type="checkbox" class="BypassGlobalTLS" ${payload.BypassGlobalTLS?"checked":""}>
  415. <label>Allow plain HTTP access<br>
  416. <small>Allow inbound connections without TLS/SSL</small></label>
  417. </div><br>
  418. <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>
  419. <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>
  420. <button class="ui basic compact tiny ${enableQuickRequestButton?"":"disabled"} button" style="margin-left: 0.4em; margin-top: 0.4em;" onclick="requestCertificateForExistingHost('${uuid}', '${certificateDomains}', this);"><i class="green lock icon"></i> Get Certificate</button>
  421. `);
  422. $(".hostAccessRuleSelector").dropdown();
  423. }else{
  424. //Unknown field. Leave it untouched
  425. }
  426. });
  427. $(".endpointAdvanceConfig").accordion();
  428. $("#httpProxyList").find(".editBtn").addClass("disabled");
  429. }
  430. //handleToggleRateLimitInput will get trigger if the "require rate limit" checkbox
  431. // is changed and toggle the disable state of the rate limit input field
  432. function handleToggleRateLimitInput(){
  433. let isRateLimitEnabled = $("#httpProxyList input.RequireRateLimit")[0].checked;
  434. if (isRateLimitEnabled){
  435. $("#httpProxyList input.RateLimit").parent().removeClass("disabled");
  436. }else{
  437. $("#httpProxyList input.RateLimit").parent().addClass("disabled");
  438. }
  439. }
  440. function exitProxyInlineEdit(){
  441. listProxyEndpoints();
  442. $("#httpProxyList").find(".editBtn").removeClass("disabled");
  443. }
  444. function saveProxyInlineEdit(uuid){
  445. uuid = uuid.hexDecode();
  446. var row = $('tr[eptuuid="' + uuid + '"]');
  447. if (row.length == 0){
  448. return;
  449. }
  450. var epttype = "host";
  451. let useStickySession = $(row).find(".UseStickySession")[0].checked;
  452. let DisableUptimeMonitor = !$(row).find(".EnableUptimeMonitor")[0].checked;
  453. let authProviderType = $(row).find(".authProviderPicker input[type='radio']:checked").val();
  454. let requireRateLimit = $(row).find(".RequireRateLimit")[0].checked;
  455. let rateLimit = $(row).find(".RateLimit").val();
  456. let bypassGlobalTLS = $(row).find(".BypassGlobalTLS")[0].checked;
  457. let tags = getTagsArrayFromEndpoint(uuid);
  458. if (tags.length > 0){
  459. tags = tags.join(",");
  460. }else{
  461. tags = "";
  462. }
  463. $.cjax({
  464. url: "/api/proxy/edit",
  465. method: "POST",
  466. data: {
  467. "type": epttype,
  468. "rootname": uuid,
  469. "ss":useStickySession,
  470. "dutm": DisableUptimeMonitor,
  471. "bpgtls": bypassGlobalTLS,
  472. "authprovider" :authProviderType,
  473. "rate" :requireRateLimit,
  474. "ratenum" :rateLimit,
  475. "tags": tags,
  476. },
  477. success: function(data){
  478. if (data.error !== undefined){
  479. msgbox(data.error, false, 6000);
  480. }else{
  481. msgbox("Proxy endpoint updated");
  482. listProxyEndpoints();
  483. }
  484. }
  485. })
  486. }
  487. //Generic functions for delete rp endpoints
  488. function deleteEndpoint(epoint){
  489. epoint = decodeURIComponent(epoint).hexDecode();
  490. if (confirm("Confirm remove proxy for :" + epoint + "?")){
  491. $.cjax({
  492. url: "/api/proxy/del",
  493. method: "POST",
  494. data: {ep: epoint},
  495. success: function(data){
  496. if (data.error == undefined){
  497. listProxyEndpoints();
  498. msgbox("Proxy Rule Deleted", true);
  499. reloadUptimeList();
  500. }else{
  501. msgbox(data.error, false);
  502. }
  503. }
  504. })
  505. }
  506. }
  507. /* button events */
  508. function editBasicAuthCredentials(uuid){
  509. let payload = encodeURIComponent(JSON.stringify({
  510. ept: "host",
  511. ep: uuid
  512. }));
  513. showSideWrapper("snippet/basicAuthEditor.html?t=" + Date.now() + "#" + payload);
  514. }
  515. function editAccessRule(uuid){
  516. let payload = encodeURIComponent(JSON.stringify({
  517. ept: "host",
  518. ep: uuid
  519. }));
  520. showSideWrapper("snippet/hostAccessEditor.html?t=" + Date.now() + "#" + payload);
  521. }
  522. function editAliasHostnames(uuid){
  523. let payload = encodeURIComponent(JSON.stringify({
  524. ept: "host",
  525. ep: uuid
  526. }));
  527. showSideWrapper("snippet/aliasEditor.html?t=" + Date.now() + "#" + payload);
  528. }
  529. function quickEditVdir(uuid){
  530. openTabById("vdir");
  531. $("#vdirBaseRoutingRule").parent().dropdown("set selected", uuid);
  532. }
  533. //Open the custom header editor
  534. function editCustomHeaders(uuid){
  535. let payload = encodeURIComponent(JSON.stringify({
  536. ept: "host",
  537. ep: uuid
  538. }));
  539. showSideWrapper("snippet/customHeaders.html?t=" + Date.now() + "#" + payload);
  540. }
  541. //Open the load balance option
  542. function editUpstreams(uuid){
  543. let payload = encodeURIComponent(JSON.stringify({
  544. ept: "host",
  545. ep: uuid
  546. }));
  547. showSideWrapper("snippet/upstreams.html?t=" + Date.now() + "#" + payload);
  548. }
  549. function handleProxyRuleToggle(object){
  550. let endpointUUID = $(object).attr("eptuuid");
  551. let isChecked = object.checked;
  552. $.cjax({
  553. url: "/api/proxy/toggle",
  554. data: {
  555. "ep": endpointUUID,
  556. "enable": isChecked
  557. },
  558. success: function(data){
  559. if (data.error != undefined){
  560. msgbox(data.error, false);
  561. }else{
  562. if (isChecked){
  563. msgbox("Proxy Rule Enabled");
  564. }else{
  565. msgbox("Proxy Rule Disabled");
  566. }
  567. }
  568. }
  569. })
  570. }
  571. /*
  572. Certificate Shortcut
  573. */
  574. function requestCertificateForExistingHost(hostUUID, RootAndAliasDomains, btn=undefined){
  575. RootAndAliasDomains = JSON.parse(decodeURIComponent(RootAndAliasDomains))
  576. let renewDomainKey = RootAndAliasDomains.join(",");
  577. let preferedACMEEmail = $("#prefACMEEmail").val();
  578. if (preferedACMEEmail == ""){
  579. msgbox("Preferred email for ACME registration not set", false);
  580. return;
  581. }
  582. let defaultCA = $("#defaultCA").dropdown("get value");
  583. if (defaultCA == ""){
  584. defaultCA = "Let's Encrypt";
  585. }
  586. //Check if the root or the alias domain contain wildcard character, if yes, return error
  587. for (var i = 0; i < RootAndAliasDomains.length; i++){
  588. if (RootAndAliasDomains[i].indexOf("*") != -1){
  589. msgbox("Wildcard domain can only be setup via ACME tool", false);
  590. return;
  591. }
  592. }
  593. //Renew the certificate
  594. renewCertificate(renewDomainKey, false, btn);
  595. }
  596. //Bind on tab switch events
  597. tabSwitchEventBind["httprp"] = function(){
  598. listProxyEndpoints();
  599. }
  600. /* Tags & Search */
  601. function handleSearchInput(event){
  602. if (event.key == "Escape"){
  603. $("#searchInput").val("");
  604. }
  605. filterProxyList();
  606. }
  607. // Function to filter the proxy list
  608. function filterProxyList() {
  609. let searchInput = $("#searchInput").val().toLowerCase();
  610. let selectedTag = $("#tagFilterDropdown").dropdown('get value');
  611. $("#httpProxyList tr").each(function() {
  612. let host = $(this).find("td[data-label='']").text().toLowerCase();
  613. let tagElements = $(this).find("td[data-label='tags']");
  614. let tags = tagElements.attr("payload");
  615. tags = JSON.parse(decodeURIComponent(tags));
  616. if ((host.includes(searchInput) || searchInput === "") && (tags.includes(selectedTag) || selectedTag === "")) {
  617. $(this).show();
  618. } else {
  619. $(this).hide();
  620. }
  621. });
  622. }
  623. // Function to generate a color based on a tag name
  624. function getTagColorByName(tagName) {
  625. function hashCode(str) {
  626. return str.split('').reduce((prevHash, currVal) =>
  627. ((prevHash << 5) - prevHash) + currVal.charCodeAt(0), 0);
  628. }
  629. let hash = hashCode(tagName);
  630. let color = '#' + ((hash >> 24) & 0xFF).toString(16).padStart(2, '0') +
  631. ((hash >> 16) & 0xFF).toString(16).padStart(2, '0') +
  632. ((hash >> 8) & 0xFF).toString(16).padStart(2, '0');
  633. return color;
  634. }
  635. function getTagTextColor(tagName){
  636. let color = getTagColorByName(tagName);
  637. let r = parseInt(color.substr(1, 2), 16);
  638. let g = parseInt(color.substr(3, 2), 16);
  639. let b = parseInt(color.substr(5, 2), 16);
  640. let brightness = Math.round(((r * 299) + (g * 587) + (b * 114)) / 1000);
  641. return brightness > 125 ? "#000000" : "#ffffff";
  642. }
  643. // Populate the tag filter dropdown
  644. function populateTagFilterDropdown(data) {
  645. let tags = new Set();
  646. data.forEach(subd => {
  647. subd.Tags.forEach(tag => tags.add(tag));
  648. });
  649. tags = Array.from(tags).sort((a, b) => a.localeCompare(b));
  650. let dropdownMenu = $("#tagFilterDropdown .tagList");
  651. dropdownMenu.html(`<div class="item tag-select" data-value="">
  652. <div class="ui grey empty circular label"></div>
  653. Show all
  654. </div>`);
  655. tags.forEach(tag => {
  656. let thisTagColor = getTagColorByName(tag);
  657. dropdownMenu.append(`<div class="item tag-select" data-value="${tag}">
  658. <div class="ui empty circular label" style="background-color: ${thisTagColor}; border-color: ${thisTagColor};" ></div>
  659. ${tag}
  660. </div>`);
  661. });
  662. }
  663. // Edit tags for a specific endpoint
  664. function editTags(uuid){
  665. let payload = encodeURIComponent(JSON.stringify({
  666. ept: "host",
  667. ep: uuid
  668. }));
  669. showSideWrapper("snippet/tagEditor.html?t=" + Date.now() + "#" + payload);
  670. }
  671. // Render the tags preview from tag editing snippet
  672. function renderTagsPreview(endpoint, tags){
  673. let targetProxyRuleEle = $(".subdEntry[eptuuid='" + endpoint + "'] td[data-label='tags']");
  674. //Update the tag DOM
  675. let newTagDOM = tags.map(tag => `<span class="ui tiny label tag-select" style="background-color: ${getTagColorByName(tag)}; color: ${getTagTextColor(tag)}">${tag}</span>`).join("");
  676. $(targetProxyRuleEle).find(".tags-list").html(newTagDOM);
  677. //Update the tag payload
  678. $(targetProxyRuleEle).attr("payload", encodeURIComponent(JSON.stringify(tags)));
  679. }
  680. function getTagsArrayFromEndpoint(endpoint){
  681. let targetProxyRuleEle = $(".subdEntry[eptuuid='" + endpoint + "'] td[data-label='tags']");
  682. let tags = $(targetProxyRuleEle).attr("payload");
  683. return JSON.parse(decodeURIComponent(tags));
  684. }
  685. // Initialize the proxy list on page load
  686. $(document).ready(function() {
  687. listProxyEndpoints();
  688. // Event listener for clicking on tags
  689. $(document).on('click', '.tag-select', function() {
  690. let tag = $(this).text().trim();
  691. $('#tagFilterDropdown').dropdown('set selected', tag);
  692. filterProxyList();
  693. });
  694. });
  695. </script>