import { api } from '../api.js'; import { el, mount, clear } from '../dom.js'; function badge(state) { const cls = state === 'completed' ? 'ok' : state === 'failed' ? 'bad' : state === 'active' ? 'warn' : 'idle'; return el('span', { class: 'status ' + cls }, state); } function row(j, onActed) { return el('li', {}, badge(j.state), ' ', el('span', { style: { fontFamily: 'var(--font-mono)' } }, j.name), ' ', el('span', { class: 'muted' }, (j.id || '').slice(0, 8)), ' ', el('button', { class: 'ghost', onclick: async () => { try { await api.post(`/api/jobs/${j.id}/retry`); onActed(); } catch (e) { alert('retry failed: ' + e.message); } } }, 'retry'), ' ', el('button', { class: 'ghost', onclick: async () => { try { await api.del(`/api/jobs/${j.id}`); onActed(); } catch (e) { alert('delete failed: ' + e.message); } } }, 'delete') ); } async function refresh(container) { let rows; try { rows = await api.get('/api/jobs?limit=100'); } catch (e) { clear(container); container.appendChild(el('p', { class: 'muted' }, 'Could not load: ' + e.message)); return; } clear(container); if (!rows.length) { container.appendChild(el('p', { class: 'muted' }, 'No jobs yet.')); return; } const byState = new Map(); for (const r of rows) { if (!byState.has(r.state)) byState.set(r.state, []); byState.get(r.state).push(r); } for (const [state, items] of byState) { container.appendChild(el('div', { class: 'sb-title', style: { margin: '14px 0 4px' } }, `${state} (${items.length})`)); container.appendChild(el('ul', { class: 'plain' }, items.map(j => row(j, () => refresh(container))))); } } export async function render(main) { const wrap = el('div'); mount(main, el('h1', { class: 'view-h1' }, 'Jobs'), el('p', { class: 'view-sub muted' }, 'pg-boss queue — polls every 10 s.'), wrap ); await refresh(wrap); const handle = setInterval(() => refresh(wrap), 10_000); window.addEventListener('hashchange', () => clearInterval(handle), { once: true }); }