- 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>
68 lines
2.9 KiB
TypeScript
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>
|
|
)
|
|
}
|