diff --git a/src/routes/admin/dashboard.js b/src/routes/admin/dashboard.js index fe2cb440..56a4718a 100644 --- a/src/routes/admin/dashboard.js +++ b/src/routes/admin/dashboard.js @@ -704,4 +704,106 @@ router.post('/cleanup', authenticateAdmin, async (req, res) => { } }) +// 📊 获取最近的使用记录 +router.get('/usage-records', authenticateAdmin, async (req, res) => { + try { + const { limit = 100, offset = 0 } = req.query + const limitNum = Math.min(parseInt(limit) || 100, 500) // 最多500条 + const offsetNum = Math.max(parseInt(offset) || 0, 0) + + // 获取所有API Keys + const apiKeys = await apiKeyService.getAllApiKeys() + if (!apiKeys || apiKeys.length === 0) { + return res.json({ success: true, data: { records: [], total: 0 } }) + } + + // 收集所有API Key的使用记录 + const allRecords = [] + for (const key of apiKeys) { + try { + const records = await redis.getUsageRecords(key.id, 100) // 每个key最多取100条 + if (records && records.length > 0) { + // 为每条记录添加API Key信息 + const enrichedRecords = records.map((record) => ({ + ...record, + apiKeyId: key.id, + apiKeyName: key.name || 'Unnamed Key' + })) + allRecords.push(...enrichedRecords) + } + } catch (error) { + logger.error(`Failed to get usage records for key ${key.id}:`, error) + continue + } + } + + // 按时间戳倒序排序(最新的在前) + allRecords.sort((a, b) => { + const timeA = new Date(a.timestamp).getTime() + const timeB = new Date(b.timestamp).getTime() + return timeB - timeA + }) + + // 分页 + const paginatedRecords = allRecords.slice(offsetNum, offsetNum + limitNum) + + // 获取账户名称映射 + const accountIds = [...new Set(paginatedRecords.map((r) => r.accountId).filter(Boolean))] + const accountNameMap = {} + + // 并发获取所有账户名称 + await Promise.all( + accountIds.map(async (accountId) => { + try { + // 尝试从不同类型的账户中获取 + const claudeAcc = await redis.getAccount(accountId) + if (claudeAcc && claudeAcc.name) { + accountNameMap[accountId] = claudeAcc.name + return + } + + const consoleAcc = await redis.getClaudeConsoleAccount(accountId) + if (consoleAcc && consoleAcc.name) { + accountNameMap[accountId] = consoleAcc.name + return + } + + const geminiAcc = await redis.getGeminiAccount(accountId) + if (geminiAcc && geminiAcc.name) { + accountNameMap[accountId] = geminiAcc.name + return + } + + // 其他平台账户... + accountNameMap[accountId] = accountId // 降级显示ID + } catch (error) { + accountNameMap[accountId] = accountId + } + }) + ) + + // 为记录添加账户名称 + const enrichedRecords = paginatedRecords.map((record) => ({ + ...record, + accountName: record.accountId ? accountNameMap[record.accountId] || record.accountId : '-' + })) + + return res.json({ + success: true, + data: { + records: enrichedRecords, + total: allRecords.length, + limit: limitNum, + offset: offsetNum + } + }) + } catch (error) { + logger.error('❌ Failed to get usage records:', error) + return res.status(500).json({ + error: 'Failed to get usage records', + message: error.message + }) + } +}) + module.exports = router diff --git a/web/admin-spa/src/views/DashboardView.vue b/web/admin-spa/src/views/DashboardView.vue index 61ac8124..8666b600 100644 --- a/web/admin-spa/src/views/DashboardView.vue +++ b/web/admin-spa/src/views/DashboardView.vue @@ -673,6 +673,158 @@ + + +
正在加载使用记录...
+暂无使用记录
+| + 时间 + | ++ API Key + | ++ 账户 + | ++ 模型 + | ++ 输入 + | ++ 输出 + | ++ 缓存创建 + | ++ 缓存读取 + | ++ 成本 + | +
|---|---|---|---|---|---|---|---|---|
| + {{ formatRecordTime(record.timestamp) }} + | +
+
+ {{ record.apiKeyName }}
+
+ |
+
+
+ {{ record.accountName }}
+
+ |
+
+
+ {{ record.model }}
+
+ |
+ + {{ formatNumber(record.inputTokens) }} + | ++ {{ formatNumber(record.outputTokens) }} + | ++ {{ formatNumber(record.cacheCreateTokens) }} + | ++ {{ formatNumber(record.cacheReadTokens) }} + | ++ ${{ formatCost(record.cost) }} + | +