"use client"; import { useEffect, useState, useCallback } from "react"; import { fleetApi, FleetAlert } from "@/lib/api/fleet"; import { Button } from "@/components/ui/button"; import { Badge } from "@/components/ui/badge"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { RefreshCw, AlertTriangle, CheckCircle2, Circle, Loader2 } from "lucide-react"; import { toast } from "sonner"; const SEVERITY_COLORS: Record = { info: "bg-blue-500/10 text-blue-600 border-blue-500/20", warning: "bg-amber-500/10 text-amber-600 border-amber-500/20", critical: "bg-red-500/10 text-red-600 border-red-500/20", }; export default function FleetAlertsPage() { const [alerts, setAlerts] = useState([]); const [rules, setRules] = useState([]); const [loading, setLoading] = useState(true); const [statusFilter, setStatusFilter] = useState<"open" | "acknowledged" | "resolved">("open"); const load = useCallback(async () => { setLoading(true); try { const [a, r] = await Promise.all([ fleetApi.getAlerts(statusFilter), fleetApi.getAlertRules(), ]); setAlerts(a.data || []); setRules(r.data || []); } catch { toast.error("알림 조회 실패"); } setLoading(false); }, [statusFilter]); useEffect(() => { load(); const t = setInterval(load, 30000); return () => clearInterval(t); }, [load]); const ackAlert = async (id: number) => { try { await fleetApi.ackAlert(id); toast.success("확인 처리"); load(); } catch { toast.error("실패"); } }; const resolveAlert = async (id: number) => { try { await fleetApi.resolveAlert(id); toast.success("해결 처리"); load(); } catch { toast.error("실패"); } }; return (

Fleet 알림

임계값 기반 자동 알림 (CPU/메모리/디스크/오프라인)

{/* 필터 */}
총 {alerts.length}건
{loading ? (
) : alerts.length === 0 ? (

해당 상태의 알림이 없습니다

) : (
{alerts.map((a) => (

{a.title}

{a.severity.toUpperCase()}

{a.message}

📱 {a.device_id} 📏 {a.metric} = {a.value} (임계 {a.threshold}) 🕒 {new Date(a.created_at).toLocaleString("ko-KR")}
{a.status === "open" && (
)} {a.status === "acknowledged" && ( )}
))}
)} {/* 규칙 */}

알림 규칙 ({rules.length}개)

{rules.map((r) => (
{r.rule_name} {r.metric} {r.operator} {r.threshold}
{r.severity}
))}
); }