mirror of
https://github.com/Wei-Shaw/claude-relay-service.git
synced 2026-01-23 00:53:33 +00:00
fix: 1. 修复调度优先级以及手动禁止调度逻辑的问题
2. 优化列表优先级显示
This commit is contained in:
@@ -793,6 +793,29 @@ router.post('/claude-accounts/:accountId/refresh', authenticateAdmin, async (req
|
||||
}
|
||||
});
|
||||
|
||||
// 切换Claude账户调度状态
|
||||
router.put('/claude-accounts/:accountId/toggle-schedulable', authenticateAdmin, async (req, res) => {
|
||||
try {
|
||||
const { accountId } = req.params;
|
||||
|
||||
const accounts = await claudeAccountService.getAllAccounts();
|
||||
const account = accounts.find(acc => acc.id === accountId);
|
||||
|
||||
if (!account) {
|
||||
return res.status(404).json({ error: 'Account not found' });
|
||||
}
|
||||
|
||||
const newSchedulable = !account.schedulable;
|
||||
await claudeAccountService.updateAccount(accountId, { schedulable: newSchedulable });
|
||||
|
||||
logger.success(`🔄 Admin toggled Claude account schedulable status: ${accountId} -> ${newSchedulable ? 'schedulable' : 'not schedulable'}`);
|
||||
res.json({ success: true, schedulable: newSchedulable });
|
||||
} catch (error) {
|
||||
logger.error('❌ Failed to toggle Claude account schedulable status:', error);
|
||||
res.status(500).json({ error: 'Failed to toggle schedulable status', message: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
// 🎮 Claude Console 账户管理
|
||||
|
||||
// 获取所有Claude Console账户
|
||||
@@ -941,6 +964,27 @@ router.put('/claude-console-accounts/:accountId/toggle', authenticateAdmin, asyn
|
||||
}
|
||||
});
|
||||
|
||||
// 切换Claude Console账户调度状态
|
||||
router.put('/claude-console-accounts/:accountId/toggle-schedulable', authenticateAdmin, async (req, res) => {
|
||||
try {
|
||||
const { accountId } = req.params;
|
||||
|
||||
const account = await claudeConsoleAccountService.getAccount(accountId);
|
||||
if (!account) {
|
||||
return res.status(404).json({ error: 'Account not found' });
|
||||
}
|
||||
|
||||
const newSchedulable = !account.schedulable;
|
||||
await claudeConsoleAccountService.updateAccount(accountId, { schedulable: newSchedulable });
|
||||
|
||||
logger.success(`🔄 Admin toggled Claude Console account schedulable status: ${accountId} -> ${newSchedulable ? 'schedulable' : 'not schedulable'}`);
|
||||
res.json({ success: true, schedulable: newSchedulable });
|
||||
} catch (error) {
|
||||
logger.error('❌ Failed to toggle Claude Console account schedulable status:', error);
|
||||
res.status(500).json({ error: 'Failed to toggle schedulable status', message: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
// 🤖 Gemini 账户管理
|
||||
|
||||
// 生成 Gemini OAuth 授权 URL
|
||||
|
||||
@@ -38,7 +38,8 @@ class ClaudeAccountService {
|
||||
proxy = null, // { type: 'socks5', host: 'localhost', port: 1080, username: '', password: '' }
|
||||
isActive = true,
|
||||
accountType = 'shared', // 'dedicated' or 'shared'
|
||||
priority = 50 // 调度优先级 (1-100,数字越小优先级越高)
|
||||
priority = 50, // 调度优先级 (1-100,数字越小优先级越高)
|
||||
schedulable = true // 是否可被调度
|
||||
} = options;
|
||||
|
||||
const accountId = uuidv4();
|
||||
@@ -66,7 +67,8 @@ class ClaudeAccountService {
|
||||
lastUsedAt: '',
|
||||
lastRefreshAt: '',
|
||||
status: 'active', // 有OAuth数据的账户直接设为active
|
||||
errorMessage: ''
|
||||
errorMessage: '',
|
||||
schedulable: schedulable.toString() // 是否可被调度
|
||||
};
|
||||
} else {
|
||||
// 兼容旧格式
|
||||
@@ -88,7 +90,8 @@ class ClaudeAccountService {
|
||||
lastUsedAt: '',
|
||||
lastRefreshAt: '',
|
||||
status: 'created', // created, active, expired, error
|
||||
errorMessage: ''
|
||||
errorMessage: '',
|
||||
schedulable: schedulable.toString() // 是否可被调度
|
||||
};
|
||||
}
|
||||
|
||||
@@ -328,7 +331,9 @@ class ClaudeAccountService {
|
||||
progress: 0,
|
||||
remainingTime: null,
|
||||
lastRequestTime: null
|
||||
}
|
||||
},
|
||||
// 添加调度状态
|
||||
schedulable: account.schedulable !== 'false' // 默认为true,兼容历史数据
|
||||
};
|
||||
}));
|
||||
|
||||
@@ -348,7 +353,7 @@ class ClaudeAccountService {
|
||||
throw new Error('Account not found');
|
||||
}
|
||||
|
||||
const allowedUpdates = ['name', 'description', 'email', 'password', 'refreshToken', 'proxy', 'isActive', 'claudeAiOauth', 'accountType', 'priority'];
|
||||
const allowedUpdates = ['name', 'description', 'email', 'password', 'refreshToken', 'proxy', 'isActive', 'claudeAiOauth', 'accountType', 'priority', 'schedulable'];
|
||||
const updatedData = { ...accountData };
|
||||
|
||||
// 检查是否新增了 refresh token
|
||||
|
||||
@@ -30,7 +30,8 @@ class ClaudeConsoleAccountService {
|
||||
rateLimitDuration = 60, // 限流时间(分钟)
|
||||
proxy = null,
|
||||
isActive = true,
|
||||
accountType = 'shared' // 'dedicated' or 'shared'
|
||||
accountType = 'shared', // 'dedicated' or 'shared'
|
||||
schedulable = true // 是否可被调度
|
||||
} = options;
|
||||
|
||||
// 验证必填字段
|
||||
@@ -60,7 +61,9 @@ class ClaudeConsoleAccountService {
|
||||
errorMessage: '',
|
||||
// 限流相关
|
||||
rateLimitedAt: '',
|
||||
rateLimitStatus: ''
|
||||
rateLimitStatus: '',
|
||||
// 调度控制
|
||||
schedulable: schedulable.toString()
|
||||
};
|
||||
|
||||
const client = redis.getClientSafe();
|
||||
@@ -126,7 +129,8 @@ class ClaudeConsoleAccountService {
|
||||
errorMessage: accountData.errorMessage,
|
||||
createdAt: accountData.createdAt,
|
||||
lastUsedAt: accountData.lastUsedAt,
|
||||
rateLimitStatus: rateLimitInfo
|
||||
rateLimitStatus: rateLimitInfo,
|
||||
schedulable: accountData.schedulable !== 'false' // 默认为true,只有明确设置为false才不可调度
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -166,6 +170,7 @@ class ClaudeConsoleAccountService {
|
||||
accountData.priority = parseInt(accountData.priority) || 50;
|
||||
accountData.rateLimitDuration = parseInt(accountData.rateLimitDuration) || 60;
|
||||
accountData.isActive = accountData.isActive === 'true';
|
||||
accountData.schedulable = accountData.schedulable !== 'false'; // 默认为true
|
||||
|
||||
if (accountData.proxy) {
|
||||
accountData.proxy = JSON.parse(accountData.proxy);
|
||||
@@ -210,6 +215,7 @@ class ClaudeConsoleAccountService {
|
||||
if (updates.rateLimitDuration !== undefined) updatedData.rateLimitDuration = updates.rateLimitDuration.toString();
|
||||
if (updates.proxy !== undefined) updatedData.proxy = updates.proxy ? JSON.stringify(updates.proxy) : '';
|
||||
if (updates.isActive !== undefined) updatedData.isActive = updates.isActive.toString();
|
||||
if (updates.schedulable !== undefined) updatedData.schedulable = updates.schedulable.toString();
|
||||
|
||||
// 处理账户类型变更
|
||||
if (updates.accountType && updates.accountType !== existingAccount.accountType) {
|
||||
|
||||
@@ -107,7 +107,8 @@ class UnifiedClaudeScheduler {
|
||||
if (account.isActive === 'true' &&
|
||||
account.status !== 'error' &&
|
||||
account.status !== 'blocked' &&
|
||||
(account.accountType === 'shared' || !account.accountType)) { // 兼容旧数据
|
||||
(account.accountType === 'shared' || !account.accountType) && // 兼容旧数据
|
||||
account.schedulable !== 'false') { // 检查是否可调度
|
||||
|
||||
// 检查是否被限流
|
||||
const isRateLimited = await claudeAccountService.isAccountRateLimited(account.id);
|
||||
@@ -128,12 +129,13 @@ class UnifiedClaudeScheduler {
|
||||
logger.info(`📋 Found ${consoleAccounts.length} total Claude Console accounts`);
|
||||
|
||||
for (const account of consoleAccounts) {
|
||||
logger.info(`🔍 Checking Claude Console account: ${account.name} - isActive: ${account.isActive}, status: ${account.status}, accountType: ${account.accountType}`);
|
||||
logger.info(`🔍 Checking Claude Console account: ${account.name} - isActive: ${account.isActive}, status: ${account.status}, accountType: ${account.accountType}, schedulable: ${account.schedulable}`);
|
||||
|
||||
// 注意:getAllAccounts返回的isActive是布尔值
|
||||
if (account.isActive === true &&
|
||||
account.status === 'active' &&
|
||||
account.accountType === 'shared') {
|
||||
account.accountType === 'shared' &&
|
||||
account.schedulable !== false) { // 检查是否可调度
|
||||
|
||||
// 检查模型支持(如果有请求的模型)
|
||||
if (requestedModel && account.supportedModels && account.supportedModels.length > 0) {
|
||||
@@ -158,7 +160,7 @@ class UnifiedClaudeScheduler {
|
||||
logger.warn(`⚠️ Claude Console account ${account.name} is rate limited`);
|
||||
}
|
||||
} else {
|
||||
logger.info(`❌ Claude Console account ${account.name} not eligible - isActive: ${account.isActive}, status: ${account.status}, accountType: ${account.accountType}`);
|
||||
logger.info(`❌ Claude Console account ${account.name} not eligible - isActive: ${account.isActive}, status: ${account.status}, accountType: ${account.accountType}, schedulable: ${account.schedulable}`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -189,12 +191,22 @@ class UnifiedClaudeScheduler {
|
||||
if (!account || account.isActive !== 'true' || account.status === 'error') {
|
||||
return false;
|
||||
}
|
||||
// 检查是否可调度
|
||||
if (account.schedulable === 'false') {
|
||||
logger.info(`🚫 Account ${accountId} is not schedulable`);
|
||||
return false;
|
||||
}
|
||||
return !(await claudeAccountService.isAccountRateLimited(accountId));
|
||||
} else if (accountType === 'claude-console') {
|
||||
const account = await claudeConsoleAccountService.getAccount(accountId);
|
||||
if (!account || !account.isActive || account.status !== 'active') {
|
||||
return false;
|
||||
}
|
||||
// 检查是否可调度
|
||||
if (account.schedulable === false) {
|
||||
logger.info(`🚫 Claude Console account ${accountId} is not schedulable`);
|
||||
return false;
|
||||
}
|
||||
return !(await claudeConsoleAccountService.isAccountRateLimited(accountId));
|
||||
}
|
||||
return false;
|
||||
|
||||
4
web/admin-spa/dist/index.html
vendored
4
web/admin-spa/dist/index.html
vendored
@@ -18,12 +18,12 @@
|
||||
<link rel="preconnect" href="https://cdnjs.cloudflare.com" crossorigin>
|
||||
<link rel="dns-prefetch" href="https://cdn.jsdelivr.net">
|
||||
<link rel="dns-prefetch" href="https://cdnjs.cloudflare.com">
|
||||
<script type="module" crossorigin src="/admin-next/assets/index-yITK4-m_.js"></script>
|
||||
<script type="module" crossorigin src="/admin-next/assets/index-C7KigxvU.js"></script>
|
||||
<link rel="modulepreload" crossorigin href="/admin-next/assets/vue-vendor-CKToUHZx.js">
|
||||
<link rel="modulepreload" crossorigin href="/admin-next/assets/vendor-BDiMbLwQ.js">
|
||||
<link rel="modulepreload" crossorigin href="/admin-next/assets/element-plus-B8Fs_0jW.js">
|
||||
<link rel="stylesheet" crossorigin href="/admin-next/assets/element-plus-CPnoEkWW.css">
|
||||
<link rel="stylesheet" crossorigin href="/admin-next/assets/index-DX8B4Y8f.css">
|
||||
<link rel="stylesheet" crossorigin href="/admin-next/assets/index-Ce1o7Q_r.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
|
||||
@@ -138,6 +138,11 @@
|
||||
<i class="fas fa-exclamation-triangle mr-1"></i>
|
||||
限流中 ({{ account.rateLimitStatus.minutesRemaining }}分钟)
|
||||
</span>
|
||||
<span v-if="account.schedulable === false"
|
||||
class="inline-flex items-center px-3 py-1 rounded-full text-xs font-semibold bg-gray-100 text-gray-700">
|
||||
<i class="fas fa-pause-circle mr-1"></i>
|
||||
不可调度
|
||||
</span>
|
||||
<span v-if="account.status === 'blocked' && account.errorMessage"
|
||||
class="text-xs text-gray-500 mt-1 max-w-xs truncate"
|
||||
:title="account.errorMessage">
|
||||
@@ -153,7 +158,7 @@
|
||||
<div v-if="account.platform === 'claude' || account.platform === 'claude-console'" class="flex items-center gap-2">
|
||||
<div class="w-16 bg-gray-200 rounded-full h-2">
|
||||
<div class="bg-gradient-to-r from-green-500 to-blue-600 h-2 rounded-full transition-all duration-300"
|
||||
:style="{ width: ((100 - (account.priority || 50)) + '%') }"></div>
|
||||
:style="{ width: ((101 - (account.priority || 50)) + '%') }"></div>
|
||||
</div>
|
||||
<span class="text-xs text-gray-700 font-medium min-w-[20px]">
|
||||
{{ account.priority || 50 }}
|
||||
@@ -232,6 +237,24 @@
|
||||
account.isRefreshing ? 'animate-spin' : ''
|
||||
]"></i>
|
||||
</button>
|
||||
<button
|
||||
@click="toggleSchedulable(account)"
|
||||
:disabled="account.isTogglingSchedulable"
|
||||
:class="[
|
||||
'px-3 py-1.5 rounded-lg text-xs font-medium transition-colors',
|
||||
account.isTogglingSchedulable
|
||||
? 'bg-gray-100 text-gray-400 cursor-not-allowed'
|
||||
: account.schedulable
|
||||
? 'bg-green-100 text-green-700 hover:bg-green-200'
|
||||
: 'bg-gray-100 text-gray-700 hover:bg-gray-200'
|
||||
]"
|
||||
:title="account.schedulable ? '点击禁用调度' : '点击启用调度'"
|
||||
>
|
||||
<i :class="[
|
||||
'fas',
|
||||
account.schedulable ? 'fa-toggle-on' : 'fa-toggle-off'
|
||||
]"></i>
|
||||
</button>
|
||||
<button
|
||||
@click="editAccount(account)"
|
||||
class="px-3 py-1.5 bg-blue-100 text-blue-700 rounded-lg text-xs font-medium hover:bg-blue-200 transition-colors"
|
||||
@@ -571,6 +594,41 @@ const refreshToken = async (account) => {
|
||||
}
|
||||
}
|
||||
|
||||
// 切换调度状态
|
||||
const toggleSchedulable = async (account) => {
|
||||
if (account.isTogglingSchedulable) return
|
||||
|
||||
try {
|
||||
account.isTogglingSchedulable = true
|
||||
|
||||
let endpoint
|
||||
if (account.platform === 'claude') {
|
||||
endpoint = `/admin/claude-accounts/${account.id}/toggle-schedulable`
|
||||
} else if (account.platform === 'claude-console') {
|
||||
endpoint = `/admin/claude-console-accounts/${account.id}/toggle-schedulable`
|
||||
} else {
|
||||
showToast('Gemini账户暂不支持调度控制', 'warning')
|
||||
return
|
||||
}
|
||||
|
||||
const data = await apiClient.put(endpoint)
|
||||
|
||||
if (data.success) {
|
||||
account.schedulable = data.schedulable
|
||||
showToast(
|
||||
data.schedulable ? '已启用调度' : '已禁用调度',
|
||||
'success'
|
||||
)
|
||||
} else {
|
||||
showToast(data.message || '操作失败', 'error')
|
||||
}
|
||||
} catch (error) {
|
||||
showToast('切换调度状态失败', 'error')
|
||||
} finally {
|
||||
account.isTogglingSchedulable = false
|
||||
}
|
||||
}
|
||||
|
||||
// 处理创建成功
|
||||
const handleCreateSuccess = () => {
|
||||
showCreateAccountModal.value = false
|
||||
|
||||
Reference in New Issue
Block a user