chore: publish clean community baseline
This commit is contained in:
@@ -0,0 +1,419 @@
|
||||
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 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 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 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 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(loadLogs, 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 (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();
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user