-
+
-
{{ 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
+
@@ -121,7 +138,7 @@
{% for job in jobs %}
-
+
{{ scheduled_count }}
Scheduled
{{ 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 %}