diff --git a/templates/jobs.html b/templates/jobs.html index fd1c24e..53b8d48 100644 --- a/templates/jobs.html +++ b/templates/jobs.html @@ -60,6 +60,23 @@ text-transform: uppercase; letter-spacing: 0.02em; } + + .jobs-stat { + cursor: pointer; + transition: transform 0.15s ease, border-color 0.2s ease, box-shadow 0.2s ease; + user-select: none; + } + .jobs-stat:hover { + transform: translateY(-2px); + box-shadow: 0 4px 12px rgba(0,0,0,0.15); + } + .jobs-stat.active { + border-color: var(--accent); + box-shadow: 0 0 0 2px var(--accent-glow), var(--shadow); + } + .jobs-stat.active .jobs-stat-lbl { + color: var(--accent); + } {% endblock %} @@ -80,27 +97,27 @@
-
+
{{ jobs|length }}
Total
-
+
{{ jobs|selectattr('status','equalto','running')|list|length }}
Running
-
+
{{ jobs|selectattr('status','equalto','finished')|list|length }}
Finished
-
+
{{ jobs|selectattr('status','equalto','queued')|list|length }}
Queued
-
+
{% set fcnt = namespace(n=0) %}{% for j in jobs %}{% if j.status.startswith('failed') %}{% set fcnt.n = fcnt.n + 1 %}{% endif %}{% endfor %}{{ fcnt.n }}
Failed
-
+
{{ scheduled_count }}
Scheduled
@@ -121,7 +138,7 @@ {% for job in jobs %} - +
{{ job.label or ('Job #' + job.id[:8]) }} @@ -239,5 +256,57 @@ if (hasRunning) { setTimeout(() => location.reload(), 8000); } + + // Status filter + const statCards = document.querySelectorAll('.jobs-stat[data-filter]'); + const tableRows = document.querySelectorAll('tbody tr[data-status]'); + let activeFilter = localStorage.getItem('jobsFilter') || 'all'; + + function applyFilter(filter) { + activeFilter = filter; + localStorage.setItem('jobsFilter', filter); + statCards.forEach(card => { + card.classList.toggle('active', card.dataset.filter === filter); + }); + let visibleCount = 0; + tableRows.forEach(row => { + let show = false; + if (filter === 'all') { + show = true; + } else if (filter === 'failed') { + show = row.dataset.status.startsWith('failed'); + } else if (filter === 'scheduled') { + show = row.dataset.scheduled === '1'; + } else { + show = row.dataset.status === filter; + } + row.style.display = show ? '' : 'none'; + if (show) visibleCount++; + }); + // Show "no matches" message if filter yields zero results + let noMatch = document.getElementById('no-filter-match'); + if (!noMatch && visibleCount === 0) { + const tbody = document.querySelector('tbody'); + noMatch = document.createElement('tr'); + noMatch.id = 'no-filter-match'; + noMatch.innerHTML = 'No jobs match this filter.'; + tbody.appendChild(noMatch); + } else if (noMatch && visibleCount > 0) { + noMatch.remove(); + } + } + + statCards.forEach(card => { + card.addEventListener('click', () => { + const filter = card.dataset.filter; + // Toggle off if clicking the already-active filter + applyFilter(filter === activeFilter ? 'all' : filter); + }); + }); + + // Apply persisted filter on load + if (activeFilter !== 'all') { + applyFilter(activeFilter); + } {% endblock %}