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

@@ -0,0 +1,52 @@
// frontend/js/utils/utils.js
function debounce(func, wait) {
let timeout;
const debounced = function(...args) {
const context = this;
const later = () => {
clearTimeout(timeout);
func.apply(context, args);
};
clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
debounced.cancel = () => {
clearTimeout(timeout);
};
return debounced;
}
function isValidApiKeyFormat(key) {
const patterns = [
// Google Gemini API Key: AIzaSy + 33 characters (alphanumeric, _, -)
/^AIzaSy[\w-]{33}$/,
// OpenAI API Key (新格式): sk- + 48 alphanumeric characters
/^sk-[\w]{48}$/,
// Google AI Studio Key: gsk_ + alphanumeric & hyphens
/^gsk_[\w-]{40,}$/,
// Anthropic API Key (示例): sk-ant-api03- + long string
/^sk-ant-api\d{2}-[\w-]{80,}$/,
// Fallback for other potential "sk-" keys with a reasonable length
/^sk-[\w-]{20,}$/
];
return patterns.some((pattern) => pattern.test(key));
}
function escapeHTML(str) {
if (typeof str !== "string") {
return str;
}
return str.replace(/[&<>"']/g, function(match) {
return {
"&": "&amp;",
"<": "&lt;",
">": "&gt;",
'"': "&quot;",
"'": "&#039;"
}[match];
});
}
export {
debounce,
isValidApiKeyFormat,
escapeHTML
};

View File

@@ -4,6 +4,11 @@ import {
taskCenterManager,
toastManager
} from "./chunk-EZAP7GR4.js";
import {
debounce,
escapeHTML,
isValidApiKeyFormat
} from "./chunk-A4OOMLXK.js";
import {
apiFetch,
apiFetchJson
@@ -670,53 +675,6 @@ var ApiKeyManager = class {
};
var apiKeyManager = new ApiKeyManager();
// frontend/js/utils/utils.js
function debounce(func, wait) {
let timeout;
const debounced = function(...args) {
const context = this;
const later = () => {
clearTimeout(timeout);
func.apply(context, args);
};
clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
debounced.cancel = () => {
clearTimeout(timeout);
};
return debounced;
}
function isValidApiKeyFormat(key) {
const patterns = [
// Google Gemini API Key: AIzaSy + 33 characters (alphanumeric, _, -)
/^AIzaSy[\w-]{33}$/,
// OpenAI API Key (新格式): sk- + 48 alphanumeric characters
/^sk-[\w]{48}$/,
// Google AI Studio Key: gsk_ + alphanumeric & hyphens
/^gsk_[\w-]{40,}$/,
// Anthropic API Key (示例): sk-ant-api03- + long string
/^sk-ant-api\d{2}-[\w-]{80,}$/,
// Fallback for other potential "sk-" keys with a reasonable length
/^sk-[\w-]{20,}$/
];
return patterns.some((pattern) => pattern.test(key));
}
function escapeHTML(str) {
if (typeof str !== "string") {
return str;
}
return str.replace(/[&<>"']/g, function(match) {
return {
"&": "&amp;",
"<": "&lt;",
">": "&gt;",
'"': "&quot;",
"'": "&#039;"
}[match];
});
}
// frontend/js/pages/keys/addApiModal.js
var AddApiModal = class {
constructor({ onImportSuccess }) {

View File

@@ -1,3 +1,6 @@
import {
escapeHTML
} from "./chunk-A4OOMLXK.js";
import {
apiFetchJson
} from "./chunk-PLQL6WIO.js";
@@ -27,7 +30,6 @@ var STATUS_CODE_MAP = {
};
var SPECIAL_CASE_MAP = [
{ code: 400, keyword: "api key not found", type: "\u65E0\u6548\u5BC6\u94A5", style: "red" },
// 之前实现的模型配置错误规则也可以移到这里,更加规范
{ code: 404, keyword: "call listmodels", type: "\u6A21\u578B\u914D\u7F6E\u9519\u8BEF", style: "orange" }
];
var styleToClass = (style) => {
@@ -46,8 +48,9 @@ var styleToClass = (style) => {
};
var errorCodeRegex = /(\d+)$/;
var LogList = class {
constructor(container) {
constructor(container, dataStore2) {
this.container = container;
this.dataStore = dataStore2;
if (!this.container) console.error("LogList: container element (tbody) not found.");
}
renderLoading() {
@@ -115,8 +118,16 @@ var LogList = class {
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)}"` : "";
@@ -125,7 +136,7 @@ var LogList = class {
<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>
@@ -143,12 +154,15 @@ var LogList = class {
var logList_default = LogList;
// frontend/js/pages/logs/index.js
var dataStore = {
groups: /* @__PURE__ */ new Map(),
keys: /* @__PURE__ */ new Map()
};
var LogsPage = class {
constructor() {
this.state = {
logs: [],
pagination: { page: 1, pages: 1, total: 0, page_size: 20 },
// 包含 page_size
isLoading: true,
filters: { page: 1, page_size: 20 }
};
@@ -157,38 +171,41 @@ var LogsPage = class {
};
this.initialized = !!this.elements.tableBody;
if (this.initialized) {
this.logList = new logList_default(this.elements.tableBody);
this.logList = new logList_default(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() {
}
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() {
this.state.isLoading = true;
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);
if (responseData && responseData.success && Array.isArray(responseData.data)) {
this.state.logs = responseData.data;
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)
// 同样不准确
};
const query = new URLSearchParams(this.state.filters);
const { success, data } = await apiFetchJson(`/admin/logs?${query.toString()}`);
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) };
await this.enrichLogsWithKeyNames(items);
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) {
@@ -198,6 +215,21 @@ var LogsPage = class {
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);
}
}
};
function logs_default() {
const page = new LogsPage();

View File

@@ -180,8 +180,8 @@ var pageModules = {
// 键 'dashboard' 对应一个函数,该函数调用 import() 返回一个 Promise
// esbuild 看到这个 import() 语法,就会自动将 dashboard.js 及其依赖打包成一个独立的 chunk 文件
"dashboard": () => import("./dashboard-CJJWKYPR.js"),
"keys": () => import("./keys-A2UAJYOX.js"),
"logs": () => import("./logs-4C4JG7BT.js")
"keys": () => import("./keys-4GCIJ7HW.js"),
"logs": () => import("./logs-AG4TD2DO.js")
// 'settings': () => import('./pages/settings.js'), // 未来启用 settings 页面
// 未来新增的页面只需在这里添加一行映射esbuild会自动处理
};