feat: add Codex proxy configuration page with full CRUD, toggle, and testing functionalities.

This commit is contained in:
2026-02-03 03:30:01 +08:00
parent cf25845a0b
commit 9b4838b604

View File

@@ -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>
<span className="text-sm text-slate-500">
{proxies.length}
</span>
<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 ? (