document.addEventListener('DOMContentLoaded', function () { var state = { logs: [], details: {}, level: 'all', search: '', period: '7d', openId: null, liveMode: true, timer: null, workspaceId: null, loading: false, loadError: '', }; var logList = document.getElementById('log-list'); 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 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 statusClass(statusCode) { if (!statusCode) { return ''; } if (statusCode < 300) { return 'ok'; } if (statusCode < 500) { return 'warn'; } return 'err'; } function normalizeLog(record) { var log = record.log; 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: record.operation_display_name, agentSlug: record.agent_slug, agentDisplayName: record.agent_display_name, requestPreview: log.request_preview, responsePreview: log.response_preview, errorKind: log.error_kind, requestId: log.request_id, }; } function renderEmpty(message) { logList.innerHTML = ''; var empty = document.createElement('div'); empty.style.cssText = 'text-align:center;padding:40px;color:var(--text-muted);font-size:13.5px;'; empty.textContent = message; logList.appendChild(empty); } function renderLogs() { if (state.loading && state.logs.length === 0) { renderEmpty('Loading logs...'); return; } if (state.loadError) { renderEmpty(state.loadError); return; } if (!state.logs.length) { renderEmpty('No log entries match the current filter'); 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.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 = '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 = 'Request preview'; 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 = 'Response preview'; 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 = 'Metadata'; 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 = 'Admin API is not available'; renderLogs(); return; } state.workspaceId = currentWorkspaceId(); if (!state.workspaceId) { state.loadError = 'Workspace is not selected'; 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 || 'Failed to load logs'; } finally { state.loading = false; renderLogs(); } } 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.style.animationPlayState = state.liveMode ? 'running' : 'paused'; liveDot.style.opacity = state.liveMode ? '' : '0.35'; liveDot.style.cursor = 'pointer'; } if (liveLabel) { liveLabel.textContent = state.liveMode ? 'Live' : 'Paused'; liveLabel.style.color = state.liveMode ? '' : 'var(--text-muted)'; liveLabel.style.cursor = 'pointer'; } } function stopPolling() { if (state.timer) { clearInterval(state.timer); state.timer = null; } } function startPolling() { stopPolling(); if (!state.liveMode) { return; } state.timer = setInterval(loadLogs, 4000); } function toggleLive() { state.liveMode = !state.liveMode; setLiveState(); startPolling(); } 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', loadLogs); } 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; loadLogs(); }); setLiveState(); startPolling(); if (window.whenWorkspacesReady) { window.whenWorkspacesReady().finally(loadLogs); } else { loadLogs(); } });