feat: Implement initial Codex Pool backend server with comprehensive APIs for configuration, S2A integration, owner management, batch processing, monitoring, and corresponding frontend pages.

This commit is contained in:
2026-02-01 03:45:53 +08:00
parent 8560a33f36
commit e27e36b0e0
8 changed files with 297 additions and 31 deletions

View File

@@ -46,6 +46,10 @@ export function ConfigProvider({ children }: { children: ReactNode }) {
priority: serverConfig.priority || 0,
groupIds: serverConfig.group_ids || [],
},
proxy: {
default: serverConfig.default_proxy || '',
enabled: serverConfig.proxy_enabled || false,
},
}))
// 更新站点名称
if (serverConfig.site_name) {

View File

@@ -10,7 +10,10 @@ import {
XCircle,
Save,
Loader2,
Globe
Globe,
Wifi,
WifiOff,
HelpCircle
} from 'lucide-react'
import { Card, CardHeader, CardTitle, CardContent, Button, Input } from '../components/common'
import { useConfig } from '../hooks/useConfig'
@@ -18,23 +21,29 @@ import { useConfig } from '../hooks/useConfig'
export default function Config() {
const { config, isConnected, refreshConfig } = useConfig()
const [siteName, setSiteName] = useState('')
const [defaultProxy, setDefaultProxy] = useState('')
const [saving, setSaving] = useState(false)
const [savingProxy, setSavingProxy] = useState(false)
const [testingProxy, setTestingProxy] = useState(false)
const [proxyStatus, setProxyStatus] = useState<'unknown' | 'success' | 'error'>('unknown')
const [proxyOriginIP, setProxyOriginIP] = useState('')
const [message, setMessage] = useState<{ type: 'success' | 'error', text: string } | null>(null)
// 加载站点名称配置
// 加载站点名称和代理配置
useEffect(() => {
const fetchSiteName = async () => {
const fetchConfig = async () => {
try {
const res = await fetch('/api/config')
const data = await res.json()
if (data.code === 0 && data.data) {
setSiteName(data.data.site_name || 'Codex Pool')
setDefaultProxy(data.data.default_proxy || '')
}
} catch (error) {
console.error('Failed to fetch site name:', error)
console.error('Failed to fetch config:', error)
}
}
fetchSiteName()
fetchConfig()
}, [])
// 保存站点名称
@@ -61,6 +70,65 @@ export default function Config() {
}
}
// 保存代理地址
const handleSaveProxy = async () => {
setSavingProxy(true)
setMessage(null)
try {
const res = await fetch('/api/config', {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ default_proxy: defaultProxy }),
})
const data = await res.json()
if (data.code === 0) {
setMessage({ type: 'success', text: '代理地址已保存' })
setProxyStatus('unknown') // 保存后重置状态
setProxyOriginIP('')
refreshConfig()
} else {
setMessage({ type: 'error', text: data.message || '保存失败' })
}
} catch {
setMessage({ type: 'error', text: '网络错误' })
} finally {
setSavingProxy(false)
}
}
// 测试代理连接
const handleTestProxy = async () => {
if (!defaultProxy.trim()) {
setMessage({ type: 'error', text: '请先输入代理地址' })
return
}
setTestingProxy(true)
setMessage(null)
setProxyStatus('unknown')
setProxyOriginIP('')
try {
const res = await fetch('/api/proxy/test', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ proxy_url: defaultProxy }),
})
const data = await res.json()
if (data.code === 0 && data.data?.connected) {
setProxyStatus('success')
setProxyOriginIP(data.data.origin_ip || '')
setMessage({ type: 'success', text: `代理连接成功${data.data.origin_ip ? `, 出口IP: ${data.data.origin_ip}` : ''}` })
} else {
setProxyStatus('error')
setMessage({ type: 'error', text: data.message || '代理连接失败' })
}
} catch (e) {
setProxyStatus('error')
setMessage({ type: 'error', text: e instanceof Error ? e.message : '网络错误' })
} finally {
setTestingProxy(false)
}
}
const configItems = [
{
to: '/config/s2a',
@@ -178,6 +246,72 @@ export default function Config() {
</p>
</div>
{/* 代理地址配置 */}
<div className="pt-4 border-t border-slate-200 dark:border-slate-700">
<div className="flex items-center gap-2 mb-2">
<label className="block text-sm font-medium text-slate-700 dark:text-slate-300">
</label>
{/* 代理状态徽章 */}
{proxyStatus === 'success' && (
<span className="inline-flex items-center gap-1 px-2 py-0.5 text-xs font-medium rounded-full bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-400">
<Wifi className="h-3 w-3" />
</span>
)}
{proxyStatus === 'error' && (
<span className="inline-flex items-center gap-1 px-2 py-0.5 text-xs font-medium rounded-full bg-red-100 text-red-700 dark:bg-red-900/30 dark:text-red-400">
<WifiOff className="h-3 w-3" />
</span>
)}
{proxyStatus === 'unknown' && defaultProxy && (
<span className="inline-flex items-center gap-1 px-2 py-0.5 text-xs font-medium rounded-full bg-slate-100 text-slate-600 dark:bg-slate-700 dark:text-slate-400">
<HelpCircle className="h-3 w-3" />
</span>
)}
</div>
<div className="flex gap-3">
<Input
value={defaultProxy}
onChange={(e) => {
setDefaultProxy(e.target.value)
setProxyStatus('unknown')
}}
placeholder="http://127.0.0.1:7890"
className="flex-1"
/>
<Button
size="sm"
onClick={handleSaveProxy}
disabled={savingProxy}
icon={savingProxy ? <Loader2 className="h-4 w-4 animate-spin" /> : <Save className="h-4 w-4" />}
className="shrink-0"
>
{savingProxy ? '保存中...' : '保存代理'}
</Button>
<Button
size="sm"
variant="outline"
onClick={handleTestProxy}
disabled={testingProxy || !defaultProxy.trim()}
icon={testingProxy ? <Loader2 className="h-4 w-4 animate-spin" /> : <Wifi className="h-4 w-4" />}
className="shrink-0"
>
{testingProxy ? '测试中...' : '测试连接'}
</Button>
</div>
<p className="mt-1 text-xs text-slate-500 dark:text-slate-400">
{proxyOriginIP && (
<span className="ml-2 text-green-600 dark:text-green-400">
IP: {proxyOriginIP}
</span>
)}
</p>
</div>
</CardContent>
</Card>

View File

@@ -67,6 +67,8 @@ export default function Monitor() {
const [checkInterval, setCheckInterval] = useState(60)
const [pollingEnabled, setPollingEnabled] = useState(false)
const [pollingInterval, setPollingInterval] = useState(60)
const [replenishUseProxy, setReplenishUseProxy] = useState(false) // 补号时使用代理
const [globalProxy, setGlobalProxy] = useState('') // 全局代理地址(只读显示)
// 倒计时状态
const [countdown, setCountdown] = useState(60)
@@ -129,6 +131,7 @@ export default function Monitor() {
check_interval: checkInterval,
polling_enabled: pollingEnabled,
polling_interval: pollingInterval,
replenish_use_proxy: replenishUseProxy,
}),
})
const data = await res.json()
@@ -253,6 +256,7 @@ export default function Monitor() {
// 从后端加载监控设置
const loadMonitorSettings = async () => {
try {
// 加载监控设置
const res = await fetch('/api/monitor/settings')
if (res.ok) {
const json = await res.json()
@@ -264,6 +268,7 @@ export default function Monitor() {
const checkIntervalVal = s.check_interval || 60
const pollingEnabledVal = s.polling_enabled || false
const interval = s.polling_interval || 60
const replenishUseProxyVal = s.replenish_use_proxy || false
setTargetInput(target)
setAutoAdd(autoAddVal)
@@ -271,11 +276,21 @@ export default function Monitor() {
setCheckInterval(checkIntervalVal)
setPollingEnabled(pollingEnabledVal)
setPollingInterval(interval)
setReplenishUseProxy(replenishUseProxyVal)
savedPollingIntervalRef.current = interval
setCountdown(interval)
// 返回加载的配置用于后续刷新
return { target, autoAdd: autoAddVal, minInterval: minIntervalVal, checkInterval: checkIntervalVal, pollingEnabled: pollingEnabledVal, pollingInterval: interval }
return { target, autoAdd: autoAddVal, minInterval: minIntervalVal, checkInterval: checkIntervalVal, pollingEnabled: pollingEnabledVal, pollingInterval: interval, replenishUseProxy: replenishUseProxyVal }
}
}
// 加载全局代理配置
const configRes = await fetch('/api/config')
if (configRes.ok) {
const configJson = await configRes.json()
if (configJson.code === 0 && configJson.data) {
setGlobalProxy(configJson.data.default_proxy || '')
}
}
} catch (e) {
@@ -511,6 +526,15 @@ export default function Monitor() {
description="开启后,当号池不足时自动补充账号"
/>
</div>
<div className="p-3 rounded-lg bg-slate-50 dark:bg-slate-800/50 border border-slate-200 dark:border-slate-700">
<Switch
checked={replenishUseProxy}
onChange={setReplenishUseProxy}
disabled={!autoAdd || !globalProxy}
label="补号时使用代理"
description={globalProxy ? `当前代理: ${globalProxy}` : '请先在系统配置中设置代理地址'}
/>
</div>
<Input
label="最小间隔 (秒)"
type="number"

View File

@@ -70,10 +70,13 @@ export default function Upload() {
const [membersPerTeam, setMembersPerTeam] = useState(4)
const [concurrentTeams, setConcurrentTeams] = useState(2)
const [browserType, setBrowserType] = useState<'chromedp' | 'rod'>('chromedp')
const [proxy, setProxy] = useState('')
const [useProxy, setUseProxy] = useState(false) // 是否使用全局代理
const [includeOwner, setIncludeOwner] = useState(false) // 母号也入库
const [processCount, setProcessCount] = useState(0) // 处理数量0表示全部
// 获取全局代理地址
const globalProxy = config.proxy?.default || ''
const hasConfig = config.s2a.apiBase && config.s2a.adminKey
// Load stats
@@ -191,7 +194,7 @@ export default function Upload() {
concurrent_teams: Math.min(concurrentTeams, stats?.valid || 1),
browser_type: browserType,
headless: true, // 始终使用无头模式
proxy,
proxy: useProxy ? globalProxy : '',
include_owner: includeOwner, // 母号也入库
process_count: processCount, // 处理数量0表示全部
}),
@@ -209,7 +212,7 @@ export default function Upload() {
alert('启动失败')
}
setLoading(false)
}, [stats, membersPerTeam, concurrentTeams, browserType, proxy, includeOwner, processCount, fetchStatus])
}, [stats, membersPerTeam, concurrentTeams, browserType, useProxy, globalProxy, includeOwner, processCount, fetchStatus])
// 停止处理
const handleStop = useCallback(async () => {
@@ -469,13 +472,15 @@ export default function Upload() {
</div>
</div>
<Input
label="代理地址(可选)"
placeholder="http://127.0.0.1:7890"
value={proxy}
onChange={(e) => setProxy(e.target.value)}
disabled={isRunning}
/>
<div className="p-3 rounded-lg bg-slate-50 dark:bg-slate-800/50 border border-slate-200 dark:border-slate-700">
<Switch
checked={useProxy}
onChange={setUseProxy}
disabled={isRunning || !globalProxy}
label="使用全局代理"
description={globalProxy ? `当前代理: ${globalProxy}` : '请先在系统配置中设置代理地址'}
/>
</div>
<div className="p-3 rounded-lg bg-slate-50 dark:bg-slate-800/50 border border-slate-200 dark:border-slate-700">
<Switch

View File

@@ -175,6 +175,10 @@ export interface AppConfig {
email: {
services: MailServiceConfig[] // 多个邮箱服务配置
}
proxy: {
default: string // 全局默认代理地址
enabled: boolean // 是否启用代理
}
}
// 默认配置
@@ -203,6 +207,10 @@ export const defaultConfig: AppConfig = {
},
],
},
proxy: {
default: '',
enabled: false,
},
}
// 检查结果