const $ = (s) => document.querySelector(s); const esc = (s) => { const d = document.createElement('div'); d.textContent = s ?? ''; return d.innerHTML; }; let currentEventSource = null; function formatDate(s) { if (!s) return '—'; return new Date(s + 'Z').toLocaleString(); } function duration(start, end) { if (!start) return '—'; const s = new Date(start + 'Z').getTime(); const e = end ? new Date(end + 'Z').getTime() : Date.now(); const secs = Math.max(0, Math.round((e - s) / 1000)); if (secs < 60) return `${secs}s`; return `${Math.floor(secs / 60)}m ${secs % 60}s`; } function statusBadge(status) { const running = ['preparing', 'signing', 'archiving', 'exporting', 'uploading'].includes(status); const cls = status === 'succeeded' ? 'succeeded' : status === 'failed' ? 'failed' : running ? 'running' : 'pending'; return `${status}`; } async function loadJobs() { const r = await fetch('/api/builds'); if (r.status === 401) { location.href = '/login'; return; } const jobs = await r.json(); const container = $('#jobs-container'); if (!jobs.length) { container.innerHTML = '
No builds yet. Start one from New Build.
| Status | Bundle | Project | Started | Duration | |
|---|---|---|---|---|---|
| ${statusBadge(j.status)} | ${esc(j.bundle_id) || '—'} | ${esc((j.source_ref || '').replace(/\.(xcodeproj|xcworkspace)$/, ''))} | ${esc(formatDate(j.started_at))} | ${esc(duration(j.started_at, j.finished_at))} | ${j.install_url ? `Install` : ''} |