feat(logs): persist business events
This commit is contained in:
@@ -15,7 +15,8 @@ export const routes = Object.freeze({
|
||||
upstreams: managementBase + "/upstreams",
|
||||
models: managementBase + "/model-suggestions",
|
||||
billingReset: managementBase + "/billing-reset",
|
||||
billingLedger: managementBase + "/billing-ledger"
|
||||
billingLedger: managementBase + "/billing-ledger",
|
||||
events: managementBase + "/events"
|
||||
});
|
||||
|
||||
export const keyInput = document.querySelector("#key");
|
||||
@@ -145,7 +146,7 @@ function readOnlyURL(managementURL) {
|
||||
const views = new Map([
|
||||
[routes.usage, "usage"], [routes.usageSummary, "usage-summary"], [routes.prices, "prices"],
|
||||
[routes.catalog, "price-catalog"], [routes.keys, "keys"], [routes.keyStats, "key-stats"],
|
||||
[routes.upstreams, "upstreams"], [routes.models, "model-suggestions"]
|
||||
[routes.upstreams, "upstreams"], [routes.models, "model-suggestions"], [routes.events, "events"]
|
||||
]);
|
||||
const view = views.get(source.pathname);
|
||||
if (!view) throw new Error("该数据不支持匿名读取");
|
||||
|
||||
@@ -352,16 +352,15 @@ async function resetAllManagedBilling() {
|
||||
if (!targets.length || !confirm(`重置全部 ${targets.length} 个用户的额度?当前剩余余额不会结转。`)) return;
|
||||
resetAllBillingNode.disabled = true;
|
||||
keyStatusNode.textContent = `正在重置 ${targets.length} 个用户`;
|
||||
const results = await Promise.allSettled(targets.map(key => managedFetch(routes.billingReset, {
|
||||
method: "POST",
|
||||
body: JSON.stringify({ id: key.id })
|
||||
})));
|
||||
const failed = results.filter(result => result.status === "rejected");
|
||||
await loadKeys();
|
||||
resetAllBillingNode.disabled = false;
|
||||
keyStatusNode.textContent = failed.length
|
||||
? `已重置 ${targets.length - failed.length} 个用户,${failed.length} 个失败`
|
||||
: `已重置全部 ${targets.length} 个用户的额度`;
|
||||
try {
|
||||
const result = await managedFetch(routes.billingReset, { method: "POST", body: JSON.stringify({ all: true }) });
|
||||
await loadKeys();
|
||||
keyStatusNode.textContent = `已重置全部 ${result.reset_count || 0} 个用户的额度`;
|
||||
} catch (error) {
|
||||
keyStatusNode.textContent = "重置失败:" + error.message;
|
||||
} finally {
|
||||
resetAllBillingNode.disabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function archiveManagedKey(key) {
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
import { dataFetch, routes } from "../core/runtime.js";
|
||||
|
||||
const rowsNode = document.querySelector("#event-rows");
|
||||
|
||||
export async function loadEvents() {
|
||||
rowsNode.replaceChildren(emptyRow("正在读取"));
|
||||
try {
|
||||
const payload = await dataFetch(routes.events + "?limit=200");
|
||||
const events = payload.events || [];
|
||||
rowsNode.replaceChildren(...(events.length ? events.map(eventRow) : [emptyRow("暂无日志")]));
|
||||
} catch (error) {
|
||||
rowsNode.replaceChildren(emptyRow("读取失败:" + error.message));
|
||||
}
|
||||
}
|
||||
|
||||
function eventRow(item) {
|
||||
const row = document.createElement("tr");
|
||||
const occurredAt = document.createElement("td");
|
||||
occurredAt.className = "left";
|
||||
occurredAt.textContent = formatTime(item.occurred_at);
|
||||
const event = document.createElement("td");
|
||||
event.className = "left";
|
||||
event.textContent = item.event || "-";
|
||||
const status = document.createElement("td");
|
||||
status.className = "left event-status " + (String(item.status || "").startsWith("成功") ? "ok" : "failed");
|
||||
status.textContent = item.status || "-";
|
||||
row.append(occurredAt, event, status);
|
||||
return row;
|
||||
}
|
||||
|
||||
function emptyRow(message) {
|
||||
const row = document.createElement("tr");
|
||||
const cell = document.createElement("td");
|
||||
cell.className = "left empty";
|
||||
cell.colSpan = 3;
|
||||
cell.textContent = message;
|
||||
row.append(cell);
|
||||
return row;
|
||||
}
|
||||
|
||||
function formatTime(value) {
|
||||
const date = new Date(value);
|
||||
if (Number.isNaN(date.getTime())) return "-";
|
||||
return new Intl.DateTimeFormat("zh-CN", {
|
||||
year: "numeric", month: "2-digit", day: "2-digit",
|
||||
hour: "2-digit", minute: "2-digit", second: "2-digit", hour12: false
|
||||
}).format(date);
|
||||
}
|
||||
@@ -2,8 +2,9 @@ import { initializeRuntime, onAccessChange } from "./core/runtime.js";
|
||||
import { closeKeyDrawer, initializeKeys, loadKeys } from "./features/keys.js";
|
||||
import { initializePricing, loadPricing } from "./features/pricing.js";
|
||||
import { activateUsage, initializeUsage, refreshVisibleUsage, resetUsage } from "./features/usage.js";
|
||||
import { loadEvents } from "./features/logs.js";
|
||||
|
||||
const loaders = Object.freeze({ stats: loadKeys, usage: activateUsage, pricing: loadPricing, logs: () => {}, users: loadKeys });
|
||||
const loaders = Object.freeze({ stats: loadKeys, usage: activateUsage, pricing: loadPricing, logs: loadEvents, users: loadKeys });
|
||||
|
||||
initializeKeys();
|
||||
initializeUsage();
|
||||
|
||||
@@ -28,3 +28,8 @@ tbody tr:hover { background: color-mix(in srgb, var(--text-primary) 3%, transpar
|
||||
.usage-filter input, .usage-filter select { height: 32px; padding: 6px 8px; font-size: 11px; }
|
||||
.usage-filter.request-filter { grid-column: span 2; }
|
||||
.usage-filter-actions { display: flex; align-items: end; justify-content: flex-end; gap: 7px; grid-column: span 2; }
|
||||
.event-table { table-layout: fixed; }
|
||||
.event-table th:nth-child(1), .event-table td:nth-child(1) { width: 190px; }
|
||||
.event-table th:nth-child(3), .event-table td:nth-child(3) { width: 220px; }
|
||||
.event-table td { height: 42px; white-space: normal; }
|
||||
.event-status { font-weight: 650; }
|
||||
|
||||
@@ -91,7 +91,7 @@
|
||||
</section>
|
||||
|
||||
<section id="view-logs" class="view">
|
||||
<div class="surface"><div class="empty">暂无日志</div></div>
|
||||
<div class="surface"><div class="table-wrap"><table class="event-table"><thead><tr><th class="left">时间</th><th class="left">事件</th><th class="left">状态</th></tr></thead><tbody id="event-rows"></tbody></table></div></div>
|
||||
</section>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user