mirror of
https://github.com/Wei-Shaw/claude-relay-service.git
synced 2026-03-30 02:31:33 +00:00
Merge pull request #947 from sczheng189/main
MOD: API-Key详情页限制的显示补充 + claude周费用补充注解以及修改
This commit is contained in:
@@ -117,7 +117,7 @@ const CSV_FIELD_MAPPING = {
|
|||||||
concurrencyLimit: '并发限制',
|
concurrencyLimit: '并发限制',
|
||||||
dailyCostLimit: '日费用限制(美元)',
|
dailyCostLimit: '日费用限制(美元)',
|
||||||
totalCostLimit: '总费用限制(美元)',
|
totalCostLimit: '总费用限制(美元)',
|
||||||
weeklyOpusCostLimit: '周Opus费用限制(美元)',
|
weeklyOpusCostLimit: '周Claude费用限制(美元)',
|
||||||
|
|
||||||
// 账户绑定
|
// 账户绑定
|
||||||
claudeAccountId: 'Claude专属账户',
|
claudeAccountId: 'Claude专属账户',
|
||||||
|
|||||||
@@ -1029,8 +1029,13 @@ router.post('/api-keys/batch-stats', authenticateAdmin, async (req, res) => {
|
|||||||
cost: 0,
|
cost: 0,
|
||||||
formattedCost: '$0.00',
|
formattedCost: '$0.00',
|
||||||
dailyCost: 0,
|
dailyCost: 0,
|
||||||
|
weeklyOpusCost: 0,
|
||||||
currentWindowCost: 0,
|
currentWindowCost: 0,
|
||||||
|
currentWindowRequests: 0,
|
||||||
|
currentWindowTokens: 0,
|
||||||
windowRemainingSeconds: null,
|
windowRemainingSeconds: null,
|
||||||
|
windowStartTime: null,
|
||||||
|
windowEndTime: null,
|
||||||
allTimeCost: 0,
|
allTimeCost: 0,
|
||||||
error: error.message
|
error: error.message
|
||||||
}
|
}
|
||||||
@@ -1109,7 +1114,10 @@ async function calculateKeyStats(keyId, timeRange, startDate, endDate) {
|
|||||||
|
|
||||||
// 获取实时限制数据(窗口数据不受时间范围筛选影响,始终获取当前窗口状态)
|
// 获取实时限制数据(窗口数据不受时间范围筛选影响,始终获取当前窗口状态)
|
||||||
let dailyCost = 0
|
let dailyCost = 0
|
||||||
|
let weeklyOpusCost = 0 // 字段名沿用 weeklyOpusCost*,语义为"Claude 周费用"
|
||||||
let currentWindowCost = 0
|
let currentWindowCost = 0
|
||||||
|
let currentWindowRequests = 0 // 当前窗口请求次数
|
||||||
|
let currentWindowTokens = 0 // 当前窗口 Token 使用量
|
||||||
let windowRemainingSeconds = null
|
let windowRemainingSeconds = null
|
||||||
let windowStartTime = null
|
let windowStartTime = null
|
||||||
let windowEndTime = null
|
let windowEndTime = null
|
||||||
@@ -1121,6 +1129,7 @@ async function calculateKeyStats(keyId, timeRange, startDate, endDate) {
|
|||||||
const rateLimitWindow = parseInt(apiKey?.rateLimitWindow) || 0
|
const rateLimitWindow = parseInt(apiKey?.rateLimitWindow) || 0
|
||||||
const dailyCostLimit = parseFloat(apiKey?.dailyCostLimit) || 0
|
const dailyCostLimit = parseFloat(apiKey?.dailyCostLimit) || 0
|
||||||
const totalCostLimit = parseFloat(apiKey?.totalCostLimit) || 0
|
const totalCostLimit = parseFloat(apiKey?.totalCostLimit) || 0
|
||||||
|
const weeklyOpusCostLimit = parseFloat(apiKey?.weeklyOpusCostLimit) || 0
|
||||||
|
|
||||||
// 只在启用了每日费用限制时查询
|
// 只在启用了每日费用限制时查询
|
||||||
if (dailyCostLimit > 0) {
|
if (dailyCostLimit > 0) {
|
||||||
@@ -1133,6 +1142,43 @@ async function calculateKeyStats(keyId, timeRange, startDate, endDate) {
|
|||||||
allTimeCost = parseFloat((await client.get(totalCostKey)) || '0')
|
allTimeCost = parseFloat((await client.get(totalCostKey)) || '0')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 只在启用了 Claude 周费用限制时查询(字段名沿用 weeklyOpusCostLimit)
|
||||||
|
if (weeklyOpusCostLimit > 0) {
|
||||||
|
weeklyOpusCost = await redis.getWeeklyOpusCost(keyId)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 只在启用了窗口限制时查询窗口数据(移到早期返回之前,确保窗口数据始终被获取)
|
||||||
|
if (rateLimitWindow > 0) {
|
||||||
|
const requestCountKey = `rate_limit:requests:${keyId}`
|
||||||
|
const tokenCountKey = `rate_limit:tokens:${keyId}`
|
||||||
|
const costCountKey = `rate_limit:cost:${keyId}`
|
||||||
|
const windowStartKey = `rate_limit:window_start:${keyId}`
|
||||||
|
|
||||||
|
currentWindowRequests = parseInt((await client.get(requestCountKey)) || '0')
|
||||||
|
currentWindowTokens = parseInt((await client.get(tokenCountKey)) || '0')
|
||||||
|
currentWindowCost = parseFloat((await client.get(costCountKey)) || '0')
|
||||||
|
|
||||||
|
// 获取窗口开始时间和计算剩余时间
|
||||||
|
const windowStart = await client.get(windowStartKey)
|
||||||
|
if (windowStart) {
|
||||||
|
const now = Date.now()
|
||||||
|
windowStartTime = parseInt(windowStart)
|
||||||
|
const windowDuration = rateLimitWindow * 60 * 1000 // 转换为毫秒
|
||||||
|
windowEndTime = windowStartTime + windowDuration
|
||||||
|
|
||||||
|
// 如果窗口还有效
|
||||||
|
if (now < windowEndTime) {
|
||||||
|
windowRemainingSeconds = Math.max(0, Math.floor((windowEndTime - now) / 1000))
|
||||||
|
} else {
|
||||||
|
// 窗口已过期
|
||||||
|
windowRemainingSeconds = 0
|
||||||
|
currentWindowRequests = 0
|
||||||
|
currentWindowTokens = 0
|
||||||
|
currentWindowCost = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 🔧 FIX: 对于 "全部时间" 时间范围,直接使用 allTimeCost
|
// 🔧 FIX: 对于 "全部时间" 时间范围,直接使用 allTimeCost
|
||||||
// 因为 usage:*:model:daily:* 键有 30 天 TTL,旧数据已经过期
|
// 因为 usage:*:model:daily:* 键有 30 天 TTL,旧数据已经过期
|
||||||
if (timeRange === 'all' && allTimeCost > 0) {
|
if (timeRange === 'all' && allTimeCost > 0) {
|
||||||
@@ -1149,39 +1195,16 @@ async function calculateKeyStats(keyId, timeRange, startDate, endDate) {
|
|||||||
formattedCost: CostCalculator.formatCost(allTimeCost),
|
formattedCost: CostCalculator.formatCost(allTimeCost),
|
||||||
// 实时限制数据(始终返回,不受时间范围影响)
|
// 实时限制数据(始终返回,不受时间范围影响)
|
||||||
dailyCost,
|
dailyCost,
|
||||||
|
weeklyOpusCost,
|
||||||
currentWindowCost,
|
currentWindowCost,
|
||||||
|
currentWindowRequests,
|
||||||
|
currentWindowTokens,
|
||||||
windowRemainingSeconds,
|
windowRemainingSeconds,
|
||||||
windowStartTime,
|
windowStartTime,
|
||||||
windowEndTime,
|
windowEndTime,
|
||||||
allTimeCost
|
allTimeCost
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 只在启用了窗口限制时查询窗口数据
|
|
||||||
if (rateLimitWindow > 0) {
|
|
||||||
const costCountKey = `rate_limit:cost:${keyId}`
|
|
||||||
const windowStartKey = `rate_limit:window_start:${keyId}`
|
|
||||||
|
|
||||||
currentWindowCost = parseFloat((await client.get(costCountKey)) || '0')
|
|
||||||
|
|
||||||
// 获取窗口开始时间和计算剩余时间
|
|
||||||
const windowStart = await client.get(windowStartKey)
|
|
||||||
if (windowStart) {
|
|
||||||
const now = Date.now()
|
|
||||||
windowStartTime = parseInt(windowStart)
|
|
||||||
const windowDuration = rateLimitWindow * 60 * 1000 // 转换为毫秒
|
|
||||||
windowEndTime = windowStartTime + windowDuration
|
|
||||||
|
|
||||||
// 如果窗口还有效
|
|
||||||
if (now < windowEndTime) {
|
|
||||||
windowRemainingSeconds = Math.max(0, Math.floor((windowEndTime - now) / 1000))
|
|
||||||
} else {
|
|
||||||
// 窗口已过期
|
|
||||||
windowRemainingSeconds = 0
|
|
||||||
currentWindowCost = 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.warn(`⚠️ 获取实时限制数据失败 (key: ${keyId}):`, error.message)
|
logger.warn(`⚠️ 获取实时限制数据失败 (key: ${keyId}):`, error.message)
|
||||||
}
|
}
|
||||||
@@ -1199,7 +1222,10 @@ async function calculateKeyStats(keyId, timeRange, startDate, endDate) {
|
|||||||
formattedCost: '$0.00',
|
formattedCost: '$0.00',
|
||||||
// 实时限制数据(始终返回,不受时间范围影响)
|
// 实时限制数据(始终返回,不受时间范围影响)
|
||||||
dailyCost,
|
dailyCost,
|
||||||
|
weeklyOpusCost,
|
||||||
currentWindowCost,
|
currentWindowCost,
|
||||||
|
currentWindowRequests,
|
||||||
|
currentWindowTokens,
|
||||||
windowRemainingSeconds,
|
windowRemainingSeconds,
|
||||||
windowStartTime,
|
windowStartTime,
|
||||||
windowEndTime,
|
windowEndTime,
|
||||||
@@ -1317,7 +1343,10 @@ async function calculateKeyStats(keyId, timeRange, startDate, endDate) {
|
|||||||
formattedCost: CostCalculator.formatCost(totalCost),
|
formattedCost: CostCalculator.formatCost(totalCost),
|
||||||
// 实时限制数据
|
// 实时限制数据
|
||||||
dailyCost,
|
dailyCost,
|
||||||
|
weeklyOpusCost,
|
||||||
currentWindowCost,
|
currentWindowCost,
|
||||||
|
currentWindowRequests,
|
||||||
|
currentWindowTokens,
|
||||||
windowRemainingSeconds,
|
windowRemainingSeconds,
|
||||||
windowStartTime,
|
windowStartTime,
|
||||||
windowEndTime,
|
windowEndTime,
|
||||||
|
|||||||
@@ -762,7 +762,7 @@ class ApiKeyService {
|
|||||||
for (const key of apiKeys) {
|
for (const key of apiKeys) {
|
||||||
key.usage = await redis.getUsageStats(key.id)
|
key.usage = await redis.getUsageStats(key.id)
|
||||||
const costStats = await redis.getCostStats(key.id)
|
const costStats = await redis.getCostStats(key.id)
|
||||||
// Add cost information to usage object for frontend compatibility
|
// 为前端兼容性:把费用信息同步到 usage 对象里
|
||||||
if (key.usage && costStats) {
|
if (key.usage && costStats) {
|
||||||
key.usage.total = key.usage.total || {}
|
key.usage.total = key.usage.total || {}
|
||||||
key.usage.total.cost = costStats.total
|
key.usage.total.cost = costStats.total
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
<div class="w-full">
|
<div class="w-full">
|
||||||
<!-- 检查是否为无限制状态 -->
|
<!-- 检查是否为无限制状态 -->
|
||||||
<div
|
<div
|
||||||
v-if="!limit || limit <= 0"
|
v-if="!limitValue || limitValue <= 0"
|
||||||
class="flex items-center justify-center rounded-lg px-3 py-2 text-xs"
|
class="flex items-center justify-center rounded-lg px-3 py-2 text-xs"
|
||||||
>
|
>
|
||||||
<div class="flex items-center gap-1.5 text-gray-600 dark:text-gray-300">
|
<div class="flex items-center gap-1.5 text-gray-600 dark:text-gray-300">
|
||||||
@@ -18,7 +18,7 @@
|
|||||||
<span>{{ label }}</span>
|
<span>{{ label }}</span>
|
||||||
</div>
|
</div>
|
||||||
<span class="text-gray-700 dark:text-gray-200"
|
<span class="text-gray-700 dark:text-gray-200"
|
||||||
>${{ current.toFixed(2) }} / ${{ limit.toFixed(2) }}</span
|
>${{ currentValue.toFixed(2) }} / ${{ limitValue.toFixed(2) }}</span
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
<div class="relative h-1.5 overflow-hidden rounded-full bg-gray-200/85 dark:bg-gray-700/70">
|
<div class="relative h-1.5 overflow-hidden rounded-full bg-gray-200/85 dark:bg-gray-700/70">
|
||||||
@@ -57,7 +57,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="flex items-center gap-1.5">
|
<div class="flex items-center gap-1.5">
|
||||||
<span class="text-xs font-bold tabular-nums" :class="currentValueClass">
|
<span class="text-xs font-bold tabular-nums" :class="currentValueClass">
|
||||||
${{ current.toFixed(2) }} / ${{ limit.toFixed(2) }}
|
${{ currentValue.toFixed(2) }} / ${{ limitValue.toFixed(2) }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -95,11 +95,11 @@ const props = defineProps({
|
|||||||
required: true
|
required: true
|
||||||
},
|
},
|
||||||
current: {
|
current: {
|
||||||
type: Number,
|
type: [Number, String],
|
||||||
default: 0
|
default: 0
|
||||||
},
|
},
|
||||||
limit: {
|
limit: {
|
||||||
type: Number,
|
type: [Number, String],
|
||||||
required: true
|
required: true
|
||||||
},
|
},
|
||||||
showShine: {
|
showShine: {
|
||||||
@@ -109,10 +109,18 @@ const props = defineProps({
|
|||||||
})
|
})
|
||||||
|
|
||||||
const isCompact = computed(() => props.variant === 'compact')
|
const isCompact = computed(() => props.variant === 'compact')
|
||||||
|
const currentValue = computed(() => {
|
||||||
|
const n = Number(props.current)
|
||||||
|
return Number.isFinite(n) ? n : 0
|
||||||
|
})
|
||||||
|
const limitValue = computed(() => {
|
||||||
|
const n = Number(props.limit)
|
||||||
|
return Number.isFinite(n) ? n : 0
|
||||||
|
})
|
||||||
const progress = computed(() => {
|
const progress = computed(() => {
|
||||||
// 无限制时不显示进度条
|
// 无限制时不显示进度条
|
||||||
if (!props.limit || props.limit <= 0) return 0
|
if (!limitValue.value || limitValue.value <= 0) return 0
|
||||||
const percentage = (props.current / props.limit) * 100
|
const percentage = (currentValue.value / limitValue.value) * 100
|
||||||
return Math.min(percentage, 100)
|
return Math.min(percentage, 100)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -155,11 +155,11 @@
|
|||||||
限制设置
|
限制设置
|
||||||
</h4>
|
</h4>
|
||||||
<div class="space-y-3 rounded-lg bg-gray-50 p-4 dark:bg-gray-700/50">
|
<div class="space-y-3 rounded-lg bg-gray-50 p-4 dark:bg-gray-700/50">
|
||||||
<div v-if="apiKey.dailyCostLimit > 0" class="space-y-1.5">
|
<div v-if="Number(apiKey.dailyCostLimit) > 0" class="space-y-1.5">
|
||||||
<LimitProgressBar
|
<LimitProgressBar
|
||||||
:current="dailyCost"
|
:current="Number(dailyCost) || 0"
|
||||||
label="每日费用限制"
|
label="每日费用限制"
|
||||||
:limit="apiKey.dailyCostLimit"
|
:limit="Number(apiKey.dailyCostLimit) || 0"
|
||||||
:show-shine="true"
|
:show-shine="true"
|
||||||
type="daily"
|
type="daily"
|
||||||
/>
|
/>
|
||||||
@@ -168,11 +168,11 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="apiKey.weeklyOpusCostLimit > 0" class="space-y-1.5">
|
<div v-if="Number(apiKey.weeklyOpusCostLimit) > 0" class="space-y-1.5">
|
||||||
<LimitProgressBar
|
<LimitProgressBar
|
||||||
:current="weeklyOpusCost"
|
:current="Number(weeklyOpusCost) || 0"
|
||||||
label="Opus 周费用限制"
|
label="Claude 周费用限制"
|
||||||
:limit="apiKey.weeklyOpusCostLimit"
|
:limit="Number(apiKey.weeklyOpusCostLimit) || 0"
|
||||||
:show-shine="true"
|
:show-shine="true"
|
||||||
type="opus"
|
type="opus"
|
||||||
/>
|
/>
|
||||||
@@ -181,11 +181,11 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="apiKey.totalCostLimit > 0" class="space-y-1.5">
|
<div v-if="Number(apiKey.totalCostLimit) > 0" class="space-y-1.5">
|
||||||
<LimitProgressBar
|
<LimitProgressBar
|
||||||
:current="totalCost"
|
:current="Number(totalCost) || 0"
|
||||||
label="总费用限制"
|
label="总费用限制"
|
||||||
:limit="apiKey.totalCostLimit"
|
:limit="Number(apiKey.totalCostLimit) || 0"
|
||||||
:show-shine="true"
|
:show-shine="true"
|
||||||
type="total"
|
type="total"
|
||||||
/>
|
/>
|
||||||
@@ -195,7 +195,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
v-if="apiKey.concurrencyLimit > 0"
|
v-if="Number(apiKey.concurrencyLimit) > 0"
|
||||||
class="flex items-center justify-between rounded-lg border border-purple-200/70 bg-white/60 px-3 py-2 text-sm shadow-sm dark:border-purple-500/40 dark:bg-purple-950/20"
|
class="flex items-center justify-between rounded-lg border border-purple-200/70 bg-white/60 px-3 py-2 text-sm shadow-sm dark:border-purple-500/40 dark:bg-purple-950/20"
|
||||||
>
|
>
|
||||||
<span class="text-gray-600 dark:text-gray-300">并发限制</span>
|
<span class="text-gray-600 dark:text-gray-300">并发限制</span>
|
||||||
@@ -204,11 +204,25 @@
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="apiKey.rateLimitWindow > 0" class="space-y-2">
|
<div
|
||||||
|
v-if="
|
||||||
|
apiKey.rateLimitWindow > 0 ||
|
||||||
|
apiKey.rateLimitRequests > 0 ||
|
||||||
|
apiKey.tokenLimit > 0 ||
|
||||||
|
apiKey.rateLimitCost > 0
|
||||||
|
"
|
||||||
|
class="space-y-2"
|
||||||
|
>
|
||||||
<h5 class="text-sm font-medium text-gray-700 dark:text-gray-300">
|
<h5 class="text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||||
<i class="fas fa-clock mr-1 text-blue-500" />
|
<i class="fas fa-clock mr-1 text-blue-500" />
|
||||||
时间窗口限制
|
时间窗口限制
|
||||||
</h5>
|
</h5>
|
||||||
|
<div
|
||||||
|
v-if="apiKey.rateLimitWindow <= 0"
|
||||||
|
class="rounded-lg border border-yellow-200 bg-yellow-50 px-3 py-2 text-xs text-yellow-800 dark:border-yellow-700/50 dark:bg-yellow-900/20 dark:text-yellow-200"
|
||||||
|
>
|
||||||
|
未设置窗口时长(rateLimitWindow=0),窗口限制不会生效。
|
||||||
|
</div>
|
||||||
<WindowCountdown
|
<WindowCountdown
|
||||||
:cost-limit="apiKey.rateLimitCost"
|
:cost-limit="apiKey.rateLimitCost"
|
||||||
:current-cost="apiKey.currentWindowCost"
|
:current-cost="apiKey.currentWindowCost"
|
||||||
@@ -225,6 +239,69 @@
|
|||||||
:window-start-time="apiKey.windowStartTime"
|
:window-start-time="apiKey.windowStartTime"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- 访问控制限制(模型/客户端/服务权限) -->
|
||||||
|
<div v-if="hasAccessRestrictions" class="space-y-2">
|
||||||
|
<h5 class="text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||||
|
<i class="fas fa-lock mr-1 text-gray-500" />
|
||||||
|
访问控制
|
||||||
|
</h5>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="rounded-lg border border-gray-200 bg-white/60 px-3 py-2 text-sm shadow-sm dark:border-gray-600/50 dark:bg-gray-800/40"
|
||||||
|
>
|
||||||
|
<div class="flex items-center justify-between">
|
||||||
|
<span class="text-gray-600 dark:text-gray-300">服务权限</span>
|
||||||
|
<span class="font-semibold text-gray-900 dark:text-gray-100">
|
||||||
|
{{ permissionsDisplay }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
v-if="enableModelRestriction"
|
||||||
|
class="rounded-lg border border-gray-200 bg-white/60 px-3 py-2 text-sm shadow-sm dark:border-gray-600/50 dark:bg-gray-800/40"
|
||||||
|
>
|
||||||
|
<div class="mb-1 flex items-center justify-between">
|
||||||
|
<span class="text-gray-600 dark:text-gray-300">模型限制(禁用列表)</span>
|
||||||
|
<span class="font-semibold text-gray-900 dark:text-gray-100">
|
||||||
|
{{ restrictedModels.length }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div v-if="restrictedModels.length > 0" class="flex flex-wrap gap-1.5">
|
||||||
|
<span
|
||||||
|
v-for="model in restrictedModels"
|
||||||
|
:key="model"
|
||||||
|
class="rounded bg-gray-100 px-2 py-0.5 text-xs text-gray-700 dark:bg-gray-700 dark:text-gray-200"
|
||||||
|
>
|
||||||
|
{{ model }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div v-else class="text-xs text-gray-500 dark:text-gray-400">未配置具体模型</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
v-if="enableClientRestriction"
|
||||||
|
class="rounded-lg border border-gray-200 bg-white/60 px-3 py-2 text-sm shadow-sm dark:border-gray-600/50 dark:bg-gray-800/40"
|
||||||
|
>
|
||||||
|
<div class="mb-1 flex items-center justify-between">
|
||||||
|
<span class="text-gray-600 dark:text-gray-300">客户端限制(允许列表)</span>
|
||||||
|
<span class="font-semibold text-gray-900 dark:text-gray-100">
|
||||||
|
{{ allowedClients.length }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div v-if="allowedClients.length > 0" class="flex flex-wrap gap-1.5">
|
||||||
|
<span
|
||||||
|
v-for="client in allowedClients"
|
||||||
|
:key="client"
|
||||||
|
class="rounded bg-gray-100 px-2 py-0.5 text-xs text-gray-700 dark:bg-gray-700 dark:text-gray-200"
|
||||||
|
>
|
||||||
|
{{ client }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div v-else class="text-xs text-gray-500 dark:text-gray-400">未配置客户端</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -280,14 +357,48 @@ const cacheReadTokens = computed(() => props.apiKey.usage?.total?.cacheReadToken
|
|||||||
const rpm = computed(() => props.apiKey.usage?.averages?.rpm || 0)
|
const rpm = computed(() => props.apiKey.usage?.averages?.rpm || 0)
|
||||||
const tpm = computed(() => props.apiKey.usage?.averages?.tpm || 0)
|
const tpm = computed(() => props.apiKey.usage?.averages?.tpm || 0)
|
||||||
|
|
||||||
|
const enableModelRestriction = computed(
|
||||||
|
() =>
|
||||||
|
props.apiKey.enableModelRestriction === true || props.apiKey.enableModelRestriction === 'true'
|
||||||
|
)
|
||||||
|
const restrictedModels = computed(() =>
|
||||||
|
Array.isArray(props.apiKey.restrictedModels) ? props.apiKey.restrictedModels : []
|
||||||
|
)
|
||||||
|
const enableClientRestriction = computed(
|
||||||
|
() =>
|
||||||
|
props.apiKey.enableClientRestriction === true || props.apiKey.enableClientRestriction === 'true'
|
||||||
|
)
|
||||||
|
const allowedClients = computed(() =>
|
||||||
|
Array.isArray(props.apiKey.allowedClients) ? props.apiKey.allowedClients : []
|
||||||
|
)
|
||||||
|
const permissions = computed(() => props.apiKey.permissions || [])
|
||||||
|
|
||||||
|
const permissionsDisplay = computed(() => {
|
||||||
|
if (!Array.isArray(permissions.value) || permissions.value.length === 0) {
|
||||||
|
return '全部'
|
||||||
|
}
|
||||||
|
return permissions.value.join(', ')
|
||||||
|
})
|
||||||
|
|
||||||
|
const hasAccessRestrictions = computed(() => {
|
||||||
|
return (
|
||||||
|
enableModelRestriction.value ||
|
||||||
|
enableClientRestriction.value ||
|
||||||
|
(Array.isArray(permissions.value) && permissions.value.length > 0)
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
const hasLimits = computed(() => {
|
const hasLimits = computed(() => {
|
||||||
return (
|
return (
|
||||||
props.apiKey.dailyCostLimit > 0 ||
|
Number(props.apiKey.dailyCostLimit) > 0 ||
|
||||||
props.apiKey.totalCostLimit > 0 ||
|
Number(props.apiKey.totalCostLimit) > 0 ||
|
||||||
props.apiKey.concurrencyLimit > 0 ||
|
Number(props.apiKey.concurrencyLimit) > 0 ||
|
||||||
props.apiKey.weeklyOpusCostLimit > 0 ||
|
Number(props.apiKey.weeklyOpusCostLimit) > 0 ||
|
||||||
props.apiKey.rateLimitWindow > 0 ||
|
Number(props.apiKey.rateLimitWindow) > 0 ||
|
||||||
props.apiKey.tokenLimit > 0
|
Number(props.apiKey.rateLimitRequests) > 0 ||
|
||||||
|
Number(props.apiKey.rateLimitCost) > 0 ||
|
||||||
|
Number(props.apiKey.tokenLimit) > 0 ||
|
||||||
|
hasAccessRestrictions.value
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -4261,7 +4261,10 @@ const showUsageDetails = (apiKey) => {
|
|||||||
const enrichedApiKey = {
|
const enrichedApiKey = {
|
||||||
...apiKey,
|
...apiKey,
|
||||||
dailyCost: cachedStats?.dailyCost ?? apiKey.dailyCost ?? 0,
|
dailyCost: cachedStats?.dailyCost ?? apiKey.dailyCost ?? 0,
|
||||||
|
weeklyOpusCost: cachedStats?.weeklyOpusCost ?? apiKey.weeklyOpusCost ?? 0,
|
||||||
currentWindowCost: cachedStats?.currentWindowCost ?? apiKey.currentWindowCost ?? 0,
|
currentWindowCost: cachedStats?.currentWindowCost ?? apiKey.currentWindowCost ?? 0,
|
||||||
|
currentWindowRequests: cachedStats?.currentWindowRequests ?? apiKey.currentWindowRequests ?? 0,
|
||||||
|
currentWindowTokens: cachedStats?.currentWindowTokens ?? apiKey.currentWindowTokens ?? 0,
|
||||||
windowRemainingSeconds: cachedStats?.windowRemainingSeconds ?? apiKey.windowRemainingSeconds,
|
windowRemainingSeconds: cachedStats?.windowRemainingSeconds ?? apiKey.windowRemainingSeconds,
|
||||||
windowStartTime: cachedStats?.windowStartTime ?? apiKey.windowStartTime ?? null,
|
windowStartTime: cachedStats?.windowStartTime ?? apiKey.windowStartTime ?? null,
|
||||||
windowEndTime: cachedStats?.windowEndTime ?? apiKey.windowEndTime ?? null,
|
windowEndTime: cachedStats?.windowEndTime ?? apiKey.windowEndTime ?? null,
|
||||||
|
|||||||
Reference in New Issue
Block a user