dockerContainersList.html 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243
  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. </head>
  10. <body>
  11. <link rel="stylesheet" href="../darktheme.css">
  12. <script src="../script/darktheme.js"></script>
  13. <br />
  14. <div class="ui container">
  15. <div class="ui form">
  16. <div class="field">
  17. <input
  18. id="searchbar"
  19. type="text"
  20. placeholder="Search..."
  21. autocomplete="off"
  22. />
  23. </div>
  24. <div class="field">
  25. <div class="ui checkbox">
  26. <input type="checkbox" id="showUnexposed" class="hidden" />
  27. <label for="showUnexposed"
  28. >Show Containers with unexposed ports
  29. <br />
  30. <small
  31. >Please make sure Zoraxy and the target container share a
  32. network</small
  33. >
  34. </label>
  35. </div>
  36. </div>
  37. </div>
  38. <div class="ui header">
  39. <div class="content">
  40. List of Docker Containers
  41. <div class="sub header">
  42. Below is a list of all detected Docker containers currently running
  43. on the system.
  44. </div>
  45. </div>
  46. </div>
  47. <div id="containersList" class="ui middle aligned divided list active">
  48. <div class="ui loader active"></div>
  49. </div>
  50. <div class="ui horizontal divider"></div>
  51. <div id="containersAddedListHeader" class="ui header" hidden>
  52. Already added containers:
  53. </div>
  54. <div
  55. id="containersAddedList"
  56. class="ui middle aligned divided list"
  57. >
  58. </div>
  59. </div>
  60. <script>
  61. // debounce function to prevent excessive calls to a function
  62. function debounce(func, delay) {
  63. let timeout;
  64. return (...args) => {
  65. clearTimeout(timeout);
  66. timeout = setTimeout(() => func(...args), delay);
  67. };
  68. }
  69. // wait until DOM is fully loaded before executing script
  70. $(document).ready(() => {
  71. const $containersList = $("#containersList");
  72. const $containersAddedList = $("#containersAddedList");
  73. const $containersAddedListHeader = $("#containersAddedListHeader");
  74. const $searchbar = $("#searchbar");
  75. const $showUnexposed = $("#showUnexposed");
  76. let lines = {};
  77. let linesAdded = {};
  78. // load showUnexposed checkbox state from local storage
  79. function loadShowUnexposedState() {
  80. const storedState = localStorage.getItem("showUnexposed");
  81. if (storedState !== null) {
  82. $showUnexposed.prop("checked", storedState === "true");
  83. }
  84. }
  85. // save showUnexposed checkbox state to local storage
  86. function saveShowUnexposedState() {
  87. localStorage.setItem("showUnexposed", $showUnexposed.prop("checked"));
  88. }
  89. // fetch docker containers
  90. function getDockerContainers() {
  91. $containersList.html('<div class="ui loader active"></div>');
  92. $containersAddedList.empty();
  93. $containersAddedListHeader.attr("hidden", true);
  94. lines = {};
  95. linesAdded = {};
  96. const hostRequest = $.get("/api/proxy/list?type=host");
  97. const dockerRequest = $.get("/api/docker/containers");
  98. Promise.all([hostRequest, dockerRequest])
  99. .then(([hostData, dockerData]) => {
  100. if (!hostData.error && !dockerData.error) {
  101. processDockerData(hostData, dockerData);
  102. } else {
  103. showError(hostData.error || dockerData.error);
  104. }
  105. })
  106. .catch((error) => {
  107. console.error(error);
  108. parent.msgbox("Error loading data: " + error.message, false);
  109. });
  110. }
  111. // process docker data and update ui
  112. function processDockerData(hostData, dockerData) {
  113. const { containers } = dockerData;
  114. const existingTargets = new Set(
  115. hostData.flatMap(({ ActiveOrigins }) =>
  116. ActiveOrigins.map(({ OriginIpOrDomain }) => OriginIpOrDomain)
  117. )
  118. );
  119. containers.forEach((container) => {
  120. const name = container.Names[0].replace(/^\//, "");
  121. container.Ports.forEach((portObject) => {
  122. let port = portObject.PublicPort || portObject.PrivatePort;
  123. if (!portObject.PublicPort && !$showUnexposed.is(":checked"))
  124. return;
  125. // if port is not exposed, use container's name and let docker handle the routing
  126. // BUT this will only work if the container is on the same network as Zoraxy
  127. const targetAddress = portObject.IP || name;
  128. const key = `${name}-${port}`;
  129. if (
  130. existingTargets.has(`${targetAddress}:${port}`) &&
  131. !linesAdded[key]
  132. ) {
  133. linesAdded[key] = { name, ip: targetAddress, port };
  134. } else if (!lines[key]) {
  135. lines[key] = { name, ip: targetAddress, port };
  136. }
  137. });
  138. });
  139. // update ui
  140. updateContainersList();
  141. updateAddedContainersList();
  142. }
  143. // update containers list
  144. function updateContainersList() {
  145. $containersList.empty();
  146. Object.entries(lines).forEach(([key, line]) => {
  147. $containersList.append(`
  148. <div class="item">
  149. <div class="right floated content">
  150. <div class="ui button add-button" data-key="${key}">Add</div>
  151. </div>
  152. <div class="content">
  153. <div class="header">${line.name}</div>
  154. <div class="description">${line.ip}:${line.port}</div>
  155. </div>
  156. </div>
  157. `);
  158. });
  159. $containersList.find(".loader").removeClass("active");
  160. }
  161. // update the added containers list
  162. function updateAddedContainersList() {
  163. Object.entries(linesAdded).forEach(([key, line]) => {
  164. $containersAddedList.append(`
  165. <div class="item">
  166. <div class="content">
  167. <div class="header">${line.name}</div>
  168. <div class="description">${line.ip}:${line.port}</div>
  169. </div>
  170. </div>
  171. `);
  172. });
  173. if (Object.keys(linesAdded).length) {
  174. $containersAddedListHeader.removeAttr("hidden");
  175. }
  176. }
  177. // show error message
  178. function showError(error) {
  179. $containersList.html(
  180. `<div class="ui basic segment"><i class="ui red times icon"></i> ${error}</div>`
  181. );
  182. parent.msgbox(`Error loading data: ${error}`, false);
  183. }
  184. //
  185. // event listeners
  186. //
  187. $showUnexposed.on("change", () => {
  188. saveShowUnexposedState(); // save the new state to local storage
  189. getDockerContainers();
  190. });
  191. $searchbar.on(
  192. "input",
  193. debounce(() => {
  194. // debounce searchbar input with 300ms delay, then filter list
  195. // this prevents excessive calls to the filter function
  196. const search = $searchbar.val().toLowerCase();
  197. $("#containersList .item").each((index, item) => {
  198. const content = $(item).text().toLowerCase();
  199. $(item).toggle(content.includes(search));
  200. });
  201. }, 300)
  202. );
  203. $containersList.on("click", ".add-button", (event) => {
  204. const key = $(event.currentTarget).data("key");
  205. if (lines[key]) {
  206. parent.addContainerItem(lines[key]);
  207. }
  208. });
  209. //
  210. // initial calls
  211. //
  212. // load state of showUnexposed checkbox
  213. loadShowUnexposedState();
  214. // initial load of docker containers
  215. getDockerContainers();
  216. });
  217. </script>
  218. </body>
  219. </html>