From e97315847273d3fc1d0571daf72b9aa82787aa52 Mon Sep 17 00:00:00 2001 From: Feng Yue <2525275@gmail.com> Date: Tue, 2 Sep 2025 16:16:43 +0800 Subject: [PATCH 01/15] show owner's name in apikey management page --- src/routes/admin.js | 25 +++++++++++++++++++++++++ web/admin-spa/src/views/ApiKeysView.vue | 10 ++++++++++ 2 files changed, 35 insertions(+) diff --git a/src/routes/admin.js b/src/routes/admin.js index 9eac7135..36adfa73 100644 --- a/src/routes/admin.js +++ b/src/routes/admin.js @@ -63,6 +63,9 @@ router.get('/api-keys', authenticateAdmin, async (req, res) => { const { timeRange = 'all' } = req.query // all, 7days, monthly const apiKeys = await apiKeyService.getAllApiKeys() + // 获取用户服务来补充owner信息 + const userService = require('../services/userService') + // 根据时间范围计算查询模式 const now = new Date() const searchPatterns = [] @@ -313,6 +316,28 @@ router.get('/api-keys', authenticateAdmin, async (req, res) => { } } + // 为每个API Key添加owner的displayName + for (const apiKey of apiKeys) { + // 如果API Key有关联的用户ID,获取用户信息 + if (apiKey.userId) { + try { + const user = await userService.getUserById(apiKey.userId, false) + if (user) { + apiKey.ownerDisplayName = user.displayName || user.username || 'Unknown User' + } else { + apiKey.ownerDisplayName = 'Unknown User' + } + } catch (error) { + logger.debug(`无法获取用户 ${apiKey.userId} 的信息:`, error) + apiKey.ownerDisplayName = 'Unknown User' + } + } else { + // 如果没有userId,使用createdBy字段或默认为Admin + apiKey.ownerDisplayName = + apiKey.createdBy === 'admin' ? 'Admin' : apiKey.createdBy || 'Admin' + } + } + return res.json({ success: true, data: apiKeys }) } catch (error) { logger.error('❌ Failed to get API keys:', error) diff --git a/web/admin-spa/src/views/ApiKeysView.vue b/web/admin-spa/src/views/ApiKeysView.vue index 54a5b350..1499026c 100644 --- a/web/admin-spa/src/views/ApiKeysView.vue +++ b/web/admin-spa/src/views/ApiKeysView.vue @@ -402,6 +402,11 @@ 使用共享池 + +
+ + {{ key.ownerDisplayName }} +
@@ -1023,6 +1028,11 @@ 使用共享池 + +
+ + {{ key.ownerDisplayName }} +
From 7a9e4abdd574ae0a123990385053b54113a5aab1 Mon Sep 17 00:00:00 2001 From: Feng Yue <2525275@gmail.com> Date: Tue, 2 Sep 2025 17:17:06 +0800 Subject: [PATCH 02/15] admin now is able to reassign apikey to admin/user --- src/routes/admin.js | 86 ++++++++++++++++++- .../components/apikeys/EditApiKeyModal.vue | 63 +++++++++++++- 2 files changed, 144 insertions(+), 5 deletions(-) diff --git a/src/routes/admin.js b/src/routes/admin.js index 36adfa73..954d2f98 100644 --- a/src/routes/admin.js +++ b/src/routes/admin.js @@ -24,6 +24,50 @@ const ProxyHelper = require('../utils/proxyHelper') const router = express.Router() +// 👥 用户管理 + +// 获取所有用户列表(用于API Key分配) +router.get('/users', authenticateAdmin, async (req, res) => { + try { + const userService = require('../services/userService') + const allUsers = await userService.getAllUsers() + + // 只返回活跃用户,并包含管理员选项 + const activeUsers = allUsers + .filter((user) => user.isActive) + .map((user) => ({ + id: user.id, + username: user.username, + displayName: user.displayName || user.username, + email: user.email, + role: user.role + })) + + // 添加Admin选项作为第一个 + const usersWithAdmin = [ + { + id: 'admin', + username: 'admin', + displayName: 'Admin', + email: '', + role: 'admin' + }, + ...activeUsers + ] + + return res.json({ + success: true, + data: usersWithAdmin + }) + } catch (error) { + logger.error('❌ Failed to get users list:', error) + return res.status(500).json({ + error: 'Failed to get users list', + message: error.message + }) + } +}) + // 🔑 API Keys 管理 // 调试:获取API Key费用详情 @@ -844,7 +888,8 @@ router.put('/api-keys/:keyId', authenticateAdmin, async (req, res) => { expiresAt, dailyCostLimit, weeklyOpusCostLimit, - tags + tags, + ownerId // 新增:所有者ID字段 } = req.body // 只允许更新指定字段 @@ -1014,6 +1059,45 @@ router.put('/api-keys/:keyId', authenticateAdmin, async (req, res) => { updates.isActive = isActive } + // 处理所有者变更 + if (ownerId !== undefined) { + const userService = require('../services/userService') + + if (ownerId === 'admin') { + // 分配给Admin + updates.userId = '' + updates.userUsername = '' + updates.createdBy = 'admin' + } else if (ownerId) { + // 分配给用户 + try { + const user = await userService.getUserById(ownerId, false) + if (!user) { + return res.status(400).json({ error: 'Invalid owner: User not found' }) + } + if (!user.isActive) { + return res.status(400).json({ error: 'Cannot assign to inactive user' }) + } + + // 设置新的所有者信息 + updates.userId = ownerId + updates.userUsername = user.username + updates.createdBy = user.username + + // 管理员重新分配时,不检查用户的API Key数量限制 + logger.info(`🔄 Admin reassigning API key ${keyId} to user ${user.username}`) + } catch (error) { + logger.error('Error fetching user for owner reassignment:', error) + return res.status(400).json({ error: 'Invalid owner ID' }) + } + } else { + // 清空所有者(分配给Admin) + updates.userId = '' + updates.userUsername = '' + updates.createdBy = 'admin' + } + } + await apiKeyService.updateApiKey(keyId, updates) logger.success(`📝 Admin updated API key: ${keyId}`) diff --git a/web/admin-spa/src/components/apikeys/EditApiKeyModal.vue b/web/admin-spa/src/components/apikeys/EditApiKeyModal.vue index f74b25f8..82e27447 100644 --- a/web/admin-spa/src/components/apikeys/EditApiKeyModal.vue +++ b/web/admin-spa/src/components/apikeys/EditApiKeyModal.vue @@ -41,6 +41,26 @@

