add admin log
This commit is contained in:
parent
bec7b3e5ef
commit
e373295cf5
8 changed files with 495 additions and 51 deletions
|
|
@ -83,3 +83,11 @@ main{padding:28px 0 60px}
|
|||
.toast-ok{background:#065f46;color:#a7f3d0}
|
||||
.toast-err{background:#7f1d1d;color:#fca5a5}
|
||||
@keyframes slideIn{from{transform:translateX(100px);opacity:0}to{transform:none;opacity:1}}
|
||||
|
||||
.request-logs-wrap{overflow:auto}
|
||||
.request-logs-table{min-width:1100px}
|
||||
.request-logs-table td{vertical-align:top}
|
||||
.log-url{max-width:320px;word-break:break-all;color:var(--muted)}
|
||||
.log-status{display:inline-flex;align-items:center;padding:2px 8px;border-radius:999px;font-size:12px;font-weight:600}
|
||||
.status-ok{background:rgba(34,197,94,.15);color:var(--green)}
|
||||
.status-error{background:rgba(239,68,68,.15);color:var(--red)}
|
||||
|
|
|
|||
|
|
@ -90,6 +90,16 @@
|
|||
</div>
|
||||
<div id="statsContent"><div class="empty">加载中…</div></div>
|
||||
</div>
|
||||
|
||||
<!-- 请求日志 -->
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h2>最近 500 条请求日志</h2>
|
||||
<button class="btn btn-ghost btn-sm" onclick="loadRequestLogs()">刷新</button>
|
||||
</div>
|
||||
<div class="hint" style="margin-top:-12px;margin-bottom:16px">显示请求时间、请求模型、实际上游模型、上游 URL、Token 统计、耗时和状态。</div>
|
||||
<div id="requestLogsContent"><div class="empty">加载中…</div></div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -72,6 +72,7 @@ async function loadDashboard() {
|
|||
await loadMappings();
|
||||
checkHealth();
|
||||
loadStats();
|
||||
loadRequestLogs();
|
||||
} catch (e) {
|
||||
toast('加载设置失败: ' + e.message, false);
|
||||
}
|
||||
|
|
@ -104,6 +105,55 @@ async function loadStats() {
|
|||
}
|
||||
}
|
||||
|
||||
async function loadRequestLogs() {
|
||||
const el = document.getElementById('requestLogsContent');
|
||||
try {
|
||||
const data = await api('/api/admin/request-logs');
|
||||
const items = data.items || [];
|
||||
if (!items.length) {
|
||||
el.innerHTML = '<div class="empty">暂无请求日志</div>';
|
||||
return;
|
||||
}
|
||||
let html = '<div class="request-logs-wrap"><table class="stats-table request-logs-table"><thead><tr><th>请求时间</th><th>请求模型</th><th>实际模型</th><th>上游 URL</th><th>Tokens</th><th>耗时</th><th>状态</th></tr></thead><tbody>';
|
||||
for (const item of items) {
|
||||
const usage = item.usage || {};
|
||||
const tokens = '输 ' + fmtNum(usage.input_tokens) + ' / 出 ' + fmtNum(usage.output_tokens) + ' / 总 ' + fmtNum(usage.total_tokens);
|
||||
const statusClass = item.status === 'ok' ? 'status-ok' : 'status-error';
|
||||
const statusText = item.status === 'ok' ? '成功' : '异常';
|
||||
html += '<tr>'
|
||||
+ '<td>' + esc(fmtTime(item.requested_at)) + '</td>'
|
||||
+ '<td>' + esc(item.requested_model || '-') + '</td>'
|
||||
+ '<td>' + esc(item.actual_model || '-') + '</td>'
|
||||
+ '<td class="log-url" title="' + esc(item.upstream_url || '') + '">' + esc(item.upstream_url || '-') + '</td>'
|
||||
+ '<td>' + esc(tokens) + '</td>'
|
||||
+ '<td>' + fmtNum(item.duration_ms) + ' ms</td>'
|
||||
+ '<td><span class="log-status ' + statusClass + '">' + statusText + '</span></td>'
|
||||
+ '</tr>';
|
||||
}
|
||||
html += '</tbody></table></div>';
|
||||
el.innerHTML = html;
|
||||
} catch (e) {
|
||||
el.innerHTML = '<div class="empty">加载请求日志失败</div>';
|
||||
}
|
||||
}
|
||||
|
||||
function fmtNum(value) {
|
||||
return Number(value || 0).toLocaleString();
|
||||
}
|
||||
|
||||
function fmtTime(value) {
|
||||
if (!value) return '-';
|
||||
const d = new Date(value);
|
||||
if (Number.isNaN(d.getTime())) return String(value);
|
||||
const pad = n => String(n).padStart(2, '0');
|
||||
return d.getFullYear() + '-'
|
||||
+ pad(d.getMonth() + 1) + '-'
|
||||
+ pad(d.getDate()) + ' '
|
||||
+ pad(d.getHours()) + ':'
|
||||
+ pad(d.getMinutes()) + ':'
|
||||
+ pad(d.getSeconds());
|
||||
}
|
||||
|
||||
async function checkHealth() {
|
||||
try {
|
||||
const r = await fetch(API + '/health');
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue