signal-batch/frontend/src/App.tsx
htlee 7a17d8e1d8 feat: Phase 4 — 비정상 항적 + 시스템 메트릭 페이지 (7/7 완성)
- AbnormalTracks: 유형별 통계, 일별 추이 차트, 검출 목록 테이블
  - abnormalApi 클라이언트 (recent, summary, types)
- ApiMetrics: 시스템 메트릭, 캐시 상세(L1/L2/L3/AIS), 처리 지연, 히트율
  - 10초 폴링으로 실시간 갱신
- i18n: abnormal.* 17키 + metrics.* 21키 한/영 추가
- 전체 7개 페이지 라우팅 완성 (Navbar 메뉴 전부 활성)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-19 19:19:51 +09:00

68 lines
2.9 KiB
TypeScript

import { lazy, Suspense, Component, type ReactNode } from 'react'
import { BrowserRouter, Routes, Route } from 'react-router-dom'
import { ThemeProvider } from './contexts/ThemeContext.tsx'
import { I18nProvider } from './i18n/I18nContext.tsx'
import AppLayout from './components/layout/AppLayout.tsx'
import LoadingSpinner from './components/common/LoadingSpinner.tsx'
const Dashboard = lazy(() => import('./pages/Dashboard.tsx'))
const JobMonitor = lazy(() => import('./pages/JobMonitor.tsx'))
const DataPipeline = lazy(() => import('./pages/DataPipeline.tsx'))
const AreaStats = lazy(() => import('./pages/AreaStats.tsx'))
const ApiExplorer = lazy(() => import('./pages/ApiExplorer.tsx'))
const AbnormalTracks = lazy(() => import('./pages/AbnormalTracks.tsx'))
const ApiMetrics = lazy(() => import('./pages/ApiMetrics.tsx'))
const BASE_URL = import.meta.env.VITE_BASE_URL || '/signal-batch'
/* React Error Boundary — 페이지 렌더링 에러 시 전체 앱 crash 방지 */
class ErrorBoundary extends Component<{ children: ReactNode }, { error: Error | null }> {
state: { error: Error | null } = { error: null }
static getDerivedStateFromError(error: Error) {
return { error }
}
render() {
if (this.state.error) {
return (
<div className="flex flex-col items-center justify-center gap-4 py-20">
<h2 className="text-xl font-bold text-danger">Rendering Error</h2>
<p className="text-sm text-muted">{this.state.error.message}</p>
<button
className="rounded bg-primary px-4 py-2 text-sm text-white"
onClick={() => this.setState({ error: null })}
>
Retry
</button>
</div>
)
}
return this.props.children
}
}
export default function App() {
return (
<I18nProvider>
<ThemeProvider>
<BrowserRouter basename={BASE_URL}>
<ErrorBoundary>
<Routes>
<Route element={<AppLayout />}>
<Route index element={<Suspense fallback={<LoadingSpinner />}><Dashboard /></Suspense>} />
<Route path="jobs" element={<Suspense fallback={<LoadingSpinner />}><JobMonitor /></Suspense>} />
<Route path="pipeline" element={<Suspense fallback={<LoadingSpinner />}><DataPipeline /></Suspense>} />
<Route path="area-stats" element={<Suspense fallback={<LoadingSpinner />}><AreaStats /></Suspense>} />
<Route path="api-explorer" element={<Suspense fallback={<LoadingSpinner />}><ApiExplorer /></Suspense>} />
<Route path="abnormal" element={<Suspense fallback={<LoadingSpinner />}><AbnormalTracks /></Suspense>} />
<Route path="metrics" element={<Suspense fallback={<LoadingSpinner />}><ApiMetrics /></Suspense>} />
</Route>
</Routes>
</ErrorBoundary>
</BrowserRouter>
</ThemeProvider>
</I18nProvider>
)
}