恢复并保存本地修改:仪表盘服务账户分类显示、WindowCountdown组件等功能

This commit is contained in:
shaw
2025-08-08 11:56:24 +08:00
parent 5bed33cd9c
commit 4adc8d9695
8 changed files with 329 additions and 214 deletions

View File

@@ -2300,6 +2300,7 @@ router.get('/dashboard', authenticateAdmin, async (req, res) => {
claudeAccounts,
claudeConsoleAccounts,
geminiAccounts,
bedrockAccountsResult,
todayStats,
systemAverages,
realtimeMetrics
@@ -2309,11 +2310,15 @@ router.get('/dashboard', authenticateAdmin, async (req, res) => {
claudeAccountService.getAllAccounts(),
claudeConsoleAccountService.getAllAccounts(),
geminiAccountService.getAllAccounts(),
bedrockAccountService.getAllAccounts(),
redis.getTodayStats(),
redis.getSystemAverages(),
redis.getRealtimeSystemMetrics()
])
// 处理Bedrock账户数据
const bedrockAccounts = bedrockAccountsResult.success ? bedrockAccountsResult.data : []
// 计算使用统计统一使用allTokens
const totalTokensUsed = apiKeys.reduce(
(sum, key) => sum + (key.usage?.total?.allTokens || 0),
@@ -2345,34 +2350,167 @@ router.get('/dashboard', authenticateAdmin, async (req, res) => {
)
const activeApiKeys = apiKeys.filter((key) => key.isActive).length
const activeClaudeAccounts = claudeAccounts.filter(
(acc) => acc.isActive && acc.status === 'active'
// Claude账户统计 - 根据账户管理页面的判断逻辑
const normalClaudeAccounts = claudeAccounts.filter(
(acc) =>
acc.isActive &&
acc.status !== 'blocked' &&
acc.status !== 'unauthorized' &&
acc.schedulable !== false
).length
const abnormalClaudeAccounts = claudeAccounts.filter(
(acc) => !acc.isActive || acc.status === 'blocked' || acc.status === 'unauthorized'
).length
const pausedClaudeAccounts = claudeAccounts.filter(
(acc) =>
acc.schedulable === false &&
acc.isActive &&
acc.status !== 'blocked' &&
acc.status !== 'unauthorized'
).length
const rateLimitedClaudeAccounts = claudeAccounts.filter(
(acc) => acc.rateLimitStatus && acc.rateLimitStatus.isRateLimited
).length
const activeClaudeConsoleAccounts = claudeConsoleAccounts.filter(
(acc) => acc.isActive && acc.status === 'active'
// Claude Console账户统计
const normalClaudeConsoleAccounts = claudeConsoleAccounts.filter(
(acc) =>
acc.isActive &&
acc.status !== 'blocked' &&
acc.status !== 'unauthorized' &&
acc.schedulable !== false
).length
const abnormalClaudeConsoleAccounts = claudeConsoleAccounts.filter(
(acc) => !acc.isActive || acc.status === 'blocked' || acc.status === 'unauthorized'
).length
const pausedClaudeConsoleAccounts = claudeConsoleAccounts.filter(
(acc) =>
acc.schedulable === false &&
acc.isActive &&
acc.status !== 'blocked' &&
acc.status !== 'unauthorized'
).length
const rateLimitedClaudeConsoleAccounts = claudeConsoleAccounts.filter(
(acc) => acc.rateLimitStatus && acc.rateLimitStatus.isRateLimited
).length
const activeGeminiAccounts = geminiAccounts.filter(
(acc) => acc.isActive && acc.status === 'active'
// Gemini账户统计
const normalGeminiAccounts = geminiAccounts.filter(
(acc) =>
acc.isActive &&
acc.status !== 'blocked' &&
acc.status !== 'unauthorized' &&
acc.schedulable !== false
).length
const abnormalGeminiAccounts = geminiAccounts.filter(
(acc) => !acc.isActive || acc.status === 'blocked' || acc.status === 'unauthorized'
).length
const pausedGeminiAccounts = geminiAccounts.filter(
(acc) =>
acc.schedulable === false &&
acc.isActive &&
acc.status !== 'blocked' &&
acc.status !== 'unauthorized'
).length
const rateLimitedGeminiAccounts = geminiAccounts.filter(
(acc) => acc.rateLimitStatus === 'limited'
).length
// Bedrock账户统计
const normalBedrockAccounts = bedrockAccounts.filter(
(acc) =>
acc.isActive &&
acc.status !== 'blocked' &&
acc.status !== 'unauthorized' &&
acc.schedulable !== false
).length
const abnormalBedrockAccounts = bedrockAccounts.filter(
(acc) => !acc.isActive || acc.status === 'blocked' || acc.status === 'unauthorized'
).length
const pausedBedrockAccounts = bedrockAccounts.filter(
(acc) =>
acc.schedulable === false &&
acc.isActive &&
acc.status !== 'blocked' &&
acc.status !== 'unauthorized'
).length
const rateLimitedBedrockAccounts = bedrockAccounts.filter(
(acc) => acc.rateLimitStatus && acc.rateLimitStatus.isRateLimited
).length
const dashboard = {
overview: {
totalApiKeys: apiKeys.length,
activeApiKeys,
// 总账户统计(所有平台)
totalAccounts:
claudeAccounts.length +
claudeConsoleAccounts.length +
geminiAccounts.length +
bedrockAccounts.length,
normalAccounts:
normalClaudeAccounts +
normalClaudeConsoleAccounts +
normalGeminiAccounts +
normalBedrockAccounts,
abnormalAccounts:
abnormalClaudeAccounts +
abnormalClaudeConsoleAccounts +
abnormalGeminiAccounts +
abnormalBedrockAccounts,
pausedAccounts:
pausedClaudeAccounts +
pausedClaudeConsoleAccounts +
pausedGeminiAccounts +
pausedBedrockAccounts,
rateLimitedAccounts:
rateLimitedClaudeAccounts +
rateLimitedClaudeConsoleAccounts +
rateLimitedGeminiAccounts +
rateLimitedBedrockAccounts,
// 各平台详细统计
accountsByPlatform: {
claude: {
total: claudeAccounts.length,
normal: normalClaudeAccounts,
abnormal: abnormalClaudeAccounts,
paused: pausedClaudeAccounts,
rateLimited: rateLimitedClaudeAccounts
},
'claude-console': {
total: claudeConsoleAccounts.length,
normal: normalClaudeConsoleAccounts,
abnormal: abnormalClaudeConsoleAccounts,
paused: pausedClaudeConsoleAccounts,
rateLimited: rateLimitedClaudeConsoleAccounts
},
gemini: {
total: geminiAccounts.length,
normal: normalGeminiAccounts,
abnormal: abnormalGeminiAccounts,
paused: pausedGeminiAccounts,
rateLimited: rateLimitedGeminiAccounts
},
bedrock: {
total: bedrockAccounts.length,
normal: normalBedrockAccounts,
abnormal: abnormalBedrockAccounts,
paused: pausedBedrockAccounts,
rateLimited: rateLimitedBedrockAccounts
}
},
// 保留旧字段以兼容
activeAccounts:
normalClaudeAccounts +
normalClaudeConsoleAccounts +
normalGeminiAccounts +
normalBedrockAccounts,
totalClaudeAccounts: claudeAccounts.length + claudeConsoleAccounts.length,
activeClaudeAccounts: activeClaudeAccounts + activeClaudeConsoleAccounts,
activeClaudeAccounts: normalClaudeAccounts + normalClaudeConsoleAccounts,
rateLimitedClaudeAccounts: rateLimitedClaudeAccounts + rateLimitedClaudeConsoleAccounts,
totalGeminiAccounts: geminiAccounts.length,
activeGeminiAccounts,
activeGeminiAccounts: normalGeminiAccounts,
rateLimitedGeminiAccounts,
totalTokensUsed,
totalRequestsUsed,
@@ -2403,8 +2541,8 @@ router.get('/dashboard', authenticateAdmin, async (req, res) => {
},
systemHealth: {
redisConnected: redis.isConnected,
claudeAccountsHealthy: activeClaudeAccounts + activeClaudeConsoleAccounts > 0,
geminiAccountsHealthy: activeGeminiAccounts > 0,
claudeAccountsHealthy: normalClaudeAccounts + normalClaudeConsoleAccounts > 0,
geminiAccountsHealthy: normalGeminiAccounts > 0,
uptime: process.uptime()
},
systemTimezone: config.system.timezoneOffset || 8

View File

@@ -213,12 +213,45 @@ class ApiKeyService {
if (key.rateLimitWindow > 0) {
const requestCountKey = `rate_limit:requests:${key.id}`
const tokenCountKey = `rate_limit:tokens:${key.id}`
const windowStartKey = `rate_limit:window_start:${key.id}`
key.currentWindowRequests = parseInt((await client.get(requestCountKey)) || '0')
key.currentWindowTokens = parseInt((await client.get(tokenCountKey)) || '0')
// 获取窗口开始时间和计算剩余时间
const windowStart = await client.get(windowStartKey)
if (windowStart) {
const now = Date.now()
const windowStartTime = parseInt(windowStart)
const windowDuration = key.rateLimitWindow * 60 * 1000 // 转换为毫秒
const windowEndTime = windowStartTime + windowDuration
// 如果窗口还有效
if (now < windowEndTime) {
key.windowStartTime = windowStartTime
key.windowEndTime = windowEndTime
key.windowRemainingSeconds = Math.max(0, Math.floor((windowEndTime - now) / 1000))
} else {
// 窗口已过期,下次请求会重置
key.windowStartTime = null
key.windowEndTime = null
key.windowRemainingSeconds = 0
// 重置计数为0因为窗口已过期
key.currentWindowRequests = 0
key.currentWindowTokens = 0
}
} else {
// 窗口还未开始(没有任何请求)
key.windowStartTime = null
key.windowEndTime = null
key.windowRemainingSeconds = null
}
} else {
key.currentWindowRequests = 0
key.currentWindowTokens = 0
key.windowStartTime = null
key.windowEndTime = null
key.windowRemainingSeconds = null
}
try {