fix: add ErrorBoundary to isolate client-side rendering errors
CI/CD / build-and-push (push) Successful in 1m31s
Details
CI/CD / build-and-push (push) Successful in 1m31s
Details
This commit is contained in:
parent
f9605360c5
commit
7a0b1841f9
|
|
@ -8,6 +8,7 @@ import TimeSeriesChart from '@/components/charts/TimeSeriesChart';
|
|||
import NetworkChart from '@/components/charts/NetworkChart';
|
||||
import NodeTable from '@/components/report/NodeTable';
|
||||
import AlertSection from '@/components/report/AlertSection';
|
||||
import ErrorBoundary from '@/components/ErrorBoundary';
|
||||
|
||||
export default function Home() {
|
||||
const [days, setDays] = useState(7);
|
||||
|
|
@ -51,7 +52,7 @@ export default function Home() {
|
|||
if (error || !data) {
|
||||
return (
|
||||
<div className="min-h-screen flex items-center justify-center">
|
||||
<div className="text-red-500">데이터 로딩 실패: {error?.message}</div>
|
||||
<div className="text-red-500">데이터 로딩 실패: {String(error)}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -59,11 +60,13 @@ export default function Home() {
|
|||
return (
|
||||
<main className="min-h-screen bg-gray-100 p-6">
|
||||
<div ref={reportRef} className="max-w-7xl mx-auto space-y-6">
|
||||
<ErrorBoundary fallback="ReportHeader 오류">
|
||||
<ReportHeader
|
||||
start={data.period.start}
|
||||
end={data.period.end}
|
||||
onExportPdf={handleExportPdf}
|
||||
/>
|
||||
</ErrorBoundary>
|
||||
|
||||
<div className="flex gap-2">
|
||||
{[1, 3, 7, 14, 30].map((d) => (
|
||||
|
|
@ -79,23 +82,36 @@ export default function Home() {
|
|||
))}
|
||||
</div>
|
||||
|
||||
<ErrorBoundary fallback="SummaryCards 오류">
|
||||
<SummaryCards
|
||||
cpu={data.metrics.cpu}
|
||||
memory={data.metrics.memory}
|
||||
disk={data.metrics.disk}
|
||||
network={data.metrics.network}
|
||||
/>
|
||||
</ErrorBoundary>
|
||||
|
||||
<ErrorBoundary fallback="CPU 차트 오류">
|
||||
<TimeSeriesChart title="CPU 사용률" instances={data.metrics.cpu} unit="%" />
|
||||
</ErrorBoundary>
|
||||
<ErrorBoundary fallback="Memory 차트 오류">
|
||||
<TimeSeriesChart title="Memory 사용률" instances={data.metrics.memory} unit="%" />
|
||||
</ErrorBoundary>
|
||||
<ErrorBoundary fallback="Disk 차트 오류">
|
||||
<TimeSeriesChart title="Disk 사용률" instances={data.metrics.disk} unit="%" />
|
||||
</ErrorBoundary>
|
||||
|
||||
{data.metrics.nas.length > 0 && (
|
||||
{data.metrics.nas && data.metrics.nas.length > 0 && (
|
||||
<ErrorBoundary fallback="NAS 차트 오류">
|
||||
<TimeSeriesChart title="NAS 사용률" instances={data.metrics.nas} unit="%" />
|
||||
</ErrorBoundary>
|
||||
)}
|
||||
|
||||
<ErrorBoundary fallback="Network 차트 오류">
|
||||
<NetworkChart data={data.metrics.network} />
|
||||
</ErrorBoundary>
|
||||
|
||||
<ErrorBoundary fallback="NodeTable 오류">
|
||||
<div>
|
||||
<h2 className="text-lg font-semibold text-gray-800 mb-3">노드별 상세</h2>
|
||||
<NodeTable
|
||||
|
|
@ -105,7 +121,9 @@ export default function Home() {
|
|||
disk={data.metrics.disk}
|
||||
/>
|
||||
</div>
|
||||
</ErrorBoundary>
|
||||
|
||||
<ErrorBoundary fallback="AlertSection 오류">
|
||||
<div>
|
||||
<h2 className="text-lg font-semibold text-gray-800 mb-3">주의 구간</h2>
|
||||
<AlertSection
|
||||
|
|
@ -114,6 +132,7 @@ export default function Home() {
|
|||
disk={data.metrics.disk}
|
||||
/>
|
||||
</div>
|
||||
</ErrorBoundary>
|
||||
</div>
|
||||
</main>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,37 @@
|
|||
'use client';
|
||||
|
||||
import { Component, ReactNode } from 'react';
|
||||
|
||||
interface Props {
|
||||
children: ReactNode;
|
||||
fallback?: string;
|
||||
}
|
||||
|
||||
interface State {
|
||||
hasError: boolean;
|
||||
error: Error | null;
|
||||
}
|
||||
|
||||
export default class ErrorBoundary extends Component<Props, State> {
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
this.state = { hasError: false, error: null };
|
||||
}
|
||||
|
||||
static getDerivedStateFromError(error: Error): State {
|
||||
return { hasError: true, error };
|
||||
}
|
||||
|
||||
render() {
|
||||
if (this.state.hasError) {
|
||||
return (
|
||||
<div className="bg-red-50 border border-red-200 rounded-lg p-4">
|
||||
<p className="text-red-800 text-sm font-medium">{this.props.fallback || '컴포넌트 렌더링 오류'}</p>
|
||||
<p className="text-red-600 text-xs mt-1">{this.state.error?.message}</p>
|
||||
<pre className="text-red-500 text-xs mt-2 overflow-auto max-h-40">{this.state.error?.stack}</pre>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return this.props.children;
|
||||
}
|
||||
}
|
||||
|
|
@ -4,7 +4,7 @@ import dynamic from 'next/dynamic';
|
|||
import type { NetworkMetrics } from '@/types/metrics';
|
||||
import { formatTimestamp, formatBytes } from '@/lib/formatters';
|
||||
|
||||
const ReactECharts = dynamic(() => import('echarts-for-react'), { ssr: false });
|
||||
const ReactECharts = dynamic(() => import('echarts-for-react').then(mod => mod), { ssr: false, loading: () => <div style={{height: 300}} /> });
|
||||
|
||||
interface NetworkChartProps {
|
||||
data: NetworkMetrics;
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import dynamic from 'next/dynamic';
|
|||
import type { MetricInstance } from '@/types/metrics';
|
||||
import { formatTimestamp } from '@/lib/formatters';
|
||||
|
||||
const ReactECharts = dynamic(() => import('echarts-for-react'), { ssr: false });
|
||||
const ReactECharts = dynamic(() => import('echarts-for-react').then(mod => mod), { ssr: false, loading: () => <div style={{height: 300}} /> });
|
||||
|
||||
interface TimeSeriesChartProps {
|
||||
title: string;
|
||||
|
|
|
|||
Loading…
Reference in New Issue