diff --git a/apps/web/app.js b/apps/web/app.js
index 99eab5d..87f3b81 100644
--- a/apps/web/app.js
+++ b/apps/web/app.js
@@ -265,7 +265,7 @@
$('jobDetailPanel').innerHTML = `
JOB DETAIL
${esc(job.name || job.type || `Job ${job.id}`)}
ID ${esc(job.id)}
${esc(jobLabel(status))}${status === 'running' ? `${progress}% complete` : jobLabel(status)}${esc(job.progress_message || job.message || '')}
${error ? `${esc(error.code || error.type || 'Job error')}${esc(error.message || error.detail || error.description || JSON.stringify(error))}
${error.details ? `
${esc(JSON.stringify(error.details, null, 2))}` : ''}
` : ''}${canCancel ? '' : ''}${canRetry ? '' : ''}${!canCancel && !canRetry && !canManageJobs() ? 'Your role cannot change jobs.' : ''}
Event timeline
${Array.isArray(events) && events.length ? `
${events.map(event => `${esc(eventText(event))}${esc(event.created_at || event.timestamp || event.at || '')}${event.progress != null ? `${esc(event.progress)}%` : ''}
`).join('')}
` : '
No events returned yet.
'}
`;
}
async function loadJobDetail(id) { selectedJobId = id; renderJobsList(); $('jobDetailPanel').innerHTML = 'Loading job detail…
'; try { const job = await jobsRequest(`/api/v1/jobs/${encodeURIComponent(id)}`); let events = job.events || job.event_timeline; if (!events) { const eventPayload = await jobsRequest(`/api/v1/jobs/${encodeURIComponent(id)}/events`); events = eventPayload.events || eventPayload.items || eventPayload; } renderJobDetail(job, events); } catch (error) { if (error.message !== 'unauthorized') $('jobDetailPanel').innerHTML = `Unable to load job
${esc(error.message)}
`; } }
- async function loadJobs({silent = false} = {}) { if (!silent) { jobMessage('Loading jobs…'); resetJobCounts(); } try { const payload = await jobsRequest('/api/v1/jobs'); jobs = Array.isArray(payload) ? payload : (payload.jobs || payload.items || []); renderJobCounts(payload); renderJobsList(); $('jobsUpdatedAt').textContent = `Updated ${new Date().toLocaleTimeString([], {hour:'2-digit', minute:'2-digit'})}`; jobMessage(''); const active = jobs.some(job => ['queued','running'].includes(jobStatus(job))); if (active && !jobPollTimer) jobPollTimer = setInterval(() => loadJobs({silent:true}), 5000); if (!active && jobPollTimer) { clearInterval(jobPollTimer); jobPollTimer = null; } if (selectedJobId) { const selected = jobs.find(job => String(job.id) === String(selectedJobId)); if (selected) await loadJobDetail(selectedJobId); } } catch (error) { jobs = []; resetJobCounts(); renderJobsList(); if (error.message !== 'unauthorized') jobMessage(error.message || 'Unable to load jobs.', true); } }
+ async function loadJobs({silent = false} = {}) { if (!silent) { jobMessage('Loading jobs…'); resetJobCounts(); } try { const payload = await jobsRequest('/api/v1/jobs'); jobs = Array.isArray(payload) ? payload : (payload.jobs || payload.items || []); renderJobCounts(payload); renderJobsList(); $('jobsUpdatedAt').textContent = `Updated ${new Date().toLocaleTimeString([], {hour:'2-digit', minute:'2-digit'})}`; jobMessage(''); const active = jobs.some(job => ['queued','running'].includes(jobStatus(job))); if (active && !jobPollTimer) jobPollTimer = setInterval(() => loadJobs({silent:true}), 5000); if (!active && jobPollTimer) { clearInterval(jobPollTimer); jobPollTimer = null; } if (selectedJobId) { const selected = jobs.find(job => String(job.id) === String(selectedJobId)); if (selected) await loadJobDetail(selectedJobId); } } catch (error) { jobs = []; resetJobCounts(); renderJobsList(); if (error.message === 'unauthorized') { showLogin('Your session expired. Please sign in again.'); } else { jobMessage(error.message || 'Unable to load jobs.', true); } } }
function updateJobPermissions() {}
let sources = [], selectedSourceId = null;
diff --git a/apps/web/asset-manifest.json b/apps/web/asset-manifest.json
index ee5f8e1..aa893a4 100644
--- a/apps/web/asset-manifest.json
+++ b/apps/web/asset-manifest.json
@@ -14,7 +14,7 @@
],
"integrity": {
"config.js": "sha256-7310c02ad6a24ab7d1b49536bfea169ef28f7eb55ccbfe77cb8b0ee1918319ca",
- "app.js": "sha256-4956869c375eda23362c2345ee951a322693295b3eeba8a9481c754fb4fa2103",
+ "app.js": "sha256-dc030f75621e40194f98dc5bf94a2dd262fec44a0a43f2b0e1b97de3526b1033",
"styles.css": "sha256-c1b192a15de735819fbb7ec3cddc9574808ae366a8255b4f690a7a40bc21caba",
"index.html": "sha256-3a6a3f7e0d4780c1fadfbd77671e6a2472d99c5e891d08c86759d8475bf38986",
"health.html": "sha256-c352a6f37aa24628cfc8d5709a70ff2d94181d192ed5fe00916cca9378a61d81",