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
|
// CodexAuth 代理池 API
|
||||||
mux.HandleFunc("/api/codex-proxy", api.CORS(api.HandleCodexProxies))
|
mux.HandleFunc("/api/codex-proxy", api.CORS(api.HandleCodexProxies))
|
||||||
mux.HandleFunc("/api/codex-proxy/test", 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
|
// Owner 降级 API
|
||||||
mux.HandleFunc("/api/demote/owner", api.CORS(api.HandleDemoteOwner))
|
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 获取代理列表
|
// listCodexProxies 获取代理列表
|
||||||
func listCodexProxies(w http.ResponseWriter, r *http.Request) {
|
func listCodexProxies(w http.ResponseWriter, r *http.Request) {
|
||||||
proxies, err := database.Instance.GetCodexProxies()
|
proxies, err := database.Instance.GetCodexProxies()
|
||||||
|
|||||||
@@ -855,8 +855,14 @@ func processSingleTeam(idx int, req TeamProcessRequest) (result TeamProcessResul
|
|||||||
return false
|
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 {
|
if err != nil {
|
||||||
logger.Error(fmt.Sprintf("%s [注册失败] %v", memberLogPrefix, err), currentEmail, "team")
|
logger.Error(fmt.Sprintf("%s [注册失败] %v", memberLogPrefix, err), currentEmail, "team")
|
||||||
continue
|
continue
|
||||||
|
|||||||
@@ -54,8 +54,8 @@ export default function Monitor() {
|
|||||||
const [checkInterval, setCheckInterval] = useState(60)
|
const [checkInterval, setCheckInterval] = useState(60)
|
||||||
const [pollingEnabled, setPollingEnabled] = useState(false)
|
const [pollingEnabled, setPollingEnabled] = useState(false)
|
||||||
const [pollingInterval, setPollingInterval] = useState(60)
|
const [pollingInterval, setPollingInterval] = useState(60)
|
||||||
const [replenishUseProxy, setReplenishUseProxy] = useState(false) // 补号时使用代理
|
const [replenishUseProxy, setReplenishUseProxy] = useState(false) // 补号时使用代理池
|
||||||
const [globalProxy, setGlobalProxy] = useState('') // 全局代理地址(只读显示)
|
const [proxyPoolStats, setProxyPoolStats] = useState<{ total: number; enabled: number } | null>(null) // 代理池统计
|
||||||
const [authMethod, setAuthMethod] = useState<'api' | 'browser'>('browser') // 授权方式
|
const [authMethod, setAuthMethod] = useState<'api' | 'browser'>('browser') // 授权方式
|
||||||
|
|
||||||
// 自动补号配置
|
// 自动补号配置
|
||||||
@@ -238,12 +238,16 @@ export default function Monitor() {
|
|||||||
// 从后端加载监控设置
|
// 从后端加载监控设置
|
||||||
const loadMonitorSettings = async () => {
|
const loadMonitorSettings = async () => {
|
||||||
try {
|
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) {
|
if (configRes.ok) {
|
||||||
const configJson = await configRes.json()
|
const configJson = await configRes.json()
|
||||||
if (configJson.code === 0 && configJson.data) {
|
if (configJson.code === 0 && configJson.data) {
|
||||||
setGlobalProxy(configJson.data.default_proxy || '')
|
|
||||||
// 加载授权方式配置
|
// 加载授权方式配置
|
||||||
if (configJson.data.auth_method) {
|
if (configJson.data.auth_method) {
|
||||||
setAuthMethod(configJson.data.auth_method === 'api' ? 'api' : 'browser')
|
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 (settingsRes.ok) {
|
||||||
if (res.ok) {
|
const json = await settingsRes.json()
|
||||||
const json = await res.json()
|
|
||||||
if (json.code === 0 && json.data) {
|
if (json.code === 0 && json.data) {
|
||||||
const s = json.data
|
const s = json.data
|
||||||
const target = s.target || 50
|
const target = s.target || 50
|
||||||
@@ -525,12 +535,12 @@ export default function Monitor() {
|
|||||||
<Switch
|
<Switch
|
||||||
checked={replenishUseProxy}
|
checked={replenishUseProxy}
|
||||||
onChange={setReplenishUseProxy}
|
onChange={setReplenishUseProxy}
|
||||||
disabled={!autoAdd || !globalProxy}
|
disabled={!autoAdd || !proxyPoolStats || proxyPoolStats.enabled === 0}
|
||||||
label="补号时使用代理"
|
label="补号时使用代理池"
|
||||||
description={
|
description={
|
||||||
globalProxy
|
proxyPoolStats && proxyPoolStats.enabled > 0
|
||||||
? `当前代理: ${globalProxy}`
|
? `代理池: ${proxyPoolStats.enabled} 个可用 / ${proxyPoolStats.total} 个总计`
|
||||||
: '请先在系统配置中设置代理地址(支持 http://host:port、http://user:pass@host:port、host:port:user:pass)'
|
: '代理池为空,请先在"代理池配置"页面添加代理'
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -757,10 +767,10 @@ export default function Monitor() {
|
|||||||
type="checkbox"
|
type="checkbox"
|
||||||
checked={autoRegUseProxy}
|
checked={autoRegUseProxy}
|
||||||
onChange={(e) => setAutoRegUseProxy(e.target.checked)}
|
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"
|
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>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -72,28 +72,33 @@ export default function Upload() {
|
|||||||
const [membersPerTeam, setMembersPerTeam] = useState(4)
|
const [membersPerTeam, setMembersPerTeam] = useState(4)
|
||||||
const [concurrentTeams, setConcurrentTeams] = useState(2)
|
const [concurrentTeams, setConcurrentTeams] = useState(2)
|
||||||
const [concurrentS2A, setConcurrentS2A] = useState(2) // 入库并发数
|
const [concurrentS2A, setConcurrentS2A] = useState(2) // 入库并发数
|
||||||
const [useProxy, setUseProxy] = useState(false) // 是否使用全局代理
|
const [useProxy, setUseProxy] = useState(false) // 是否使用代理池
|
||||||
const [includeOwner, setIncludeOwner] = useState(false) // 母号也入库
|
const [includeOwner, setIncludeOwner] = useState(false) // 母号也入库
|
||||||
const [processCount, setProcessCount] = useState(0) // 处理数量,0表示全部
|
const [processCount, setProcessCount] = useState(0) // 处理数量,0表示全部
|
||||||
const [authMethod, setAuthMethod] = useState<'api' | 'browser'>('browser') // 授权方式
|
const [authMethod, setAuthMethod] = useState<'api' | 'browser'>('browser') // 授权方式
|
||||||
|
const [proxyPoolStats, setProxyPoolStats] = useState<{ total: number; enabled: number } | null>(null)
|
||||||
|
|
||||||
// 获取全局代理地址
|
// 加载全局配置中的授权方式和代理池统计
|
||||||
const globalProxy = config.proxy?.default || ''
|
|
||||||
|
|
||||||
// 加载全局配置中的授权方式
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchAuthMethod = async () => {
|
const fetchConfig = async () => {
|
||||||
try {
|
try {
|
||||||
const res = await fetch('/api/config')
|
const [configRes, proxyRes] = await Promise.all([
|
||||||
const data = await res.json()
|
fetch('/api/config'),
|
||||||
if (data.code === 0 && data.data?.auth_method) {
|
fetch('/api/codex-proxy/stats'),
|
||||||
setAuthMethod(data.data.auth_method === 'api' ? 'api' : 'browser')
|
])
|
||||||
|
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) {
|
} 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
|
const hasConfig = config.s2a.apiBase && config.s2a.adminKey
|
||||||
@@ -214,7 +219,7 @@ export default function Upload() {
|
|||||||
concurrent_s2a: concurrentS2A, // 入库并发数
|
concurrent_s2a: concurrentS2A, // 入库并发数
|
||||||
browser_type: 'chromedp', // 固定使用 chromedp
|
browser_type: 'chromedp', // 固定使用 chromedp
|
||||||
headless: true, // 始终使用无头模式
|
headless: true, // 始终使用无头模式
|
||||||
proxy: useProxy ? globalProxy : '',
|
proxy: useProxy ? 'pool:random' : '',
|
||||||
include_owner: includeOwner, // 母号也入库
|
include_owner: includeOwner, // 母号也入库
|
||||||
process_count: processCount, // 处理数量,0表示全部
|
process_count: processCount, // 处理数量,0表示全部
|
||||||
}),
|
}),
|
||||||
@@ -232,7 +237,7 @@ export default function Upload() {
|
|||||||
alert('启动失败')
|
alert('启动失败')
|
||||||
}
|
}
|
||||||
setLoading(false)
|
setLoading(false)
|
||||||
}, [stats, membersPerTeam, concurrentTeams, concurrentS2A, useProxy, globalProxy, includeOwner, processCount, fetchStatus])
|
}, [stats, membersPerTeam, concurrentTeams, concurrentS2A, useProxy, includeOwner, processCount, fetchStatus])
|
||||||
|
|
||||||
// 停止处理
|
// 停止处理
|
||||||
const handleStop = useCallback(async () => {
|
const handleStop = useCallback(async () => {
|
||||||
@@ -508,12 +513,12 @@ export default function Upload() {
|
|||||||
<Switch
|
<Switch
|
||||||
checked={useProxy}
|
checked={useProxy}
|
||||||
onChange={setUseProxy}
|
onChange={setUseProxy}
|
||||||
disabled={isRunning || !globalProxy}
|
disabled={isRunning || !proxyPoolStats || proxyPoolStats.enabled === 0}
|
||||||
label="使用全局代理"
|
label="使用代理池"
|
||||||
description={
|
description={
|
||||||
globalProxy
|
proxyPoolStats && proxyPoolStats.enabled > 0
|
||||||
? `当前代理: ${globalProxy}`
|
? `代理池: ${proxyPoolStats.enabled} 个可用 / ${proxyPoolStats.total} 个总计`
|
||||||
: '请先在系统配置中设置代理地址(支持 http://host:port、http://user:pass@host:port、host:port:user:pass)'
|
: '代理池为空,请先在"代理池配置"页面添加代理'
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user