{% extends "base.html" %}{% block content %}
<script>
  let autoRefreshInterval;
  const protocol = "{{ protocol }}";
  const host = window.location.host;
  const prefix = "{{ prefix }}";
  const baseurl = protocol + "://" + host + prefix;

  function escapeHtml(s) {
    if (s == null) return '';
    return String(s).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;');
  }

  function showNotification(message) {
    var notification = document.createElement("div");
    notification.className = "notification";
    notification.textContent = message;
    document.body.appendChild(notification);
    setTimeout(function () { document.body.removeChild(notification); }, 3000);
  }

  function showErrorNotification(message) {
    var notification = document.createElement("div");
    notification.className = "error-notification";
    notification.textContent = message;
    document.body.appendChild(notification);
    setTimeout(function () { document.body.removeChild(notification); }, 3000);
  }

  function startAutorefresh() {
    clearInterval(autoRefreshInterval);
    autoRefreshInterval = setInterval(function () { if (window.autoRefreshEnabled) updateJobsData(); }, 5000);
  }

  function stopAutorefresh() {
    clearInterval(autoRefreshInterval);
  }

  let page = 1;
  let perPage = {{ per_page if per_page is defined else 10 }};
  let totalPages = {{ total_pages }};
  let totalJobs = {{ total }};

  function goToPage(p) {
    if (p < 1 || p > totalPages) return;
    page = p;
    updateJobsData();
  }

  function changePerPage(newPerPage) {
    perPage = newPerPage;
    page = 1;
    updateJobsData();
  }

  function updateJobsData() {
    var selectedState = document.getElementById("state-filter").value;
    var selectedQueue = document.getElementById("queue-name-filter").value;
    fetch(baseurl + "/jobs/json?state=" + selectedState + "&queue_name=" + selectedQueue + "&page=" + page + "&per_page=" + perPage)
      .then(function (r) { return r.json(); })
      .then(function (response) {
        totalPages = response.total_pages;
        totalJobs = response.total;
        page = response.page;
        perPage = response.per_page;
        updateJobsTable(response.data);
        renderPagination();
      })
      .catch(function (error) {
        console.error("Error fetching jobs data: ", error);
        showErrorNotification("Error fetching jobs data");
      });
  }

  function renderPagination() {
    var container = document.getElementById("pagination");
    container.innerHTML = "";

    // Page navigation (only when more than 1 page)
    if (totalPages > 1) {
      // Previous button
      var prevBtn = document.createElement("button");
      prevBtn.className = "btn-pagination" + (page <= 1 ? " opacity-40 cursor-not-allowed" : "");
      prevBtn.disabled = page <= 1;
      prevBtn.title = "Previous page";
      prevBtn.innerHTML = '<svg class="w-4 h-4" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M15 19l-7-7 7-7"/></svg>';
      prevBtn.addEventListener("click", function () { goToPage(page - 1); });
      container.appendChild(prevBtn);

      // Page numbers
      var pages = getPageNumbers(page, totalPages);
      pages.forEach(function (p) {
        if (p === "...") {
          var ellipsis = document.createElement("span");
          ellipsis.className = "text-sm text-gray-400 dark:text-gray-500 px-0.5 select-none";
          ellipsis.textContent = "...";
          container.appendChild(ellipsis);
        } else {
          var btn = document.createElement("button");
          btn.textContent = p;
          if (p === page) {
            btn.className = "inline-flex items-center justify-center min-w-9 h-9 px-2 rounded-md text-sm font-medium bg-blue-600 text-white dark:bg-blue-500 cursor-default";
          } else {
            btn.className = "inline-flex items-center justify-center min-w-9 h-9 px-2 rounded-md text-sm font-medium bg-gray-100 text-gray-700 hover:bg-gray-200 dark:bg-gray-800 dark:text-gray-300 dark:hover:bg-gray-700 cursor-pointer border-none transition-colors duration-150";
            btn.addEventListener("click", (function (pageNum) {
              return function () { goToPage(pageNum); };
            })(p));
          }
          container.appendChild(btn);
        }
      });

      // Next button
      var nextBtn = document.createElement("button");
      nextBtn.className = "btn-pagination" + (page >= totalPages ? " opacity-40 cursor-not-allowed" : "");
      nextBtn.disabled = page >= totalPages;
      nextBtn.title = "Next page";
      nextBtn.innerHTML = '<svg class="w-4 h-4" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M9 5l7 7-7 7"/></svg>';
      nextBtn.addEventListener("click", function () { goToPage(page + 1); });
      container.appendChild(nextBtn);
    }

    // Per-page selector (always visible)
    var perPageWrapper = document.createElement("span");
    perPageWrapper.className = (totalPages > 1 ? "ml-6 " : "") + "flex items-center gap-2";
    var perPageLabel = document.createElement("span");
    perPageLabel.className = "text-xs text-gray-500 dark:text-gray-400";
    perPageLabel.textContent = "Show";
    perPageWrapper.appendChild(perPageLabel);
    var perPageSelect = document.createElement("select");
    perPageSelect.className = "text-xs px-2.5 py-1.5 rounded-md border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-200 cursor-pointer";
    [10, 50, 100].forEach(function (val) {
      var opt = document.createElement("option");
      opt.value = val;
      opt.textContent = val;
      if (val === perPage) opt.selected = true;
      perPageSelect.appendChild(opt);
    });
    perPageSelect.addEventListener("change", function () { changePerPage(Number(this.value)); });
    perPageWrapper.appendChild(perPageSelect);
    container.appendChild(perPageWrapper);

    // Total count (always visible)
    var info = document.createElement("span");
    info.className = "text-xs text-gray-500 dark:text-gray-400 ml-4";
    info.textContent = totalJobs + " job" + (totalJobs !== 1 ? "s" : "");
    container.appendChild(info);
  }

  function getPageNumbers(current, total) {
    if (total <= 7) {
      var arr = [];
      for (var i = 1; i <= total; i++) arr.push(i);
      return arr;
    }
    var pages = [];
    pages.push(1);
    if (current > 3) pages.push("...");
    var start = Math.max(2, current - 1);
    var end = Math.min(total - 1, current + 1);
    for (var i = start; i <= end; i++) pages.push(i);
    if (current < total - 2) pages.push("...");
    pages.push(total);
    return pages;
  }

  function updateJobsTable(data) {
    var tableBody = document.querySelector("#jobs tbody");
    tableBody.innerHTML = "";

    var states = ["scheduled", "queued", "started", "failed", "deferred", "finished"];
    data.forEach(function (queue) {
      states.forEach(function (state) {
        if (!queue[state]) return;
        queue[state].forEach(function (job) {
          var formattedDate = new Date(job.created_at).toLocaleString();
          var requeueBtn = state === "failed"
            ? '<button class="btn-requeue" data-requeue-id="' + job.id + '" title="Requeue"><svg class="w-4 h-4" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M1 4v6h6M23 20v-6h-6"/><path stroke-linecap="round" stroke-linejoin="round" d="M20.49 9A9 9 0 005.64 5.64L1 10m22 4l-4.64 4.36A9 9 0 013.51 15"/></svg></button>'
            : '';
          var safeId = escapeHtml(job.id);
          var safeName = escapeHtml(job.name);
          tableBody.innerHTML +=
            '<tr class="tr-stripe">' +
            '<td class="td-cell"><a class="text-blue-700 dark:text-blue-400" href="' + baseurl + '/job/' + encodeURIComponent(job.id) + '">' + safeId + '</a></td>' +
            '<td class="td-cell"><span class="badge-' + state + '">' + state + '</span></td>' +
            '<td class="td-cell max-w-[200px] overflow-hidden text-ellipsis whitespace-nowrap">' + safeName + '</td>' +
            '<td class="td-cell">' + formattedDate + '</td>' +
            '<td class="td-cell"><div class="flex items-center gap-1"><button class="btn-delete" data-delete-id="' + safeId + '" title="Delete"><svg class="w-4 h-4" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M3 6h18M8 6V4a2 2 0 012-2h4a2 2 0 012 2v2m3 0v14a2 2 0 01-2 2H7a2 2 0 01-2-2V6h14"/></svg></button>' + requeueBtn + '</div></td>' +
            '</tr>';
        });
      });
    });

    tableBody.querySelectorAll('[data-delete-id]').forEach(function (btn) {
      btn.addEventListener('click', function () { deleteJob(this.getAttribute('data-delete-id'), this); });
    });
    tableBody.querySelectorAll('[data-requeue-id]').forEach(function (btn) {
      btn.addEventListener('click', function () { requeueJob(this.getAttribute('data-requeue-id'), this); });
    });
  }

  function setBtnLoading(btn) {
    btn.disabled = true;
    btn.classList.add('opacity-50', 'cursor-not-allowed');
    btn.dataset.originalHtml = btn.innerHTML;
    btn.innerHTML = '<svg class="w-4 h-4 animate-spin" fill="none" viewBox="0 0 24 24"><circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"/><path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z"/></svg>';
  }

  function resetBtn(btn) {
    btn.disabled = false;
    btn.classList.remove('opacity-50', 'cursor-not-allowed');
    btn.innerHTML = btn.dataset.originalHtml;
  }

  function deleteJob(job_id, btn) {
    setBtnLoading(btn);
    fetch(baseurl + "/job/" + job_id, { method: "DELETE" })
      .then(function (r) {
        if (!r.ok) { showErrorNotification("Error deleting job"); resetBtn(btn); }
        else { updateJobsData(); }
      })
      .catch(function (error) {
        console.error("Error deleting job: ", error);
        showErrorNotification("Error deleting job");
        resetBtn(btn);
      });
  }

  function requeueJob(job_id, btn) {
    setBtnLoading(btn);
    fetch(baseurl + "/job/" + job_id + "/requeue", { method: "POST" })
      .then(function (r) {
        if (!r.ok) { showErrorNotification("Error requeueing job"); resetBtn(btn); }
        else { updateJobsData(); }
      })
      .catch(function (error) {
        console.error("Error requeueing job: ", error);
        showErrorNotification("Error requeueing job");
        resetBtn(btn);
      });
  }

  document.addEventListener("DOMContentLoaded", function () {
    if (window.autoRefreshEnabled) { startAutorefresh(); }

    var urlParams = new URLSearchParams(window.location.search);
    var defaultState = urlParams.get("state") || "all";
    document.getElementById("state-filter").value = defaultState;
    var defaultQueue = urlParams.get("queue_name") || "all";
    document.getElementById("queue-name-filter").value = defaultQueue;

    document.getElementById("state-filter").addEventListener("change", function () { page = 1; updateJobsData(); });
    document.getElementById("queue-name-filter").addEventListener("change", function () { page = 1; updateJobsData(); });

    var jobLinks = document.querySelectorAll("a[data-job-id]");
    jobLinks.forEach(function (link) {
      var jobId = link.getAttribute("data-job-id");
      link.href = baseurl + "/job/" + jobId;
    });

    renderPagination();
    updateJobsData();
  });

  document.addEventListener("autorefresh-changed", function (e) {
    if (e.detail.enabled) {
      startAutorefresh();
      showNotification("Autorefresh enabled");
    } else {
      stopAutorefresh();
      showNotification("Autorefresh disabled");
    }
  });
