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 NetworkChart from '@/components/charts/NetworkChart';
|
||||||
import NodeTable from '@/components/report/NodeTable';
|
import NodeTable from '@/components/report/NodeTable';
|
||||||
import AlertSection from '@/components/report/AlertSection';
|
import AlertSection from '@/components/report/AlertSection';
|
||||||
|
import ErrorBoundary from '@/components/ErrorBoundary';
|
||||||
|
|
||||||
export default function Home() {
|
export default function Home() {
|
||||||
const [days, setDays] = useState(7);
|
const [days, setDays] = useState(7);
|
||||||
|
|
@ -51,7 +52,7 @@ export default function Home() {
|
||||||
if (error || !data) {
|
if (error || !data) {
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen flex items-center justify-center">
|
<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>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -59,11 +60,13 @@ export default function Home() {
|
||||||
return (
|
return (
|
||||||
<main className="min-h-screen bg-gray-100 p-6">
|
<main className="min-h-screen bg-gray-100 p-6">
|
||||||
<div ref={reportRef} className="max-w-7xl mx-auto space-y-6">
|
<div ref={reportRef} className="max-w-7xl mx-auto space-y-6">
|
||||||
|
<ErrorBoundary fallback="ReportHeader 오류">
|
||||||
<ReportHeader
|
<ReportHeader
|
||||||
start={data.period.start}
|
start={data.period.start}
|
||||||
end={data.period.end}
|
end={data.period.end}
|
||||||
onExportPdf={handleExportPdf}
|
onExportPdf={handleExportPdf}
|
||||||
/>
|
/>
|
||||||
|
</ErrorBoundary>
|
||||||
|
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-2">
|
||||||
{[1, 3, 7, 14, 30].map((d) => (
|
{[1, 3, 7, 14, 30].map((d) => (
|
||||||
|
|
@ -79,23 +82,36 @@ export default function Home() {
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<ErrorBoundary fallback="SummaryCards 오류">
|
||||||
<SummaryCards
|
<SummaryCards
|
||||||
cpu={data.metrics.cpu}
|
cpu={data.metrics.cpu}
|
||||||
memory={data.metrics.memory}
|
memory={data.metrics.memory}
|
||||||
disk={data.metrics.disk}
|
disk={data.metrics.disk}
|
||||||
network={data.metrics.network}
|
network={data.metrics.network}
|
||||||
/>
|
/>
|
||||||
|
</ErrorBoundary>
|
||||||
|
|
||||||
|
<ErrorBoundary fallback="CPU 차트 오류">
|
||||||
<TimeSeriesChart title="CPU 사용률" instances={data.metrics.cpu} unit="%" />
|
<TimeSeriesChart title="CPU 사용률" instances={data.metrics.cpu} unit="%" />
|
||||||
|
</ErrorBoundary>
|
||||||
|
<ErrorBoundary fallback="Memory 차트 오류">
|
||||||
<TimeSeriesChart title="Memory 사용률" instances={data.metrics.memory} unit="%" />
|
<TimeSeriesChart title="Memory 사용률" instances={data.metrics.memory} unit="%" />
|
||||||
|
</ErrorBoundary>
|
||||||
|
<ErrorBoundary fallback="Disk 차트 오류">
|
||||||
<TimeSeriesChart title="Disk 사용률" instances={data.metrics.disk} unit="%" />
|
<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="%" />
|
<TimeSeriesChart title="NAS 사용률" instances={data.metrics.nas} unit="%" />
|
||||||
|
</ErrorBoundary>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
<ErrorBoundary fallback="Network 차트 오류">
|
||||||
<NetworkChart data={data.metrics.network} />
|
<NetworkChart data={data.metrics.network} />
|
||||||
|
</ErrorBoundary>
|
||||||
|
|
||||||
|
<ErrorBoundary fallback="NodeTable 오류">
|
||||||
<div>
|
<div>
|
||||||
<h2 className="text-lg font-semibold text-gray-800 mb-3">노드별 상세</h2>
|
<h2 className="text-lg font-semibold text-gray-800 mb-3">노드별 상세</h2>
|
||||||
<NodeTable
|
<NodeTable
|
||||||
|
|
@ -105,7 +121,9 @@ export default function Home() {
|
||||||
disk={data.metrics.disk}
|
disk={data.metrics.disk}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
</ErrorBoundary>
|
||||||
|
|
||||||
|
<ErrorBoundary fallback="AlertSection 오류">
|
||||||
<div>
|
<div>
|
||||||
<h2 className="text-lg font-semibold text-gray-800 mb-3">주의 구간</h2>
|
<h2 className="text-lg font-semibold text-gray-800 mb-3">주의 구간</h2>
|
||||||
<AlertSection
|
<AlertSection
|
||||||
|
|
@ -114,6 +132,7 @@ export default function Home() {
|
||||||
disk={data.metrics.disk}
|
disk={data.metrics.disk}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
</ErrorBoundary>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</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 type { NetworkMetrics } from '@/types/metrics';
|
||||||
import { formatTimestamp, formatBytes } from '@/lib/formatters';
|
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 {
|
interface NetworkChartProps {
|
||||||
data: NetworkMetrics;
|
data: NetworkMetrics;
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ import dynamic from 'next/dynamic';
|
||||||
import type { MetricInstance } from '@/types/metrics';
|
import type { MetricInstance } from '@/types/metrics';
|
||||||
import { formatTimestamp } from '@/lib/formatters';
|
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 {
|
interface TimeSeriesChartProps {
|
||||||
title: string;
|
title: string;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue