mirror of
https://github.com/NoFxAiOS/nofx.git
synced 2026-07-06 04:20:59 +08:00
- Redesign dashboard into a cream-paper + vermilion IBM Plex Mono terminal (live L2 order book, cost/liq map, WS K-line, signal matrix, orchestration topology, risk radar, execution log, current positions, equity curve) - Convert all user-facing UI and backend strings/prompts from Chinese to English (multi-language retained, default English) - Add /api/statistics/full endpoint + full-stats frontend wiring - Fix Autopilot launch: reuse the existing trader instead of creating duplicates (eliminates repeat ~35s create cost and stale-trader 404s); launch sends 5m scan interval - Fix unreadable toasts: cream theme with high-contrast text + per-type accent - Silence background dashboard polls (getTraderConfig) to stop error-toast spam
162 lines
5.0 KiB
TypeScript
162 lines
5.0 KiB
TypeScript
import { useCallback, useEffect, useState, type ReactNode } from 'react'
|
|
import { Loader2, ShieldAlert, ShieldCheck, ShieldMinus } from 'lucide-react'
|
|
import { CryptoService, diagnoseWebCryptoEnvironment } from '../../lib/crypto'
|
|
import { t, type Language } from '../../i18n/translations'
|
|
|
|
export type WebCryptoCheckStatus =
|
|
| 'idle'
|
|
| 'checking'
|
|
| 'secure'
|
|
| 'insecure'
|
|
| 'unsupported'
|
|
| 'disabled' // Transport encryption disabled
|
|
|
|
interface WebCryptoEnvironmentCheckProps {
|
|
language: Language
|
|
variant?: 'card' | 'compact'
|
|
onStatusChange?: (status: WebCryptoCheckStatus) => void
|
|
}
|
|
|
|
export function WebCryptoEnvironmentCheck({
|
|
language,
|
|
variant = 'card',
|
|
onStatusChange,
|
|
}: WebCryptoEnvironmentCheckProps) {
|
|
const [status, setStatus] = useState<WebCryptoCheckStatus>('idle')
|
|
const [summary, setSummary] = useState<string | null>(null)
|
|
|
|
useEffect(() => {
|
|
onStatusChange?.(status)
|
|
}, [onStatusChange, status])
|
|
|
|
const runCheck = useCallback(async () => {
|
|
setStatus('checking')
|
|
setSummary(null)
|
|
|
|
try {
|
|
// First check if transport encryption is enabled on the server
|
|
const config = await CryptoService.fetchCryptoConfig()
|
|
|
|
if (!config.transport_encryption) {
|
|
setStatus('disabled')
|
|
return
|
|
}
|
|
|
|
const result = diagnoseWebCryptoEnvironment()
|
|
setSummary(
|
|
t('environmentCheck.summary', language, {
|
|
origin: result.origin || 'N/A',
|
|
protocol: result.protocol || 'unknown',
|
|
})
|
|
)
|
|
|
|
if (!result.isBrowser || !result.hasSubtleCrypto) {
|
|
setStatus('unsupported')
|
|
return
|
|
}
|
|
|
|
if (!result.isSecureContext) {
|
|
setStatus('insecure')
|
|
return
|
|
}
|
|
|
|
setStatus('secure')
|
|
} catch {
|
|
// If we can't fetch config, assume encryption is disabled
|
|
setStatus('disabled')
|
|
}
|
|
}, [language])
|
|
|
|
useEffect(() => {
|
|
runCheck()
|
|
}, [runCheck])
|
|
|
|
const isCompact = variant === 'compact'
|
|
const containerClass = isCompact
|
|
? 'p-3 rounded border border-[rgba(26,24,19,0.14)] bg-nofx-bg-lighter space-y-3'
|
|
: 'p-4 rounded border border-[rgba(26,24,19,0.14)] bg-nofx-bg-lighter space-y-4'
|
|
|
|
const descriptionColor = isCompact ? '#8A8478' : '#8A8478'
|
|
const showInfo = status !== 'idle'
|
|
|
|
const statusRendererMap: Record<WebCryptoCheckStatus, () => ReactNode> = {
|
|
secure: () => (
|
|
<div className="flex items-start gap-2 text-[#2E8B57] text-xs">
|
|
<ShieldCheck className="w-4 h-4 flex-shrink-0" />
|
|
<div>
|
|
<div className="font-semibold">
|
|
{t('environmentCheck.secureTitle', language)}
|
|
</div>
|
|
<div>{t('environmentCheck.secureDesc', language)}</div>
|
|
</div>
|
|
</div>
|
|
),
|
|
insecure: () => (
|
|
<div className="text-xs" style={{ color: '#F59E0B' }}>
|
|
<div className="flex items-start gap-2 mb-1">
|
|
<ShieldAlert className="w-4 h-4 flex-shrink-0" />
|
|
<div className="font-semibold">
|
|
{t('environmentCheck.insecureTitle', language)}
|
|
</div>
|
|
</div>
|
|
<div>{t('environmentCheck.insecureDesc', language)}</div>
|
|
<div className="mt-2 font-semibold">
|
|
{t('environmentCheck.tipsTitle', language)}
|
|
</div>
|
|
<ul className="list-disc pl-5 space-y-1 mt-1">
|
|
<li>{t('environmentCheck.tipHTTPS', language)}</li>
|
|
<li>{t('environmentCheck.tipLocalhost', language)}</li>
|
|
<li>{t('environmentCheck.tipIframe', language)}</li>
|
|
</ul>
|
|
</div>
|
|
),
|
|
unsupported: () => (
|
|
<div className="text-xs" style={{ color: '#D6433A' }}>
|
|
<div className="flex items-start gap-2 mb-1">
|
|
<ShieldAlert className="w-4 h-4 flex-shrink-0" />
|
|
<div className="font-semibold">
|
|
{t('environmentCheck.unsupportedTitle', language)}
|
|
</div>
|
|
</div>
|
|
<div>{t('environmentCheck.unsupportedDesc', language)}</div>
|
|
</div>
|
|
),
|
|
disabled: () => (
|
|
<div className="flex items-start gap-2 text-nofx-text-muted text-xs">
|
|
<ShieldMinus className="w-4 h-4 flex-shrink-0" />
|
|
<div>
|
|
<div className="font-semibold">
|
|
{t('environmentCheck.disabledTitle', language)}
|
|
</div>
|
|
<div>{t('environmentCheck.disabledDesc', language)}</div>
|
|
</div>
|
|
</div>
|
|
),
|
|
checking: () => (
|
|
<div
|
|
className="flex items-center gap-2 text-xs"
|
|
style={{ color: '#1A1813' }}
|
|
>
|
|
<Loader2 className="w-4 h-4 animate-spin" />
|
|
<span>{t('environmentCheck.checking', language)}</span>
|
|
</div>
|
|
),
|
|
idle: () => null,
|
|
}
|
|
|
|
const renderStatus = () => statusRendererMap[status]()
|
|
|
|
return (
|
|
<div className={containerClass}>
|
|
<div className="flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between">
|
|
{showInfo && (
|
|
<div className="text-xs" style={{ color: descriptionColor }}>
|
|
{summary ?? t('environmentCheck.description', language)}
|
|
</div>
|
|
)}
|
|
</div>
|
|
{showInfo && <div className="min-h-[1.5rem]">{renderStatus()}</div>}
|
|
</div>
|
|
)
|
|
}
|