Fix loglist
This commit is contained in:
52
web/static/js/chunk-A4OOMLXK.js
Normal file
52
web/static/js/chunk-A4OOMLXK.js
Normal 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 {
|
||||
"&": "&",
|
||||
"<": "<",
|
||||
">": ">",
|
||||
'"': """,
|
||||
"'": "'"
|
||||
}[match];
|
||||
});
|
||||
}
|
||||
|
||||
export {
|
||||
debounce,
|
||||
isValidApiKeyFormat,
|
||||
escapeHTML
|
||||
};
|
||||
@@ -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 {
|
||||
"&": "&",
|
||||
"<": "<",
|
||||
">": ">",
|
||||
'"': """,
|
||||
"'": "'"
|
||||
}[match];
|
||||
});
|
||||
}
|
||||
|
||||
// frontend/js/pages/keys/addApiModal.js
|
||||
var AddApiModal = class {
|
||||
constructor({ onImportSuccess }) {
|
||||
@@ -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();
|
||||
@@ -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会自动处理
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user