feat: Introduce a new monitoring and configuration dashboard with backend API for autopool management and S2A integration.
This commit is contained in:
@@ -13,7 +13,9 @@ import {
|
||||
Globe,
|
||||
Wifi,
|
||||
WifiOff,
|
||||
HelpCircle
|
||||
HelpCircle,
|
||||
Zap,
|
||||
Monitor
|
||||
} from 'lucide-react'
|
||||
import { Card, CardHeader, CardTitle, CardContent, Button, Input } from '../components/common'
|
||||
import { useConfig } from '../hooks/useConfig'
|
||||
@@ -33,6 +35,8 @@ export default function Config() {
|
||||
const [teamRegProxyStatus, setTeamRegProxyStatus] = useState<'unknown' | 'success' | 'error'>('unknown')
|
||||
const [proxyOriginIP, setProxyOriginIP] = useState('')
|
||||
const [teamRegProxyIP, setTeamRegProxyIP] = useState('')
|
||||
const [authMethod, setAuthMethod] = useState<'api' | 'browser'>('browser')
|
||||
const [savingAuthMethod, setSavingAuthMethod] = useState(false)
|
||||
const { toasts, toast, removeToast } = useToast()
|
||||
|
||||
// 加载站点名称和代理配置
|
||||
@@ -61,6 +65,10 @@ export default function Config() {
|
||||
if (data.data.team_reg_proxy_test_ip) {
|
||||
setTeamRegProxyIP(data.data.team_reg_proxy_test_ip)
|
||||
}
|
||||
// 加载授权方式
|
||||
if (data.data.auth_method) {
|
||||
setAuthMethod(data.data.auth_method === 'api' ? 'api' : 'browser')
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch config:', error)
|
||||
@@ -204,6 +212,33 @@ export default function Config() {
|
||||
}
|
||||
}
|
||||
|
||||
// 保存授权方式
|
||||
const handleSaveAuthMethod = async (method: 'api' | 'browser') => {
|
||||
setSavingAuthMethod(true)
|
||||
const previousMethod = authMethod
|
||||
setAuthMethod(method) // 立即更新 UI
|
||||
try {
|
||||
const res = await fetch('/api/config', {
|
||||
method: 'PUT',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ auth_method: method }),
|
||||
})
|
||||
const data = await res.json()
|
||||
if (data.code === 0) {
|
||||
toast.success(`授权方式已切换为 ${method === 'api' ? 'CodexAuth API' : '浏览器模拟'}`)
|
||||
refreshConfig()
|
||||
} else {
|
||||
setAuthMethod(previousMethod) // 回滚
|
||||
toast.error(data.message || '保存失败')
|
||||
}
|
||||
} catch {
|
||||
setAuthMethod(previousMethod) // 回滚
|
||||
toast.error('网络错误')
|
||||
} finally {
|
||||
setSavingAuthMethod(false)
|
||||
}
|
||||
}
|
||||
|
||||
const configItems = [
|
||||
{
|
||||
to: '/config/s2a',
|
||||
@@ -311,7 +346,91 @@ export default function Config() {
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* 代理地址配置 */}
|
||||
{/* 授权方式配置 */}
|
||||
<div className="pt-4 border-t border-slate-200 dark:border-slate-700">
|
||||
<label className="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-3">
|
||||
授权方式
|
||||
</label>
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
{/* API 模式 - CodexAuth */}
|
||||
<button
|
||||
onClick={() => handleSaveAuthMethod('api')}
|
||||
disabled={savingAuthMethod}
|
||||
className={`relative flex flex-col items-center gap-2 p-4 rounded-xl border-2 transition-all duration-200 ${authMethod === 'api'
|
||||
? 'border-blue-500 bg-gradient-to-br from-blue-50 to-indigo-50 dark:from-blue-900/30 dark:to-indigo-900/30 shadow-sm'
|
||||
: 'border-slate-200 dark:border-slate-700 hover:border-blue-300 dark:hover:border-blue-800 hover:bg-slate-50 dark:hover:bg-slate-800/50'
|
||||
}`}
|
||||
>
|
||||
{authMethod === 'api' && (
|
||||
<div className="absolute top-2 right-2">
|
||||
<CheckCircle className="h-4 w-4 text-blue-500" />
|
||||
</div>
|
||||
)}
|
||||
<div className={`p-3 rounded-lg ${authMethod === 'api'
|
||||
? 'bg-blue-500 text-white'
|
||||
: 'bg-slate-100 dark:bg-slate-800 text-slate-600 dark:text-slate-400'
|
||||
}`}>
|
||||
<Zap className="h-6 w-6" />
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<div className={`font-semibold ${authMethod === 'api'
|
||||
? 'text-blue-700 dark:text-blue-300'
|
||||
: 'text-slate-700 dark:text-slate-300'
|
||||
}`}>
|
||||
CodexAuth API
|
||||
</div>
|
||||
<div className="text-xs text-slate-500 dark:text-slate-400 mt-1">
|
||||
纯 API 模式,快速稳定
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
|
||||
{/* 浏览器模式 */}
|
||||
<button
|
||||
onClick={() => handleSaveAuthMethod('browser')}
|
||||
disabled={savingAuthMethod}
|
||||
className={`relative flex flex-col items-center gap-2 p-4 rounded-xl border-2 transition-all duration-200 ${authMethod === 'browser'
|
||||
? 'border-emerald-500 bg-gradient-to-br from-emerald-50 to-green-50 dark:from-emerald-900/30 dark:to-green-900/30 shadow-sm'
|
||||
: 'border-slate-200 dark:border-slate-700 hover:border-emerald-300 dark:hover:border-emerald-800 hover:bg-slate-50 dark:hover:bg-slate-800/50'
|
||||
}`}
|
||||
>
|
||||
{authMethod === 'browser' && (
|
||||
<div className="absolute top-2 right-2">
|
||||
<CheckCircle className="h-4 w-4 text-emerald-500" />
|
||||
</div>
|
||||
)}
|
||||
<div className={`p-3 rounded-lg ${authMethod === 'browser'
|
||||
? 'bg-emerald-500 text-white'
|
||||
: 'bg-slate-100 dark:bg-slate-800 text-slate-600 dark:text-slate-400'
|
||||
}`}>
|
||||
<Monitor className="h-6 w-6" />
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<div className={`font-semibold ${authMethod === 'browser'
|
||||
? 'text-emerald-700 dark:text-emerald-300'
|
||||
: 'text-slate-700 dark:text-slate-300'
|
||||
}`}>
|
||||
浏览器模拟
|
||||
</div>
|
||||
<div className="text-xs text-slate-500 dark:text-slate-400 mt-1">
|
||||
Chromedp/Rod,兼容性好
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
{authMethod === 'api' && (
|
||||
<div className="mt-3 p-3 rounded-lg bg-gradient-to-r from-blue-500 to-indigo-500 text-white shadow-sm">
|
||||
<div className="flex items-center gap-2">
|
||||
<Zap className="h-4 w-4" />
|
||||
<span className="font-medium">CodexAuth API 模式已启用</span>
|
||||
</div>
|
||||
<p className="text-xs text-blue-100 mt-1">
|
||||
使用纯 API 调用进行授权,无需启动浏览器,速度更快、更稳定
|
||||
</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">
|
||||
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
CheckCircle,
|
||||
Clock,
|
||||
Save,
|
||||
Monitor as MonitorIcon,
|
||||
} from 'lucide-react'
|
||||
import { Card, CardHeader, CardTitle, CardContent, Button, Input, Switch } from '../components/common'
|
||||
import LiveLogViewer from '../components/LiveLogViewer'
|
||||
@@ -70,6 +71,7 @@ export default function Monitor() {
|
||||
const [replenishUseProxy, setReplenishUseProxy] = useState(false) // 补号时使用代理
|
||||
const [globalProxy, setGlobalProxy] = useState('') // 全局代理地址(只读显示)
|
||||
const [browserType, setBrowserType] = useState<'chromedp' | 'rod'>('chromedp') // 授权浏览器引擎
|
||||
const [authMethod, setAuthMethod] = useState<'api' | 'browser'>('browser') // 授权方式
|
||||
|
||||
// 倒计时状态
|
||||
const [countdown, setCountdown] = useState(60)
|
||||
@@ -264,6 +266,10 @@ export default function Monitor() {
|
||||
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')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -543,37 +549,36 @@ export default function Monitor() {
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
{/* 浏览器选择器 */}
|
||||
<div className="space-y-2">
|
||||
<label className="block text-sm font-medium text-slate-700 dark:text-slate-300">
|
||||
授权浏览器引擎
|
||||
</label>
|
||||
<div className="flex rounded-lg overflow-hidden border border-slate-200 dark:border-slate-700">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setBrowserType('chromedp')}
|
||||
disabled={!autoAdd}
|
||||
className={`flex-1 px-4 py-2.5 text-sm font-medium transition-all duration-200 ${browserType === 'chromedp'
|
||||
? 'bg-blue-600 text-white'
|
||||
: 'bg-slate-100 dark:bg-slate-800 text-slate-600 dark:text-slate-400 hover:bg-slate-200 dark:hover:bg-slate-700'
|
||||
} ${!autoAdd ? 'opacity-50 cursor-not-allowed' : ''}`}
|
||||
>
|
||||
Chromedp
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setBrowserType('rod')}
|
||||
disabled={!autoAdd}
|
||||
className={`flex-1 px-4 py-2.5 text-sm font-medium transition-all duration-200 border-l border-slate-200 dark:border-slate-700 ${browserType === 'rod'
|
||||
? 'bg-blue-600 text-white'
|
||||
: 'bg-slate-100 dark:bg-slate-800 text-slate-600 dark:text-slate-400 hover:bg-slate-200 dark:hover:bg-slate-700'
|
||||
} ${!autoAdd ? 'opacity-50 cursor-not-allowed' : ''}`}
|
||||
>
|
||||
Rod
|
||||
</button>
|
||||
{/* 授权方式状态显示 */}
|
||||
<div className={`p-4 rounded-lg border ${authMethod === 'api'
|
||||
? 'bg-gradient-to-r from-blue-50 to-indigo-50 dark:from-blue-900/20 dark:to-indigo-900/20 border-blue-200 dark:border-blue-800'
|
||||
: 'bg-gradient-to-r from-emerald-50 to-green-50 dark:from-emerald-900/20 dark:to-green-900/20 border-emerald-200 dark:border-emerald-800'
|
||||
}`}>
|
||||
<div className="flex items-center gap-3">
|
||||
{authMethod === 'api' ? (
|
||||
<>
|
||||
<div className="p-2 rounded-lg bg-blue-500 text-white">
|
||||
<Zap className="h-5 w-5" />
|
||||
</div>
|
||||
<div>
|
||||
<div className="font-semibold text-blue-700 dark:text-blue-300">CodexAuth API 模式</div>
|
||||
<div className="text-xs text-blue-600/70 dark:text-blue-400/70">纯 API 调用,无需浏览器,速度更快、更稳定</div>
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<div className="p-2 rounded-lg bg-emerald-500 text-white">
|
||||
<MonitorIcon className="h-5 w-5" />
|
||||
</div>
|
||||
<div>
|
||||
<div className="font-semibold text-emerald-700 dark:text-emerald-300">浏览器模拟模式 ({browserType})</div>
|
||||
<div className="text-xs text-emerald-600/70 dark:text-emerald-400/70">使用浏览器自动化进行授权,兼容性更好</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
<p className="text-xs text-slate-500 dark:text-slate-400">
|
||||
选择用于自动授权的浏览器引擎,Chromedp 为默认高效选项,Rod 为备选
|
||||
<p className="mt-2 text-xs text-slate-500 dark:text-slate-400">
|
||||
要切换授权方式,请在“基础配置”页面中修改
|
||||
</p>
|
||||
</div>
|
||||
<Input
|
||||
|
||||
@@ -10,6 +10,8 @@ import {
|
||||
Activity,
|
||||
CheckCircle,
|
||||
RefreshCw,
|
||||
Zap,
|
||||
Monitor,
|
||||
} from 'lucide-react'
|
||||
import { FileDropzone } from '../components/upload'
|
||||
import LogStream from '../components/upload/LogStream'
|
||||
@@ -70,14 +72,31 @@ export default function Upload() {
|
||||
const [membersPerTeam, setMembersPerTeam] = useState(4)
|
||||
const [concurrentTeams, setConcurrentTeams] = useState(2)
|
||||
const [concurrentS2A, setConcurrentS2A] = useState(2) // 入库并发数
|
||||
const [browserType, setBrowserType] = useState<'chromedp' | 'rod'>('chromedp')
|
||||
const [browserType] = useState<'chromedp' | 'rod'>('chromedp') // 浏览器引擎类型(只读)
|
||||
const [useProxy, setUseProxy] = useState(false) // 是否使用全局代理
|
||||
const [includeOwner, setIncludeOwner] = useState(false) // 母号也入库
|
||||
const [processCount, setProcessCount] = useState(0) // 处理数量,0表示全部
|
||||
const [authMethod, setAuthMethod] = useState<'api' | 'browser'>('browser') // 授权方式
|
||||
|
||||
// 获取全局代理地址
|
||||
const globalProxy = config.proxy?.default || ''
|
||||
|
||||
// 加载全局配置中的授权方式
|
||||
useEffect(() => {
|
||||
const fetchAuthMethod = 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')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch auth method:', error)
|
||||
}
|
||||
}
|
||||
fetchAuthMethod()
|
||||
}, [])
|
||||
|
||||
const hasConfig = config.s2a.apiBase && config.s2a.adminKey
|
||||
|
||||
// Load stats
|
||||
@@ -456,31 +475,33 @@ export default function Upload() {
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-2">
|
||||
浏览器引擎
|
||||
</label>
|
||||
<div className="flex gap-2">
|
||||
<button
|
||||
onClick={() => setBrowserType('chromedp')}
|
||||
disabled={isRunning}
|
||||
className={`flex-1 px-4 py-2 rounded-lg text-sm font-medium transition-colors ${browserType === 'chromedp'
|
||||
? 'bg-blue-500 text-white'
|
||||
: 'bg-slate-100 dark:bg-slate-800 text-slate-600 dark:text-slate-400 hover:bg-slate-200'
|
||||
}`}
|
||||
>
|
||||
Chromedp (推荐)
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setBrowserType('rod')}
|
||||
disabled={isRunning}
|
||||
className={`flex-1 px-4 py-2 rounded-lg text-sm font-medium transition-colors ${browserType === 'rod'
|
||||
? 'bg-blue-500 text-white'
|
||||
: 'bg-slate-100 dark:bg-slate-800 text-slate-600 dark:text-slate-400 hover:bg-slate-200'
|
||||
}`}
|
||||
>
|
||||
Rod
|
||||
</button>
|
||||
{/* 授权方式状态显示 */}
|
||||
<div className={`p-3 rounded-lg border ${authMethod === 'api'
|
||||
? 'bg-gradient-to-r from-blue-50 to-indigo-50 dark:from-blue-900/20 dark:to-indigo-900/20 border-blue-200 dark:border-blue-800'
|
||||
: 'bg-gradient-to-r from-emerald-50 to-green-50 dark:from-emerald-900/20 dark:to-green-900/20 border-emerald-200 dark:border-emerald-800'
|
||||
}`}>
|
||||
<div className="flex items-center gap-2">
|
||||
{authMethod === 'api' ? (
|
||||
<>
|
||||
<div className="p-1.5 rounded bg-blue-500 text-white">
|
||||
<Zap className="h-4 w-4" />
|
||||
</div>
|
||||
<div>
|
||||
<div className="font-medium text-blue-700 dark:text-blue-300">CodexAuth API 模式</div>
|
||||
<div className="text-xs text-blue-600/70 dark:text-blue-400/70">纯 API 调用,无需浏览器,速度更快</div>
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<div className="p-1.5 rounded bg-emerald-500 text-white">
|
||||
<Monitor className="h-4 w-4" />
|
||||
</div>
|
||||
<div>
|
||||
<div className="font-medium text-emerald-700 dark:text-emerald-300">浏览器模拟模式 ({browserType})</div>
|
||||
<div className="text-xs text-emerald-600/70 dark:text-emerald-400/70">使用浏览器自动化进行授权</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user