cratery 1.11.1

Cratery -- a private cargo registry
<!DOCTYPE html>
<html lang="en" class="dark">

<head>
  <meta charset="UTF-8">
  <meta name="description" content="">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <link rel="icon" type="image/png" href="/webapp/favicon.png">
  <title>
    Cratery -- Workers nodes
  </title>
  <script src="https://cdn.tailwindcss.com"></script>
</head>

<header style="position: sticky; top: 0;">
  <nav class="bg-white border-gray-200 px-4 lg:px-6 py-2.5 dark:bg-gray-800">
      <div class="flex flex-wrap justify-between items-center mx-auto max-w-screen-xl">
          <a href="/webapp/index.html" class="flex items-center">
              <picture>
                  <source srcset="./logo-white.svg" media="(prefers-color-scheme: dark)" />
                  <source srcset="./logo-black.svg" media="(prefers-color-scheme: light)" />
                  <img src="./logo-white.svg" class="mr-3 h-6 sm:h-9" style="min-width: 200px;" alt="Cratery Logo" />
              </picture>
          </a>
          <div class="flex items-center lg:order-2">
            <a id="link-admin" href="/webapp/admin.html" style="cursor: pointer;" class="text-gray-800 dark:text-white hover:bg-gray-50 focus:ring-4 focus:ring-gray-300 font-medium rounded-lg text-sm px-4 lg:px-5 py-2 lg:py-2.5 mr-2 dark:hover:bg-gray-700 focus:outline-none dark:focus:ring-gray-800">Admin</a>
            <a id="link-account" href="/webapp/account.html" style="cursor: pointer;" class="text-gray-800 dark:text-white hover:bg-gray-50 focus:ring-4 focus:ring-gray-300 font-medium rounded-lg text-sm px-4 lg:px-5 py-2 lg:py-2.5 mr-2 dark:hover:bg-gray-700 focus:outline-none dark:focus:ring-gray-800">My Account</a>
            <a onclick="doLogout()" style="cursor: pointer;" class="text-gray-800 dark:text-white hover:bg-gray-50 focus:ring-4 focus:ring-gray-300 font-medium rounded-lg text-sm px-4 lg:px-5 py-2 lg:py-2.5 mr-2 dark:hover:bg-gray-700 focus:outline-none dark:focus:ring-gray-800">Logout</a>
          </div>
      </div>
  </nav>
</header>
<body onload="doPageLoad()" class="bg-white dark:bg-gray-800">
  <section class="bg-white dark:bg-gray-900">
    <div class="p-2 flex flex-row flex-wrap">
      <a href="/webapp/admin.html" class="font-medium text-blue-600 dark:text-blue-500 hover:underline">
        <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-6" style="display: inline-block;">
          <path stroke-linecap="round" stroke-linejoin="round" d="M10.5 19.5 3 12m0 0 7.5-7.5M3 12h18" />
        </svg>
       Back to admin
      </a>
    </div>
    <div class="py-4 lg:py-4 px-4 mx-auto max-w-screen-xxl">
      <h2 class="mb-4 text-4xl tracking-tight font-extrabold text-center text-gray-900 dark:text-white">Worker nodes</h2>
      <div class="relative overflow-x-auto space-y-8">
        <table class="w-full text-sm text-left rtl:text-right text-gray-500 dark:text-gray-400">
          <thead class="text-xs text-gray-700 uppercase bg-gray-50 dark:bg-gray-700 dark:text-gray-400">
              <tr>
                  <th scope="col" class="px-6 py-3">
                    Node
                  </th>
                  <th scope="col" class="px-6 py-3">
                    Host
                  </th>
                  <th scope="col" class="px-6 py-3">
                    Versions
                  </th>
                  <th scope="col" class="px-6 py-3">
                    Installed targets
                  </th>
                  <th scope="col" class="px-6 py-3">
                    Capabilities
                  </th>
                  <th scope="col" class="px-6 py-3">
                    Status
                  </th>
              </tr>
          </thead>
          <tbody id="workers">
          </tbody>
      </table>
      </div>
    </div>
  </section>
</body>
<footer class="p-4 bg-white md:p-8 lg:p-10 dark:bg-gray-800">
  <div class="mx-auto max-w-screen-xl text-center">
      <span class="text-sm text-gray-500 sm:text-center dark:text-gray-400">Version <span id="version"></span>, Copyright © <span id="year"></span> <a href="https://cenotelie.fr/" target="_blank" class="hover:underline">Cénotélie</a>. All Rights Reserved.</span>
  </div>
