feat: Implement a pool monitoring dashboard and file upload functionality.
This commit is contained in:
@@ -264,9 +264,19 @@ func handleS2AProxy(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 提取路径: /api/s2a/proxy/xxx -> /api/v1/admin/xxx
|
// 提取路径: /api/s2a/proxy/xxx -> 目标路径
|
||||||
path := strings.TrimPrefix(r.URL.Path, "/api/s2a/proxy")
|
path := strings.TrimPrefix(r.URL.Path, "/api/s2a/proxy")
|
||||||
targetURL := config.Global.S2AApiBase + "/api/v1/admin" + path
|
|
||||||
|
// 如果路径不是以 /api/ 开通的,默认补上 /api/v1/admin 开头(兼容 dashboard 统计等)
|
||||||
|
// 如果已经是 /api/ 开头(如 /api/pool/polling),则保持原样
|
||||||
|
var targetPath string
|
||||||
|
if strings.HasPrefix(path, "/api/") {
|
||||||
|
targetPath = path
|
||||||
|
} else {
|
||||||
|
targetPath = "/api/v1/admin" + path
|
||||||
|
}
|
||||||
|
|
||||||
|
targetURL := config.Global.S2AApiBase + targetPath
|
||||||
if r.URL.RawQuery != "" {
|
if r.URL.RawQuery != "" {
|
||||||
targetURL += "?" + r.URL.RawQuery
|
targetURL += "?" + r.URL.RawQuery
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ import {
|
|||||||
Clock,
|
Clock,
|
||||||
} 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 type { DashboardStats } from '../types'
|
import type { DashboardStats } from '../types'
|
||||||
|
|
||||||
interface PoolStatus {
|
interface PoolStatus {
|
||||||
@@ -50,7 +49,6 @@ interface AutoAddLog {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default function Monitor() {
|
export default function Monitor() {
|
||||||
const { config } = useConfig()
|
|
||||||
const [stats, setStats] = useState<DashboardStats | null>(null)
|
const [stats, setStats] = useState<DashboardStats | null>(null)
|
||||||
const [poolStatus, setPoolStatus] = useState<PoolStatus | null>(null)
|
const [poolStatus, setPoolStatus] = useState<PoolStatus | null>(null)
|
||||||
const [healthResults, setHealthResults] = useState<HealthCheckResult[]>([])
|
const [healthResults, setHealthResults] = useState<HealthCheckResult[]>([])
|
||||||
@@ -67,51 +65,54 @@ export default function Monitor() {
|
|||||||
const [pollingEnabled, setPollingEnabled] = useState(false)
|
const [pollingEnabled, setPollingEnabled] = useState(false)
|
||||||
const [pollingInterval, setPollingInterval] = useState(60)
|
const [pollingInterval, setPollingInterval] = useState(60)
|
||||||
|
|
||||||
const backendUrl = config.s2a.apiBase.replace(/\/api.*$/, '').replace(/:8080/, ':8088')
|
// 使用后端代理路径
|
||||||
|
const proxyBase = '/api/s2a/proxy'
|
||||||
|
|
||||||
|
// 辅助函数:解包 S2A 响应
|
||||||
|
const requestS2A = async (url: string, options: RequestInit = {}) => {
|
||||||
|
const res = await fetch(url, options)
|
||||||
|
if (!res.ok) throw new Error(`HTTP ${res.status}`)
|
||||||
|
const data = await res.json()
|
||||||
|
if (data && typeof data === 'object' && 'code' in data && 'data' in data) {
|
||||||
|
if (data.code !== 0) throw new Error(data.message || 'API error')
|
||||||
|
return data.data
|
||||||
|
}
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
|
||||||
// 获取号池状态
|
// 获取号池状态
|
||||||
const fetchPoolStatus = useCallback(async () => {
|
const fetchPoolStatus = useCallback(async () => {
|
||||||
try {
|
try {
|
||||||
const res = await fetch(`${backendUrl}/api/pool/status`)
|
const data = await requestS2A(`${proxyBase}/api/pool/status`)
|
||||||
if (res.ok) {
|
setPoolStatus(data)
|
||||||
const data = await res.json()
|
setTargetInput(data.target)
|
||||||
if (data.code === 0) {
|
setAutoAdd(data.auto_add)
|
||||||
setPoolStatus(data.data)
|
setMinInterval(data.min_interval)
|
||||||
setTargetInput(data.data.target)
|
setPollingEnabled(data.polling_enabled)
|
||||||
setAutoAdd(data.data.auto_add)
|
setPollingInterval(data.polling_interval)
|
||||||
setMinInterval(data.data.min_interval)
|
|
||||||
setPollingEnabled(data.data.polling_enabled)
|
|
||||||
setPollingInterval(data.data.polling_interval)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('获取号池状态失败:', e)
|
console.error('获取号池状态失败:', e)
|
||||||
}
|
}
|
||||||
}, [backendUrl])
|
}, [])
|
||||||
|
|
||||||
// 刷新 S2A 统计
|
// 刷新 S2A 统计
|
||||||
const refreshStats = useCallback(async () => {
|
const refreshStats = useCallback(async () => {
|
||||||
setRefreshing(true)
|
setRefreshing(true)
|
||||||
try {
|
try {
|
||||||
const res = await fetch(`${backendUrl}/api/pool/refresh`, { method: 'POST' })
|
const data = await requestS2A(`${proxyBase}/api/pool/refresh`, { method: 'POST' })
|
||||||
if (res.ok) {
|
setStats(data)
|
||||||
const data = await res.json()
|
|
||||||
if (data.code === 0) {
|
|
||||||
setStats(data.data)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
await fetchPoolStatus()
|
await fetchPoolStatus()
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('刷新统计失败:', e)
|
console.error('刷新统计失败:', e)
|
||||||
}
|
}
|
||||||
setRefreshing(false)
|
setRefreshing(false)
|
||||||
}, [backendUrl, fetchPoolStatus])
|
}, [fetchPoolStatus])
|
||||||
|
|
||||||
// 设置目标
|
// 设置目标
|
||||||
const handleSetTarget = async () => {
|
const handleSetTarget = async () => {
|
||||||
setLoading(true)
|
setLoading(true)
|
||||||
try {
|
try {
|
||||||
await fetch(`${backendUrl}/api/pool/target`, {
|
await requestS2A(`${proxyBase}/api/pool/target`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
@@ -131,7 +132,7 @@ export default function Monitor() {
|
|||||||
const handleTogglePolling = async () => {
|
const handleTogglePolling = async () => {
|
||||||
setLoading(true)
|
setLoading(true)
|
||||||
try {
|
try {
|
||||||
await fetch(`${backendUrl}/api/pool/polling`, {
|
await requestS2A(`${proxyBase}/api/pool/polling`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
@@ -151,19 +152,18 @@ export default function Monitor() {
|
|||||||
const handleHealthCheck = async (autoPause: boolean = false) => {
|
const handleHealthCheck = async (autoPause: boolean = false) => {
|
||||||
setCheckingHealth(true)
|
setCheckingHealth(true)
|
||||||
try {
|
try {
|
||||||
await fetch(`${backendUrl}/api/health-check/start`, {
|
await requestS2A(`${proxyBase}/api/health-check/start`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify({ auto_pause: autoPause }),
|
body: JSON.stringify({ auto_pause: autoPause }),
|
||||||
})
|
})
|
||||||
// 等待一会儿再获取结果
|
// 等待一会儿再获取结果
|
||||||
setTimeout(async () => {
|
setTimeout(async () => {
|
||||||
const res = await fetch(`${backendUrl}/api/health-check/results`)
|
try {
|
||||||
if (res.ok) {
|
const data = await requestS2A(`${proxyBase}/api/health-check/results`)
|
||||||
const data = await res.json()
|
setHealthResults(data || [])
|
||||||
if (data.code === 0) {
|
} catch (e) {
|
||||||
setHealthResults(data.data || [])
|
console.error('获取健康检查结果失败:', e)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
setCheckingHealth(false)
|
setCheckingHealth(false)
|
||||||
}, 5000)
|
}, 5000)
|
||||||
@@ -176,13 +176,8 @@ export default function Monitor() {
|
|||||||
// 获取自动补号日志
|
// 获取自动补号日志
|
||||||
const fetchAutoAddLogs = async () => {
|
const fetchAutoAddLogs = async () => {
|
||||||
try {
|
try {
|
||||||
const res = await fetch(`${backendUrl}/api/auto-add/logs`)
|
const data = await requestS2A(`${proxyBase}/api/auto-add/logs`)
|
||||||
if (res.ok) {
|
setAutoAddLogs(data || [])
|
||||||
const data = await res.json()
|
|
||||||
if (data.code === 0) {
|
|
||||||
setAutoAddLogs(data.data || [])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('获取日志失败:', e)
|
console.error('获取日志失败:', e)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user