Fix loglist

This commit is contained in:
XOF
2025-11-21 19:33:05 +08:00
parent 1f7aa70810
commit 6a0f344e5c
22 changed files with 380 additions and 357 deletions

View File

@@ -3,11 +3,17 @@
import { apiFetchJson } from '../../services/api.js';
import LogList from './logList.js';
// [最终版] 创建一个共享的数据仓库,用于缓存 Groups 和 Keys
const dataStore = {
groups: new Map(),
keys: new Map(),
};
class LogsPage {
constructor() {
this.state = {
logs: [],
pagination: { page: 1, pages: 1, total: 0, page_size: 20 }, // 包含 page_size
pagination: { page: 1, pages: 1, total: 0, page_size: 20 },
isLoading: true,
filters: { page: 1, page_size: 20 }
};
@@ -19,21 +25,30 @@ class LogsPage {
this.initialized = !!this.elements.tableBody;
if (this.initialized) {
this.logList = new LogList(this.elements.tableBody);
this.logList = new LogList(this.elements.tableBody, dataStore);
}
}
async init() {
if (!this.initialized) {
console.error("LogsPage: Could not initialize. Essential container element 'logs-table-body' is missing.");
return;
}
if (!this.initialized) return;
this.initEventListeners();
// 页面初始化:先加载群组,再加载日志
await this.loadGroupsOnce();
await this.loadAndRenderLogs();
}
initEventListeners() {
// 分页和筛选的事件监听器将在后续任务中添加
initEventListeners() { /* 分页和筛选的事件监听器 */ }
async loadGroupsOnce() {
if (dataStore.groups.size > 0) return; // 防止重复加载
try {
const { success, data } = await apiFetchJson("/admin/keygroups");
if (success && Array.isArray(data)) {
data.forEach(group => dataStore.groups.set(group.id, group));
}
} catch (error) {
console.error("Failed to load key groups:", error);
}
}
async loadAndRenderLogs() {
@@ -41,36 +56,45 @@ class LogsPage {
this.logList.renderLoading();
try {
const url = `/admin/logs?page=${this.state.filters.page}&page_size=${this.state.filters.page_size}`;
const responseData = await apiFetchJson(url);
const query = new URLSearchParams(this.state.filters);
const { success, data } = await apiFetchJson(`/admin/logs?${query.toString()}`);
if (responseData && responseData.success && Array.isArray(responseData.data)) {
this.state.logs = responseData.data;
if (success && typeof data === 'object') {
const { items, total, page, page_size } = data;
this.state.logs = items;
this.state.pagination = { page, page_size, total, pages: Math.ceil(total / page_size) };
// [假设] 由于当前响应不包含分页信息,我们基于请求和返回的数据来模拟
// TODO: 当后端API返回分页对象时替换此处的模拟数据
this.state.pagination = {
page: this.state.filters.page,
page_size: this.state.filters.page_size,
total: responseData.data.length, // 这是一个不准确的临时值
pages: Math.ceil(responseData.data.length / this.state.filters.page_size) // 同样不准确
};
// [核心] 在渲染前按需批量加载本页日志所需的、尚未缓存的Key信息
await this.enrichLogsWithKeyNames(items);
// [修改] 将分页状态传递给 render 方法
// 调用 render此时 dataStore 中已包含所有需要的数据
this.logList.render(this.state.logs, this.state.pagination);
} else {
console.error("API response for logs is incorrect:", responseData);
this.logList.render([], this.state.pagination);
}
} catch (error)
{
} catch (error) {
console.error("Failed to load logs:", error);
this.logList.render([], this.state.pagination);
} finally {
this.state.isLoading = false;
}
}
async enrichLogsWithKeyNames(logs) {
const missingKeyIds = [...new Set(
logs.filter(log => log.KeyID && !dataStore.keys.has(log.KeyID)).map(log => log.KeyID)
)];
if (missingKeyIds.length === 0) return;
try {
const idsQuery = missingKeyIds.join(',');
const { success, data } = await apiFetchJson(`/admin/apikeys?ids=${idsQuery}`);
if (success && Array.isArray(data)) {
data.forEach(key => dataStore.keys.set(key.ID, key));
}
} catch (error) {
console.error(`Failed to fetch key details:`, error);
}
}
}
export default function() {

View File

@@ -1,5 +1,6 @@
// Filename: frontend/js/pages/logs/logList.js
// --- [扩展] 静态错误码与样式的映射表 (源自Gemini官方文档) ---
import { escapeHTML } from '../../utils/utils.js';
const STATIC_ERROR_MAP = {
'API_KEY_INVALID': { type: '密钥无效', style: 'red' },
'INVALID_ARGUMENT': { type: '参数无效', style: 'red' },
@@ -23,10 +24,8 @@ const STATUS_CODE_MAP = {
500: { type: '内部服务错误', style: 'yellow' },
503: { type: '服务不可用', style: 'yellow' }
};
// --- [新增] 特殊场景判断规则 (高优先级) ---
const SPECIAL_CASE_MAP = [
{ code: 400, keyword: 'api key not found', type: '无效密钥', style: 'red' },
// 之前实现的模型配置错误规则也可以移到这里,更加规范
{ code: 404, keyword: 'call listmodels', type: '模型配置错误', style: 'orange' }
];
@@ -41,16 +40,13 @@ const styleToClass = (style) => {
}
};
// [修正] 修正了正则表达式的名称,使其语义清晰
const errorCodeRegex = /(\d+)$/;
// [修正] 移除了 MODEL_STYLE_MAP 的声明,因为它未在 _formatModelName 中使用
// 如果未来需要,可以重新添加
// const MODEL_STYLE_MAP = { ... };
class LogList {
constructor(container) {
constructor(container, dataStore) {
this.container = container;
this.dataStore = dataStore;
if (!this.container) console.error("LogList: container element (tbody) not found.");
}
@@ -125,15 +121,21 @@ class LogList {
}
_formatModelName(modelName) {
// [修正] 移除了对 MODEL_STYLE_MAP 的依赖,简化为统一的样式
// 这样可以避免因 MODEL_STYLE_MAP 未定义而产生的潜在错误
const styleClass = ''; // 可根据需要添加回样式逻辑
const styleClass = '';
return `<div class="inline-block rounded bg-zinc-100 dark:bg-zinc-800 px-2 py-0.5"><span class="font-quinquefive text-xs tracking-wider ${styleClass}">${modelName}</span></div>`;
}
createLogRowHtml(log, index) {
const groupName = log.GroupDisplayName || (log.GroupID ? `Group #${log.GroupID}` : 'N/A');
const apiKeyName = log.APIKeyName || (log.KeyID ? `Key #${log.KeyID}` : 'N/A');
const group = this.dataStore.groups.get(log.GroupID);
const groupName = group ? group.display_name : (log.GroupID ? `Group #${log.GroupID}` : 'N/A');
const key = this.dataStore.keys.get(log.KeyID);
let apiKeyDisplay;
if (key && key.APIKey && key.APIKey.length >= 8) {
const masked = `${key.APIKey.substring(0, 4)}......${key.APIKey.substring(key.APIKey.length - 4)}`;
apiKeyDisplay = escapeHTML(masked);
} else {
apiKeyDisplay = log.KeyID ? `Key #${log.KeyID}` : 'N/A';
}
const errorInfo = this._interpretError(log);
const modelNameFormatted = this._formatModelName(log.ModelName);
const errorMessageAttr = log.ErrorMessage ? `data-error-message="${escape(log.ErrorMessage)}"` : '';
@@ -142,7 +144,7 @@ class LogList {
<tr class="table-row" data-log-id="${log.ID}" ${errorMessageAttr}>
<td class="table-cell"><input type="checkbox" class="h-4 w-4 rounded border-zinc-300 text-blue-600 focus:ring-blue-500"></td>
<td class="table-cell font-mono text-muted-foreground">${index}</td>
<td class="table-cell font-medium font-mono">${apiKeyName}</td>
<td class="table-cell font-medium font-mono">${apiKeyDisplay}</td>
<td class="table-cell">${groupName}</td>
<td class="table-cell">${errorInfo.type}</td>
<td class="table-cell">${errorInfo.statusCodeHtml}</td>
@@ -158,5 +160,4 @@ class LogList {
}
}
// [核心修正] 移除了文件末尾所有多余的代码,只保留最核心的默认导出
export default LogList;