document.addEventListener('DOMContentLoaded', function () { var state = { items: [], workspaceId: null, loading: false, error: '', openId: null, }; var list = document.getElementById('async-jobs-list'); var summary = document.getElementById('async-jobs-summary'); var refreshButton = document.getElementById('async-jobs-refresh'); var statusFilter = document.getElementById('async-jobs-status'); function tKey(key, vars) { if (!window.t) return key; return t(key, vars); } function currentWorkspaceId() { var workspace = window.getCurrentWorkspace ? window.getCurrentWorkspace() : null; return workspace ? workspace.id : null; } function formatJson(value) { if (value === null || value === undefined) return ''; if (typeof value === 'string') return value; return JSON.stringify(value, null, 2); } function formatDateTime(value) { if (!value) return '—'; return new Date(value).toLocaleString(); } function renderEmpty(title, body) { list.innerHTML = '
' + title + '
' + body + '
'; } function render() { summary.textContent = tKey('async_jobs.summary', { count: state.items.length }); if (state.loading && state.items.length === 0) { renderEmpty(tKey('async_jobs.loading.title'), tKey('async_jobs.loading.body')); return; } if (state.error) { renderEmpty(tKey('async_jobs.error.title'), state.error); return; } if (!state.items.length) { renderEmpty(tKey('async_jobs.empty.title'), tKey('async_jobs.empty.body')); return; } list.innerHTML = state.items.map(function(item) { var open = state.openId === item.id; var statusLabel = tKey('async_jobs.status.' + item.status); return [ '
', '
', '
', '
' + item.id + '
', '
' + tKey('async_jobs.operation', { operation: item.operation_id }) + '
', '
', ' ' + statusLabel + '', '
', '
', '
', item.status === 'created' || item.status === 'running' ? ' ' : '', ' ', '
', '
', '
', '
' + tKey('async_jobs.meta.created') + '
' + formatDateTime(item.created_at) + '
', '
' + tKey('async_jobs.meta.updated') + '
' + formatDateTime(item.updated_at) + '
', '
' + tKey('async_jobs.meta.finished') + '
' + formatDateTime(item.finished_at) + '
', '
' + tKey('async_jobs.meta.expires') + '
' + formatDateTime(item.expires_at) + '
', '
', open ? '
' + tKey('async_jobs.progress') + '
' + formatJson(item.progress) + '
' : '', open ? '
' + tKey('async_jobs.error_preview') + '
' + formatJson(item.error) + '
' : '', open && (item.status === 'completed' || item.status === 'failed' || item.status === 'cancelled' || item.status === 'expired') ? '
' + tKey('async_jobs.result') + '
' + tKey('async_jobs.result_loading') + '
' : '', '
' ].join(''); }).join(''); list.querySelectorAll('[data-action="toggle"]').forEach(function(button) { button.addEventListener('click', function () { var jobId = this.getAttribute('data-job-id'); state.openId = state.openId === jobId ? null : jobId; if (state.openId) { void loadDetail(jobId); } else { render(); } }); }); list.querySelectorAll('[data-action="cancel"]').forEach(function(button) { button.addEventListener('click', async function () { try { var jobId = this.getAttribute('data-job-id'); await window.CrankApi.cancelAsyncJob(state.workspaceId, jobId); if (window.CrankUi) { window.CrankUi.success(tKey('async_jobs.toast.cancel.body'), tKey('async_jobs.toast.cancel.title')); } await load(); } catch (error) { if (window.CrankUi) { window.CrankUi.error(error.message || tKey('async_jobs.toast.cancel_error.body'), tKey('async_jobs.toast.cancel_error.title')); } } }); }); } async function loadResult(jobId) { try { var result = await window.CrankApi.getAsyncJobResult(state.workspaceId, jobId); var node = document.querySelector('[data-result-for="' + jobId + '"]'); if (node) node.textContent = formatJson(result); } catch (error) { var failedNode = document.querySelector('[data-result-for="' + jobId + '"]'); if (failedNode) failedNode.textContent = error.message || tKey('async_jobs.result_error'); } } async function loadDetail(jobId) { var detail = await window.CrankApi.getAsyncJob(state.workspaceId, jobId); state.items = state.items.map(function(item) { return item.id === jobId ? detail : item; }); render(); if (detail.status === 'completed' || detail.status === 'failed' || detail.status === 'cancelled' || detail.status === 'expired') { await loadResult(jobId); } } async function load() { state.workspaceId = currentWorkspaceId(); if (!state.workspaceId) { state.items = []; state.error = tKey('async_jobs.error.workspace'); render(); return; } state.loading = true; state.error = ''; render(); try { var response = await window.CrankApi.listAsyncJobs(state.workspaceId, { status: statusFilter.value || null, page: 1, page_size: 50, }); state.items = response.items || []; } catch (error) { state.error = error.message || tKey('async_jobs.error.load'); } finally { state.loading = false; render(); } } refreshButton.addEventListener('click', load); statusFilter.addEventListener('change', load); document.addEventListener('workspace:changed', load); void load(); });