mirror of
https://github.com/Wei-Shaw/claude-relay-service.git
synced 2026-01-22 16:43:35 +00:00
feat: 实现基于滑动窗口的实时RPM/TPM统计
- 添加系统级分钟统计,支持1-60分钟可配置时间窗口 - 新增 getRealtimeSystemMetrics 方法计算滑动窗口内的平均值 - 前端显示实时RPM/TPM,标注时间窗口和数据来源 - 修复 EditApiKeyModal 中模型限制和客户端限制复选框状态错误 - 优化性能:使用Pipeline批量操作替代Promise.all - TPM包含所有token类型:input、output、cache_creation、cache_read - 添加降级方案:实时数据不可用时返回历史平均值 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -45,6 +45,7 @@ TOKEN_USAGE_RETENTION=2592000000
|
||||
HEALTH_CHECK_INTERVAL=60000
|
||||
SYSTEM_TIMEZONE=Asia/Shanghai
|
||||
TIMEZONE_OFFSET=8
|
||||
METRICS_WINDOW=5 # 实时指标统计窗口(分钟),可选1-60,默认5分钟
|
||||
|
||||
# 🎨 Web 界面配置
|
||||
WEB_TITLE=Claude Relay Service
|
||||
|
||||
@@ -186,6 +186,10 @@ class RedisClient {
|
||||
const keyModelMonthly = `usage:${keyId}:model:monthly:${model}:${currentMonth}`;
|
||||
const keyModelHourly = `usage:${keyId}:model:hourly:${model}:${currentHour}`; // 新增API Key模型小时级别
|
||||
|
||||
// 新增:系统级分钟统计
|
||||
const minuteTimestamp = Math.floor(now.getTime() / 60000);
|
||||
const systemMinuteKey = `system:metrics:minute:${minuteTimestamp}`;
|
||||
|
||||
// 智能处理输入输出token分配
|
||||
const finalInputTokens = inputTokens || 0;
|
||||
const finalOutputTokens = outputTokens || (finalInputTokens > 0 ? 0 : tokens);
|
||||
@@ -197,96 +201,122 @@ class RedisClient {
|
||||
// 核心token(不包括缓存)- 用于与历史数据兼容
|
||||
const coreTokens = finalInputTokens + finalOutputTokens;
|
||||
|
||||
await Promise.all([
|
||||
// 使用Pipeline优化性能
|
||||
const pipeline = this.client.pipeline();
|
||||
|
||||
// 现有的统计保持不变
|
||||
// 核心token统计(保持向后兼容)
|
||||
this.client.hincrby(key, 'totalTokens', coreTokens),
|
||||
this.client.hincrby(key, 'totalInputTokens', finalInputTokens),
|
||||
this.client.hincrby(key, 'totalOutputTokens', finalOutputTokens),
|
||||
pipeline.hincrby(key, 'totalTokens', coreTokens);
|
||||
pipeline.hincrby(key, 'totalInputTokens', finalInputTokens);
|
||||
pipeline.hincrby(key, 'totalOutputTokens', finalOutputTokens);
|
||||
// 缓存token统计(新增)
|
||||
this.client.hincrby(key, 'totalCacheCreateTokens', finalCacheCreateTokens),
|
||||
this.client.hincrby(key, 'totalCacheReadTokens', finalCacheReadTokens),
|
||||
this.client.hincrby(key, 'totalAllTokens', totalTokens), // 包含所有类型的总token
|
||||
pipeline.hincrby(key, 'totalCacheCreateTokens', finalCacheCreateTokens);
|
||||
pipeline.hincrby(key, 'totalCacheReadTokens', finalCacheReadTokens);
|
||||
pipeline.hincrby(key, 'totalAllTokens', totalTokens); // 包含所有类型的总token
|
||||
// 请求计数
|
||||
this.client.hincrby(key, 'totalRequests', 1),
|
||||
pipeline.hincrby(key, 'totalRequests', 1);
|
||||
|
||||
// 每日统计
|
||||
this.client.hincrby(daily, 'tokens', coreTokens),
|
||||
this.client.hincrby(daily, 'inputTokens', finalInputTokens),
|
||||
this.client.hincrby(daily, 'outputTokens', finalOutputTokens),
|
||||
this.client.hincrby(daily, 'cacheCreateTokens', finalCacheCreateTokens),
|
||||
this.client.hincrby(daily, 'cacheReadTokens', finalCacheReadTokens),
|
||||
this.client.hincrby(daily, 'allTokens', totalTokens),
|
||||
this.client.hincrby(daily, 'requests', 1),
|
||||
pipeline.hincrby(daily, 'tokens', coreTokens);
|
||||
pipeline.hincrby(daily, 'inputTokens', finalInputTokens);
|
||||
pipeline.hincrby(daily, 'outputTokens', finalOutputTokens);
|
||||
pipeline.hincrby(daily, 'cacheCreateTokens', finalCacheCreateTokens);
|
||||
pipeline.hincrby(daily, 'cacheReadTokens', finalCacheReadTokens);
|
||||
pipeline.hincrby(daily, 'allTokens', totalTokens);
|
||||
pipeline.hincrby(daily, 'requests', 1);
|
||||
|
||||
// 每月统计
|
||||
this.client.hincrby(monthly, 'tokens', coreTokens),
|
||||
this.client.hincrby(monthly, 'inputTokens', finalInputTokens),
|
||||
this.client.hincrby(monthly, 'outputTokens', finalOutputTokens),
|
||||
this.client.hincrby(monthly, 'cacheCreateTokens', finalCacheCreateTokens),
|
||||
this.client.hincrby(monthly, 'cacheReadTokens', finalCacheReadTokens),
|
||||
this.client.hincrby(monthly, 'allTokens', totalTokens),
|
||||
this.client.hincrby(monthly, 'requests', 1),
|
||||
pipeline.hincrby(monthly, 'tokens', coreTokens);
|
||||
pipeline.hincrby(monthly, 'inputTokens', finalInputTokens);
|
||||
pipeline.hincrby(monthly, 'outputTokens', finalOutputTokens);
|
||||
pipeline.hincrby(monthly, 'cacheCreateTokens', finalCacheCreateTokens);
|
||||
pipeline.hincrby(monthly, 'cacheReadTokens', finalCacheReadTokens);
|
||||
pipeline.hincrby(monthly, 'allTokens', totalTokens);
|
||||
pipeline.hincrby(monthly, 'requests', 1);
|
||||
|
||||
// 按模型统计 - 每日
|
||||
this.client.hincrby(modelDaily, 'inputTokens', finalInputTokens),
|
||||
this.client.hincrby(modelDaily, 'outputTokens', finalOutputTokens),
|
||||
this.client.hincrby(modelDaily, 'cacheCreateTokens', finalCacheCreateTokens),
|
||||
this.client.hincrby(modelDaily, 'cacheReadTokens', finalCacheReadTokens),
|
||||
this.client.hincrby(modelDaily, 'allTokens', totalTokens),
|
||||
this.client.hincrby(modelDaily, 'requests', 1),
|
||||
pipeline.hincrby(modelDaily, 'inputTokens', finalInputTokens);
|
||||
pipeline.hincrby(modelDaily, 'outputTokens', finalOutputTokens);
|
||||
pipeline.hincrby(modelDaily, 'cacheCreateTokens', finalCacheCreateTokens);
|
||||
pipeline.hincrby(modelDaily, 'cacheReadTokens', finalCacheReadTokens);
|
||||
pipeline.hincrby(modelDaily, 'allTokens', totalTokens);
|
||||
pipeline.hincrby(modelDaily, 'requests', 1);
|
||||
|
||||
// 按模型统计 - 每月
|
||||
this.client.hincrby(modelMonthly, 'inputTokens', finalInputTokens),
|
||||
this.client.hincrby(modelMonthly, 'outputTokens', finalOutputTokens),
|
||||
this.client.hincrby(modelMonthly, 'cacheCreateTokens', finalCacheCreateTokens),
|
||||
this.client.hincrby(modelMonthly, 'cacheReadTokens', finalCacheReadTokens),
|
||||
this.client.hincrby(modelMonthly, 'allTokens', totalTokens),
|
||||
this.client.hincrby(modelMonthly, 'requests', 1),
|
||||
pipeline.hincrby(modelMonthly, 'inputTokens', finalInputTokens);
|
||||
pipeline.hincrby(modelMonthly, 'outputTokens', finalOutputTokens);
|
||||
pipeline.hincrby(modelMonthly, 'cacheCreateTokens', finalCacheCreateTokens);
|
||||
pipeline.hincrby(modelMonthly, 'cacheReadTokens', finalCacheReadTokens);
|
||||
pipeline.hincrby(modelMonthly, 'allTokens', totalTokens);
|
||||
pipeline.hincrby(modelMonthly, 'requests', 1);
|
||||
|
||||
// API Key级别的模型统计 - 每日
|
||||
this.client.hincrby(keyModelDaily, 'inputTokens', finalInputTokens),
|
||||
this.client.hincrby(keyModelDaily, 'outputTokens', finalOutputTokens),
|
||||
this.client.hincrby(keyModelDaily, 'cacheCreateTokens', finalCacheCreateTokens),
|
||||
this.client.hincrby(keyModelDaily, 'cacheReadTokens', finalCacheReadTokens),
|
||||
this.client.hincrby(keyModelDaily, 'allTokens', totalTokens),
|
||||
this.client.hincrby(keyModelDaily, 'requests', 1),
|
||||
pipeline.hincrby(keyModelDaily, 'inputTokens', finalInputTokens);
|
||||
pipeline.hincrby(keyModelDaily, 'outputTokens', finalOutputTokens);
|
||||
pipeline.hincrby(keyModelDaily, 'cacheCreateTokens', finalCacheCreateTokens);
|
||||
pipeline.hincrby(keyModelDaily, 'cacheReadTokens', finalCacheReadTokens);
|
||||
pipeline.hincrby(keyModelDaily, 'allTokens', totalTokens);
|
||||
pipeline.hincrby(keyModelDaily, 'requests', 1);
|
||||
|
||||
// API Key级别的模型统计 - 每月
|
||||
this.client.hincrby(keyModelMonthly, 'inputTokens', finalInputTokens),
|
||||
this.client.hincrby(keyModelMonthly, 'outputTokens', finalOutputTokens),
|
||||
this.client.hincrby(keyModelMonthly, 'cacheCreateTokens', finalCacheCreateTokens),
|
||||
this.client.hincrby(keyModelMonthly, 'cacheReadTokens', finalCacheReadTokens),
|
||||
this.client.hincrby(keyModelMonthly, 'allTokens', totalTokens),
|
||||
this.client.hincrby(keyModelMonthly, 'requests', 1),
|
||||
pipeline.hincrby(keyModelMonthly, 'inputTokens', finalInputTokens);
|
||||
pipeline.hincrby(keyModelMonthly, 'outputTokens', finalOutputTokens);
|
||||
pipeline.hincrby(keyModelMonthly, 'cacheCreateTokens', finalCacheCreateTokens);
|
||||
pipeline.hincrby(keyModelMonthly, 'cacheReadTokens', finalCacheReadTokens);
|
||||
pipeline.hincrby(keyModelMonthly, 'allTokens', totalTokens);
|
||||
pipeline.hincrby(keyModelMonthly, 'requests', 1);
|
||||
|
||||
// 小时级别统计
|
||||
this.client.hincrby(hourly, 'tokens', coreTokens),
|
||||
this.client.hincrby(hourly, 'inputTokens', finalInputTokens),
|
||||
this.client.hincrby(hourly, 'outputTokens', finalOutputTokens),
|
||||
this.client.hincrby(hourly, 'cacheCreateTokens', finalCacheCreateTokens),
|
||||
this.client.hincrby(hourly, 'cacheReadTokens', finalCacheReadTokens),
|
||||
this.client.hincrby(hourly, 'allTokens', totalTokens),
|
||||
this.client.hincrby(hourly, 'requests', 1),
|
||||
pipeline.hincrby(hourly, 'tokens', coreTokens);
|
||||
pipeline.hincrby(hourly, 'inputTokens', finalInputTokens);
|
||||
pipeline.hincrby(hourly, 'outputTokens', finalOutputTokens);
|
||||
pipeline.hincrby(hourly, 'cacheCreateTokens', finalCacheCreateTokens);
|
||||
pipeline.hincrby(hourly, 'cacheReadTokens', finalCacheReadTokens);
|
||||
pipeline.hincrby(hourly, 'allTokens', totalTokens);
|
||||
pipeline.hincrby(hourly, 'requests', 1);
|
||||
|
||||
// 按模型统计 - 每小时
|
||||
this.client.hincrby(modelHourly, 'inputTokens', finalInputTokens),
|
||||
this.client.hincrby(modelHourly, 'outputTokens', finalOutputTokens),
|
||||
this.client.hincrby(modelHourly, 'cacheCreateTokens', finalCacheCreateTokens),
|
||||
this.client.hincrby(modelHourly, 'cacheReadTokens', finalCacheReadTokens),
|
||||
this.client.hincrby(modelHourly, 'allTokens', totalTokens),
|
||||
this.client.hincrby(modelHourly, 'requests', 1),
|
||||
pipeline.hincrby(modelHourly, 'inputTokens', finalInputTokens);
|
||||
pipeline.hincrby(modelHourly, 'outputTokens', finalOutputTokens);
|
||||
pipeline.hincrby(modelHourly, 'cacheCreateTokens', finalCacheCreateTokens);
|
||||
pipeline.hincrby(modelHourly, 'cacheReadTokens', finalCacheReadTokens);
|
||||
pipeline.hincrby(modelHourly, 'allTokens', totalTokens);
|
||||
pipeline.hincrby(modelHourly, 'requests', 1);
|
||||
|
||||
// API Key级别的模型统计 - 每小时
|
||||
this.client.hincrby(keyModelHourly, 'inputTokens', finalInputTokens),
|
||||
this.client.hincrby(keyModelHourly, 'outputTokens', finalOutputTokens),
|
||||
this.client.hincrby(keyModelHourly, 'cacheCreateTokens', finalCacheCreateTokens),
|
||||
this.client.hincrby(keyModelHourly, 'cacheReadTokens', finalCacheReadTokens),
|
||||
this.client.hincrby(keyModelHourly, 'allTokens', totalTokens),
|
||||
this.client.hincrby(keyModelHourly, 'requests', 1),
|
||||
pipeline.hincrby(keyModelHourly, 'inputTokens', finalInputTokens);
|
||||
pipeline.hincrby(keyModelHourly, 'outputTokens', finalOutputTokens);
|
||||
pipeline.hincrby(keyModelHourly, 'cacheCreateTokens', finalCacheCreateTokens);
|
||||
pipeline.hincrby(keyModelHourly, 'cacheReadTokens', finalCacheReadTokens);
|
||||
pipeline.hincrby(keyModelHourly, 'allTokens', totalTokens);
|
||||
pipeline.hincrby(keyModelHourly, 'requests', 1);
|
||||
|
||||
// 新增:系统级分钟统计
|
||||
pipeline.hincrby(systemMinuteKey, 'requests', 1);
|
||||
pipeline.hincrby(systemMinuteKey, 'totalTokens', totalTokens);
|
||||
pipeline.hincrby(systemMinuteKey, 'inputTokens', finalInputTokens);
|
||||
pipeline.hincrby(systemMinuteKey, 'outputTokens', finalOutputTokens);
|
||||
pipeline.hincrby(systemMinuteKey, 'cacheCreateTokens', finalCacheCreateTokens);
|
||||
pipeline.hincrby(systemMinuteKey, 'cacheReadTokens', finalCacheReadTokens);
|
||||
|
||||
// 设置过期时间
|
||||
this.client.expire(daily, 86400 * 32), // 32天过期
|
||||
this.client.expire(monthly, 86400 * 365), // 1年过期
|
||||
this.client.expire(hourly, 86400 * 7), // 小时统计7天过期
|
||||
this.client.expire(modelDaily, 86400 * 32), // 模型每日统计32天过期
|
||||
this.client.expire(modelMonthly, 86400 * 365), // 模型每月统计1年过期
|
||||
this.client.expire(modelHourly, 86400 * 7), // 模型小时统计7天过期
|
||||
this.client.expire(keyModelDaily, 86400 * 32), // API Key模型每日统计32天过期
|
||||
this.client.expire(keyModelMonthly, 86400 * 365), // API Key模型每月统计1年过期
|
||||
this.client.expire(keyModelHourly, 86400 * 7) // API Key模型小时统计7天过期
|
||||
]);
|
||||
pipeline.expire(daily, 86400 * 32); // 32天过期
|
||||
pipeline.expire(monthly, 86400 * 365); // 1年过期
|
||||
pipeline.expire(hourly, 86400 * 7); // 小时统计7天过期
|
||||
pipeline.expire(modelDaily, 86400 * 32); // 模型每日统计32天过期
|
||||
pipeline.expire(modelMonthly, 86400 * 365); // 模型每月统计1年过期
|
||||
pipeline.expire(modelHourly, 86400 * 7); // 模型小时统计7天过期
|
||||
pipeline.expire(keyModelDaily, 86400 * 32); // API Key模型每日统计32天过期
|
||||
pipeline.expire(keyModelMonthly, 86400 * 365); // API Key模型每月统计1年过期
|
||||
pipeline.expire(keyModelHourly, 86400 * 7); // API Key模型小时统计7天过期
|
||||
|
||||
// 系统级分钟统计的过期时间(窗口时间的2倍)
|
||||
const config = require('../../config/config');
|
||||
const metricsWindow = config.system.metricsWindow;
|
||||
pipeline.expire(systemMinuteKey, metricsWindow * 60 * 2);
|
||||
|
||||
// 执行Pipeline
|
||||
await pipeline.exec();
|
||||
}
|
||||
|
||||
// 📊 记录账户级别的使用统计
|
||||
@@ -974,6 +1004,76 @@ class RedisClient {
|
||||
}
|
||||
}
|
||||
|
||||
// 📊 获取实时系统指标(基于滑动窗口)
|
||||
async getRealtimeSystemMetrics() {
|
||||
try {
|
||||
const config = require('../../config/config');
|
||||
const windowMinutes = config.system.metricsWindow;
|
||||
|
||||
const now = new Date();
|
||||
const currentMinute = Math.floor(now.getTime() / 60000);
|
||||
|
||||
// 使用Pipeline批量获取窗口内的所有分钟数据
|
||||
const pipeline = this.client.pipeline();
|
||||
for (let i = 0; i < windowMinutes; i++) {
|
||||
const minuteKey = `system:metrics:minute:${currentMinute - i}`;
|
||||
pipeline.hgetall(minuteKey);
|
||||
}
|
||||
|
||||
const results = await pipeline.exec();
|
||||
|
||||
// 聚合计算
|
||||
let totalRequests = 0;
|
||||
let totalTokens = 0;
|
||||
let totalInputTokens = 0;
|
||||
let totalOutputTokens = 0;
|
||||
let totalCacheCreateTokens = 0;
|
||||
let totalCacheReadTokens = 0;
|
||||
|
||||
results.forEach(([err, data]) => {
|
||||
if (!err && data) {
|
||||
totalRequests += parseInt(data.requests || 0);
|
||||
totalTokens += parseInt(data.totalTokens || 0);
|
||||
totalInputTokens += parseInt(data.inputTokens || 0);
|
||||
totalOutputTokens += parseInt(data.outputTokens || 0);
|
||||
totalCacheCreateTokens += parseInt(data.cacheCreateTokens || 0);
|
||||
totalCacheReadTokens += parseInt(data.cacheReadTokens || 0);
|
||||
}
|
||||
});
|
||||
|
||||
// 计算平均值(每分钟)
|
||||
const realtimeRPM = windowMinutes > 0 ? Math.round((totalRequests / windowMinutes) * 100) / 100 : 0;
|
||||
const realtimeTPM = windowMinutes > 0 ? Math.round((totalTokens / windowMinutes) * 100) / 100 : 0;
|
||||
|
||||
return {
|
||||
realtimeRPM,
|
||||
realtimeTPM,
|
||||
windowMinutes,
|
||||
totalRequests,
|
||||
totalTokens,
|
||||
totalInputTokens,
|
||||
totalOutputTokens,
|
||||
totalCacheCreateTokens,
|
||||
totalCacheReadTokens
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Error getting realtime system metrics:', error);
|
||||
// 如果出错,返回历史平均值作为降级方案
|
||||
const historicalMetrics = await this.getSystemAverages();
|
||||
return {
|
||||
realtimeRPM: historicalMetrics.systemRPM,
|
||||
realtimeTPM: historicalMetrics.systemTPM,
|
||||
windowMinutes: 0, // 标识使用了历史数据
|
||||
totalRequests: 0,
|
||||
totalTokens: historicalMetrics.totalTokens,
|
||||
totalInputTokens: historicalMetrics.totalInputTokens,
|
||||
totalOutputTokens: historicalMetrics.totalOutputTokens,
|
||||
totalCacheCreateTokens: 0,
|
||||
totalCacheReadTokens: 0
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// 🔗 会话sticky映射管理
|
||||
async setSessionAccountMapping(sessionHash, accountId, ttl = 3600) {
|
||||
const key = `sticky_session:${sessionHash}`;
|
||||
|
||||
@@ -1261,13 +1261,14 @@ router.get('/accounts/:accountId/usage-stats', authenticateAdmin, async (req, re
|
||||
// 获取系统概览
|
||||
router.get('/dashboard', authenticateAdmin, async (req, res) => {
|
||||
try {
|
||||
const [, apiKeys, claudeAccounts, geminiAccounts, todayStats, systemAverages] = await Promise.all([
|
||||
const [, apiKeys, claudeAccounts, geminiAccounts, todayStats, systemAverages, realtimeMetrics] = await Promise.all([
|
||||
redis.getSystemStats(),
|
||||
apiKeyService.getAllApiKeys(),
|
||||
claudeAccountService.getAllAccounts(),
|
||||
geminiAccountService.getAllAccounts(),
|
||||
redis.getTodayStats(),
|
||||
redis.getSystemAverages()
|
||||
redis.getSystemAverages(),
|
||||
redis.getRealtimeSystemMetrics()
|
||||
]);
|
||||
|
||||
// 计算使用统计(统一使用allTokens)
|
||||
@@ -1316,6 +1317,12 @@ router.get('/dashboard', authenticateAdmin, async (req, res) => {
|
||||
rpm: systemAverages.systemRPM,
|
||||
tpm: systemAverages.systemTPM
|
||||
},
|
||||
realtimeMetrics: {
|
||||
rpm: realtimeMetrics.realtimeRPM,
|
||||
tpm: realtimeMetrics.realtimeTPM,
|
||||
windowMinutes: realtimeMetrics.windowMinutes,
|
||||
isHistorical: realtimeMetrics.windowMinutes === 0 // 标识是否使用了历史数据
|
||||
},
|
||||
systemHealth: {
|
||||
redisConnected: redis.isConnected,
|
||||
claudeAccountsHealthy: activeClaudeAccounts > 0,
|
||||
@@ -1483,7 +1490,7 @@ router.get('/usage-trend', authenticateAdmin, async (req, res) => {
|
||||
endTime = new Date(endDate);
|
||||
|
||||
// 调试日志
|
||||
logger.info(`📊 Usage trend hour granularity - received times:`);
|
||||
logger.info('📊 Usage trend hour granularity - received times:');
|
||||
logger.info(` startDate (raw): ${startDate}`);
|
||||
logger.info(` endDate (raw): ${endDate}`);
|
||||
logger.info(` startTime (parsed): ${startTime.toISOString()}`);
|
||||
|
||||
@@ -88,11 +88,11 @@ class ClaudeConsoleRelayService {
|
||||
logger.debug(`[DEBUG] Adding beta header: ${options.betaHeader}`);
|
||||
requestConfig.headers['anthropic-beta'] = options.betaHeader;
|
||||
} else {
|
||||
logger.debug(`[DEBUG] No beta header to add`);
|
||||
logger.debug('[DEBUG] No beta header to add');
|
||||
}
|
||||
|
||||
// 发送请求
|
||||
logger.debug(`📤 Sending request to Claude Console API with headers:`, JSON.stringify(requestConfig.headers, null, 2));
|
||||
logger.debug('📤 Sending request to Claude Console API with headers:', JSON.stringify(requestConfig.headers, null, 2));
|
||||
const response = await axios(requestConfig);
|
||||
|
||||
// 移除监听器(请求成功完成)
|
||||
|
||||
@@ -522,8 +522,9 @@ onMounted(async () => {
|
||||
form.restrictedModels = props.apiKey.restrictedModels || []
|
||||
form.allowedClients = props.apiKey.allowedClients || []
|
||||
form.tags = props.apiKey.tags || []
|
||||
form.enableModelRestriction = form.restrictedModels.length > 0
|
||||
form.enableClientRestriction = form.allowedClients.length > 0
|
||||
// 从后端数据中获取实际的启用状态,而不是根据数组长度推断
|
||||
form.enableModelRestriction = props.apiKey.enableModelRestriction || false
|
||||
form.enableClientRestriction = props.apiKey.enableClientRestriction || false
|
||||
})
|
||||
</script>
|
||||
|
||||
|
||||
@@ -26,6 +26,10 @@ export const useDashboardStore = defineStore('dashboard', () => {
|
||||
todayCacheReadTokens: 0,
|
||||
systemRPM: 0,
|
||||
systemTPM: 0,
|
||||
realtimeRPM: 0,
|
||||
realtimeTPM: 0,
|
||||
metricsWindow: 5,
|
||||
isHistoricalMetrics: false,
|
||||
systemStatus: '正常',
|
||||
uptime: 0,
|
||||
systemTimezone: 8 // 默认 UTC+8
|
||||
@@ -129,6 +133,7 @@ export const useDashboardStore = defineStore('dashboard', () => {
|
||||
const overview = dashboardResponse.data.overview || {}
|
||||
const recentActivity = dashboardResponse.data.recentActivity || {}
|
||||
const systemAverages = dashboardResponse.data.systemAverages || {}
|
||||
const realtimeMetrics = dashboardResponse.data.realtimeMetrics || {}
|
||||
const systemHealth = dashboardResponse.data.systemHealth || {}
|
||||
|
||||
dashboardData.value = {
|
||||
@@ -151,6 +156,10 @@ export const useDashboardStore = defineStore('dashboard', () => {
|
||||
todayCacheReadTokens: recentActivity.cacheReadTokensToday || 0,
|
||||
systemRPM: systemAverages.rpm || 0,
|
||||
systemTPM: systemAverages.tpm || 0,
|
||||
realtimeRPM: realtimeMetrics.rpm || 0,
|
||||
realtimeTPM: realtimeMetrics.tpm || 0,
|
||||
metricsWindow: realtimeMetrics.windowMinutes || 5,
|
||||
isHistoricalMetrics: realtimeMetrics.isHistorical || false,
|
||||
systemStatus: systemHealth.redisConnected ? '正常' : '异常',
|
||||
uptime: systemHealth.uptime || 0,
|
||||
systemTimezone: dashboardResponse.data.systemTimezone || 8
|
||||
|
||||
@@ -111,9 +111,17 @@
|
||||
<div class="stat-card">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<p class="text-sm font-semibold text-gray-600 mb-1">平均RPM</p>
|
||||
<p class="text-3xl font-bold text-orange-600">{{ dashboardData.systemRPM || 0 }}</p>
|
||||
<p class="text-xs text-gray-500 mt-1">每分钟请求数</p>
|
||||
<p class="text-sm font-semibold text-gray-600 mb-1">
|
||||
实时RPM
|
||||
<span class="text-xs text-gray-400">({{ dashboardData.metricsWindow }}分钟)</span>
|
||||
</p>
|
||||
<p class="text-3xl font-bold text-orange-600">{{ dashboardData.realtimeRPM || 0 }}</p>
|
||||
<p class="text-xs text-gray-500 mt-1">
|
||||
每分钟请求数
|
||||
<span v-if="dashboardData.isHistoricalMetrics" class="text-yellow-600">
|
||||
<i class="fas fa-exclamation-circle"></i> 历史数据
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
<div class="stat-icon flex-shrink-0 bg-gradient-to-br from-orange-500 to-orange-600">
|
||||
<i class="fas fa-tachometer-alt"></i>
|
||||
@@ -124,9 +132,17 @@
|
||||
<div class="stat-card">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<p class="text-sm font-semibold text-gray-600 mb-1">平均TPM</p>
|
||||
<p class="text-3xl font-bold text-rose-600">{{ dashboardData.systemTPM || 0 }}</p>
|
||||
<p class="text-xs text-gray-500 mt-1">每分钟Token数</p>
|
||||
<p class="text-sm font-semibold text-gray-600 mb-1">
|
||||
实时TPM
|
||||
<span class="text-xs text-gray-400">({{ dashboardData.metricsWindow }}分钟)</span>
|
||||
</p>
|
||||
<p class="text-3xl font-bold text-rose-600">{{ dashboardData.realtimeTPM || 0 }}</p>
|
||||
<p class="text-xs text-gray-500 mt-1">
|
||||
每分钟Token数
|
||||
<span v-if="dashboardData.isHistoricalMetrics" class="text-yellow-600">
|
||||
<i class="fas fa-exclamation-circle"></i> 历史数据
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
<div class="stat-icon flex-shrink-0 bg-gradient-to-br from-rose-500 to-rose-600">
|
||||
<i class="fas fa-rocket"></i>
|
||||
|
||||
Reference in New Issue
Block a user