</script>

<div class="max-w-[90%] mx-auto mt-6 px-4">
  <p class="text-sm text-gray-600 dark:text-gray-400">
    This list contains all the jobs that are currently queued. Filter the search
    by job state and queue name. Each job can be deleted or requeued by clicking
    the corresponding button.
  </p>
  <table id="jobs" class="data-table">
    <thead>
      <tr>
        <th class="th-cell">ID</th>
        <th class="th-cell">
          <div class="flex flex-col gap-1">
            <span>Status</span>
            <select id="state-filter" class="text-xs font-normal normal-case tracking-normal px-1.5 py-1 rounded border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-200 cursor-pointer">
              <option value="all">All</option>
              <option value="scheduled">Scheduled</option>
              <option value="queued">Queued</option>
              <option value="started">Started</option>
              <option value="failed">Failed</option>
              <option value="deferred">Deferred</option>
              <option value="finished">Finished</option>
            </select>
          </div>
        </th>
        <th class="th-cell">
          <div class="flex flex-col gap-1">
            <span>Name</span>
            <select id="queue-name-filter" class="text-xs font-normal normal-case tracking-normal px-1.5 py-1 rounded border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-200 cursor-pointer">
              <option value="all">All queues</option>
              {% for queue in job_data %}
              <option value="{{ queue.queue_name }}">{{ queue.queue_name }}</option>
              {% endfor %}
            </select>
          </div>
        </th>
        <th class="th-cell">Created At</th>
        <th class="th-cell">Options</th>
      </tr>
    </thead>
    <tbody>
      {% for queue in job_data %} {% for state in ["queued", "started",
      "failed", "deferred", "scheduled", "finished"] %} {% for job in queue[state] %}
      <tr class="tr-stripe">
        <td class="td-cell">
          <a class="text-blue-700 dark:text-blue-400" href="#" data-job-id="{{ job.id }}">{{ job.id }}</a>
        </td>
        <td class="td-cell"><span class="badge-{{ state }}">{{ state }}</span></td>
        <td class="td-cell max-w-[200px] overflow-hidden text-ellipsis whitespace-nowrap">{{ job.name }}</td>
        <td class="td-cell">{{ job.created_at }}</td>
        <td class="td-cell">
          <div class="flex items-center gap-1">
            <button class="btn-delete" data-delete-ssr="{{ job.id }}" title="Delete">
              <svg class="w-4 h-4" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M3 6h18M8 6V4a2 2 0 012-2h4a2 2 0 012 2v2m3 0v14a2 2 0 01-2 2H7a2 2 0 01-2-2V6h14"/></svg>
            </button>
            {% if state == "failed" %}
            <button class="btn-requeue" data-requeue-ssr="{{ job.id }}" title="Requeue">
              <svg class="w-4 h-4" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M1 4v6h6M23 20v-6h-6"/><path stroke-linecap="round" stroke-linejoin="round" d="M20.49 9A9 9 0 005.64 5.64L1 10m22 4l-4.64 4.36A9 9 0 013.51 15"/></svg>
            </button>
            {% endif %}
          </div>
        </td>
      </tr>
      {% endfor %} {% endfor %} {% endfor %}
    </tbody>
  </table>
  <div id="pagination" class="flex justify-center mt-6 mb-4 gap-2 items-center py-3"></div>
</div>

<script>
  document.addEventListener("DOMContentLoaded", function () {
    document.querySelectorAll("[data-delete-ssr]").forEach(function (btn) {
      btn.addEventListener("click", function () { deleteJob(this.getAttribute("data-delete-ssr"), this); });
    });
    document.querySelectorAll("[data-requeue-ssr]").forEach(function (btn) {
      btn.addEventListener("click", function () { requeueJob(this.getAttribute("data-requeue-ssr"), this); });
    });
  });
</script>
{% endblock %}
