dockerContainersList.html 8.0 KB

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