</footer>

<link href="/webapp/index.css" rel="stylesheet" />
<script src="/webapp/api.js"></script>
<script src="/webapp/index.js"></script>
<script src="/webapp/stream.js"></script>
<script>
    function doPageLoad() {
      onPageLoad().then((_user) => {
        apiGetWorkers().then((workers) => {
          const table = document.getElementById("workers");
          for (const worker of workers) {
            table.appendChild(renderWorker(worker));
          }
        });
        // listen for worker updates
        const sourceForWorkers = new StreamEventSource("/api/v1/admin/workers/updates", null);
        sourceForWorkers.map(message => JSON.parse(message.data)).then((event) => onWorkerEvent(event)).catch(() => {});
      });
    }

    function renderWorker(worker) {
      const row = document.createElement("tr");
      row.className = "bg-white border-b dark:bg-gray-800 dark:border-gray-700";
      row.id = `worker-${worker.descriptor.identifier}`;
      const cell1 = document.createElement("th");
      cell1.setAttribute("scope", "row");
      cell1.className = "px-6 py-4 font-medium text-gray-900 whitespace-nowrap dark:text-white";
      cell1.appendChild(document.createTextNode(`${worker.descriptor.name} (${worker.descriptor.identifier})`));
      const cell2 = document.createElement("td");
      cell2.className = "px-6 py-4";
      cell2.appendChild(document.createTextNode(worker.descriptor.toolchainHost));
      const cell3 = document.createElement("td");
      cell3.className = "px-6 py-4";
      cell3.appendChild(document.createTextNode(`${worker.descriptor.toolchainVersionStable}, ${worker.descriptor.toolchainVersionNightly	}`));
      const cell4 = document.createElement("td");
      cell4.className = "px-6 py-4";
      for (const target of worker.descriptor.toolchainInstalledTargets) {
        const el = document.createElement("div");
        el.appendChild(document.createTextNode(target));
        cell4.appendChild(el);
      }
      const cell5 = document.createElement("td");
      cell5.className = "px-6 py-4";
      for (const target of worker.descriptor.capabilities) {
        const el = document.createElement("div");
        el.appendChild(document.createTextNode(target));
        cell5.appendChild(el);
      }
      const cell6 = document.createElement("td");
      cell6.className = "px-6 py-4";
      cell6.id = `worker-state-${worker.descriptor.identifier}`;
      cell6.appendChild(renderWorkerStatus(worker.state));

      row.appendChild(cell1);
      row.appendChild(cell2);
      row.appendChild(cell3);
      row.appendChild(cell4);
      row.appendChild(cell5);
      row.appendChild(cell6);
      return row;
    }
  
    function renderWorkerStatus(state) {
      const color = state === "Available" ? "green" : "blue";
      const statusEl = document.createElement("span");
      statusEl.className = `bg-${color}-100 text-${color}-800 text-xs font-medium me-2 px-2.5 py-0.5 rounded dark:bg-${color}-900 dark:text-${color}-300`;
      statusEl.style.display = "inline-block";
      statusEl.appendChild(document.createTextNode(state === "Available" ? "Available" : "Working ..."));
      return statusEl;
    }

    function onWorkerEvent(event) {
      const keys = Object.getOwnPropertyNames(event);
      if (keys.includes("WorkerConnected")) {
        const table = document.getElementById("workers");
        table.appendChild(renderWorker(event.WorkerConnected));
      } else if (keys.includes("WorkerRemoved")) {
        const row = document.getElementById(`worker-${event.WorkerRemoved.worker_id}`);
        row.remove();
      } else if (keys.includes("WorkerStartedJob")) {
        const cell6 = document.getElementById(`worker-state-${event.WorkerStartedJob.worker_id}`);
        removeAllChildren(cell6);
        cell6.appendChild(renderWorkerStatus("InUse"));
      } else if (keys.includes("WorkerAvailable")) {
        const cell6 = document.getElementById(`worker-state-${event.WorkerAvailable.worker_id}`);
        removeAllChildren(cell6);
        cell6.appendChild(renderWorkerStatus("Available"));
      }
    }
</script>
</html>