241 lines
9.9 KiB
JavaScript
241 lines
9.9 KiB
JavaScript
import {
|
|
escapeHTML
|
|
} from "./chunk-A4OOMLXK.js";
|
|
import {
|
|
apiFetchJson
|
|
} from "./chunk-PLQL6WIO.js";
|
|
|
|
// frontend/js/pages/logs/logList.js
|
|
var STATIC_ERROR_MAP = {
|
|
"API_KEY_INVALID": { type: "\u5BC6\u94A5\u65E0\u6548", style: "red" },
|
|
"INVALID_ARGUMENT": { type: "\u53C2\u6570\u65E0\u6548", style: "red" },
|
|
"PERMISSION_DENIED": { type: "\u6743\u9650\u4E0D\u8DB3", style: "red" },
|
|
"NOT_FOUND": { type: "\u8D44\u6E90\u672A\u627E\u5230", style: "gray" },
|
|
"RESOURCE_EXHAUSTED": { type: "\u8D44\u6E90\u8017\u5C3D", style: "orange" },
|
|
"QUOTA_EXCEEDED": { type: "\u914D\u989D\u8017\u5C3D", style: "orange" },
|
|
"DEADLINE_EXCEEDED": { type: "\u8BF7\u6C42\u8D85\u65F6", style: "yellow" },
|
|
"CANCELLED": { type: "\u8BF7\u6C42\u5DF2\u53D6\u6D88", style: "gray" },
|
|
"INTERNAL": { type: "Google\u5185\u90E8\u9519\u8BEF", style: "yellow" },
|
|
"UNAVAILABLE": { type: "\u670D\u52A1\u4E0D\u53EF\u7528", style: "yellow" }
|
|
};
|
|
var STATUS_CODE_MAP = {
|
|
400: { type: "\u9519\u8BEF\u8BF7\u6C42", style: "red" },
|
|
401: { type: "\u8BA4\u8BC1\u5931\u8D25", style: "red" },
|
|
403: { type: "\u7981\u6B62\u8BBF\u95EE", style: "red" },
|
|
404: { type: "\u8D44\u6E90\u672A\u627E\u5230", style: "gray" },
|
|
413: { type: "\u8BF7\u6C42\u4F53\u8FC7\u5927", style: "orange" },
|
|
429: { type: "\u8BF7\u6C42\u9891\u7387\u8FC7\u9AD8", style: "orange" },
|
|
500: { type: "\u5185\u90E8\u670D\u52A1\u9519\u8BEF", style: "yellow" },
|
|
503: { type: "\u670D\u52A1\u4E0D\u53EF\u7528", style: "yellow" }
|
|
};
|
|
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) => {
|
|
switch (style) {
|
|
case "red":
|
|
return "bg-red-500/10 text-red-600";
|
|
case "orange":
|
|
return "bg-orange-500/10 text-orange-600";
|
|
case "yellow":
|
|
return "bg-yellow-500/10 text-yellow-600";
|
|
case "gray":
|
|
return "bg-zinc-500/10 text-zinc-600";
|
|
default:
|
|
return "bg-destructive/10 text-destructive";
|
|
}
|
|
};
|
|
var errorCodeRegex = /(\d+)$/;
|
|
var LogList = class {
|
|
constructor(container, dataStore2) {
|
|
this.container = container;
|
|
this.dataStore = dataStore2;
|
|
if (!this.container) console.error("LogList: container element (tbody) not found.");
|
|
}
|
|
renderLoading() {
|
|
if (!this.container) return;
|
|
this.container.innerHTML = `<tr><td colspan="9" class="p-8 text-center text-muted-foreground"><i class="fas fa-spinner fa-spin mr-2"></i> \u52A0\u8F7D\u65E5\u5FD7\u4E2D...</td></tr>`;
|
|
}
|
|
render(logs, pagination) {
|
|
if (!this.container) return;
|
|
if (!logs || logs.length === 0) {
|
|
this.container.innerHTML = `<tr><td colspan="9" class="p-8 text-center text-muted-foreground">\u6CA1\u6709\u627E\u5230\u76F8\u5173\u7684\u65E5\u5FD7\u8BB0\u5F55\u3002</td></tr>`;
|
|
return;
|
|
}
|
|
const { page, page_size } = pagination;
|
|
const startIndex = (page - 1) * page_size;
|
|
const logsHtml = logs.map((log, index) => this.createLogRowHtml(log, startIndex + index + 1)).join("");
|
|
this.container.innerHTML = logsHtml;
|
|
}
|
|
_interpretError(log) {
|
|
if (log.IsSuccess) {
|
|
return {
|
|
type: "N/A",
|
|
statusCodeHtml: `<span class="inline-flex items-center rounded-md bg-green-500/10 px-2 py-1 text-xs font-medium text-green-600">\u6210\u529F</span>`
|
|
};
|
|
}
|
|
const codeMatch = log.ErrorCode ? log.ErrorCode.match(errorCodeRegex) : null;
|
|
if (codeMatch && codeMatch[1] && log.ErrorMessage) {
|
|
const code = parseInt(codeMatch[1], 10);
|
|
const lowerCaseMsg = log.ErrorMessage.toLowerCase();
|
|
for (const rule of SPECIAL_CASE_MAP) {
|
|
if (code === rule.code && lowerCaseMsg.includes(rule.keyword)) {
|
|
return {
|
|
type: rule.type,
|
|
statusCodeHtml: `<span class="inline-flex items-center rounded-md px-2 py-1 text-xs font-medium ${styleToClass(rule.style)}">${code}</span>`
|
|
};
|
|
}
|
|
}
|
|
}
|
|
if (log.ErrorCode && STATIC_ERROR_MAP[log.ErrorCode]) {
|
|
const mapping = STATIC_ERROR_MAP[log.ErrorCode];
|
|
return {
|
|
type: mapping.type,
|
|
statusCodeHtml: `<span class="inline-flex items-center rounded-md px-2 py-1 text-xs font-medium ${styleToClass(mapping.style)}">${log.ErrorCode}</span>`
|
|
};
|
|
}
|
|
if (codeMatch && codeMatch[1]) {
|
|
const code = parseInt(codeMatch[1], 10);
|
|
let mapping = STATUS_CODE_MAP[code];
|
|
if (!mapping && code >= 500 && code < 600) {
|
|
mapping = STATUS_CODE_MAP[500];
|
|
}
|
|
if (mapping) {
|
|
return {
|
|
type: mapping.type,
|
|
statusCodeHtml: `<span class="inline-flex items-center rounded-md px-2 py-1 text-xs font-medium ${styleToClass(mapping.style)}">${code}</span>`
|
|
};
|
|
}
|
|
}
|
|
if (!log.ErrorCode && !log.ErrorMessage) {
|
|
return { type: "\u672A\u77E5", statusCodeHtml: `<span class="inline-flex items-center rounded-md px-2 py-1 text-xs font-medium ${styleToClass("gray")}">N/A</span>` };
|
|
}
|
|
return { type: "\u672A\u77E5\u9519\u8BEF", statusCodeHtml: `<span class="inline-flex items-center rounded-md px-2 py-1 text-xs font-medium ${styleToClass("default")}">\u5931\u8D25</span>` };
|
|
}
|
|
_formatModelName(modelName) {
|
|
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 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)}"` : "";
|
|
const requestTime = new Date(log.RequestTime).toLocaleString();
|
|
return `
|
|
<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">${apiKeyDisplay}</td>
|
|
<td class="table-cell">${groupName}</td>
|
|
<td class="table-cell">${errorInfo.type}</td>
|
|
<td class="table-cell">${errorInfo.statusCodeHtml}</td>
|
|
<td class="table-cell">${modelNameFormatted}</td>
|
|
<td class="table-cell text-muted-foreground text-xs">${requestTime}</td>
|
|
<td class="table-cell">
|
|
<button class="btn btn-ghost btn-icon btn-sm" aria-label="\u67E5\u770B\u8BE6\u60C5">
|
|
<i class="fas fa-ellipsis-h h-4 w-4"></i>
|
|
</button>
|
|
</td>
|
|
</tr>
|
|
`;
|
|
}
|
|
};
|
|
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 },
|
|
isLoading: true,
|
|
filters: { page: 1, page_size: 20 }
|
|
};
|
|
this.elements = {
|
|
tableBody: document.getElementById("logs-table-body")
|
|
};
|
|
this.initialized = !!this.elements.tableBody;
|
|
if (this.initialized) {
|
|
this.logList = new logList_default(this.elements.tableBody, dataStore);
|
|
}
|
|
}
|
|
async init() {
|
|
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 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 {
|
|
this.logList.render([], this.state.pagination);
|
|
}
|
|
} 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);
|
|
}
|
|
}
|
|
};
|
|
function logs_default() {
|
|
const page = new LogsPage();
|
|
page.init();
|
|
}
|
|
export {
|
|
logs_default as default
|
|
};
|