/** * DDL 로그 뷰어 컴포넌트 * DDL 실행 로그와 통계를 표시 */ "use client"; import { useState, useEffect } from "react"; import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@/components/ui/dialog"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Badge } from "@/components/ui/badge"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { RefreshCw, Search, Calendar, User, Database, CheckCircle2, XCircle, BarChart3, Clock, Trash2, } from "lucide-react"; import { toast } from "sonner"; import { format } from "date-fns"; import { ko } from "date-fns/locale"; import { ddlApi } from "../../lib/api/ddl"; import { DDLLogViewerProps, DDLExecutionLog, DDLStatistics } from "../../types/ddl"; export function DDLLogViewer({ isOpen, onClose }: DDLLogViewerProps) { const [logs, setLogs] = useState([]); const [statistics, setStatistics] = useState(null); const [loading, setLoading] = useState(false); const [refreshing, setRefreshing] = useState(false); // 필터 상태 const [userFilter, setUserFilter] = useState(""); const [ddlTypeFilter, setDdlTypeFilter] = useState("all"); const [limit, setLimit] = useState(50); /** * 로그 및 통계 로드 */ const loadData = async (showLoading = true) => { if (showLoading) setLoading(true); setRefreshing(true); try { // 로그와 통계를 병렬로 로드 const [logsResult, statsResult] = await Promise.all([ ddlApi.getDDLLogs({ limit, userId: userFilter || undefined, ddlType: ddlTypeFilter === "all" ? undefined : ddlTypeFilter, }), ddlApi.getDDLStatistics(), ]); setLogs(logsResult.logs); setStatistics(statsResult); } catch (error) { console.error("DDL 로그 로드 실패:", error); toast.error("DDL 로그를 불러오는데 실패했습니다."); } finally { if (showLoading) setLoading(false); setRefreshing(false); } }; /** * 필터 적용 */ const applyFilters = () => { loadData(false); }; /** * 필터 초기화 */ const resetFilters = () => { setUserFilter(""); setDdlTypeFilter(""); setLimit(50); }; /** * 로그 정리 */ const cleanupLogs = async () => { if (!confirm("90일 이전의 오래된 로그를 삭제하시겠습니까?")) { return; } try { const result = await ddlApi.cleanupOldLogs(90); toast.success(`${result.deletedCount}개의 오래된 로그가 삭제되었습니다.`); loadData(false); } catch (error) { console.error("로그 정리 실패:", error); toast.error("로그 정리에 실패했습니다."); } }; /** * 컴포넌트 마운트 시 데이터 로드 */ useEffect(() => { if (isOpen) { loadData(); } }, [isOpen]); /** * DDL 타입 배지 색상 */ const getDDLTypeBadgeVariant = (ddlType: string) => { switch (ddlType) { case "CREATE_TABLE": return "default"; case "ADD_COLUMN": return "secondary"; case "DROP_TABLE": return "destructive"; case "DROP_COLUMN": return "outline"; default: return "outline"; } }; /** * 성공률 계산 */ const getSuccessRate = (stats: DDLStatistics) => { if (stats.totalExecutions === 0) return 0; return Math.round((stats.successfulExecutions / stats.totalExecutions) * 100); }; return ( DDL 실행 로그 및 통계 실행 로그 통계 {/* 실행 로그 탭 */} {/* 필터 및 컨트롤 */}
setUserFilter(e.target.value)} className="w-32" />
{/* 로그 테이블 */} {loading ? (
로그를 불러오는 중...
) : (
실행 시간 사용자 DDL 타입 테이블명 결과 쿼리 미리보기 {logs.length === 0 ? ( 표시할 로그가 없습니다. ) : ( logs.map((log) => (
{format(new Date(log.executed_at), "yyyy-MM-dd HH:mm:ss", { locale: ko })}
{log.user_id} {log.ddl_type} {log.table_name}
{log.success ? ( ) : ( )} {log.success ? "성공" : "실패"}
{log.error_message && (
{log.error_message}
)}
{log.ddl_query_preview}
)) )}
)} {/* 통계 탭 */} {statistics && (
{/* 전체 통계 */}
전체 실행
{statistics.totalExecutions}
성공
{statistics.successfulExecutions}
실패
{statistics.failedExecutions}
성공률
{getSuccessRate(statistics)}%
{/* DDL 타입별 통계 */}
DDL 타입별 실행 횟수 {Object.entries(statistics.byDDLType).map(([type, count]) => (
{type} {count}회
))}
사용자별 실행 횟수 {Object.entries(statistics.byUser).map(([user, count]) => (
{user} {count}회
))}
{/* 최근 실패 로그 */} {statistics.recentFailures.length > 0 && ( 최근 실패 로그 최근 발생한 DDL 실행 실패 내역입니다.
{statistics.recentFailures.map((failure, index) => (
{failure.ddl_type} {failure.table_name}
{format(new Date(failure.executed_at), "MM-dd HH:mm", { locale: ko })}
{failure.error_message}
))}
)}
)}
); }