243 lines
9.0 KiB
TypeScript
243 lines
9.0 KiB
TypeScript
'use client';
|
|
|
|
import { useRef, useState } from 'react';
|
|
import { useReport } from '@/hooks/useReport';
|
|
import ReportHeader from '@/components/report/ReportHeader';
|
|
import SummaryCards from '@/components/report/SummaryCards';
|
|
import TimeSeriesChart from '@/components/charts/TimeSeriesChart';
|
|
import NetworkChart from '@/components/charts/NetworkChart';
|
|
import NodeTable from '@/components/report/NodeTable';
|
|
import NasTable from '@/components/report/NasTable';
|
|
import NetworkTable from '@/components/report/NetworkTable';
|
|
import AlertPanel from '@/components/report/AlertPanel';
|
|
import NetworkAlertPanel from '@/components/report/NetworkAlertPanel';
|
|
import ErrorBoundary from '@/components/ErrorBoundary';
|
|
|
|
function getDefaultRange() {
|
|
const end = new Date();
|
|
const start = new Date(end.getTime() - 7 * 24 * 60 * 60 * 1000);
|
|
return {
|
|
start: start.toISOString().slice(0, 10),
|
|
end: end.toISOString().slice(0, 10),
|
|
};
|
|
}
|
|
|
|
export default function Home() {
|
|
const defaultRange = getDefaultRange();
|
|
const [startDate, setStartDate] = useState(defaultRange.start);
|
|
const [endDate, setEndDate] = useState(defaultRange.end);
|
|
const [appliedStart, setAppliedStart] = useState(defaultRange.start);
|
|
const [appliedEnd, setAppliedEnd] = useState(defaultRange.end);
|
|
const { data, isLoading, error } = useReport(appliedStart, appliedEnd);
|
|
const reportRef = useRef<HTMLDivElement>(null);
|
|
const isDirty = startDate !== appliedStart || endDate !== appliedEnd;
|
|
|
|
const applyRange = () => {
|
|
setAppliedStart(startDate);
|
|
setAppliedEnd(endDate);
|
|
};
|
|
|
|
const handleExportPdf = async () => {
|
|
if (!reportRef.current) return;
|
|
const html2canvas = (await import('html2canvas')).default;
|
|
const { default: jsPDF } = await import('jspdf');
|
|
const canvas = await html2canvas(reportRef.current, { scale: 2, useCORS: true });
|
|
const imgData = canvas.toDataURL('image/png');
|
|
const pdf = new jsPDF('p', 'mm', 'a4');
|
|
const pdfWidth = pdf.internal.pageSize.getWidth();
|
|
const pdfHeight = (canvas.height * pdfWidth) / canvas.width;
|
|
|
|
let heightLeft = pdfHeight;
|
|
let position = 0;
|
|
|
|
pdf.addImage(imgData, 'PNG', 0, position, pdfWidth, pdfHeight);
|
|
heightLeft -= pdf.internal.pageSize.getHeight();
|
|
|
|
while (heightLeft > 0) {
|
|
position -= pdf.internal.pageSize.getHeight();
|
|
pdf.addPage();
|
|
pdf.addImage(imgData, 'PNG', 0, position, pdfWidth, pdfHeight);
|
|
heightLeft -= pdf.internal.pageSize.getHeight();
|
|
}
|
|
|
|
pdf.save(`infra-report-${startDate}_${endDate}.pdf`);
|
|
};
|
|
|
|
const setPresetRange = (days: number) => {
|
|
const end = new Date();
|
|
const start = new Date(end.getTime() - days * 24 * 60 * 60 * 1000);
|
|
setStartDate(start.toISOString().slice(0, 10));
|
|
setEndDate(end.toISOString().slice(0, 10));
|
|
};
|
|
|
|
if (isLoading) {
|
|
return (
|
|
<div className="min-h-screen flex items-center justify-center">
|
|
<div className="text-gray-500">데이터 로딩 중...</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
if (error || !data) {
|
|
return (
|
|
<div className="min-h-screen flex items-center justify-center">
|
|
<div className="text-red-500">데이터 로딩 실패: {String(error)}</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
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 items-center gap-4 flex-wrap">
|
|
<div className="flex items-center gap-2">
|
|
<input
|
|
type="date"
|
|
value={startDate}
|
|
onChange={(e) => setStartDate(e.target.value)}
|
|
className="px-3 py-1.5 text-sm border rounded bg-white"
|
|
/>
|
|
<span className="text-gray-500">~</span>
|
|
<input
|
|
type="date"
|
|
value={endDate}
|
|
onChange={(e) => setEndDate(e.target.value)}
|
|
className="px-3 py-1.5 text-sm border rounded bg-white"
|
|
/>
|
|
</div>
|
|
<div className="flex gap-1">
|
|
{[1, 3, 7, 14, 30].map((d) => (
|
|
<button
|
|
key={d}
|
|
onClick={() => setPresetRange(d)}
|
|
className="px-3 py-1.5 text-sm rounded bg-white text-gray-700 hover:bg-gray-50 border"
|
|
>
|
|
{d}일
|
|
</button>
|
|
))}
|
|
</div>
|
|
<button
|
|
onClick={applyRange}
|
|
disabled={!isDirty}
|
|
className={`px-4 py-1.5 text-sm rounded font-medium ${
|
|
isDirty
|
|
? 'bg-blue-600 text-white hover:bg-blue-700'
|
|
: 'bg-gray-200 text-gray-400 cursor-not-allowed'
|
|
}`}
|
|
>
|
|
적용
|
|
</button>
|
|
</div>
|
|
|
|
<ErrorBoundary fallback="SummaryCards 오류">
|
|
<SummaryCards
|
|
cpu={data.metrics.cpu}
|
|
memory={data.metrics.memory}
|
|
disk={data.metrics.disk}
|
|
ingress={data.metrics.ingress}
|
|
istio={data.metrics.istio}
|
|
/>
|
|
</ErrorBoundary>
|
|
|
|
<div className="grid grid-cols-[1fr_280px] gap-4">
|
|
<ErrorBoundary fallback="CPU 차트 오류">
|
|
<TimeSeriesChart title="CPU 사용률" instances={data.metrics.cpu} unit="%" />
|
|
</ErrorBoundary>
|
|
<ErrorBoundary fallback="CPU 주의 구간 오류">
|
|
<AlertPanel instances={data.metrics.cpu} type="cpu" height={300} />
|
|
</ErrorBoundary>
|
|
</div>
|
|
<div className="grid grid-cols-[1fr_280px] gap-4">
|
|
<ErrorBoundary fallback="Memory 차트 오류">
|
|
<TimeSeriesChart title="Memory 사용률" instances={data.metrics.memory} unit="%" />
|
|
</ErrorBoundary>
|
|
<ErrorBoundary fallback="Memory 주의 구간 오류">
|
|
<AlertPanel instances={data.metrics.memory} type="memory" height={300} />
|
|
</ErrorBoundary>
|
|
</div>
|
|
<div className="grid grid-cols-[1fr_280px] gap-4">
|
|
<ErrorBoundary fallback="Disk 차트 오류">
|
|
<TimeSeriesChart title="Disk 사용률" instances={data.metrics.disk} unit="%" />
|
|
</ErrorBoundary>
|
|
<ErrorBoundary fallback="Disk 주의 구간 오류">
|
|
<AlertPanel instances={data.metrics.disk} type="disk" height={300} />
|
|
</ErrorBoundary>
|
|
</div>
|
|
|
|
{data.metrics.nas && data.metrics.nas.length > 0 && (
|
|
<div className="grid grid-cols-[1fr_280px] gap-4">
|
|
<ErrorBoundary fallback="NAS 차트 오류">
|
|
<TimeSeriesChart title="NAS 사용률" instances={data.metrics.nas} unit="%" />
|
|
</ErrorBoundary>
|
|
<ErrorBoundary fallback="NAS 주의 구간 오류">
|
|
<AlertPanel instances={data.metrics.nas} type="nas" height={300} />
|
|
</ErrorBoundary>
|
|
</div>
|
|
)}
|
|
|
|
<div className="grid grid-cols-[1fr_280px] gap-4">
|
|
<ErrorBoundary fallback="Ingress Network 차트 오류">
|
|
<NetworkChart title="Ingress Network Traffic" data={data.metrics.ingress} />
|
|
</ErrorBoundary>
|
|
<ErrorBoundary fallback="Ingress 주의 구간 오류">
|
|
<NetworkAlertPanel data={data.metrics.ingress} height={300} />
|
|
</ErrorBoundary>
|
|
</div>
|
|
|
|
<div className="grid grid-cols-[1fr_280px] gap-4">
|
|
<ErrorBoundary fallback="Istio Network 차트 오류">
|
|
<NetworkChart title="Istio Network Traffic" data={data.metrics.istio} />
|
|
</ErrorBoundary>
|
|
<ErrorBoundary fallback="Istio 주의 구간 오류">
|
|
<NetworkAlertPanel data={data.metrics.istio} height={300} />
|
|
</ErrorBoundary>
|
|
</div>
|
|
|
|
<ErrorBoundary fallback="NodeTable 오류">
|
|
<div>
|
|
<h2 className="text-lg font-semibold text-gray-800 mb-3">노드별 상세</h2>
|
|
<NodeTable
|
|
nodes={data.nodes}
|
|
cpu={data.metrics.cpu}
|
|
memory={data.metrics.memory}
|
|
disk={data.metrics.disk}
|
|
/>
|
|
</div>
|
|
</ErrorBoundary>
|
|
|
|
{data.metrics.nas && data.metrics.nas.length > 0 && (
|
|
<ErrorBoundary fallback="NAS 상세 오류">
|
|
<div>
|
|
<h2 className="text-lg font-semibold text-gray-800 mb-3">NAS 사용률 상세</h2>
|
|
<NasTable nas={data.metrics.nas} />
|
|
</div>
|
|
</ErrorBoundary>
|
|
)}
|
|
|
|
<ErrorBoundary fallback="Ingress 상세 오류">
|
|
<div>
|
|
<h2 className="text-lg font-semibold text-gray-800 mb-3">Ingress Network 상세</h2>
|
|
<NetworkTable title="Ingress" data={data.metrics.ingress} />
|
|
</div>
|
|
</ErrorBoundary>
|
|
|
|
<ErrorBoundary fallback="Istio 상세 오류">
|
|
<div>
|
|
<h2 className="text-lg font-semibold text-gray-800 mb-3">Istio Network 상세</h2>
|
|
<NetworkTable title="Istio" data={data.metrics.istio} />
|
|
</div>
|
|
</ErrorBoundary>
|
|
|
|
</div>
|
|
</main>
|
|
);
|
|
}
|