mirror of
https://github.com/Wei-Shaw/claude-relay-service.git
synced 2026-01-22 16:43:35 +00:00
@@ -1146,7 +1146,27 @@ router.post('/claude-accounts/exchange-setup-token-code', authenticateAdmin, asy
|
||||
// 获取所有Claude账户
|
||||
router.get('/claude-accounts', authenticateAdmin, async (req, res) => {
|
||||
try {
|
||||
const accounts = await claudeAccountService.getAllAccounts()
|
||||
const { platform, groupId } = req.query
|
||||
let accounts = await claudeAccountService.getAllAccounts()
|
||||
|
||||
// 根据查询参数进行筛选
|
||||
if (platform && platform !== 'all' && platform !== 'claude') {
|
||||
// 如果指定了其他平台,返回空数组
|
||||
accounts = []
|
||||
}
|
||||
|
||||
// 如果指定了分组筛选
|
||||
if (groupId && groupId !== 'all') {
|
||||
if (groupId === 'ungrouped') {
|
||||
// 筛选未分组账户
|
||||
accounts = accounts.filter((account) => !account.groupInfo)
|
||||
} else {
|
||||
// 筛选特定分组的账户
|
||||
accounts = accounts.filter(
|
||||
(account) => account.groupInfo && account.groupInfo.id === groupId
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// 为每个账户添加使用统计信息
|
||||
const accountsWithStats = await Promise.all(
|
||||
@@ -1403,7 +1423,27 @@ router.put(
|
||||
// 获取所有Claude Console账户
|
||||
router.get('/claude-console-accounts', authenticateAdmin, async (req, res) => {
|
||||
try {
|
||||
const accounts = await claudeConsoleAccountService.getAllAccounts()
|
||||
const { platform, groupId } = req.query
|
||||
let accounts = await claudeConsoleAccountService.getAllAccounts()
|
||||
|
||||
// 根据查询参数进行筛选
|
||||
if (platform && platform !== 'all' && platform !== 'claude-console') {
|
||||
// 如果指定了其他平台,返回空数组
|
||||
accounts = []
|
||||
}
|
||||
|
||||
// 如果指定了分组筛选
|
||||
if (groupId && groupId !== 'all') {
|
||||
if (groupId === 'ungrouped') {
|
||||
// 筛选未分组账户
|
||||
accounts = accounts.filter((account) => !account.groupInfo)
|
||||
} else {
|
||||
// 筛选特定分组的账户
|
||||
accounts = accounts.filter(
|
||||
(account) => account.groupInfo && account.groupInfo.id === groupId
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// 为每个账户添加使用统计信息
|
||||
const accountsWithStats = await Promise.all(
|
||||
@@ -1652,6 +1692,7 @@ router.put(
|
||||
// 获取所有Bedrock账户
|
||||
router.get('/bedrock-accounts', authenticateAdmin, async (req, res) => {
|
||||
try {
|
||||
const { platform, groupId } = req.query
|
||||
const result = await bedrockAccountService.getAllAccounts()
|
||||
if (!result.success) {
|
||||
return res
|
||||
@@ -1659,9 +1700,30 @@ router.get('/bedrock-accounts', authenticateAdmin, async (req, res) => {
|
||||
.json({ error: 'Failed to get Bedrock accounts', message: result.error })
|
||||
}
|
||||
|
||||
let accounts = result.data
|
||||
|
||||
// 根据查询参数进行筛选
|
||||
if (platform && platform !== 'all' && platform !== 'bedrock') {
|
||||
// 如果指定了其他平台,返回空数组
|
||||
accounts = []
|
||||
}
|
||||
|
||||
// 如果指定了分组筛选
|
||||
if (groupId && groupId !== 'all') {
|
||||
if (groupId === 'ungrouped') {
|
||||
// 筛选未分组账户
|
||||
accounts = accounts.filter((account) => !account.groupInfo)
|
||||
} else {
|
||||
// 筛选特定分组的账户
|
||||
accounts = accounts.filter(
|
||||
(account) => account.groupInfo && account.groupInfo.id === groupId
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// 为每个账户添加使用统计信息
|
||||
const accountsWithStats = await Promise.all(
|
||||
result.data.map(async (account) => {
|
||||
accounts.map(async (account) => {
|
||||
try {
|
||||
const usageStats = await redis.getAccountUsageStats(account.id)
|
||||
return {
|
||||
@@ -2027,7 +2089,27 @@ router.post('/gemini-accounts/exchange-code', authenticateAdmin, async (req, res
|
||||
// 获取所有 Gemini 账户
|
||||
router.get('/gemini-accounts', authenticateAdmin, async (req, res) => {
|
||||
try {
|
||||
const accounts = await geminiAccountService.getAllAccounts()
|
||||
const { platform, groupId } = req.query
|
||||
let accounts = await geminiAccountService.getAllAccounts()
|
||||
|
||||
// 根据查询参数进行筛选
|
||||
if (platform && platform !== 'all' && platform !== 'gemini') {
|
||||
// 如果指定了其他平台,返回空数组
|
||||
accounts = []
|
||||
}
|
||||
|
||||
// 如果指定了分组筛选
|
||||
if (groupId && groupId !== 'all') {
|
||||
if (groupId === 'ungrouped') {
|
||||
// 筛选未分组账户
|
||||
accounts = accounts.filter((account) => !account.groupInfo)
|
||||
} else {
|
||||
// 筛选特定分组的账户
|
||||
accounts = accounts.filter(
|
||||
(account) => account.groupInfo && account.groupInfo.id === groupId
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// 为每个账户添加使用统计信息(与Claude账户相同的逻辑)
|
||||
const accountsWithStats = await Promise.all(
|
||||
@@ -2368,7 +2450,8 @@ router.get('/dashboard', authenticateAdmin, async (req, res) => {
|
||||
acc.isActive &&
|
||||
acc.status !== 'blocked' &&
|
||||
acc.status !== 'unauthorized' &&
|
||||
acc.schedulable !== false
|
||||
acc.schedulable !== false &&
|
||||
!(acc.rateLimitStatus && acc.rateLimitStatus.isRateLimited)
|
||||
).length
|
||||
const abnormalClaudeAccounts = claudeAccounts.filter(
|
||||
(acc) => !acc.isActive || acc.status === 'blocked' || acc.status === 'unauthorized'
|
||||
@@ -2390,7 +2473,8 @@ router.get('/dashboard', authenticateAdmin, async (req, res) => {
|
||||
acc.isActive &&
|
||||
acc.status !== 'blocked' &&
|
||||
acc.status !== 'unauthorized' &&
|
||||
acc.schedulable !== false
|
||||
acc.schedulable !== false &&
|
||||
!(acc.rateLimitStatus && acc.rateLimitStatus.isRateLimited)
|
||||
).length
|
||||
const abnormalClaudeConsoleAccounts = claudeConsoleAccounts.filter(
|
||||
(acc) => !acc.isActive || acc.status === 'blocked' || acc.status === 'unauthorized'
|
||||
@@ -2412,7 +2496,11 @@ router.get('/dashboard', authenticateAdmin, async (req, res) => {
|
||||
acc.isActive &&
|
||||
acc.status !== 'blocked' &&
|
||||
acc.status !== 'unauthorized' &&
|
||||
acc.schedulable !== false
|
||||
acc.schedulable !== false &&
|
||||
!(
|
||||
acc.rateLimitStatus === 'limited' ||
|
||||
(acc.rateLimitStatus && acc.rateLimitStatus.isRateLimited)
|
||||
)
|
||||
).length
|
||||
const abnormalGeminiAccounts = geminiAccounts.filter(
|
||||
(acc) => !acc.isActive || acc.status === 'blocked' || acc.status === 'unauthorized'
|
||||
@@ -2425,7 +2513,9 @@ router.get('/dashboard', authenticateAdmin, async (req, res) => {
|
||||
acc.status !== 'unauthorized'
|
||||
).length
|
||||
const rateLimitedGeminiAccounts = geminiAccounts.filter(
|
||||
(acc) => acc.rateLimitStatus === 'limited'
|
||||
(acc) =>
|
||||
acc.rateLimitStatus === 'limited' ||
|
||||
(acc.rateLimitStatus && acc.rateLimitStatus.isRateLimited)
|
||||
).length
|
||||
|
||||
// Bedrock账户统计
|
||||
@@ -2434,7 +2524,8 @@ router.get('/dashboard', authenticateAdmin, async (req, res) => {
|
||||
acc.isActive &&
|
||||
acc.status !== 'blocked' &&
|
||||
acc.status !== 'unauthorized' &&
|
||||
acc.schedulable !== false
|
||||
acc.schedulable !== false &&
|
||||
!(acc.rateLimitStatus && acc.rateLimitStatus.isRateLimited)
|
||||
).length
|
||||
const abnormalBedrockAccounts = bedrockAccounts.filter(
|
||||
(acc) => !acc.isActive || acc.status === 'blocked' || acc.status === 'unauthorized'
|
||||
|
||||
@@ -504,6 +504,9 @@ async function getAllAccounts() {
|
||||
for (const key of keys) {
|
||||
const accountData = await client.hgetall(key)
|
||||
if (accountData && Object.keys(accountData).length > 0) {
|
||||
// 获取限流状态信息
|
||||
const rateLimitInfo = await getAccountRateLimitInfo(accountData.id)
|
||||
|
||||
// 解析代理配置
|
||||
if (accountData.proxy) {
|
||||
try {
|
||||
@@ -519,7 +522,19 @@ async function getAllAccounts() {
|
||||
...accountData,
|
||||
geminiOauth: accountData.geminiOauth ? '[ENCRYPTED]' : '',
|
||||
accessToken: accountData.accessToken ? '[ENCRYPTED]' : '',
|
||||
refreshToken: accountData.refreshToken ? '[ENCRYPTED]' : ''
|
||||
refreshToken: accountData.refreshToken ? '[ENCRYPTED]' : '',
|
||||
// 添加限流状态信息(统一格式)
|
||||
rateLimitStatus: rateLimitInfo
|
||||
? {
|
||||
isRateLimited: rateLimitInfo.isRateLimited,
|
||||
rateLimitedAt: rateLimitInfo.rateLimitedAt,
|
||||
minutesRemaining: rateLimitInfo.minutesRemaining
|
||||
}
|
||||
: {
|
||||
isRateLimited: false,
|
||||
rateLimitedAt: null,
|
||||
minutesRemaining: 0
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -774,6 +789,45 @@ async function setAccountRateLimited(accountId, isLimited = true) {
|
||||
await updateAccount(accountId, updates)
|
||||
}
|
||||
|
||||
// 获取账户的限流信息(参考 claudeAccountService 的实现)
|
||||
async function getAccountRateLimitInfo(accountId) {
|
||||
try {
|
||||
const account = await getAccount(accountId)
|
||||
if (!account) {
|
||||
return null
|
||||
}
|
||||
|
||||
if (account.rateLimitStatus === 'limited' && account.rateLimitedAt) {
|
||||
const rateLimitedAt = new Date(account.rateLimitedAt)
|
||||
const now = new Date()
|
||||
const minutesSinceRateLimit = Math.floor((now - rateLimitedAt) / (1000 * 60))
|
||||
|
||||
// Gemini 限流持续时间为 1 小时
|
||||
const minutesRemaining = Math.max(0, 60 - minutesSinceRateLimit)
|
||||
const rateLimitEndAt = new Date(rateLimitedAt.getTime() + 60 * 60 * 1000).toISOString()
|
||||
|
||||
return {
|
||||
isRateLimited: minutesRemaining > 0,
|
||||
rateLimitedAt: account.rateLimitedAt,
|
||||
minutesSinceRateLimit,
|
||||
minutesRemaining,
|
||||
rateLimitEndAt
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
isRateLimited: false,
|
||||
rateLimitedAt: null,
|
||||
minutesSinceRateLimit: 0,
|
||||
minutesRemaining: 0,
|
||||
rateLimitEndAt: null
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error(`❌ Failed to get rate limit info for Gemini account: ${accountId}`, error)
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
// 获取配置的OAuth客户端 - 参考GeminiCliSimulator的getOauthClient方法
|
||||
async function getOauthClient(accessToken, refreshToken) {
|
||||
const client = new OAuth2Client({
|
||||
@@ -1137,6 +1191,7 @@ module.exports = {
|
||||
refreshAccountToken,
|
||||
markAccountUsed,
|
||||
setAccountRateLimited,
|
||||
getAccountRateLimitInfo,
|
||||
isTokenExpired,
|
||||
getOauthClient,
|
||||
loadCodeAssist,
|
||||
|
||||
@@ -24,6 +24,21 @@
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 平台筛选器 -->
|
||||
<div class="group relative min-w-[140px]">
|
||||
<div
|
||||
class="absolute -inset-0.5 rounded-lg bg-gradient-to-r from-blue-500 to-indigo-500 opacity-0 blur transition duration-300 group-hover:opacity-20"
|
||||
></div>
|
||||
<CustomDropdown
|
||||
v-model="platformFilter"
|
||||
icon="fa-server"
|
||||
icon-color="text-blue-500"
|
||||
:options="platformOptions"
|
||||
placeholder="选择平台"
|
||||
@change="filterByPlatform"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 分组筛选器 -->
|
||||
<div class="group relative min-w-[160px]">
|
||||
<div
|
||||
@@ -40,10 +55,18 @@
|
||||
</div>
|
||||
|
||||
<!-- 刷新按钮 -->
|
||||
<div class="relative">
|
||||
<el-tooltip
|
||||
content="刷新数据 (Ctrl/⌘+点击强制刷新所有缓存)"
|
||||
effect="dark"
|
||||
placement="bottom"
|
||||
>
|
||||
<button
|
||||
class="group relative flex items-center justify-center gap-2 rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 shadow-sm transition-all duration-200 hover:border-gray-300 hover:shadow-md disabled:cursor-not-allowed disabled:opacity-50 sm:w-auto"
|
||||
:disabled="accountsLoading"
|
||||
@click="loadAccounts()"
|
||||
@click.ctrl.exact="loadAccounts(true)"
|
||||
@click.exact="loadAccounts(false)"
|
||||
@click.meta.exact="loadAccounts(true)"
|
||||
>
|
||||
<div
|
||||
class="absolute -inset-0.5 rounded-lg bg-gradient-to-r from-green-500 to-teal-500 opacity-0 blur transition duration-300 group-hover:opacity-20"
|
||||
@@ -56,6 +79,8 @@
|
||||
/>
|
||||
<span class="relative">刷新</span>
|
||||
</button>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 添加账户按钮 -->
|
||||
@@ -307,11 +332,22 @@
|
||||
}}
|
||||
</span>
|
||||
<span
|
||||
v-if="account.rateLimitStatus && account.rateLimitStatus.isRateLimited"
|
||||
v-if="
|
||||
(account.rateLimitStatus && account.rateLimitStatus.isRateLimited) ||
|
||||
account.rateLimitStatus === 'limited'
|
||||
"
|
||||
class="inline-flex items-center rounded-full bg-yellow-100 px-3 py-1 text-xs font-semibold text-yellow-800"
|
||||
>
|
||||
<i class="fas fa-exclamation-triangle mr-1" />
|
||||
限流中 ({{ account.rateLimitStatus.minutesRemaining }}分钟)
|
||||
限流中
|
||||
<span
|
||||
v-if="
|
||||
account.rateLimitStatus &&
|
||||
typeof account.rateLimitStatus === 'object' &&
|
||||
account.rateLimitStatus.minutesRemaining > 0
|
||||
"
|
||||
>({{ account.rateLimitStatus.minutesRemaining }}分钟)</span
|
||||
>
|
||||
</span>
|
||||
<span
|
||||
v-if="account.schedulable === false"
|
||||
@@ -458,6 +494,7 @@
|
||||
(account.status === 'unauthorized' ||
|
||||
account.status !== 'active' ||
|
||||
account.rateLimitStatus?.isRateLimited ||
|
||||
account.rateLimitStatus === 'limited' ||
|
||||
!account.isActive)
|
||||
"
|
||||
:class="[
|
||||
@@ -754,7 +791,13 @@ const apiKeys = ref([])
|
||||
const refreshingTokens = ref({})
|
||||
const accountGroups = ref([])
|
||||
const groupFilter = ref('all')
|
||||
const filteredAccounts = ref([])
|
||||
const platformFilter = ref('all')
|
||||
|
||||
// 缓存状态标志
|
||||
const apiKeysLoaded = ref(false)
|
||||
const groupsLoaded = ref(false)
|
||||
const groupMembersLoaded = ref(false)
|
||||
const accountGroupMap = ref(new Map())
|
||||
|
||||
// 下拉选项数据
|
||||
const sortOptions = ref([
|
||||
@@ -765,6 +808,14 @@ const sortOptions = ref([
|
||||
{ value: 'lastUsed', label: '按最后使用排序', icon: 'fa-clock' }
|
||||
])
|
||||
|
||||
const platformOptions = ref([
|
||||
{ value: 'all', label: '所有平台', icon: 'fa-globe' },
|
||||
{ value: 'claude', label: 'Claude', icon: 'fa-brain' },
|
||||
{ value: 'claude-console', label: 'Claude Console', icon: 'fa-terminal' },
|
||||
{ value: 'gemini', label: 'Gemini', icon: 'fa-robot' },
|
||||
{ value: 'bedrock', label: 'Bedrock', icon: 'fab fa-aws' }
|
||||
])
|
||||
|
||||
const groupOptions = computed(() => {
|
||||
const options = [
|
||||
{ value: 'all', label: '所有账户', icon: 'fa-globe' },
|
||||
@@ -787,7 +838,7 @@ const editingAccount = ref(null)
|
||||
|
||||
// 计算排序后的账户列表
|
||||
const sortedAccounts = computed(() => {
|
||||
const sourceAccounts = filteredAccounts.value.length > 0 ? filteredAccounts.value : accounts.value
|
||||
const sourceAccounts = accounts.value
|
||||
if (!accountsSortBy.value) return sourceAccounts
|
||||
|
||||
const sorted = [...sourceAccounts].sort((a, b) => {
|
||||
@@ -827,48 +878,74 @@ const sortedAccounts = computed(() => {
|
||||
})
|
||||
|
||||
// 加载账户列表
|
||||
const loadAccounts = async () => {
|
||||
const loadAccounts = async (forceReload = false) => {
|
||||
accountsLoading.value = true
|
||||
try {
|
||||
const [claudeData, claudeConsoleData, bedrockData, geminiData, apiKeysData, groupsData] =
|
||||
await Promise.all([
|
||||
apiClient.get('/admin/claude-accounts'),
|
||||
apiClient.get('/admin/claude-console-accounts'),
|
||||
apiClient.get('/admin/bedrock-accounts'),
|
||||
apiClient.get('/admin/gemini-accounts'),
|
||||
apiClient.get('/admin/api-keys'),
|
||||
apiClient.get('/admin/account-groups')
|
||||
])
|
||||
|
||||
// 更新API Keys列表
|
||||
if (apiKeysData.success) {
|
||||
apiKeys.value = apiKeysData.data || []
|
||||
// 构建查询参数
|
||||
const params = {}
|
||||
if (platformFilter.value !== 'all') {
|
||||
params.platform = platformFilter.value
|
||||
}
|
||||
if (groupFilter.value !== 'all') {
|
||||
params.groupId = groupFilter.value
|
||||
}
|
||||
|
||||
// 更新分组列表
|
||||
if (groupsData.success) {
|
||||
accountGroups.value = groupsData.data || []
|
||||
// 根据平台筛选决定需要请求哪些接口
|
||||
const requests = []
|
||||
|
||||
if (platformFilter.value === 'all') {
|
||||
// 请求所有平台
|
||||
requests.push(
|
||||
apiClient.get('/admin/claude-accounts', { params }),
|
||||
apiClient.get('/admin/claude-console-accounts', { params }),
|
||||
apiClient.get('/admin/bedrock-accounts', { params }),
|
||||
apiClient.get('/admin/gemini-accounts', { params })
|
||||
)
|
||||
} else {
|
||||
// 只请求指定平台,其他平台设为null占位
|
||||
switch (platformFilter.value) {
|
||||
case 'claude':
|
||||
requests.push(
|
||||
apiClient.get('/admin/claude-accounts', { params }),
|
||||
Promise.resolve({ success: true, data: [] }), // claude-console 占位
|
||||
Promise.resolve({ success: true, data: [] }), // bedrock 占位
|
||||
Promise.resolve({ success: true, data: [] }) // gemini 占位
|
||||
)
|
||||
break
|
||||
case 'claude-console':
|
||||
requests.push(
|
||||
Promise.resolve({ success: true, data: [] }), // claude 占位
|
||||
apiClient.get('/admin/claude-console-accounts', { params }),
|
||||
Promise.resolve({ success: true, data: [] }), // bedrock 占位
|
||||
Promise.resolve({ success: true, data: [] }) // gemini 占位
|
||||
)
|
||||
break
|
||||
case 'bedrock':
|
||||
requests.push(
|
||||
Promise.resolve({ success: true, data: [] }), // claude 占位
|
||||
Promise.resolve({ success: true, data: [] }), // claude-console 占位
|
||||
apiClient.get('/admin/bedrock-accounts', { params }),
|
||||
Promise.resolve({ success: true, data: [] }) // gemini 占位
|
||||
)
|
||||
break
|
||||
case 'gemini':
|
||||
requests.push(
|
||||
Promise.resolve({ success: true, data: [] }), // claude 占位
|
||||
Promise.resolve({ success: true, data: [] }), // claude-console 占位
|
||||
Promise.resolve({ success: true, data: [] }), // bedrock 占位
|
||||
apiClient.get('/admin/gemini-accounts', { params })
|
||||
)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// 创建分组ID到分组信息的映射
|
||||
const groupMap = new Map()
|
||||
const accountGroupMap = new Map()
|
||||
// 使用缓存机制加载 API Keys 和分组数据
|
||||
await Promise.all([loadApiKeys(forceReload), loadAccountGroups(forceReload)])
|
||||
|
||||
// 获取所有分组的成员信息
|
||||
for (const group of accountGroups.value) {
|
||||
groupMap.set(group.id, group)
|
||||
try {
|
||||
const membersResponse = await apiClient.get(`/admin/account-groups/${group.id}/members`)
|
||||
if (membersResponse.success) {
|
||||
const members = membersResponse.data || []
|
||||
members.forEach((member) => {
|
||||
accountGroupMap.set(member.id, group)
|
||||
})
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`Failed to load members for group ${group.id}:`, error)
|
||||
}
|
||||
}
|
||||
// 加载分组成员关系(需要在分组数据加载完成后)
|
||||
await loadGroupMembers(forceReload)
|
||||
|
||||
const [claudeData, claudeConsoleData, bedrockData, geminiData] = await Promise.all(requests)
|
||||
|
||||
const allAccounts = []
|
||||
|
||||
@@ -879,7 +956,7 @@ const loadAccounts = async () => {
|
||||
(key) => key.claudeAccountId === acc.id
|
||||
).length
|
||||
// 检查是否属于某个分组
|
||||
const groupInfo = accountGroupMap.get(acc.id) || null
|
||||
const groupInfo = accountGroupMap.value.get(acc.id) || null
|
||||
return { ...acc, platform: 'claude', boundApiKeysCount, groupInfo }
|
||||
})
|
||||
allAccounts.push(...claudeAccounts)
|
||||
@@ -888,7 +965,7 @@ const loadAccounts = async () => {
|
||||
if (claudeConsoleData.success) {
|
||||
const claudeConsoleAccounts = (claudeConsoleData.data || []).map((acc) => {
|
||||
// Claude Console账户暂时不支持直接绑定
|
||||
const groupInfo = accountGroupMap.get(acc.id) || null
|
||||
const groupInfo = accountGroupMap.value.get(acc.id) || null
|
||||
return { ...acc, platform: 'claude-console', boundApiKeysCount: 0, groupInfo }
|
||||
})
|
||||
allAccounts.push(...claudeConsoleAccounts)
|
||||
@@ -897,7 +974,7 @@ const loadAccounts = async () => {
|
||||
if (bedrockData.success) {
|
||||
const bedrockAccounts = (bedrockData.data || []).map((acc) => {
|
||||
// Bedrock账户暂时不支持直接绑定
|
||||
const groupInfo = accountGroupMap.get(acc.id) || null
|
||||
const groupInfo = accountGroupMap.value.get(acc.id) || null
|
||||
return { ...acc, platform: 'bedrock', boundApiKeysCount: 0, groupInfo }
|
||||
})
|
||||
allAccounts.push(...bedrockAccounts)
|
||||
@@ -909,15 +986,13 @@ const loadAccounts = async () => {
|
||||
const boundApiKeysCount = apiKeys.value.filter(
|
||||
(key) => key.geminiAccountId === acc.id
|
||||
).length
|
||||
const groupInfo = accountGroupMap.get(acc.id) || null
|
||||
const groupInfo = accountGroupMap.value.get(acc.id) || null
|
||||
return { ...acc, platform: 'gemini', boundApiKeysCount, groupInfo }
|
||||
})
|
||||
allAccounts.push(...geminiAccounts)
|
||||
}
|
||||
|
||||
accounts.value = allAccounts
|
||||
// 初始化过滤后的账户列表
|
||||
filterByGroup()
|
||||
} catch (error) {
|
||||
showToast('加载账户失败', 'error')
|
||||
} finally {
|
||||
@@ -963,30 +1038,86 @@ const formatLastUsed = (dateString) => {
|
||||
return date.toLocaleDateString('zh-CN')
|
||||
}
|
||||
|
||||
// 加载API Keys列表
|
||||
const loadApiKeys = async () => {
|
||||
// 加载API Keys列表(缓存版本)
|
||||
const loadApiKeys = async (forceReload = false) => {
|
||||
if (!forceReload && apiKeysLoaded.value) {
|
||||
return // 使用缓存数据
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await apiClient.get('/admin/api-keys')
|
||||
if (response.success) {
|
||||
apiKeys.value = response.data
|
||||
apiKeys.value = response.data || []
|
||||
apiKeysLoaded.value = true
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to load API keys:', error)
|
||||
}
|
||||
}
|
||||
|
||||
// 加载账户分组列表(缓存版本)
|
||||
const loadAccountGroups = async (forceReload = false) => {
|
||||
if (!forceReload && groupsLoaded.value) {
|
||||
return // 使用缓存数据
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await apiClient.get('/admin/account-groups')
|
||||
if (response.success) {
|
||||
accountGroups.value = response.data || []
|
||||
groupsLoaded.value = true
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to load account groups:', error)
|
||||
}
|
||||
}
|
||||
|
||||
// 加载分组成员关系(缓存版本)
|
||||
const loadGroupMembers = async (forceReload = false) => {
|
||||
if (!forceReload && groupMembersLoaded.value) {
|
||||
return // 使用缓存数据
|
||||
}
|
||||
|
||||
try {
|
||||
// 重置映射
|
||||
accountGroupMap.value.clear()
|
||||
|
||||
// 获取所有分组的成员信息
|
||||
for (const group of accountGroups.value) {
|
||||
try {
|
||||
const membersResponse = await apiClient.get(`/admin/account-groups/${group.id}/members`)
|
||||
if (membersResponse.success) {
|
||||
const members = membersResponse.data || []
|
||||
members.forEach((member) => {
|
||||
accountGroupMap.value.set(member.id, group)
|
||||
})
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`Failed to load members for group ${group.id}:`, error)
|
||||
}
|
||||
}
|
||||
groupMembersLoaded.value = true
|
||||
} catch (error) {
|
||||
console.error('Failed to load group members:', error)
|
||||
}
|
||||
}
|
||||
|
||||
// 清空缓存的函数
|
||||
const clearCache = () => {
|
||||
apiKeysLoaded.value = false
|
||||
groupsLoaded.value = false
|
||||
groupMembersLoaded.value = false
|
||||
accountGroupMap.value.clear()
|
||||
}
|
||||
|
||||
// 按平台筛选账户
|
||||
const filterByPlatform = () => {
|
||||
loadAccounts()
|
||||
}
|
||||
|
||||
// 按分组筛选账户
|
||||
const filterByGroup = () => {
|
||||
if (groupFilter.value === 'all') {
|
||||
filteredAccounts.value = accounts.value
|
||||
} else if (groupFilter.value === 'ungrouped') {
|
||||
filteredAccounts.value = accounts.value.filter((acc) => !acc.groupInfo)
|
||||
} else {
|
||||
// 按特定分组筛选
|
||||
filteredAccounts.value = accounts.value.filter(
|
||||
(acc) => acc.groupInfo && acc.groupInfo.id === groupFilter.value
|
||||
)
|
||||
}
|
||||
loadAccounts()
|
||||
}
|
||||
|
||||
// 格式化代理信息显示
|
||||
@@ -1091,6 +1222,8 @@ const deleteAccount = async (account) => {
|
||||
|
||||
if (data.success) {
|
||||
showToast('账户已删除', 'success')
|
||||
// 清空分组成员缓存,因为账户可能从分组中移除
|
||||
groupMembersLoaded.value = false
|
||||
loadAccounts()
|
||||
} else {
|
||||
showToast(data.message || '删除失败', 'error')
|
||||
@@ -1196,6 +1329,8 @@ const toggleSchedulable = async (account) => {
|
||||
const handleCreateSuccess = () => {
|
||||
showCreateAccountModal.value = false
|
||||
showToast('账户创建成功', 'success')
|
||||
// 清空缓存,因为可能涉及分组关系变化
|
||||
clearCache()
|
||||
loadAccounts()
|
||||
}
|
||||
|
||||
@@ -1203,6 +1338,8 @@ const handleCreateSuccess = () => {
|
||||
const handleEditSuccess = () => {
|
||||
showEditAccountModal.value = false
|
||||
showToast('账户更新成功', 'success')
|
||||
// 清空分组成员缓存,因为账户类型和分组可能发生变化
|
||||
groupMembersLoaded.value = false
|
||||
loadAccounts()
|
||||
}
|
||||
|
||||
@@ -1216,7 +1353,8 @@ const getAccountStatusText = (account) => {
|
||||
if (
|
||||
account.isRateLimited ||
|
||||
account.status === 'rate_limited' ||
|
||||
(account.rateLimitStatus && account.rateLimitStatus.isRateLimited)
|
||||
(account.rateLimitStatus && account.rateLimitStatus.isRateLimited) ||
|
||||
account.rateLimitStatus === 'limited'
|
||||
)
|
||||
return '限流中'
|
||||
// 检查是否错误
|
||||
@@ -1238,7 +1376,8 @@ const getAccountStatusClass = (account) => {
|
||||
if (
|
||||
account.isRateLimited ||
|
||||
account.status === 'rate_limited' ||
|
||||
(account.rateLimitStatus && account.rateLimitStatus.isRateLimited)
|
||||
(account.rateLimitStatus && account.rateLimitStatus.isRateLimited) ||
|
||||
account.rateLimitStatus === 'limited'
|
||||
) {
|
||||
return 'bg-orange-100 text-orange-800'
|
||||
}
|
||||
@@ -1262,7 +1401,8 @@ const getAccountStatusDotClass = (account) => {
|
||||
if (
|
||||
account.isRateLimited ||
|
||||
account.status === 'rate_limited' ||
|
||||
(account.rateLimitStatus && account.rateLimitStatus.isRateLimited)
|
||||
(account.rateLimitStatus && account.rateLimitStatus.isRateLimited) ||
|
||||
account.rateLimitStatus === 'limited'
|
||||
) {
|
||||
return 'bg-orange-500'
|
||||
}
|
||||
@@ -1330,8 +1470,8 @@ watch(accountSortBy, (newVal) => {
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
loadAccounts()
|
||||
loadApiKeys()
|
||||
// 首次加载时强制刷新所有数据
|
||||
loadAccounts(true)
|
||||
})
|
||||
</script>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user