名称不可修改

+ +
+ + +

+ 分配此 API Key 给指定用户或管理员,管理员分配时不受用户 API Key 数量限制 +

+
+
@@ -798,6 +802,7 @@ const updateApiKey = async () => { try { // 准备提交的数据 const data = { + name: form.name, // 添加名称字段 tokenLimit: 0, // 清除历史token限制 rateLimitWindow: form.rateLimitWindow !== '' && form.rateLimitWindow !== null diff --git a/web/admin-spa/src/views/ApiKeysView.vue b/web/admin-spa/src/views/ApiKeysView.vue index d1c36f59..20e48a7a 100644 --- a/web/admin-spa/src/views/ApiKeysView.vue +++ b/web/admin-spa/src/views/ApiKeysView.vue @@ -105,7 +105,7 @@ @@ -405,7 +405,10 @@ -
+
{{ key.ownerDisplayName }}
@@ -1031,7 +1034,7 @@ 使用共享池
-
+
{{ key.ownerDisplayName }}
@@ -1337,6 +1340,7 @@ 名称 创建者 @@ -1393,7 +1397,7 @@
- +
@@ -1555,6 +1559,7 @@ import { ref, computed, onMounted, watch } from 'vue' import { showToast } from '@/utils/toast' import { apiClient } from '@/config/api' import { useClientsStore } from '@/stores/clients' +import { useAuthStore } from '@/stores/auth' import CreateApiKeyModal from '@/components/apikeys/CreateApiKeyModal.vue' import EditApiKeyModal from '@/components/apikeys/EditApiKeyModal.vue' import RenewApiKeyModal from '@/components/apikeys/RenewApiKeyModal.vue' @@ -1568,8 +1573,12 @@ import CustomDropdown from '@/components/common/CustomDropdown.vue' // 响应式数据 const clientsStore = useClientsStore() +const authStore = useAuthStore() const apiKeys = ref([]) +// 获取 LDAP 启用状态 +const isLdapEnabled = computed(() => authStore.oemSettings?.ldapEnabled || false) + // 多选相关状态 const selectedApiKeys = ref([]) const selectAllChecked = ref(false) @@ -1663,11 +1672,15 @@ const sortedApiKeys = computed(() => { filteredKeys = filteredKeys.filter((key) => { // 搜索API Key名称 const nameMatch = key.name && key.name.toLowerCase().includes(keyword) - // 搜索所有者名称 - const ownerMatch = - key.ownerDisplayName && key.ownerDisplayName.toLowerCase().includes(keyword) - // 如果API Key名称或所有者名称匹配,则包含该条目 - return nameMatch || ownerMatch + // 如果启用了 LDAP,搜索所有者名称 + if (isLdapEnabled.value) { + const ownerMatch = + key.ownerDisplayName && key.ownerDisplayName.toLowerCase().includes(keyword) + // 如果API Key名称或所有者名称匹配,则包含该条目 + return nameMatch || ownerMatch + } + // 未启用 LDAP 时只搜索名称 + return nameMatch }) }