mirror of
https://github.com/Wei-Shaw/claude-relay-service.git
synced 2026-01-23 19:18:59 +00:00
feat: enhance user API keys view and fix admin cost display
- Add deleted API keys display to user's My API Keys view - Show deleted status with gray indicator and "Deleted" badge - Display deletion date and hide delete button for deleted keys - Fix cost calculation in admin deleted API keys tab - Add getCostStats call to properly populate cost data - Support includeDeleted parameter in user API keys endpoint 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -130,7 +130,8 @@ router.get('/profile', authenticateUser, async (req, res) => {
|
||||
// 🔑 获取用户的API Keys
|
||||
router.get('/api-keys', authenticateUser, async (req, res) => {
|
||||
try {
|
||||
const apiKeys = await apiKeyService.getUserApiKeys(req.user.id)
|
||||
const { includeDeleted = 'false' } = req.query
|
||||
const apiKeys = await apiKeyService.getUserApiKeys(req.user.id, includeDeleted === 'true')
|
||||
|
||||
// 移除敏感信息并格式化usage数据
|
||||
const safeApiKeys = apiKeys.map((key) => {
|
||||
|
||||
@@ -208,6 +208,14 @@ class ApiKeyService {
|
||||
// 为每个key添加使用统计和当前并发数
|
||||
for (const key of apiKeys) {
|
||||
key.usage = await redis.getUsageStats(key.id)
|
||||
const costStats = await redis.getCostStats(key.id)
|
||||
// Add cost information to usage object for frontend compatibility
|
||||
if (key.usage && costStats) {
|
||||
key.usage.total = key.usage.total || {}
|
||||
key.usage.total.cost = costStats.total
|
||||
key.usage.totalCost = costStats.total
|
||||
}
|
||||
key.totalCost = costStats ? costStats.total : 0
|
||||
key.tokenLimit = parseInt(key.tokenLimit)
|
||||
key.concurrencyLimit = parseInt(key.concurrencyLimit || 0)
|
||||
key.rateLimitWindow = parseInt(key.rateLimitWindow || 0)
|
||||
|
||||
@@ -83,14 +83,27 @@
|
||||
<div class="flex items-center">
|
||||
<div class="flex-shrink-0">
|
||||
<div
|
||||
:class="['h-2 w-2 rounded-full', apiKey.isActive ? 'bg-green-400' : 'bg-red-400']"
|
||||
:class="[
|
||||
'h-2 w-2 rounded-full',
|
||||
apiKey.isDeleted === 'true' || apiKey.deletedAt
|
||||
? 'bg-gray-400'
|
||||
: apiKey.isActive
|
||||
? 'bg-green-400'
|
||||
: 'bg-red-400'
|
||||
]"
|
||||
></div>
|
||||
</div>
|
||||
<div class="ml-4">
|
||||
<div class="flex items-center">
|
||||
<p class="text-sm font-medium text-gray-900">{{ apiKey.name }}</p>
|
||||
<span
|
||||
v-if="!apiKey.isActive"
|
||||
v-if="apiKey.isDeleted === 'true' || apiKey.deletedAt"
|
||||
class="ml-2 inline-flex items-center rounded-full bg-gray-100 px-2.5 py-0.5 text-xs font-medium text-gray-800"
|
||||
>
|
||||
Deleted
|
||||
</span>
|
||||
<span
|
||||
v-else-if="!apiKey.isActive"
|
||||
class="ml-2 inline-flex items-center rounded-full bg-red-100 px-2.5 py-0.5 text-xs font-medium text-red-800"
|
||||
>
|
||||
Disabled
|
||||
@@ -100,11 +113,17 @@
|
||||
<p class="text-sm text-gray-500">{{ apiKey.description || 'No description' }}</p>
|
||||
<div class="mt-1 flex items-center space-x-4 text-xs text-gray-400">
|
||||
<span>Created: {{ formatDate(apiKey.createdAt) }}</span>
|
||||
<span v-if="apiKey.lastUsedAt"
|
||||
<span v-if="apiKey.isDeleted === 'true' || apiKey.deletedAt"
|
||||
>Deleted: {{ formatDate(apiKey.deletedAt) }}</span
|
||||
>
|
||||
<span v-else-if="apiKey.lastUsedAt"
|
||||
>Last used: {{ formatDate(apiKey.lastUsedAt) }}</span
|
||||
>
|
||||
<span v-else>Never used</span>
|
||||
<span v-if="apiKey.expiresAt">Expires: {{ formatDate(apiKey.expiresAt) }}</span>
|
||||
<span
|
||||
v-if="apiKey.expiresAt && !(apiKey.isDeleted === 'true' || apiKey.deletedAt)"
|
||||
>Expires: {{ formatDate(apiKey.expiresAt) }}</span
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -140,6 +159,7 @@
|
||||
</button>
|
||||
|
||||
<button
|
||||
v-if="!(apiKey.isDeleted === 'true' || apiKey.deletedAt)"
|
||||
class="inline-flex items-center rounded border border-transparent p-1 text-red-400 hover:text-red-600"
|
||||
title="Delete API Key"
|
||||
@click="deleteApiKey(apiKey)"
|
||||
@@ -264,7 +284,7 @@ const formatDate = (dateString) => {
|
||||
const loadApiKeys = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
apiKeys.value = await userStore.getUserApiKeys()
|
||||
apiKeys.value = await userStore.getUserApiKeys(true) // Include deleted keys
|
||||
} catch (error) {
|
||||
console.error('Failed to load API keys:', error)
|
||||
showToast('Failed to load API keys', 'error')
|
||||
|
||||
@@ -118,9 +118,13 @@ export const useUserStore = defineStore('user', {
|
||||
},
|
||||
|
||||
// 🔑 获取用户API Keys
|
||||
async getUserApiKeys() {
|
||||
async getUserApiKeys(includeDeleted = false) {
|
||||
try {
|
||||
const response = await axios.get(`${API_BASE}/api-keys`)
|
||||
const params = {}
|
||||
if (includeDeleted) {
|
||||
params.includeDeleted = 'true'
|
||||
}
|
||||
const response = await axios.get(`${API_BASE}/api-keys`, { params })
|
||||
return response.data.success ? response.data.apiKeys : []
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch API keys:', error)
|
||||
|
||||
Reference in New Issue
Block a user