summaryrefslogtreecommitdiff
path: root/web/app.js
diff options
context:
space:
mode:
Diffstat (limited to 'web/app.js')
-rw-r--r--web/app.js103
1 files changed, 70 insertions, 33 deletions
diff --git a/web/app.js b/web/app.js
index ce1394d..97721d3 100644
--- a/web/app.js
+++ b/web/app.js
@@ -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() {