feat: add Codex proxy configuration page with full CRUD, toggle, and testing functionalities.
This commit is contained in:
@@ -2,7 +2,7 @@ import { useState, useEffect, useCallback } from 'react'
|
||||
import {
|
||||
Globe, Plus, Trash2, ToggleLeft, ToggleRight,
|
||||
Loader2, Save, RefreshCcw, CheckCircle, XCircle,
|
||||
AlertTriangle, Clock, MapPin, Play
|
||||
AlertTriangle, Clock, MapPin, Play, PlayCircle
|
||||
} from 'lucide-react'
|
||||
import { Card, CardHeader, CardTitle, CardContent, Button, Input } from '../components/common'
|
||||
|
||||
@@ -31,6 +31,7 @@ export default function CodexProxyConfig() {
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [saving, setSaving] = useState(false)
|
||||
const [testingIds, setTestingIds] = useState<Set<number>>(new Set())
|
||||
const [testingAll, setTestingAll] = useState(false)
|
||||
const [message, setMessage] = useState<{ type: 'success' | 'error', text: string } | null>(null)
|
||||
|
||||
// 单个添加
|
||||
@@ -177,6 +178,70 @@ export default function CodexProxyConfig() {
|
||||
}
|
||||
}
|
||||
|
||||
// 一键测试所有代理
|
||||
const handleTestAll = async () => {
|
||||
if (testingAll || proxies.length === 0) return
|
||||
setTestingAll(true)
|
||||
setMessage(null)
|
||||
|
||||
let successCount = 0
|
||||
let failCount = 0
|
||||
|
||||
// 并发测试所有代理(最多5个并发)
|
||||
const concurrency = 5
|
||||
const chunks: CodexProxy[][] = []
|
||||
for (let i = 0; i < proxies.length; i += concurrency) {
|
||||
chunks.push(proxies.slice(i, i + concurrency))
|
||||
}
|
||||
|
||||
for (const chunk of chunks) {
|
||||
// 标记当前批次为测试中
|
||||
const chunkIds = chunk.map(p => p.id)
|
||||
setTestingIds(prev => {
|
||||
const next = new Set(prev)
|
||||
chunkIds.forEach(id => next.add(id))
|
||||
return next
|
||||
})
|
||||
|
||||
// 并发执行当前批次
|
||||
const results = await Promise.allSettled(
|
||||
chunk.map(async (proxy) => {
|
||||
try {
|
||||
const res = await fetch(`/api/codex-proxy/test?id=${proxy.id}`, { method: 'POST' })
|
||||
const data = await res.json()
|
||||
return { id: proxy.id, success: data.code === 0, data }
|
||||
} catch {
|
||||
return { id: proxy.id, success: false }
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
// 统计结果
|
||||
results.forEach(result => {
|
||||
if (result.status === 'fulfilled' && result.value.success) {
|
||||
successCount++
|
||||
} else {
|
||||
failCount++
|
||||
}
|
||||
})
|
||||
|
||||
// 移除当前批次的测试状态
|
||||
setTestingIds(prev => {
|
||||
const next = new Set(prev)
|
||||
chunkIds.forEach(id => next.delete(id))
|
||||
return next
|
||||
})
|
||||
}
|
||||
|
||||
// 刷新列表获取最新数据
|
||||
await fetchProxies()
|
||||
setTestingAll(false)
|
||||
setMessage({
|
||||
type: successCount > 0 ? 'success' : 'error',
|
||||
text: `测试完成: ${successCount} 成功, ${failCount} 失败`
|
||||
})
|
||||
}
|
||||
|
||||
// 清空所有
|
||||
const handleClearAll = async () => {
|
||||
if (!confirm('确定要清空所有代理吗?此操作不可恢复!')) return
|
||||
@@ -376,9 +441,22 @@ export default function CodexProxyConfig() {
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>代理列表</CardTitle>
|
||||
<div className="flex items-center gap-3">
|
||||
{proxies.length > 0 && (
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
onClick={handleTestAll}
|
||||
disabled={testingAll}
|
||||
icon={testingAll ? <Loader2 className="h-4 w-4 animate-spin" /> : <PlayCircle className="h-4 w-4" />}
|
||||
>
|
||||
{testingAll ? '测试中...' : '一键测试'}
|
||||
</Button>
|
||||
)}
|
||||
<span className="text-sm text-slate-500">
|
||||
共 {proxies.length} 个代理
|
||||
</span>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
{proxies.length === 0 ? (
|
||||
|
||||
Reference in New Issue
Block a user