mirror of
https://github.com/Wei-Shaw/claude-relay-service.git
synced 2026-01-23 09:24:25 +00:00
admin now is able to reassign apikey to admin/user
This commit is contained in:
@@ -24,6 +24,50 @@ const ProxyHelper = require('../utils/proxyHelper')
|
|||||||
|
|
||||||
const router = express.Router()
|
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 Keys 管理
|
||||||
|
|
||||||
// 调试:获取API Key费用详情
|
// 调试:获取API Key费用详情
|
||||||
@@ -844,7 +888,8 @@ router.put('/api-keys/:keyId', authenticateAdmin, async (req, res) => {
|
|||||||
expiresAt,
|
expiresAt,
|
||||||
dailyCostLimit,
|
dailyCostLimit,
|
||||||
weeklyOpusCostLimit,
|
weeklyOpusCostLimit,
|
||||||
tags
|
tags,
|
||||||
|
ownerId // 新增:所有者ID字段
|
||||||
} = req.body
|
} = req.body
|
||||||
|
|
||||||
// 只允许更新指定字段
|
// 只允许更新指定字段
|
||||||
@@ -1014,6 +1059,45 @@ router.put('/api-keys/:keyId', authenticateAdmin, async (req, res) => {
|
|||||||
updates.isActive = isActive
|
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)
|
await apiKeyService.updateApiKey(keyId, updates)
|
||||||
|
|
||||||
logger.success(`📝 Admin updated API key: ${keyId}`)
|
logger.success(`📝 Admin updated API key: ${keyId}`)
|
||||||
|
|||||||
@@ -41,6 +41,26 @@
|
|||||||
<p class="mt-1 text-xs text-gray-500 dark:text-gray-400 sm:mt-2">名称不可修改</p>
|
<p class="mt-1 text-xs text-gray-500 dark:text-gray-400 sm:mt-2">名称不可修改</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- 所有者选择 -->
|
||||||
|
<div>
|
||||||
|
<label
|
||||||
|
class="mb-1.5 block text-xs font-semibold text-gray-700 dark:text-gray-300 sm:mb-3 sm:text-sm"
|
||||||
|
>所有者</label
|
||||||
|
>
|
||||||
|
<select
|
||||||
|
v-model="form.ownerId"
|
||||||
|
class="form-input w-full dark:border-gray-600 dark:bg-gray-700 dark:text-gray-200"
|
||||||
|
>
|
||||||
|
<option v-for="user in availableUsers" :key="user.id" :value="user.id">
|
||||||
|
{{ user.displayName }} ({{ user.username }})
|
||||||
|
<span v-if="user.role === 'admin'" class="text-gray-500">- 管理员</span>
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
<p class="mt-1 text-xs text-gray-500 dark:text-gray-400 sm:mt-2">
|
||||||
|
分配此 API Key 给指定用户或管理员,管理员分配时不受用户 API Key 数量限制
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- 标签 -->
|
<!-- 标签 -->
|
||||||
<div>
|
<div>
|
||||||
<label
|
<label
|
||||||
@@ -666,6 +686,9 @@ const localAccounts = ref({
|
|||||||
// 支持的客户端列表
|
// 支持的客户端列表
|
||||||
const supportedClients = ref([])
|
const supportedClients = ref([])
|
||||||
|
|
||||||
|
// 可用用户列表
|
||||||
|
const availableUsers = ref([])
|
||||||
|
|
||||||
// 标签相关
|
// 标签相关
|
||||||
const newTag = ref('')
|
const newTag = ref('')
|
||||||
const availableTags = ref([])
|
const availableTags = ref([])
|
||||||
@@ -696,7 +719,8 @@ const form = reactive({
|
|||||||
enableClientRestriction: false,
|
enableClientRestriction: false,
|
||||||
allowedClients: [],
|
allowedClients: [],
|
||||||
tags: [],
|
tags: [],
|
||||||
isActive: true
|
isActive: true,
|
||||||
|
ownerId: '' // 新增:所有者ID
|
||||||
})
|
})
|
||||||
|
|
||||||
// 添加限制的模型
|
// 添加限制的模型
|
||||||
@@ -856,6 +880,11 @@ const updateApiKey = async () => {
|
|||||||
// 活跃状态
|
// 活跃状态
|
||||||
data.isActive = form.isActive
|
data.isActive = form.isActive
|
||||||
|
|
||||||
|
// 所有者
|
||||||
|
if (form.ownerId !== undefined) {
|
||||||
|
data.ownerId = form.ownerId
|
||||||
|
}
|
||||||
|
|
||||||
const result = await apiClient.put(`/admin/api-keys/${props.apiKey.id}`, data)
|
const result = await apiClient.put(`/admin/api-keys/${props.apiKey.id}`, data)
|
||||||
|
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
@@ -947,11 +976,34 @@ const refreshAccounts = async () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 加载用户列表
|
||||||
|
const loadUsers = async () => {
|
||||||
|
try {
|
||||||
|
const response = await apiClient.get('/admin/users')
|
||||||
|
if (response.success) {
|
||||||
|
availableUsers.value = response.data || []
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to load users:', error)
|
||||||
|
availableUsers.value = [
|
||||||
|
{
|
||||||
|
id: 'admin',
|
||||||
|
username: 'admin',
|
||||||
|
displayName: 'Admin',
|
||||||
|
email: '',
|
||||||
|
role: 'admin'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 初始化表单数据
|
// 初始化表单数据
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
// 加载支持的客户端和已存在的标签
|
// 并行加载所有需要的数据
|
||||||
supportedClients.value = await clientsStore.loadSupportedClients()
|
await Promise.all([clientsStore.loadSupportedClients(), apiKeysStore.fetchTags(), loadUsers()])
|
||||||
availableTags.value = await apiKeysStore.fetchTags()
|
|
||||||
|
supportedClients.value = clientsStore.supportedClients
|
||||||
|
availableTags.value = apiKeysStore.availableTags
|
||||||
|
|
||||||
// 初始化账号数据
|
// 初始化账号数据
|
||||||
if (props.accounts) {
|
if (props.accounts) {
|
||||||
@@ -1001,6 +1053,9 @@ onMounted(async () => {
|
|||||||
form.enableClientRestriction = props.apiKey.enableClientRestriction || false
|
form.enableClientRestriction = props.apiKey.enableClientRestriction || false
|
||||||
// 初始化活跃状态,默认为 true
|
// 初始化活跃状态,默认为 true
|
||||||
form.isActive = props.apiKey.isActive !== undefined ? props.apiKey.isActive : true
|
form.isActive = props.apiKey.isActive !== undefined ? props.apiKey.isActive : true
|
||||||
|
|
||||||
|
// 初始化所有者
|
||||||
|
form.ownerId = props.apiKey.userId || 'admin'
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user