<!DOCTYPE html>
<html>
  <head>
    <!-- Notes: This should be open in its original path-->
    <meta charset="utf-8" />
    <link rel="stylesheet" href="../script/semantic/semantic.min.css" />
    <script src="../script/jquery-3.6.0.min.js"></script>
    <script src="../script/semantic/semantic.min.js"></script>
  </head>
  <body>
    <link rel="stylesheet" href="../darktheme.css">
    <script src="../script/darktheme.js"></script>
    <br />
    <div class="ui container">
      <div class="ui form">
        <div class="field">
          <input
            id="searchbar"
            type="text"
            placeholder="Search..."
            autocomplete="off"
          />
        </div>
        <div class="field">
          <div class="ui checkbox">
            <input type="checkbox" id="showUnexposed" class="hidden" />
            <label for="showUnexposed"
              >Show Containers with unexposed ports
              <br />
              <small
                >Please make sure Zoraxy and the target container share a
                network</small
              >
            </label>
          </div>
        </div>
      </div>
      <div class="ui header">
        <div class="content">
          List of Docker Containers
          <div class="sub header">
            Below is a list of all detected Docker containers currently running
            on the system.
          </div>
        </div>
      </div>
      <div id="containersList" class="ui middle aligned divided list active">
        <div class="ui loader active"></div>
      </div>
      <div class="ui horizontal divider"></div>
      <div id="containersAddedListHeader" class="ui header" hidden>
        Already added containers:
      </div>
      <div
        id="containersAddedList"
        class="ui middle aligned divided list"
      ></div>
    </div>

    <script>
      // debounce function to prevent excessive calls to a function
      function debounce(func, delay) {
        let timeout;
        return (...args) => {
          clearTimeout(timeout);
          timeout = setTimeout(() => func(...args), delay);
        };
      }

      // wait until DOM is fully loaded before executing script
      $(document).ready(() => {
        const $containersList = $("#containersList");
        const $containersAddedList = $("#containersAddedList");
        const $containersAddedListHeader = $("#containersAddedListHeader");
        const $searchbar = $("#searchbar");
        const $showUnexposed = $("#showUnexposed");

        let lines = {};
        let linesAdded = {};

        // load showUnexposed checkbox state from local storage
        function loadShowUnexposedState() {
          const storedState = localStorage.getItem("showUnexposed");
          if (storedState !== null) {
            $showUnexposed.prop("checked", storedState === "true");
          }
        }

        // save showUnexposed checkbox state to local storage
        function saveShowUnexposedState() {
          localStorage.setItem("showUnexposed", $showUnexposed.prop("checked"));
        }

        // fetch docker containers
        function getDockerContainers() {
          $containersList.html('<div class="ui loader active"></div>');
          $containersAddedList.empty();
          $containersAddedListHeader.attr("hidden", true);

          lines = {};
          linesAdded = {};

          const hostRequest = $.get("/api/proxy/list?type=host");
          const dockerRequest = $.get("/api/docker/containers");

          Promise.all([hostRequest, dockerRequest])
            .then(([hostData, dockerData]) => {
              if (!hostData.error && !dockerData.error) {
                processDockerData(hostData, dockerData);
              } else {
                showError(hostData.error || dockerData.error);
              }
            })
            .catch((error) => {
              console.error(error);
              parent.msgbox("Error loading data: " + error.message, false);
            });
        }

        // process docker data and update ui
        function processDockerData(hostData, dockerData) {
          const { containers } = dockerData;
          const existingTargets = new Set(
            hostData.flatMap(({ ActiveOrigins }) =>
              ActiveOrigins.map(({ OriginIpOrDomain }) => OriginIpOrDomain)
            )
          );

          containers.forEach((container) => {
            const name = container.Names[0].replace(/^\//, "");
            container.Ports.forEach((portObject) => {
              let port = portObject.PublicPort || portObject.PrivatePort;
              if (!portObject.PublicPort && !$showUnexposed.is(":checked"))
                return;

              // if port is not exposed, use container's name and let docker handle the routing
              // BUT this will only work if the container is on the same network as Zoraxy
              const targetAddress = portObject.IP || name;
              const key = `${name}-${port}`;

              if (
                existingTargets.has(`${targetAddress}:${port}`) &&
                !linesAdded[key]
              ) {
                linesAdded[key] = { name, ip: targetAddress, port };
              } else if (!lines[key]) {
                lines[key] = { name, ip: targetAddress, port };
              }
            });
          });

          // update ui
          updateContainersList();
          updateAddedContainersList();
        }

        // update containers list
        function updateContainersList() {
          $containersList.empty();
          Object.entries(lines).forEach(([key, line]) => {
            $containersList.append(`
              <div class="item">
                <div class="right floated content">
                  <div class="ui button add-button" data-key="${key}">Add</div>
                </div>
                <div class="content">
                  <div class="header">${line.name}</div>
                  <div class="description">${line.ip}:${line.port}</div>
                </div>
              </div>
            `);
          });
          $containersList.find(".loader").removeClass("active");
        }

        // update the added containers list
        function updateAddedContainersList() {
          Object.entries(linesAdded).forEach(([key, line]) => {
            $containersAddedList.append(`
              <div class="item">
                <div class="content">
                  <div class="header">${line.name}</div>
                  <div class="description">${line.ip}:${line.port}</div>
                </div>
              </div>
            `);
          });
          if (Object.keys(linesAdded).length) {
            $containersAddedListHeader.removeAttr("hidden");
          }
        }

        // show error message
        function showError(error) {
          $containersList.html(
            `<div class="ui basic segment"><i class="ui red times icon"></i> ${error}</div>`
          );
          parent.msgbox(`Error loading data: ${error}`, false);
        }

        //
        // event listeners
        //

        $showUnexposed.on("change", () => {
          saveShowUnexposedState(); // save the new state to local storage
          getDockerContainers();
        });

        $searchbar.on(
          "input",
          debounce(() => {
            // debounce searchbar input with 300ms delay, then filter list
            // this prevents excessive calls to the filter function
            const search = $searchbar.val().toLowerCase();
            $("#containersList .item").each((index, item) => {
              const content = $(item).text().toLowerCase();
              $(item).toggle(content.includes(search));
            });
          }, 300)
        );

        $containersList.on("click", ".add-button", (event) => {
          const key = $(event.currentTarget).data("key");
          if (lines[key]) {
            parent.addContainerItem(lines[key]);
          }
        });

        //
        // initial calls
        //

        // load state of showUnexposed checkbox
        loadShowUnexposedState();

        // initial load of docker containers
        getDockerContainers();
      });
    </script>
  </body>
</html>