diff --git a/src/app/page.tsx b/src/app/page.tsx
index e3bedc6..b424696 100644
--- a/src/app/page.tsx
+++ b/src/app/page.tsx
@@ -9,7 +9,7 @@ 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 AlertSection from '@/components/report/AlertSection';
+import AlertPanel from '@/components/report/AlertPanel';
import ErrorBoundary from '@/components/ErrorBoundary';
function getDefaultRange() {
@@ -146,15 +146,30 @@ export default function Home() {
/>
-
-
-
-
-
-
-
-
-
+
+
+
{data.metrics.nas && data.metrics.nas.length > 0 && (
@@ -205,16 +220,6 @@ export default function Home() {
-
-
-
);
diff --git a/src/components/report/AlertPanel.tsx b/src/components/report/AlertPanel.tsx
new file mode 100644
index 0000000..91648db
--- /dev/null
+++ b/src/components/report/AlertPanel.tsx
@@ -0,0 +1,99 @@
+'use client';
+
+import type { MetricInstance } from '@/types/metrics';
+import { formatTimestamp, formatPercent } from '@/lib/formatters';
+
+interface AlertPanelProps {
+ instances: MetricInstance[];
+ type: 'cpu' | 'memory' | 'disk';
+ height?: number;
+}
+
+interface Alert {
+ level: 'warning' | 'critical';
+ instance: string;
+ message: string;
+}
+
+function generateAlerts(instances: MetricInstance[], type: string): Alert[] {
+ const alerts: Alert[] = [];
+
+ instances.forEach((inst) => {
+ if (type === 'cpu') {
+ if (inst.stats.max > 90) {
+ alerts.push({
+ level: 'critical',
+ instance: inst.instance,
+ message: `최대 ${formatPercent(inst.stats.max)} 도달`,
+ });
+ }
+ inst.peaks.peaks.forEach((peak) => {
+ alerts.push({
+ level: 'warning',
+ instance: inst.instance,
+ message: `${formatTimestamp(peak.startTime)} ~ ${formatTimestamp(peak.endTime)} Peak (max: ${formatPercent(peak.maxValue)})`,
+ });
+ });
+ } else if (type === 'memory') {
+ if (inst.stats.max > 85) {
+ alerts.push({
+ level: inst.stats.max > 95 ? 'critical' : 'warning',
+ instance: inst.instance,
+ message: `최대 ${formatPercent(inst.stats.max)} 도달`,
+ });
+ }
+ inst.peaks.peaks.forEach((peak) => {
+ alerts.push({
+ level: 'warning',
+ instance: inst.instance,
+ message: `${formatTimestamp(peak.startTime)} ~ ${formatTimestamp(peak.endTime)} Peak (max: ${formatPercent(peak.maxValue)})`,
+ });
+ });
+ } else if (type === 'disk') {
+ if (inst.stats.max > 80) {
+ alerts.push({
+ level: inst.stats.max > 90 ? 'critical' : 'warning',
+ instance: inst.instance,
+ message: `사용률 ${formatPercent(inst.stats.max)} (증설 검토 필요)`,
+ });
+ }
+ }
+ });
+
+ return alerts;
+}
+
+export default function AlertPanel({ instances, type, height = 300 }: AlertPanelProps) {
+ const alerts = generateAlerts(instances, type);
+
+ if (alerts.length === 0) {
+ return (
+
+ );
+ }
+
+ return (
+
+
+
주의 구간 ({alerts.length}건)
+
+
+ {alerts.map((alert, idx) => (
+ -
+
+
+
{alert.instance}
+
{alert.message}
+
+
+ ))}
+
+
+ );
+}