优化代理检查结果展示与地域检测文案

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
WrBug
2026-05-27 00:54:22 +08:00
co-authored by Cursor
parent 727fa7ed58
commit 83a415fb7e
16 changed files with 844 additions and 111 deletions
@@ -0,0 +1,139 @@
import { useMemo } from 'react'
import { Button, Popover, Space } from 'antd'
import { GlobalOutlined, LinkOutlined, LoadingOutlined, ReloadOutlined } from '@ant-design/icons'
import { useTranslation } from 'react-i18next'
import { useGeoblockCheck } from '../hooks/useGeoblockCheck'
const GEOBLOCK_DOCS_URL = 'https://docs.polymarket.com/api-reference/geoblock'
type StatusTone = 'loading' | 'ok' | 'blocked' | 'warn'
const TONE_COLOR: Record<StatusTone, string> = {
loading: 'rgba(255, 255, 255, 0.45)',
ok: '#52c41a',
blocked: '#ff7875',
warn: '#faad14'
}
function formatLocation(country: string, region: string): string {
if (country && region) {
return `${country}/${region}`
}
return country || region || '—'
}
interface GeoblockStatusTriggerProps {
iconSize?: number
dotSize?: number
}
const GeoblockStatusTrigger: React.FC<GeoblockStatusTriggerProps> = ({ iconSize = 18, dotSize = 12 }) => {
const { t } = useTranslation()
const { status, data, refresh, loading } = useGeoblockCheck(true)
const locationText = useMemo(() => {
if (!data) return '—'
return formatLocation(data.country, data.region)
}, [data])
let tone: StatusTone = 'loading'
let label = t('geoblock.checkingShort')
if (status === 'loading' || status === 'idle') {
tone = 'loading'
label = t('geoblock.checkingShort')
} else if (status === 'error') {
tone = 'warn'
label = t('geoblock.unknown.short')
} else if (data?.blocked) {
tone = 'blocked'
label = t('geoblock.menu.blocked', { location: locationText })
} else if (status === 'success' && data) {
tone = 'ok'
label = t('geoblock.menu.ok', { location: locationText })
}
const accent = TONE_COLOR[tone]
const popoverContent = (
<div style={{ maxWidth: 240 }}>
<div style={{ fontWeight: 500, marginBottom: 6 }}>{t('geoblock.title')}</div>
<div style={{ fontSize: 13, marginBottom: 8, lineHeight: 1.5 }}>{label}</div>
{data && (
<div style={{ fontSize: 12, color: 'rgba(0, 0, 0, 0.45)', marginBottom: 10 }}>
<div>IP: {data.ip || '—'}</div>
<div>{t('geoblock.location')}: {locationText}</div>
</div>
)}
<Space size={8}>
<Button
type="link"
size="small"
icon={loading ? <LoadingOutlined /> : <ReloadOutlined />}
onClick={() => refresh()}
loading={loading}
style={{ padding: 0, height: 'auto' }}
>
{t('geoblock.refresh')}
</Button>
<Button
type="link"
size="small"
icon={<LinkOutlined />}
href={GEOBLOCK_DOCS_URL}
target="_blank"
rel="noopener noreferrer"
style={{ padding: 0, height: 'auto' }}
>
{t('geoblock.viewDocsShort')}
</Button>
</Space>
</div>
)
return (
<Popover content={popoverContent} trigger="click" placement="bottom">
<button
type="button"
title={label}
aria-label={t('geoblock.title')}
style={{
position: 'relative',
color: '#fff',
fontSize: iconSize,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
background: 'none',
border: 'none',
cursor: 'pointer',
padding: 0,
lineHeight: 1
}}
>
{tone === 'loading' ? (
<LoadingOutlined style={{ fontSize: iconSize, color: accent }} />
) : (
<GlobalOutlined style={{ fontSize: iconSize }} />
)}
{tone !== 'loading' && (
<span
style={{
position: 'absolute',
right: -Math.round(dotSize / 3),
bottom: -Math.round(dotSize / 3),
width: dotSize,
height: dotSize,
borderRadius: '50%',
background: accent,
border: '2px solid #001529',
boxShadow: tone === 'ok' ? `0 0 5px ${accent}` : undefined
}}
/>
)}
</button>
</Popover>
)
}
export default GeoblockStatusTrigger
+8 -2
View File
@@ -31,6 +31,7 @@ import { removeToken, getVersionText, getVersionInfo } from '../utils'
import { wsManager } from '../services/websocket'
import { apiClient } from '../services/api'
import Logo from './Logo'
import GeoblockStatusTrigger from './GeoblockStatusTrigger'
const { Header, Content, Sider } = AntLayout
@@ -346,6 +347,7 @@ const Layout: React.FC<LayoutProps> = ({ children }) => {
>
<SendOutlined />
</a>
<GeoblockStatusTrigger iconSize={16} dotSize={10} />
<Button
type="text"
icon={<MenuOutlined />}
@@ -393,7 +395,9 @@ const Layout: React.FC<LayoutProps> = ({ children }) => {
position: 'fixed',
left: 0,
top: 0,
overflow: 'hidden'
overflow: 'hidden',
display: 'flex',
flexDirection: 'column'
}}
>
<div style={{
@@ -473,6 +477,7 @@ const Layout: React.FC<LayoutProps> = ({ children }) => {
>
<SendOutlined />
</a>
<GeoblockStatusTrigger iconSize={18} dotSize={12} />
</div>
</div>
<Menu
@@ -483,7 +488,8 @@ const Layout: React.FC<LayoutProps> = ({ children }) => {
items={menuItems}
onClick={handleMenuClick}
style={{
height: 'calc(100vh - 100px)',
flex: 1,
minHeight: 0,
borderRight: 0,
overflowY: 'auto'
}}
@@ -0,0 +1,181 @@
import { Alert, Tag, Typography } from 'antd'
import { CloseCircleOutlined, WarningOutlined } from '@ant-design/icons'
import { useTranslation } from 'react-i18next'
import { useMediaQuery } from 'react-responsive'
const { Text } = Typography
export interface ProxyCheckGeoblockResult {
checked: boolean
blocked?: boolean | null
ip?: string | null
country?: string | null
region?: string | null
message?: string | null
}
export interface ProxyCheckResponse {
success: boolean
message: string
responseTime?: number
latency?: number
geoblock?: ProxyCheckGeoblockResult | null
}
interface ProxyCheckResultAlertProps {
result: ProxyCheckResponse
style?: React.CSSProperties
}
interface ResultRowProps {
label: string
children: React.ReactNode
fullWidth?: boolean
isMobile: boolean
}
function formatLocation(country?: string | null, region?: string | null): string {
if (country && region) {
return `${country} / ${region}`
}
return country || region || '—'
}
function stripGeoblockSuffix(message: string, geoblockMessage?: string | null): string {
if (!geoblockMessage) {
return message
}
const suffix = `${geoblockMessage}`
if (message.endsWith(suffix)) {
return message.slice(0, -suffix.length)
}
const semi = message.indexOf('')
if (semi > 0) {
return message.slice(0, semi)
}
return message
}
const ResultRow: React.FC<ResultRowProps> = ({ label, children, fullWidth, isMobile }) => (
<div
style={{
gridColumn: fullWidth && !isMobile ? '1 / -1' : undefined,
display: 'flex',
alignItems: 'baseline',
gap: 12,
minWidth: 0,
lineHeight: 1.5
}}
>
<Text type="secondary" style={{ flexShrink: 0, width: isMobile ? 96 : 108, fontSize: 13 }}>
{label}
</Text>
<div style={{ flex: 1, minWidth: 0, fontSize: 13 }}>{children}</div>
</div>
)
const ProxyCheckResultAlert: React.FC<ProxyCheckResultAlertProps> = ({ result, style }) => {
const { t } = useTranslation()
const isMobile = useMediaQuery({ maxWidth: 768 })
const geoblock = result.geoblock
const geoblockChecked = Boolean(geoblock?.checked)
const geoblockBlocked = geoblockChecked && geoblock?.blocked === true
const geoblockUnknown = geoblockChecked && geoblock?.blocked == null
const alertType = !result.success ? 'error' : geoblockBlocked ? 'warning' : 'success'
const alertMessage = !result.success
? t('proxySettings.checkFailed')
: geoblockBlocked
? t('proxySettings.checkSuccessWithGeoblockWarning')
: t('proxySettings.checkSuccess')
const latencyMs = result.latency ?? result.responseTime
const connectionSummary = stripGeoblockSuffix(result.message, geoblock?.message)
const locationText = formatLocation(geoblock?.country, geoblock?.region)
const renderGeoblockValue = () => {
if (!geoblockChecked) {
return null
}
if (geoblockUnknown) {
return (
<Tag icon={<WarningOutlined />} color="warning">
{t('proxySettings.checkResult.geoblockUnknown')}
</Tag>
)
}
if (geoblockBlocked) {
return (
<Tag icon={<CloseCircleOutlined />} color="error">
{t('proxySettings.checkResult.geoblockBlocked')}
</Tag>
)
}
return <Text>{t('proxySettings.checkResult.geoblockOk')}</Text>
}
return (
<Alert
type={alertType}
message={alertMessage}
description={
<div
style={{
marginTop: 8,
display: 'grid',
gridTemplateColumns: isMobile ? '1fr' : '1fr 1fr',
gap: isMobile ? 10 : '10px 24px'
}}
>
<ResultRow label={t('proxySettings.checkResult.connection')} isMobile={isMobile}>
{result.success ? (
<Text>{t('proxySettings.checkResult.connected')}</Text>
) : (
<Tag icon={<CloseCircleOutlined />} color="error">
{t('proxySettings.checkResult.failed')}
</Tag>
)}
</ResultRow>
{latencyMs !== undefined && (
<ResultRow label={t('proxySettings.checkResult.latency')} isMobile={isMobile}>
<Text type={latencyMs >= 3000 ? 'warning' : undefined}>{latencyMs} ms</Text>
</ResultRow>
)}
{geoblockChecked && (
<>
<ResultRow label={t('proxySettings.geoblockTitle')} isMobile={isMobile}>
{renderGeoblockValue()}
</ResultRow>
<ResultRow label={t('geoblock.location')} isMobile={isMobile}>
<Text>{locationText}</Text>
</ResultRow>
{geoblock?.ip && (
<ResultRow label={t('geoblock.ip')} isMobile={isMobile} fullWidth>
<Text style={{ wordBreak: 'break-all' }}>{geoblock.ip}</Text>
</ResultRow>
)}
{geoblockUnknown && geoblock?.message && (
<ResultRow label={t('proxySettings.checkResult.detail')} isMobile={isMobile} fullWidth>
<Text type="secondary">{geoblock.message}</Text>
</ResultRow>
)}
</>
)}
{!result.success && connectionSummary && (
<ResultRow label={t('proxySettings.checkResult.detail')} isMobile={isMobile} fullWidth>
<Text type="secondary">{connectionSummary}</Text>
</ResultRow>
)}
</div>
}
style={style}
showIcon
/>
)
}
export default ProxyCheckResultAlert
+105
View File
@@ -0,0 +1,105 @@
import { useCallback, useEffect, useRef, useState } from 'react'
import { apiService } from '../services/api'
export interface GeoblockCheckResult {
blocked: boolean
ip: string
country: string
region: string
checkedAt: number
source: string
}
export type GeoblockCheckStatus = 'idle' | 'loading' | 'success' | 'error'
const CACHE_KEY = 'geoblock_check_cache'
const CACHE_TTL_MS = 5 * 60 * 1000
interface GeoblockCacheEntry {
data: GeoblockCheckResult
cachedAt: number
}
function readCache(): GeoblockCheckResult | null {
try {
const raw = sessionStorage.getItem(CACHE_KEY)
if (!raw) return null
const entry = JSON.parse(raw) as GeoblockCacheEntry
if (Date.now() - entry.cachedAt > CACHE_TTL_MS) {
sessionStorage.removeItem(CACHE_KEY)
return null
}
return entry.data
} catch {
return null
}
}
function writeCache(data: GeoblockCheckResult): void {
const entry: GeoblockCacheEntry = { data, cachedAt: Date.now() }
sessionStorage.setItem(CACHE_KEY, JSON.stringify(entry))
}
export function useGeoblockCheck(autoFetch = true) {
const [status, setStatus] = useState<GeoblockCheckStatus>('idle')
const [data, setData] = useState<GeoblockCheckResult | null>(null)
const [errorMessage, setErrorMessage] = useState<string | null>(null)
const fetchingRef = useRef(false)
const fetchGeoblock = useCallback(async (force = false) => {
if (fetchingRef.current) return
if (!force) {
const cached = readCache()
if (cached) {
setData(cached)
setStatus('success')
setErrorMessage(null)
return
}
}
fetchingRef.current = true
setStatus('loading')
setErrorMessage(null)
try {
const response = await apiService.proxyConfig.checkGeoblock()
if (response.data.code === 0 && response.data.data) {
const result: GeoblockCheckResult = {
blocked: response.data.data.blocked,
ip: response.data.data.ip,
country: response.data.data.country,
region: response.data.data.region,
checkedAt: response.data.data.checkedAt,
source: response.data.data.source ?? 'server'
}
writeCache(result)
setData(result)
setStatus('success')
} else {
setStatus('error')
setErrorMessage(response.data.msg ?? 'Geoblock check failed')
setData(null)
}
} catch (err: unknown) {
setStatus('error')
const message = err instanceof Error ? err.message : 'Geoblock check failed'
setErrorMessage(message)
setData(null)
} finally {
fetchingRef.current = false
}
}, [])
useEffect(() => {
if (autoFetch) {
fetchGeoblock()
}
}, [autoFetch, fetchGeoblock])
return {
status,
data,
errorMessage,
refresh: () => fetchGeoblock(true),
loading: status === 'loading'
}
}
+45 -1
View File
@@ -381,7 +381,21 @@
"saveSuccess": "Configuration saved successfully",
"saveFailed": "Failed to save configuration",
"getFailed": "Failed to get proxy configuration",
"latency": "Latency"
"latency": "Latency",
"checkResult": {
"connection": "Proxy connection",
"connected": "OK",
"failed": "Failed",
"latency": "Response time",
"detail": "Details",
"geoblockOk": "Can trade",
"geoblockBlocked": "Region restricted",
"geoblockUnknown": "Check failed"
},
"checkSuccessWithGeoblockWarning": "Proxy is OK, but trading region is restricted",
"geoblockTitle": "Geo check",
"geoblockAvailable": "Egress IP can trade ({{location}})",
"geoblockBlocked": "Egress IP is restricted and orders are blocked ({{location}})"
},
"configPage": {
"title": "Global Configuration",
@@ -1286,6 +1300,36 @@
"webhookUrlPlaceholder": "Webhook URL (from Slack App settings)",
"webhookUrlRequired": "Please enter Webhook URL"
},
"geoblock": {
"title": "Trading Region Check",
"checking": "Checking geographic restrictions for the trading server egress IP…",
"checkingShort": "Checking server region…",
"ip": "Detected IP",
"location": "Region",
"refresh": "Refresh",
"viewDocs": "View official geo restrictions",
"viewDocsShort": "Docs",
"sourceServer": "Source: trading server",
"menu": {
"ok": "Region OK · {{location}}",
"blocked": "Restricted · {{location}}"
},
"available": {
"title": "Trading server can place orders on Polymarket",
"description": "IP: {{ip}}, region: {{location}}",
"short": "Server OK · {{location}} · {{ip}}"
},
"blocked": {
"title": "Trading server IP is in a restricted region",
"description": "Polymarket rejects orders from restricted regions. Configure a compliant network or proxy for your trading server before copy trading.",
"short": "Server IP restricted ({{location}} · {{ip}}) — orders blocked"
},
"unknown": {
"title": "Could not complete region check",
"description": "Check server network, proxy settings, or try again later. You can still read announcements, but verify your network before copy trading.",
"short": "Region check failed"
}
},
"announcements": {
"title": "Announcements",
"noAnnouncements": "No announcements",
+44
View File
@@ -366,11 +366,25 @@
"passwordHelpUpdate": "留空则不更新密码,输入新密码则更新",
"check": "检查代理",
"checkSuccess": "代理检查成功",
"checkSuccessWithGeoblockWarning": "代理可用,但地域受限",
"checkFailed": "代理检查失败",
"geoblockTitle": "地域检测",
"geoblockAvailable": "出口 IP 可下单({{location}}",
"geoblockBlocked": "出口 IP 受限,无法下单({{location}}",
"saveSuccess": "保存配置成功",
"saveFailed": "保存配置失败",
"getFailed": "获取代理配置失败",
"latency": "延迟",
"checkResult": {
"connection": "代理连接",
"connected": "正常",
"failed": "失败",
"latency": "响应延迟",
"detail": "详情",
"geoblockOk": "可下单",
"geoblockBlocked": "地域受限",
"geoblockUnknown": "检测异常"
},
"hostInvalid": "请输入有效的主机地址",
"portInvalid": "端口必须在 1-65535 之间"
},
@@ -1286,6 +1300,36 @@
"webhookUrlPlaceholder": "Webhook URL(从 Slack App 设置中获取)",
"webhookUrlRequired": "请输入 Webhook URL"
},
"geoblock": {
"title": "交易地域检查",
"checking": "正在检测交易服务器出口 IP 的地域限制…",
"checkingShort": "正在检测服务器地域…",
"ip": "检测 IP",
"location": "地区",
"refresh": "刷新",
"viewDocs": "查看官方地域限制说明",
"viewDocsShort": "说明",
"sourceServer": "检测对象:交易服务器",
"menu": {
"ok": "地域可用 · {{location}}",
"blocked": "地域受限 · {{location}}"
},
"available": {
"title": "当前服务器网络可向 Polymarket 提交订单",
"description": "检测 IP{{ip}},地区:{{location}}",
"short": "服务器可交易 · {{location}} · {{ip}}"
},
"blocked": {
"title": "当前服务器 IP 所在地区无法向 Polymarket 下单",
"description": "Polymarket 会拒绝来自受限地区的订单。请为交易服务器配置合规的网络或代理环境后再进行跟单。",
"short": "服务器 IP 受限({{location}} · {{ip}}),无法下单"
},
"unknown": {
"title": "无法完成地域检测",
"description": "请检查服务器网络、代理配置或稍后重试。检测失败不会阻止您查看公告,但跟单前请自行确认网络环境。",
"short": "地域检测失败"
}
},
"announcements": {
"title": "公告",
"noAnnouncements": "暂无公告",
+45 -1
View File
@@ -381,7 +381,21 @@
"saveSuccess": "保存配置成功",
"saveFailed": "保存配置失敗",
"getFailed": "獲取代理配置失敗",
"latency": "延遲"
"latency": "延遲",
"checkResult": {
"connection": "代理連線",
"connected": "正常",
"failed": "失敗",
"latency": "回應延遲",
"detail": "詳情",
"geoblockOk": "可下單",
"geoblockBlocked": "地域受限",
"geoblockUnknown": "檢測異常"
},
"checkSuccessWithGeoblockWarning": "代理可用,但地域受限",
"geoblockTitle": "地域檢測",
"geoblockAvailable": "出口 IP 可下單({{location}}",
"geoblockBlocked": "出口 IP 受限,無法下單({{location}}"
},
"configPage": {
"title": "全局配置",
@@ -1286,6 +1300,36 @@
"webhookUrlPlaceholder": "Webhook URL(從 Slack App 設置中獲取)",
"webhookUrlRequired": "請輸入 Webhook URL"
},
"geoblock": {
"title": "交易地域檢查",
"checking": "正在檢測交易伺服器出口 IP 的地域限制…",
"checkingShort": "正在檢測伺服器地域…",
"ip": "檢測 IP",
"location": "地區",
"refresh": "刷新",
"viewDocs": "查看官方地域限制說明",
"viewDocsShort": "說明",
"sourceServer": "檢測對象:交易伺服器",
"menu": {
"ok": "地域可用 · {{location}}",
"blocked": "地域受限 · {{location}}"
},
"available": {
"title": "當前伺服器網路可向 Polymarket 提交訂單",
"description": "檢測 IP{{ip}},地區:{{location}}",
"short": "伺服器可交易 · {{location}} · {{ip}}"
},
"blocked": {
"title": "當前伺服器 IP 所在地區無法向 Polymarket 下單",
"description": "Polymarket 會拒絕來自受限地區的訂單。請為交易伺服器配置合規的網路或代理環境後再進行跟單。",
"short": "伺服器 IP 受限({{location}} · {{ip}}),無法下單"
},
"unknown": {
"title": "無法完成地域檢測",
"description": "請檢查伺服器網路、代理配置或稍後重試。檢測失敗不會阻止您查看公告,但跟單前請自行確認網路環境。",
"short": "地域檢測失敗"
}
},
"announcements": {
"title": "公告",
"noAnnouncements": "暫無公告",
-1
View File
@@ -6,7 +6,6 @@ import { apiService } from '../services/api'
import { useMediaQuery } from 'react-responsive'
import ReactMarkdown from 'react-markdown'
import remarkGfm from 'remark-gfm'
const { Title, Text } = Typography
interface Reactions {
+4 -27
View File
@@ -1,11 +1,12 @@
import { useEffect, useState } from 'react'
import { Card, Form, Button, Switch, Input, InputNumber, message, Typography, Space, Alert } from 'antd'
import { Card, Form, Button, Switch, Input, InputNumber, message, Typography, Space } from 'antd'
import { SaveOutlined, CheckCircleOutlined, ReloadOutlined } from '@ant-design/icons'
import { apiService } from '../services/api'
import { useTranslation } from 'react-i18next'
import { useMediaQuery } from 'react-responsive'
import ProxyCheckResultAlert, { type ProxyCheckResponse } from '../components/ProxyCheckResultAlert'
const { Title, Text } = Typography
const { Title } = Typography
interface ProxyConfig {
id?: number
@@ -20,13 +21,6 @@ interface ProxyConfig {
updatedAt: number
}
interface ProxyCheckResponse {
success: boolean
message: string
responseTime?: number
latency?: number
}
const ProxySettings: React.FC = () => {
const { t } = useTranslation()
const isMobile = useMediaQuery({ maxWidth: 768 })
@@ -211,24 +205,7 @@ const ProxySettings: React.FC = () => {
</Form>
{checkResult && (
<Alert
type={checkResult.success ? 'success' : 'error'}
message={checkResult.success ? (t('proxySettings.checkSuccess') || '代理检查成功') : (t('proxySettings.checkFailed') || '代理检查失败')}
description={
<div>
<Text>{checkResult.message}</Text>
{(checkResult.responseTime !== undefined || checkResult.latency !== undefined) && (
<div style={{ marginTop: '8px' }}>
<Text type="secondary">
{t('proxySettings.latency') || '延迟'}: {(checkResult.latency ?? checkResult.responseTime) ?? 0}ms
</Text>
</div>
)}
</div>
}
style={{ marginTop: '16px' }}
showIcon
/>
<ProxyCheckResultAlert result={checkResult} style={{ marginTop: '16px' }} />
)}
</Card>
</div>
+8 -27
View File
@@ -7,7 +7,7 @@ import { useMediaQuery } from 'react-responsive'
import { useTranslation } from 'react-i18next'
import type { SystemConfig, BuilderApiKeyUpdateRequest } from '../types'
import SystemUpdate from './SystemUpdate'
import ProxyCheckResultAlert, { type ProxyCheckResponse } from '../components/ProxyCheckResultAlert'
const { Title, Text, Paragraph } = Typography
interface ProxyConfig {
@@ -23,13 +23,6 @@ interface ProxyConfig {
updatedAt: number
}
interface ProxyCheckResponse {
success: boolean
message: string
responseTime?: number
latency?: number
}
const SystemSettings: React.FC = () => {
const { t, i18n: i18nInstance } = useTranslation()
const isMobile = useMediaQuery({ maxWidth: 768 })
@@ -228,7 +221,12 @@ const SystemSettings: React.FC = () => {
const result = response.data.data
setProxyCheckResult(result)
if (result.success) {
message.success(`代理检查成功:${result.message}${result.responseTime ? ` (响应时间: ${result.responseTime}ms)` : ''}`)
const geoblockHint = result.geoblock?.blocked
? `${result.geoblock.message ?? t('proxySettings.geoblockBlocked', { location: `${result.geoblock.country}/${result.geoblock.region}` })}`
: result.geoblock?.message
? `${result.geoblock.message}`
: ''
message.success(`代理检查成功:${result.message}${result.responseTime ? ` (响应时间: ${result.responseTime}ms)` : ''}${geoblockHint}`)
} else {
message.warning(`代理检查失败:${result.message}`)
}
@@ -576,24 +574,7 @@ const SystemSettings: React.FC = () => {
</Form>
{proxyCheckResult && (
<Alert
type={proxyCheckResult.success ? 'success' : 'error'}
message={proxyCheckResult.success ? (t('proxySettings.checkSuccess') || '代理检查成功') : (t('proxySettings.checkFailed') || '代理检查失败')}
description={
<div>
<Text>{proxyCheckResult.message}</Text>
{(proxyCheckResult.responseTime !== undefined || proxyCheckResult.latency !== undefined) && (
<div style={{ marginTop: '8px' }}>
<Text type="secondary">
{t('proxySettings.latency') || '延迟'}: {(proxyCheckResult.latency ?? proxyCheckResult.responseTime) ?? 0}ms
</Text>
</div>
)}
</div>
}
style={{ marginTop: '16px' }}
showIcon
/>
<ProxyCheckResultAlert result={proxyCheckResult} style={{ marginTop: '16px' }} />
)}
</Card>
+23 -1
View File
@@ -625,6 +625,15 @@ export const apiService = {
success: boolean
message: string
responseTime?: number
latency?: number
geoblock?: {
checked: boolean
blocked?: boolean | null
ip?: string | null
country?: string | null
region?: string | null
message?: string | null
} | null
}>>('/system/proxy/check', {}),
/**
@@ -645,7 +654,20 @@ export const apiService = {
message: string
responseTime?: number
}>
}>>('/system/proxy/api-health-check', {})
}>>('/system/proxy/api-health-check', {}),
/**
* IP Polymarket Geoblock
*/
checkGeoblock: () =>
apiClient.post<ApiResponse<{
blocked: boolean
ip: string
country: string
region: string
checkedAt: number
source: string
}>>('/system/proxy/geoblock-check', {})
},
/**