diff options
Diffstat (limited to 'web/app.js')
| -rw-r--r-- | web/app.js | 103 |
1 files changed, 70 insertions, 33 deletions
@@ -1,5 +1,5 @@ -const BASE_PATH = document.querySelector('meta[name="base-path"]')?.content ?? ''; -const API_BASE = window.location.origin + BASE_PATH; +const BASE_PATH = (typeof document !== 'undefined') ? document.querySelector('meta[name="base-path"]')?.content ?? '' : ''; +const API_BASE = (typeof window !== 'undefined') ? window.location.origin + BASE_PATH : ''; // ── Fetch ───────────────────────────────────────────────────────────────────── @@ -160,17 +160,56 @@ function createTaskCard(task) { return card; } +// ── Sort ────────────────────────────────────────────────────────────────────── + +function sortTasksByDate(tasks) { + return [...tasks].sort((a, b) => { + if (!a.created_at && !b.created_at) return 0; + if (!a.created_at) return 1; + if (!b.created_at) return -1; + return new Date(a.created_at) - new Date(b.created_at); + }); +} + // ── Filter ──────────────────────────────────────────────────────────────────── const HIDE_STATES = new Set(['COMPLETED', 'FAILED']); +const ACTIVE_STATES = new Set(['PENDING', 'QUEUED', 'RUNNING', 'READY', 'BLOCKED']); +const DONE_STATES = new Set(['COMPLETED', 'FAILED', 'TIMED_OUT', 'CANCELLED', 'BUDGET_EXCEEDED']); -let showHiddenFold = false; +// filterActiveTasks uses its own set (excludes PENDING — tasks "in-flight" only) +const _PANEL_ACTIVE_STATES = new Set(['RUNNING', 'READY', 'QUEUED', 'BLOCKED']); -function filterTasks(tasks, hideCompletedFailed = false) { +export function filterTasks(tasks, hideCompletedFailed = false) { if (!hideCompletedFailed) return tasks; return tasks.filter(t => !HIDE_STATES.has(t.state)); } +export function filterActiveTasks(tasks) { + return tasks.filter(t => _PANEL_ACTIVE_STATES.has(t.state)); +} + +export function filterTasksByTab(tasks, tab) { + if (tab === 'active') return tasks.filter(t => ACTIVE_STATES.has(t.state)); + if (tab === 'done') return tasks.filter(t => DONE_STATES.has(t.state)); + return tasks; +} + +export function getTaskFilterTab() { + return localStorage.getItem('taskFilterTab') ?? 'active'; +} + +export function setTaskFilterTab(tab) { + localStorage.setItem('taskFilterTab', tab); +} + +export function updateFilterTabs() { + const current = getTaskFilterTab(); + document.querySelectorAll('.filter-tab[data-filter]').forEach(el => { + el.classList.toggle('active', el.dataset.filter === current); + }); +} + function getHideCompletedFailed() { const stored = localStorage.getItem('hideCompletedFailed'); return stored === null ? true : stored === 'true'; @@ -196,36 +235,30 @@ function renderTaskList(tasks) { return; } - const hide = getHideCompletedFailed(); - const visible = filterTasks(tasks, hide); - const hiddenCount = tasks.length - visible.length; + const visible = sortTasksByDate(filterTasksByTab(tasks, getTaskFilterTab())); // Replace contents with task cards container.innerHTML = ''; for (const task of visible) { container.appendChild(createTaskCard(task)); } +} - if (hiddenCount > 0) { - const info = document.createElement('button'); - info.className = 'hidden-tasks-info'; - const arrow = showHiddenFold ? '▼' : '▶'; - info.textContent = `${arrow} ${hiddenCount} hidden task${hiddenCount === 1 ? '' : 's'}`; - info.addEventListener('click', () => { - showHiddenFold = !showHiddenFold; - renderTaskList(tasks); - }); - container.appendChild(info); - - if (showHiddenFold) { - const fold = document.createElement('div'); - fold.className = 'hidden-tasks-fold'; - const hiddenTasks = tasks.filter(t => HIDE_STATES.has(t.state)); - for (const task of hiddenTasks) { - fold.appendChild(createTaskCard(task)); - } - container.appendChild(fold); - } +function renderActiveTaskList(tasks) { + const container = document.querySelector('.active-task-list'); + if (!container) return; + if (!tasks || tasks.length === 0) { + container.innerHTML = '<div id="loading">No active tasks.</div>'; + return; + } + const active = sortTasksByDate(filterActiveTasks(tasks)); + container.innerHTML = ''; + if (active.length === 0) { + container.innerHTML = '<div id="loading">No active tasks.</div>'; + return; + } + for (const task of active) { + container.appendChild(createTaskCard(task)); } } @@ -762,6 +795,7 @@ async function poll() { try { const tasks = await fetchTasks(); renderTaskList(tasks); + renderActiveTaskList(tasks); } catch { document.querySelector('.task-list').innerHTML = '<div id="loading">Could not reach server.</div>'; @@ -1539,12 +1573,15 @@ function switchTab(name) { // ── Boot ────────────────────────────────────────────────────────────────────── -document.addEventListener('DOMContentLoaded', () => { - updateToggleButton(); - document.getElementById('btn-toggle-completed').addEventListener('click', async () => { - setHideCompletedFailed(!getHideCompletedFailed()); - updateToggleButton(); - await poll(); +if (typeof document !== 'undefined') document.addEventListener('DOMContentLoaded', () => { + updateFilterTabs(); + + document.querySelectorAll(".filter-tab[data-filter]").forEach(btn => { + btn.addEventListener("click", () => { + setTaskFilterTab(btn.dataset.filter); + updateFilterTabs(); + poll(); + }); }); document.getElementById('btn-start-next').addEventListener('click', function() { |
