feat: Implement account pool monitoring and management dashboard with S2A integration and add a new upload page.
This commit is contained in:
@@ -174,6 +174,7 @@ func startServer(cfg *config.Config) {
|
||||
// CodexAuth 代理池 API
|
||||
mux.HandleFunc("/api/codex-proxy", api.CORS(api.HandleCodexProxies))
|
||||
mux.HandleFunc("/api/codex-proxy/test", api.CORS(api.HandleCodexProxies))
|
||||
mux.HandleFunc("/api/codex-proxy/stats", api.CORS(api.HandleCodexProxyStats))
|
||||
|
||||
// Owner 降级 API
|
||||
mux.HandleFunc("/api/demote/owner", api.CORS(api.HandleDemoteOwner))
|
||||
|
||||
@@ -32,6 +32,20 @@ func HandleCodexProxies(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}
|
||||
|
||||
// HandleCodexProxyStats 获取代理池统计
|
||||
func HandleCodexProxyStats(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodGet {
|
||||
Error(w, http.StatusMethodNotAllowed, "仅支持 GET")
|
||||
return
|
||||
}
|
||||
if database.Instance == nil {
|
||||
Error(w, http.StatusInternalServerError, "数据库未初始化")
|
||||
return
|
||||
}
|
||||
stats := database.Instance.GetCodexProxyStats()
|
||||
Success(w, stats)
|
||||
}
|
||||
|
||||
// listCodexProxies 获取代理列表
|
||||
func listCodexProxies(w http.ResponseWriter, r *http.Request) {
|
||||
proxies, err := database.Instance.GetCodexProxies()
|
||||
|
||||
@@ -855,8 +855,14 @@ func processSingleTeam(idx int, req TeamProcessRequest) (result TeamProcessResul
|
||||
return false
|
||||
}
|
||||
|
||||
// 注册
|
||||
_, err := register.APIRegister(currentEmail, currentPassword, name, birthdate, req.Proxy, memberLogPrefix)
|
||||
// 注册 - 从代理池获取随机代理
|
||||
regProxy := req.Proxy
|
||||
if database.Instance != nil {
|
||||
if poolProxy, poolErr := database.Instance.GetRandomCodexProxy(); poolErr == nil && poolProxy != "" {
|
||||
regProxy = poolProxy
|
||||
}
|
||||
}
|
||||
_, err := register.APIRegister(currentEmail, currentPassword, name, birthdate, regProxy, memberLogPrefix)
|
||||
if err != nil {
|
||||
logger.Error(fmt.Sprintf("%s [注册失败] %v", memberLogPrefix, err), currentEmail, "team")
|
||||
continue
|
||||
|
||||
@@ -54,8 +54,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 [replenishUseProxy, setReplenishUseProxy] = useState(false) // 补号时使用代理池
|
||||
const [proxyPoolStats, setProxyPoolStats] = useState<{ total: number; enabled: number } | null>(null) // 代理池统计
|
||||
const [authMethod, setAuthMethod] = useState<'api' | 'browser'>('browser') // 授权方式
|
||||
|
||||
// 自动补号配置
|
||||
@@ -238,12 +238,16 @@ export default function Monitor() {
|
||||
// 从后端加载监控设置
|
||||
const loadMonitorSettings = async () => {
|
||||
try {
|
||||
// 加载全局代理配置(始终执行)
|
||||
const configRes = await fetch('/api/config')
|
||||
// 加载代理池统计和全局配置(并行)
|
||||
const [configRes, proxyStatsRes, settingsRes] = await Promise.all([
|
||||
fetch('/api/config'),
|
||||
fetch('/api/codex-proxy/stats'),
|
||||
fetch('/api/monitor/settings'),
|
||||
])
|
||||
|
||||
if (configRes.ok) {
|
||||
const configJson = await configRes.json()
|
||||
if (configJson.code === 0 && configJson.data) {
|
||||
setGlobalProxy(configJson.data.default_proxy || '')
|
||||
// 加载授权方式配置
|
||||
if (configJson.data.auth_method) {
|
||||
setAuthMethod(configJson.data.auth_method === 'api' ? 'api' : 'browser')
|
||||
@@ -251,10 +255,16 @@ export default function Monitor() {
|
||||
}
|
||||
}
|
||||
|
||||
if (proxyStatsRes.ok) {
|
||||
const proxyJson = await proxyStatsRes.json()
|
||||
if (proxyJson.code === 0 && proxyJson.data) {
|
||||
setProxyPoolStats(proxyJson.data)
|
||||
}
|
||||
}
|
||||
|
||||
// 加载监控设置
|
||||
const res = await fetch('/api/monitor/settings')
|
||||
if (res.ok) {
|
||||
const json = await res.json()
|
||||
if (settingsRes.ok) {
|
||||
const json = await settingsRes.json()
|
||||
if (json.code === 0 && json.data) {
|
||||
const s = json.data
|
||||
const target = s.target || 50
|
||||
@@ -525,12 +535,12 @@ export default function Monitor() {
|
||||
<Switch
|
||||
checked={replenishUseProxy}
|
||||
onChange={setReplenishUseProxy}
|
||||
disabled={!autoAdd || !globalProxy}
|
||||
label="补号时使用代理"
|
||||
disabled={!autoAdd || !proxyPoolStats || proxyPoolStats.enabled === 0}
|
||||
label="补号时使用代理池"
|
||||
description={
|
||||
globalProxy
|
||||
? `当前代理: ${globalProxy}`
|
||||
: '请先在系统配置中设置代理地址(支持 http://host:port、http://user:pass@host:port、host:port:user:pass)'
|
||||
proxyPoolStats && proxyPoolStats.enabled > 0
|
||||
? `代理池: ${proxyPoolStats.enabled} 个可用 / ${proxyPoolStats.total} 个总计`
|
||||
: '代理池为空,请先在"代理池配置"页面添加代理'
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
@@ -757,10 +767,10 @@ export default function Monitor() {
|
||||
type="checkbox"
|
||||
checked={autoRegUseProxy}
|
||||
onChange={(e) => setAutoRegUseProxy(e.target.checked)}
|
||||
disabled={!globalProxy}
|
||||
disabled={!proxyPoolStats || proxyPoolStats.enabled === 0}
|
||||
className="h-4 w-4 rounded border-slate-300 text-purple-600 focus:ring-purple-500 disabled:opacity-50"
|
||||
/>
|
||||
<span>使用代理</span>
|
||||
<span>使用代理池{proxyPoolStats && proxyPoolStats.enabled > 0 ? ` (${proxyPoolStats.enabled})` : ''}</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -72,28 +72,33 @@ export default function Upload() {
|
||||
const [membersPerTeam, setMembersPerTeam] = useState(4)
|
||||
const [concurrentTeams, setConcurrentTeams] = useState(2)
|
||||
const [concurrentS2A, setConcurrentS2A] = useState(2) // 入库并发数
|
||||
const [useProxy, setUseProxy] = useState(false) // 是否使用全局代理
|
||||
const [useProxy, setUseProxy] = useState(false) // 是否使用代理池
|
||||
const [includeOwner, setIncludeOwner] = useState(false) // 母号也入库
|
||||
const [processCount, setProcessCount] = useState(0) // 处理数量,0表示全部
|
||||
const [authMethod, setAuthMethod] = useState<'api' | 'browser'>('browser') // 授权方式
|
||||
const [proxyPoolStats, setProxyPoolStats] = useState<{ total: number; enabled: number } | null>(null)
|
||||
|
||||
// 获取全局代理地址
|
||||
const globalProxy = config.proxy?.default || ''
|
||||
|
||||
// 加载全局配置中的授权方式
|
||||
// 加载全局配置中的授权方式和代理池统计
|
||||
useEffect(() => {
|
||||
const fetchAuthMethod = async () => {
|
||||
const fetchConfig = async () => {
|
||||
try {
|
||||
const res = await fetch('/api/config')
|
||||
const data = await res.json()
|
||||
if (data.code === 0 && data.data?.auth_method) {
|
||||
setAuthMethod(data.data.auth_method === 'api' ? 'api' : 'browser')
|
||||
const [configRes, proxyRes] = await Promise.all([
|
||||
fetch('/api/config'),
|
||||
fetch('/api/codex-proxy/stats'),
|
||||
])
|
||||
const configData = await configRes.json()
|
||||
if (configData.code === 0 && configData.data?.auth_method) {
|
||||
setAuthMethod(configData.data.auth_method === 'api' ? 'api' : 'browser')
|
||||
}
|
||||
const proxyData = await proxyRes.json()
|
||||
if (proxyData.code === 0 && proxyData.data) {
|
||||
setProxyPoolStats(proxyData.data)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch auth method:', error)
|
||||
console.error('Failed to fetch config:', error)
|
||||
}
|
||||
}
|
||||
fetchAuthMethod()
|
||||
fetchConfig()
|
||||
}, [])
|
||||
|
||||
const hasConfig = config.s2a.apiBase && config.s2a.adminKey
|
||||
@@ -214,7 +219,7 @@ export default function Upload() {
|
||||
concurrent_s2a: concurrentS2A, // 入库并发数
|
||||
browser_type: 'chromedp', // 固定使用 chromedp
|
||||
headless: true, // 始终使用无头模式
|
||||
proxy: useProxy ? globalProxy : '',
|
||||
proxy: useProxy ? 'pool:random' : '',
|
||||
include_owner: includeOwner, // 母号也入库
|
||||
process_count: processCount, // 处理数量,0表示全部
|
||||
}),
|
||||
@@ -232,7 +237,7 @@ export default function Upload() {
|
||||
alert('启动失败')
|
||||
}
|
||||
setLoading(false)
|
||||
}, [stats, membersPerTeam, concurrentTeams, concurrentS2A, useProxy, globalProxy, includeOwner, processCount, fetchStatus])
|
||||
}, [stats, membersPerTeam, concurrentTeams, concurrentS2A, useProxy, includeOwner, processCount, fetchStatus])
|
||||
|
||||
// 停止处理
|
||||
const handleStop = useCallback(async () => {
|
||||
@@ -508,12 +513,12 @@ export default function Upload() {
|
||||
<Switch
|
||||
checked={useProxy}
|
||||
onChange={setUseProxy}
|
||||
disabled={isRunning || !globalProxy}
|
||||
label="使用全局代理"
|
||||
disabled={isRunning || !proxyPoolStats || proxyPoolStats.enabled === 0}
|
||||
label="使用代理池"
|
||||
description={
|
||||
globalProxy
|
||||
? `当前代理: ${globalProxy}`
|
||||
: '请先在系统配置中设置代理地址(支持 http://host:port、http://user:pass@host:port、host:port:user:pass)'
|
||||
proxyPoolStats && proxyPoolStats.enabled > 0
|
||||
? `代理池: ${proxyPoolStats.enabled} 个可用 / ${proxyPoolStats.total} 个总计`
|
||||
: '代理池为空,请先在"代理池配置"页面添加代理'
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user