/** * GoldenChart Command Center — Master Plan [01] Standard + [04] System/Tech */ import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import type { Theme } from '../types'; import { loadDashboardSummary, type DashboardSummaryDto, type ProcessMonitorDto, type SystemMonitorDto, } from '../utils/backendApi'; import DashboardGauge from './dashboard/DashboardGauge'; import DashboardSegmentBar from './dashboard/DashboardSegmentBar'; import DashboardSparkChart from './dashboard/DashboardSparkChart'; import DashboardIndicatorBar from './dashboard/DashboardIndicatorBar'; import '../styles/strategyEditorTheme.css'; import '../styles/dashboardCommand.css'; interface Props { theme: Theme; onGoChart?: (market: string) => void; } const STATUS_LABEL: Record = { healthy: '정상', degraded: '주의', down: '중단', disabled: '비활성', }; const fmtKrw = (n: number) => n >= 1e8 ? `${(n / 1e8).toFixed(2)}억` : n >= 1e4 ? `${(n / 1e4).toFixed(0)}만` : n.toLocaleString('ko-KR'); const fmtPct = (v: number) => `${v >= 0 ? '+' : ''}${(v * 100).toFixed(1)}%`; function formatAgo(ms: number): string { if (!ms) return '—'; const sec = Math.floor((Date.now() - ms) / 1000); if (sec < 60) return `${sec}초 전`; if (sec < 3600) return `${Math.floor(sec / 60)}분 전`; return `${Math.floor(sec / 3600)}시간 전`; } function statusClass(s: string): string { if (s === 'healthy') return 'gc-proc--ok'; if (s === 'degraded') return 'gc-proc--warn'; if (s === 'down') return 'gc-proc--down'; return 'gc-proc--off'; } function healthLevel(mon: SystemMonitorDto): 'green' | 'yellow' | 'red' { if (!mon.pipelineEnabled) return 'red'; if (mon.jvm.heapPct > 85 || mon.queue.dropped > 0 || mon.staleMarkets.length > 5) return 'yellow'; if (mon.processes.some(p => p.status === 'down')) return 'red'; if (mon.processes.some(p => p.status === 'degraded')) return 'yellow'; return 'green'; } const ProcessCard: React.FC<{ proc: ProcessMonitorDto }> = ({ proc }) => (
{proc.layer} {STATUS_LABEL[proc.status] ?? proc.status}

{proc.name}

); const HISTORY_LEN = 48; const DashboardPage: React.FC = ({ theme, onGoChart }) => { const [data, setData] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const tickHistory = useRef([]); const wsHistory = useRef([]); const [, bump] = useState(0); const pushHistory = useCallback((ref: React.MutableRefObject, v: number) => { ref.current = [...ref.current.slice(-(HISTORY_LEN - 1)), v]; }, []); const refresh = useCallback(() => { setLoading(true); loadDashboardSummary() .then(d => { if (d?.systemMonitor) { pushHistory(tickHistory, d.systemMonitor.traffic.ticksProcessedPerSec ?? 0); pushHistory(wsHistory, d.systemMonitor.traffic.wsMessagesPerSec ?? 0); bump(n => n + 1); } setData(d); setError(null); }) .catch(e => setError((e as Error).message)) .finally(() => setLoading(false)); }, [pushHistory]); useEffect(() => { refresh(); const id = window.setInterval(refresh, 5_000); return () => window.clearInterval(id); }, [refresh]); const mon = data?.systemMonitor; const app = data?.app as Record | undefined; const health = mon ? healthLevel(mon) : 'yellow'; const latencyMs = useMemo(() => { if (!mon) return 0; const tps = mon.traffic.ticksProcessedPerSec ?? 0; return tps > 0 ? Math.max(12, Math.round(1000 / Math.max(tps, 1))) : 45; }, [mon]); const stompConnections = data?.monitoredMarkets ?? 0; const ta4jPct = mon && mon.memory.maxBarsPerSeries > 0 ? Math.min(100, (mon.memory.totalBars / (mon.memory.series * mon.memory.maxBarsPerSeries || 1)) * 100) : 0; return (
[01] Standard Dashboard

GoldenChart Command Center

실시간 투자 분석 · 파이프라인 모니터링 · 시스템 헬스체크

Global Latency: {latencyMs}ms Active Markets: {stompConnections} {mon && ( Pipeline {mon.pipelineEnabled ? 'ON' : 'OFF'} {mon.sampledAtMs ? ` · ${formatAgo(mon.sampledAtMs)}` : ''} )}
{error &&

{error}

} {data && mon && (
{/* ── LEFT: Portfolio + Indicators ── */} {/* ── CENTER: Infrastructure + Chart ── */}

[04] 데이터 파이프라인 · Ta4j 엔진

0.7 ? 'amber' : 'cyan'} />

멀티스레드 프로세스

{mon.processes.map(p => )}
{mon.staleMarkets.length > 0 && (

틱 정체 종목 ({mon.staleMarkets.length})

{mon.staleMarkets.slice(0, 8).map(s => ( ))}
)}
{/* ── RIGHT: Gauges + Signals ── */}
)}
); }; export default DashboardPage;