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:
@@ -7,6 +7,7 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strconv"
|
"strconv"
|
||||||
@@ -98,6 +99,7 @@ func startServer(cfg *config.Config) {
|
|||||||
// 基础 API
|
// 基础 API
|
||||||
mux.HandleFunc("/api/health", api.CORS(handleHealth))
|
mux.HandleFunc("/api/health", api.CORS(handleHealth))
|
||||||
mux.HandleFunc("/api/config", api.CORS(handleConfig))
|
mux.HandleFunc("/api/config", api.CORS(handleConfig))
|
||||||
|
mux.HandleFunc("/api/proxy/test", api.CORS(handleProxyTest)) // 代理测试
|
||||||
|
|
||||||
// 日志 API
|
// 日志 API
|
||||||
mux.HandleFunc("/api/logs", api.CORS(handleGetLogs))
|
mux.HandleFunc("/api/logs", api.CORS(handleGetLogs))
|
||||||
@@ -126,8 +128,8 @@ func startServer(cfg *config.Config) {
|
|||||||
mux.HandleFunc("/api/upload/validate", api.CORS(api.HandleUploadValidate))
|
mux.HandleFunc("/api/upload/validate", api.CORS(api.HandleUploadValidate))
|
||||||
|
|
||||||
// 母号封禁检查 API
|
// 母号封禁检查 API
|
||||||
mux.HandleFunc("/api/db/owners/ban-check", api.CORS(api.HandleManualBanCheck)) // 手动触发检查
|
mux.HandleFunc("/api/db/owners/ban-check", api.CORS(api.HandleManualBanCheck)) // 手动触发检查
|
||||||
mux.HandleFunc("/api/db/owners/ban-check/status", api.CORS(api.HandleBanCheckStatus)) // 检查状态
|
mux.HandleFunc("/api/db/owners/ban-check/status", api.CORS(api.HandleBanCheckStatus)) // 检查状态
|
||||||
mux.HandleFunc("/api/db/owners/ban-check/settings", api.CORS(api.HandleBanCheckSettings)) // 配置
|
mux.HandleFunc("/api/db/owners/ban-check/settings", api.CORS(api.HandleBanCheckSettings)) // 配置
|
||||||
|
|
||||||
// 注册测试 API
|
// 注册测试 API
|
||||||
@@ -956,3 +958,84 @@ func handleCleanerSettings(w http.ResponseWriter, r *http.Request) {
|
|||||||
api.Error(w, http.StatusMethodNotAllowed, "不支持的方法")
|
api.Error(w, http.StatusMethodNotAllowed, "不支持的方法")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// handleProxyTest POST /api/proxy/test - 测试代理连接
|
||||||
|
func handleProxyTest(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if r.Method != http.MethodPost {
|
||||||
|
api.Error(w, http.StatusMethodNotAllowed, "仅支持 POST")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var req struct {
|
||||||
|
ProxyURL string `json:"proxy_url"`
|
||||||
|
}
|
||||||
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||||
|
api.Error(w, http.StatusBadRequest, "请求格式错误")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
proxyURL := req.ProxyURL
|
||||||
|
if proxyURL == "" {
|
||||||
|
api.Error(w, http.StatusBadRequest, "代理地址不能为空")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Info(fmt.Sprintf("测试代理连接: %s", proxyURL), "", "proxy")
|
||||||
|
|
||||||
|
// 解析代理 URL
|
||||||
|
proxyParsed, err := parseProxyURL(proxyURL)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error(fmt.Sprintf("代理地址格式错误: %v", err), "", "proxy")
|
||||||
|
api.Error(w, http.StatusBadRequest, fmt.Sprintf("代理地址格式错误: %v", err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建带代理的 HTTP 客户端
|
||||||
|
client := &http.Client{
|
||||||
|
Timeout: 15 * time.Second,
|
||||||
|
Transport: &http.Transport{
|
||||||
|
Proxy: http.ProxyURL(proxyParsed),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// 测试请求 - 使用 httpbin.org/ip 获取出口 IP
|
||||||
|
testURL := "https://httpbin.org/ip"
|
||||||
|
resp, err := client.Get(testURL)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error(fmt.Sprintf("代理连接失败: %v", err), "", "proxy")
|
||||||
|
api.Error(w, http.StatusBadGateway, fmt.Sprintf("代理连接失败: %v", err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
logger.Error(fmt.Sprintf("代理测试失败: HTTP %d", resp.StatusCode), "", "proxy")
|
||||||
|
api.Error(w, http.StatusBadGateway, fmt.Sprintf("代理测试失败: HTTP %d", resp.StatusCode))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解析响应获取出口 IP
|
||||||
|
var ipResp struct {
|
||||||
|
Origin string `json:"origin"`
|
||||||
|
}
|
||||||
|
if err := json.NewDecoder(resp.Body).Decode(&ipResp); err != nil {
|
||||||
|
logger.Warning(fmt.Sprintf("解析代理响应失败: %v", err), "", "proxy")
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Success(fmt.Sprintf("代理连接成功, 出口IP: %s", ipResp.Origin), "", "proxy")
|
||||||
|
|
||||||
|
api.Success(w, map[string]interface{}{
|
||||||
|
"connected": true,
|
||||||
|
"message": "代理连接成功",
|
||||||
|
"origin_ip": ipResp.Origin,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseProxyURL 解析代理 URL
|
||||||
|
func parseProxyURL(proxyURL string) (*url.URL, error) {
|
||||||
|
// 如果没有协议前缀,默认添加 http://
|
||||||
|
if !strings.HasPrefix(proxyURL, "http://") && !strings.HasPrefix(proxyURL, "https://") && !strings.HasPrefix(proxyURL, "socks5://") {
|
||||||
|
proxyURL = "http://" + proxyURL
|
||||||
|
}
|
||||||
|
return url.Parse(proxyURL)
|
||||||
|
}
|
||||||
|
|||||||
@@ -11,12 +11,13 @@ import (
|
|||||||
|
|
||||||
// MonitorSettings 监控设置
|
// MonitorSettings 监控设置
|
||||||
type MonitorSettings struct {
|
type MonitorSettings struct {
|
||||||
Target int `json:"target"`
|
Target int `json:"target"`
|
||||||
AutoAdd bool `json:"auto_add"`
|
AutoAdd bool `json:"auto_add"`
|
||||||
MinInterval int `json:"min_interval"`
|
MinInterval int `json:"min_interval"`
|
||||||
CheckInterval int `json:"check_interval"` // 自动补号检查间隔(秒)
|
CheckInterval int `json:"check_interval"` // 自动补号检查间隔(秒)
|
||||||
PollingEnabled bool `json:"polling_enabled"`
|
PollingEnabled bool `json:"polling_enabled"`
|
||||||
PollingInterval int `json:"polling_interval"`
|
PollingInterval int `json:"polling_interval"`
|
||||||
|
ReplenishUseProxy bool `json:"replenish_use_proxy"` // 补号时使用代理
|
||||||
}
|
}
|
||||||
|
|
||||||
// HandleGetMonitorSettings 获取监控设置
|
// HandleGetMonitorSettings 获取监控设置
|
||||||
@@ -32,12 +33,13 @@ func HandleGetMonitorSettings(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
settings := MonitorSettings{
|
settings := MonitorSettings{
|
||||||
Target: 50,
|
Target: 50,
|
||||||
AutoAdd: false,
|
AutoAdd: false,
|
||||||
MinInterval: 300,
|
MinInterval: 300,
|
||||||
CheckInterval: 60,
|
CheckInterval: 60,
|
||||||
PollingEnabled: false,
|
PollingEnabled: false,
|
||||||
PollingInterval: 60,
|
PollingInterval: 60,
|
||||||
|
ReplenishUseProxy: false,
|
||||||
}
|
}
|
||||||
|
|
||||||
if val, _ := database.Instance.GetConfig("monitor_target"); val != "" {
|
if val, _ := database.Instance.GetConfig("monitor_target"); val != "" {
|
||||||
@@ -66,6 +68,9 @@ func HandleGetMonitorSettings(w http.ResponseWriter, r *http.Request) {
|
|||||||
settings.PollingInterval = v
|
settings.PollingInterval = v
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if val, _ := database.Instance.GetConfig("monitor_replenish_use_proxy"); val == "true" {
|
||||||
|
settings.ReplenishUseProxy = true
|
||||||
|
}
|
||||||
|
|
||||||
Success(w, settings)
|
Success(w, settings)
|
||||||
}
|
}
|
||||||
@@ -120,6 +125,9 @@ func HandleSaveMonitorSettings(w http.ResponseWriter, r *http.Request) {
|
|||||||
if err := database.Instance.SetConfig("monitor_polling_interval", strconv.Itoa(settings.PollingInterval)); err != nil {
|
if err := database.Instance.SetConfig("monitor_polling_interval", strconv.Itoa(settings.PollingInterval)); err != nil {
|
||||||
saveErrors = append(saveErrors, "polling_interval: "+err.Error())
|
saveErrors = append(saveErrors, "polling_interval: "+err.Error())
|
||||||
}
|
}
|
||||||
|
if err := database.Instance.SetConfig("monitor_replenish_use_proxy", strconv.FormatBool(settings.ReplenishUseProxy)); err != nil {
|
||||||
|
saveErrors = append(saveErrors, "replenish_use_proxy: "+err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
if len(saveErrors) > 0 {
|
if len(saveErrors) > 0 {
|
||||||
errMsg := "保存监控设置部分失败: " + saveErrors[0]
|
errMsg := "保存监控设置部分失败: " + saveErrors[0]
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import (
|
|||||||
// S2AAccountItem S2A 账号信息
|
// S2AAccountItem S2A 账号信息
|
||||||
type S2AAccountItem struct {
|
type S2AAccountItem struct {
|
||||||
ID int `json:"id"`
|
ID int `json:"id"`
|
||||||
Email string `json:"email"`
|
Email string `json:"account"` // S2A API 返回的字段名是 account
|
||||||
Status string `json:"status"`
|
Status string `json:"status"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -46,6 +46,10 @@ export function ConfigProvider({ children }: { children: ReactNode }) {
|
|||||||
priority: serverConfig.priority || 0,
|
priority: serverConfig.priority || 0,
|
||||||
groupIds: serverConfig.group_ids || [],
|
groupIds: serverConfig.group_ids || [],
|
||||||
},
|
},
|
||||||
|
proxy: {
|
||||||
|
default: serverConfig.default_proxy || '',
|
||||||
|
enabled: serverConfig.proxy_enabled || false,
|
||||||
|
},
|
||||||
}))
|
}))
|
||||||
// 更新站点名称
|
// 更新站点名称
|
||||||
if (serverConfig.site_name) {
|
if (serverConfig.site_name) {
|
||||||
|
|||||||
@@ -10,7 +10,10 @@ import {
|
|||||||
XCircle,
|
XCircle,
|
||||||
Save,
|
Save,
|
||||||
Loader2,
|
Loader2,
|
||||||
Globe
|
Globe,
|
||||||
|
Wifi,
|
||||||
|
WifiOff,
|
||||||
|
HelpCircle
|
||||||
} from 'lucide-react'
|
} from 'lucide-react'
|
||||||
import { Card, CardHeader, CardTitle, CardContent, Button, Input } from '../components/common'
|
import { Card, CardHeader, CardTitle, CardContent, Button, Input } from '../components/common'
|
||||||
import { useConfig } from '../hooks/useConfig'
|
import { useConfig } from '../hooks/useConfig'
|
||||||
@@ -18,23 +21,29 @@ import { useConfig } from '../hooks/useConfig'
|
|||||||
export default function Config() {
|
export default function Config() {
|
||||||
const { config, isConnected, refreshConfig } = useConfig()
|
const { config, isConnected, refreshConfig } = useConfig()
|
||||||
const [siteName, setSiteName] = useState('')
|
const [siteName, setSiteName] = useState('')
|
||||||
|
const [defaultProxy, setDefaultProxy] = useState('')
|
||||||
const [saving, setSaving] = useState(false)
|
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)
|
const [message, setMessage] = useState<{ type: 'success' | 'error', text: string } | null>(null)
|
||||||
|
|
||||||
// 加载站点名称配置
|
// 加载站点名称和代理配置
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchSiteName = async () => {
|
const fetchConfig = async () => {
|
||||||
try {
|
try {
|
||||||
const res = await fetch('/api/config')
|
const res = await fetch('/api/config')
|
||||||
const data = await res.json()
|
const data = await res.json()
|
||||||
if (data.code === 0 && data.data) {
|
if (data.code === 0 && data.data) {
|
||||||
setSiteName(data.data.site_name || 'Codex Pool')
|
setSiteName(data.data.site_name || 'Codex Pool')
|
||||||
|
setDefaultProxy(data.data.default_proxy || '')
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} 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 = [
|
const configItems = [
|
||||||
{
|
{
|
||||||
to: '/config/s2a',
|
to: '/config/s2a',
|
||||||
@@ -178,6 +246,72 @@ export default function Config() {
|
|||||||
该名称将显示在侧边栏标题和浏览器标签页
|
该名称将显示在侧边栏标题和浏览器标签页
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</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>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
|
|||||||
@@ -67,6 +67,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 [globalProxy, setGlobalProxy] = useState('') // 全局代理地址(只读显示)
|
||||||
|
|
||||||
// 倒计时状态
|
// 倒计时状态
|
||||||
const [countdown, setCountdown] = useState(60)
|
const [countdown, setCountdown] = useState(60)
|
||||||
@@ -129,6 +131,7 @@ export default function Monitor() {
|
|||||||
check_interval: checkInterval,
|
check_interval: checkInterval,
|
||||||
polling_enabled: pollingEnabled,
|
polling_enabled: pollingEnabled,
|
||||||
polling_interval: pollingInterval,
|
polling_interval: pollingInterval,
|
||||||
|
replenish_use_proxy: replenishUseProxy,
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
const data = await res.json()
|
const data = await res.json()
|
||||||
@@ -253,6 +256,7 @@ export default function Monitor() {
|
|||||||
// 从后端加载监控设置
|
// 从后端加载监控设置
|
||||||
const loadMonitorSettings = async () => {
|
const loadMonitorSettings = async () => {
|
||||||
try {
|
try {
|
||||||
|
// 加载监控设置
|
||||||
const res = await fetch('/api/monitor/settings')
|
const res = await fetch('/api/monitor/settings')
|
||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
const json = await res.json()
|
const json = await res.json()
|
||||||
@@ -264,6 +268,7 @@ export default function Monitor() {
|
|||||||
const checkIntervalVal = s.check_interval || 60
|
const checkIntervalVal = s.check_interval || 60
|
||||||
const pollingEnabledVal = s.polling_enabled || false
|
const pollingEnabledVal = s.polling_enabled || false
|
||||||
const interval = s.polling_interval || 60
|
const interval = s.polling_interval || 60
|
||||||
|
const replenishUseProxyVal = s.replenish_use_proxy || false
|
||||||
|
|
||||||
setTargetInput(target)
|
setTargetInput(target)
|
||||||
setAutoAdd(autoAddVal)
|
setAutoAdd(autoAddVal)
|
||||||
@@ -271,11 +276,21 @@ export default function Monitor() {
|
|||||||
setCheckInterval(checkIntervalVal)
|
setCheckInterval(checkIntervalVal)
|
||||||
setPollingEnabled(pollingEnabledVal)
|
setPollingEnabled(pollingEnabledVal)
|
||||||
setPollingInterval(interval)
|
setPollingInterval(interval)
|
||||||
|
setReplenishUseProxy(replenishUseProxyVal)
|
||||||
savedPollingIntervalRef.current = interval
|
savedPollingIntervalRef.current = interval
|
||||||
setCountdown(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) {
|
} catch (e) {
|
||||||
@@ -511,6 +526,15 @@ export default function Monitor() {
|
|||||||
description="开启后,当号池不足时自动补充账号"
|
description="开启后,当号池不足时自动补充账号"
|
||||||
/>
|
/>
|
||||||
</div>
|
</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
|
<Input
|
||||||
label="最小间隔 (秒)"
|
label="最小间隔 (秒)"
|
||||||
type="number"
|
type="number"
|
||||||
|
|||||||
@@ -70,10 +70,13 @@ 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 [browserType, setBrowserType] = useState<'chromedp' | 'rod'>('chromedp')
|
const [browserType, setBrowserType] = useState<'chromedp' | 'rod'>('chromedp')
|
||||||
const [proxy, setProxy] = useState('')
|
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 globalProxy = config.proxy?.default || ''
|
||||||
|
|
||||||
const hasConfig = config.s2a.apiBase && config.s2a.adminKey
|
const hasConfig = config.s2a.apiBase && config.s2a.adminKey
|
||||||
|
|
||||||
// Load stats
|
// Load stats
|
||||||
@@ -191,7 +194,7 @@ export default function Upload() {
|
|||||||
concurrent_teams: Math.min(concurrentTeams, stats?.valid || 1),
|
concurrent_teams: Math.min(concurrentTeams, stats?.valid || 1),
|
||||||
browser_type: browserType,
|
browser_type: browserType,
|
||||||
headless: true, // 始终使用无头模式
|
headless: true, // 始终使用无头模式
|
||||||
proxy,
|
proxy: useProxy ? globalProxy : '',
|
||||||
include_owner: includeOwner, // 母号也入库
|
include_owner: includeOwner, // 母号也入库
|
||||||
process_count: processCount, // 处理数量,0表示全部
|
process_count: processCount, // 处理数量,0表示全部
|
||||||
}),
|
}),
|
||||||
@@ -209,7 +212,7 @@ export default function Upload() {
|
|||||||
alert('启动失败')
|
alert('启动失败')
|
||||||
}
|
}
|
||||||
setLoading(false)
|
setLoading(false)
|
||||||
}, [stats, membersPerTeam, concurrentTeams, browserType, proxy, includeOwner, processCount, fetchStatus])
|
}, [stats, membersPerTeam, concurrentTeams, browserType, useProxy, globalProxy, includeOwner, processCount, fetchStatus])
|
||||||
|
|
||||||
// 停止处理
|
// 停止处理
|
||||||
const handleStop = useCallback(async () => {
|
const handleStop = useCallback(async () => {
|
||||||
@@ -469,13 +472,15 @@ export default function Upload() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Input
|
<div className="p-3 rounded-lg bg-slate-50 dark:bg-slate-800/50 border border-slate-200 dark:border-slate-700">
|
||||||
label="代理地址(可选)"
|
<Switch
|
||||||
placeholder="http://127.0.0.1:7890"
|
checked={useProxy}
|
||||||
value={proxy}
|
onChange={setUseProxy}
|
||||||
onChange={(e) => setProxy(e.target.value)}
|
disabled={isRunning || !globalProxy}
|
||||||
disabled={isRunning}
|
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">
|
<div className="p-3 rounded-lg bg-slate-50 dark:bg-slate-800/50 border border-slate-200 dark:border-slate-700">
|
||||||
<Switch
|
<Switch
|
||||||
|
|||||||
@@ -175,6 +175,10 @@ export interface AppConfig {
|
|||||||
email: {
|
email: {
|
||||||
services: MailServiceConfig[] // 多个邮箱服务配置
|
services: MailServiceConfig[] // 多个邮箱服务配置
|
||||||
}
|
}
|
||||||
|
proxy: {
|
||||||
|
default: string // 全局默认代理地址
|
||||||
|
enabled: boolean // 是否启用代理
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 默认配置
|
// 默认配置
|
||||||
@@ -203,6 +207,10 @@ export const defaultConfig: AppConfig = {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
proxy: {
|
||||||
|
default: '',
|
||||||
|
enabled: false,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检查结果
|
// 检查结果
|
||||||
|
|||||||
Reference in New Issue
Block a user