document.addEventListener('DOMContentLoaded', function () { var state = { logs: [], details: {}, level: 'all', search: '', period: '7d', openId: null, liveMode: true, timer: null, workspaceId: null, loading: false, loadError: '', approvals: [], approvalsLoading: false, approvalsError: '', }; var logList = document.getElementById('log-list'); var approvalList = document.getElementById('approval-list'); var approvalRefreshBtn = document.getElementById('approval-refresh-btn'); var logSearch = document.getElementById('log-search'); var refreshBtn = document.getElementById('refresh-btn'); var timeRangeSel = document.getElementById('time-range'); var liveDot = document.querySelector('.live-dot'); var liveLabel = document.querySelector('.live-label'); function tKey(key) { return window.t ? t(key) : key; } function currentWorkspaceId() { var workspace = window.getCurrentWorkspace ? window.getCurrentWorkspace() : null; return workspace ? workspace.id : null; } function durationLabel(durationMs) { if (!durationMs) { return '0ms'; } if (durationMs >= 1000) { return (durationMs / 1000).toFixed(1) + 's'; } return durationMs + 'ms'; } function formatJson(value) { if (value === null || value === undefined) { return ''; } if (typeof value === 'string') { return value; } return JSON.stringify(value, null, 2); } function formatTime(timestamp) { var date = new Date(timestamp); return date.toISOString().slice(11, 23); } function formatDateTime(timestamp) { if (!timestamp) { return ''; } var date = new Date(timestamp); return date.toLocaleString(window.CrankLocale || undefined, { day: '2-digit', month: 'short', hour: '2-digit', minute: '2-digit', }); } function element(tag, className, text) { var node = document.createElement(tag); if (className) node.className = className; if (text !== undefined && text !== null) node.textContent = text; return node; } function statusClass(statusCode) { if (!statusCode) { return ''; } if (statusCode < 300) { return 'ok'; } if (statusCode < 500) { return 'warn'; } return 'err'; } function normalizeLog(record) { var log = record.log; var agent = window.localizeDemoAgent ? window.localizeDemoAgent({ slug: record.agent_slug, display_name: record.agent_display_name, description: '', }) : null; var operation = window.localizeDemoOperation ? window.localizeDemoOperation({ name: record.operation_name, operation_display_name: record.operation_display_name, }) : null; return { id: log.id, createdAt: log.created_at, level: log.level, source: log.source, status: log.status, statusCode: log.status_code, durationMs: log.duration_ms, toolName: log.tool_name, message: log.message, operationName: record.operation_name, operationDisplayName: operation ? operation.operation_display_name : record.operation_display_name, agentSlug: record.agent_slug, agentDisplayName: agent ? agent.display_name : record.agent_display_name, requestPreview: log.request_preview, responsePreview: log.response_preview, errorKind: log.error_kind, requestId: log.request_id, }; } function normalizeApproval(record) { var approval = record.approval || record; return { id: approval.id, agentId: approval.agent_id, operationId: approval.operation_id, operationVersion: approval.operation_version, status: approval.status, riskLevel: approval.risk_level, requestPayload: approval.request_payload, responsePayload: approval.response_payload, createdAt: approval.created_at, expiresAt: approval.expires_at, decidedAt: approval.decided_at, note: approval.decision_note, }; } function approvalStatusLabel(status) { var key = 'approvals.status.' + status; var translated = tKey(key); return translated === key ? status : translated; } function renderApprovals() { if (!approvalList) { return; } approvalList.innerHTML = ''; if (state.approvalsLoading && state.approvals.length === 0) { var loading = element('div', 'approval-empty', tKey('approvals.loading')); approvalList.appendChild(loading); return; } if (state.approvalsError) { var error = element('div', 'approval-empty approval-empty-error', state.approvalsError); approvalList.appendChild(error); return; } if (!state.approvals.length) { var empty = element('div', 'approval-empty', tKey('approvals.empty')); approvalList.appendChild(empty); return; } var fragment = document.createDocumentFragment(); state.approvals.forEach(function (item) { var card = element('article', 'approval-item approval-' + item.status); var header = element('div', 'approval-item-header'); var titleWrap = element('div', 'approval-item-title-wrap'); titleWrap.appendChild(element('div', 'approval-item-title', tKey('approvals.untitled') + ' ' + item.id)); var meta = element('div', 'approval-item-meta'); meta.textContent = [ tKey('approvals.operation') + ': ' + item.operationId + ' v' + item.operationVersion, tKey('approvals.agent') + ': ' + item.agentId, ].join(' ยท '); titleWrap.appendChild(meta); header.appendChild(titleWrap); var badge = element('span', 'approval-status approval-status-' + item.status, approvalStatusLabel(item.status)); header.appendChild(badge); card.appendChild(header); var timing = element('div', 'approval-timing'); timing.textContent = item.status === 'pending' ? tKey('approvals.expires_at') + ': ' + formatDateTime(item.expiresAt) : tKey('approvals.updated_at') + ': ' + formatDateTime(item.decidedAt || item.createdAt); card.appendChild(timing); var payloadGrid = element('div', 'approval-payload-grid'); var requestBlock = element('div', 'approval-payload'); requestBlock.appendChild(element('div', 'approval-payload-label', tKey('approvals.request'))); var requestPre = element('pre', 'approval-payload-code'); requestPre.textContent = formatJson(item.requestPayload); requestBlock.appendChild(requestPre); payloadGrid.appendChild(requestBlock); if (item.responsePayload !== null && item.responsePayload !== undefined) { var responseBlock = element('div', 'approval-payload'); responseBlock.appendChild(element('div', 'approval-payload-label', tKey('approvals.response'))); var responsePre = element('pre', 'approval-payload-code'); responsePre.textContent = formatJson(item.responsePayload); responseBlock.appendChild(responsePre); payloadGrid.appendChild(responseBlock); } card.appendChild(payloadGrid); if (item.note) { card.appendChild(element('div', 'approval-note', item.note)); } fragment.appendChild(card); }); approvalList.appendChild(fragment); } function renderEmpty(title, message) { logList.innerHTML = ''; var empty = element('div', 'empty-state'); empty.appendChild(element('div', 'empty-state-title', title)); empty.appendChild(element('div', 'empty-state-text', message)); logList.appendChild(empty); } function renderLogs() { if (state.loading && state.logs.length === 0) { renderEmpty(tKey('logs.loading.title'), tKey('logs.loading.sub')); return; } if (state.loadError) { renderEmpty(tKey('logs.error.title'), state.loadError); return; } if (!state.logs.length) { renderEmpty( tKey('logs.empty.title'), state.search || state.level !== 'all' ? tKey('logs.empty.filtered') : tKey('logs.empty.initial') ); return; } var fragment = document.createDocumentFragment(); state.logs.forEach(function (item) { var row = document.createElement('div'); row.className = 'log-entry'; row.setAttribute('data-id', item.id); var timeEl = document.createElement('span'); timeEl.className = 'log-time'; timeEl.textContent = formatTime(item.createdAt); row.appendChild(timeEl); var levelEl = document.createElement('span'); levelEl.className = 'log-level ' + item.level; levelEl.textContent = item.level.toUpperCase(); row.appendChild(levelEl); var msgDiv = document.createElement('div'); msgDiv.className = 'log-msg'; var opSpan = document.createElement('span'); opSpan.className = 'log-op-name'; opSpan.textContent = item.operationDisplayName || item.operationName; msgDiv.appendChild(opSpan); msgDiv.appendChild(document.createTextNode('\u00a0\u00a0')); if (item.statusCode) { var statusEl = document.createElement('span'); statusEl.className = 'log-status ' + statusClass(item.statusCode); statusEl.textContent = item.statusCode; msgDiv.appendChild(statusEl); msgDiv.appendChild(document.createTextNode(' \u00b7 ')); } msgDiv.appendChild(document.createTextNode(item.message)); row.appendChild(msgDiv); var durationEl = document.createElement('span'); var durationClass = item.level === 'error' ? ' error' : (item.durationMs >= 1000 ? ' slow' : ''); durationEl.className = 'log-duration' + durationClass; durationEl.textContent = durationLabel(item.durationMs); row.appendChild(durationEl); fragment.appendChild(row); var expanded = document.createElement('div'); expanded.className = 'log-entry-expanded' + (item.id === state.openId ? ' open' : ''); expanded.setAttribute('data-exp', item.id); if (item.id === state.openId) { var detail = state.details[item.id] || item; if (detail.agentDisplayName || detail.agentSlug) { var agentLabel = document.createElement('div'); agentLabel.className = 'log-detail-label'; agentLabel.textContent = tKey('logs.detail.agent'); expanded.appendChild(agentLabel); var agentPre = document.createElement('pre'); agentPre.className = 'log-detail-block'; agentPre.textContent = detail.agentDisplayName || detail.agentSlug; expanded.appendChild(agentPre); } var requestLabel = document.createElement('div'); requestLabel.className = 'log-detail-label'; requestLabel.textContent = tKey('logs.detail.request'); expanded.appendChild(requestLabel); var requestPre = document.createElement('pre'); requestPre.className = 'log-detail-block'; requestPre.textContent = formatJson(detail.requestPreview); expanded.appendChild(requestPre); var responseLabel = document.createElement('div'); responseLabel.className = 'log-detail-label'; responseLabel.textContent = tKey('logs.detail.response'); expanded.appendChild(responseLabel); var responsePre = document.createElement('pre'); responsePre.className = 'log-detail-block'; responsePre.textContent = formatJson(detail.responsePreview); expanded.appendChild(responsePre); if (detail.errorKind || detail.requestId) { var metaLabel = document.createElement('div'); metaLabel.className = 'log-detail-label'; metaLabel.textContent = tKey('logs.detail.meta'); expanded.appendChild(metaLabel); var metaPre = document.createElement('pre'); metaPre.className = 'log-detail-block'; metaPre.textContent = formatJson({ request_id: detail.requestId || null, error_kind: detail.errorKind || null, source: detail.source, status: detail.status, }); expanded.appendChild(metaPre); } } fragment.appendChild(expanded); }); logList.innerHTML = ''; logList.appendChild(fragment); logList.querySelectorAll('.log-entry').forEach(function (row) { row.addEventListener('click', async function () { var id = this.getAttribute('data-id'); state.openId = state.openId === id ? null : id; renderLogs(); if (state.openId) { await loadLogDetail(id); } }); }); } function queryParams() { var params = { period: state.period, limit: 100, }; if (state.level !== 'all') { params.level = state.level; } if (state.search) { params.search = state.search; } return params; } async function loadLogs() { if (!window.CrankApi) { state.loadError = tKey('logs.error.api'); renderLogs(); return; } state.workspaceId = currentWorkspaceId(); if (!state.workspaceId) { state.loadError = tKey('logs.error.workspace'); renderLogs(); return; } state.loading = true; state.loadError = ''; renderLogs(); try { var response = await window.CrankApi.listLogs(state.workspaceId, queryParams()); state.logs = (response && response.items ? response.items : []).map(normalizeLog); if (state.openId && !state.logs.some(function (item) { return item.id === state.openId; })) { state.openId = null; } } catch (error) { state.loadError = error.message || tKey('logs.error.load'); } finally { state.loading = false; renderLogs(); } } async function loadApprovals() { if (!window.CrankApi) { state.approvalsError = tKey('logs.error.api'); renderApprovals(); return; } state.workspaceId = currentWorkspaceId(); if (!state.workspaceId) { state.approvalsError = tKey('logs.error.workspace'); renderApprovals(); return; } state.approvalsLoading = true; state.approvalsError = ''; renderApprovals(); try { var response = await window.CrankApi.listApprovals(state.workspaceId, { limit: 20 }); state.approvals = (response && response.items ? response.items : []).map(normalizeApproval); } catch (error) { state.approvalsError = error.message || tKey('approvals.error.load'); } finally { state.approvalsLoading = false; renderApprovals(); } } async function refreshOperationalData() { await Promise.all([loadLogs(), loadApprovals()]); } async function loadLogDetail(logId) { if (!window.CrankApi || !state.workspaceId || state.details[logId]) { return; } try { var record = await window.CrankApi.getLog(state.workspaceId, logId); state.details[logId] = normalizeLog(record); if (state.openId === logId) { renderLogs(); } } catch (_error) { } } function setLiveState() { if (liveDot) { liveDot.classList.toggle('is-paused', !state.liveMode); } if (liveLabel) { liveLabel.textContent = state.liveMode ? tKey('logs.live') : tKey('logs.paused'); liveLabel.classList.toggle('is-paused', !state.liveMode); } } function stopPolling() { if (state.timer) { clearInterval(state.timer); state.timer = null; } } function startPolling() { stopPolling(); if (!state.liveMode) { return; } state.timer = setInterval(refreshOperationalData, 4000); } function toggleLive() { state.liveMode = !state.liveMode; setLiveState(); startPolling(); if (window.CrankUi) { window.CrankUi.info( state.liveMode ? tKey('logs.live.on.body') : tKey('logs.live.off.body'), state.liveMode ? tKey('logs.live.on.title') : tKey('logs.live.off.title') ); } } document.querySelectorAll('.filter-chip[data-level]').forEach(function (button) { button.addEventListener('click', function () { state.level = this.getAttribute('data-level'); document.querySelectorAll('.filter-chip[data-level]').forEach(function (item) { item.classList.remove('active'); }); this.classList.add('active'); loadLogs(); }); }); if (logSearch) { logSearch.addEventListener('input', function () { state.search = this.value.trim(); loadLogs(); }); } if (refreshBtn) { refreshBtn.addEventListener('click', function () { loadLogs().then(function () { if (!state.loadError && window.CrankUi) { window.CrankUi.info(tKey('logs.refresh.body'), tKey('logs.refresh.title')); } }); }); } if (approvalRefreshBtn) { approvalRefreshBtn.addEventListener('click', function () { loadApprovals().then(function () { if (!state.approvalsError && window.CrankUi) { window.CrankUi.info(tKey('approvals.refresh.body'), tKey('approvals.refresh.title')); } }); }); } if (timeRangeSel) { timeRangeSel.value = state.period; timeRangeSel.addEventListener('change', function () { state.period = this.value; loadLogs(); }); } if (liveDot) { liveDot.addEventListener('click', toggleLive); } if (liveLabel) { liveLabel.addEventListener('click', toggleLive); } window.addEventListener('crank:workspacechange', function () { state.details = {}; state.openId = null; refreshOperationalData(); }); setLiveState(); startPolling(); if (window.whenWorkspacesReady) { window.whenWorkspacesReady().finally(refreshOperationalData); } else { refreshOperationalData(); } });