(() => { const terminalStatuses = new Set(['success', 'failed', 'partial', 'timeout', 'cancelled']); const dispatchingStatuses = new Set(['sending', 'running']); const bulkBlockedStatuses = new Set(['success', 'sending', 'running']); function init() { const table = document.querySelector('table[data-storage-key="software-jobs"]'); if (!table) return; const rows = () => [...table.querySelectorAll('tbody tr[data-job-id]')]; const activeRows = () => rows().filter(row => !terminalStatuses.has(row.dataset.jobStatus || 'created')); function renderRestartCell(row, status) { const cell = row.querySelector('[data-job-restart-cell]'); if (!cell) return; const currentlyBlocked = Boolean(cell.querySelector('.muted')); const mustBlock = dispatchingStatuses.has(status); if (currentlyBlocked === mustBlock) return; cell.replaceChildren(); if (mustBlock) { const placeholder = document.createElement('span'); placeholder.className = 'muted'; placeholder.textContent = '—'; cell.appendChild(placeholder); return; } const form = document.createElement('form'); form.method = 'post'; form.action = `/jobs/${row.dataset.jobId}/restart`; form.className = 'inline-form'; form.addEventListener('submit', event => { const message = table.dataset.restartConfirm || ''; if (message && !window.confirm(message)) event.preventDefault(); }); const button = document.createElement('button'); button.type = 'submit'; button.className = 'button button-secondary button-small'; button.textContent = table.dataset.restartLabel || '↻'; form.appendChild(button); cell.appendChild(form); } function applyStatus(item) { const row = table.querySelector(`tbody tr[data-job-id="${CSS.escape(String(item.id))}"]`); if (!row) return; const status = String(item.status || 'created'); const previous = row.dataset.jobStatus || ''; row.dataset.jobStatus = status; const field = row.querySelector('[data-job-status-field]'); if (field) { field.textContent = item.status_label || status; field.className = `job-status job-status-${status}`; } const checkbox = row.querySelector('.software-job-restart-select'); if (checkbox) { const hasNode = row.dataset.jobHasNode === '1'; checkbox.disabled = !hasNode || bulkBlockedStatuses.has(status); if (checkbox.disabled) checkbox.checked = false; } renderRestartCell(row, status); if (previous !== status) { window.dispatchEvent(new CustomEvent('software-job-status-changed', {detail: {row, status}})); } } let timer = null; let requestInProgress = false; async function refresh() { if (requestInProgress) return; const active = activeRows(); if (!active.length) return; const ids = active.map(row => Number(row.dataset.jobId)).filter(Number.isInteger); requestInProgress = true; try { const response = await fetch('/api/software-jobs/statuses', { method: 'POST', cache: 'no-store', headers: {'Content-Type': 'application/json'}, body: JSON.stringify(ids), }); if (!response.ok) throw new Error(`HTTP ${response.status}`); const payload = await response.json(); (payload.jobs || []).forEach(applyStatus); if (activeRows().length) timer = window.setTimeout(refresh, 3000); } catch (_) { timer = window.setTimeout(refresh, 5000); } finally { requestInProgress = false; } } if (activeRows().length) timer = window.setTimeout(refresh, 1200); window.addEventListener('asset-table-content-reloaded', event => { if (event.detail?.table !== table || !activeRows().length) return; if (timer) window.clearTimeout(timer); timer = window.setTimeout(refresh, 250); }); window.addEventListener('beforeunload', () => { if (timer) window.clearTimeout(timer); }, {once: true}); } if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', init, {once: true}); } else { init(); } })();