"use client"; import React, { useState, useEffect } from "react"; import { Plus, Search, Pencil, Trash2, Database, Terminal, Globe } from "lucide-react"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Badge } from "@/components/ui/badge"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, } from "@/components/ui/alert-dialog"; import { useToast } from "@/hooks/use-toast"; import { ExternalDbConnectionAPI, ExternalDbConnection, ExternalDbConnectionFilter, } from "@/lib/api/externalDbConnection"; import { ExternalDbConnectionModal } from "@/components/admin/ExternalDbConnectionModal"; import { SqlQueryModal } from "@/components/admin/SqlQueryModal"; import { RestApiConnectionList } from "@/components/admin/RestApiConnectionList"; import { ResponsiveDataView, RDVColumn, RDVCardField } from "@/components/common/ResponsiveDataView"; import { ScrollToTop } from "@/components/common/ScrollToTop"; type ConnectionTabType = "database" | "rest-api"; // DB 타입 매핑 const DB_TYPE_LABELS: Record = { mysql: "MySQL", postgresql: "PostgreSQL", oracle: "Oracle", mssql: "SQL Server", sqlite: "SQLite", }; // 활성 상태 옵션 const ACTIVE_STATUS_OPTIONS = [ { value: "ALL", label: "전체" }, { value: "Y", label: "활성" }, { value: "N", label: "비활성" }, ]; export default function ExternalConnectionsPage() { const { toast } = useToast(); // 탭 상태 const [activeTab, setActiveTab] = useState("database"); // 상태 관리 const [connections, setConnections] = useState([]); const [loading, setLoading] = useState(true); const [searchTerm, setSearchTerm] = useState(""); const [dbTypeFilter, setDbTypeFilter] = useState("ALL"); const [activeStatusFilter, setActiveStatusFilter] = useState("ALL"); const [isModalOpen, setIsModalOpen] = useState(false); const [editingConnection, setEditingConnection] = useState(); const [supportedDbTypes, setSupportedDbTypes] = useState>([]); const [deleteDialogOpen, setDeleteDialogOpen] = useState(false); const [connectionToDelete, setConnectionToDelete] = useState(null); const [testingConnections, setTestingConnections] = useState>(new Set()); const [testResults, setTestResults] = useState>(new Map()); const [sqlModalOpen, setSqlModalOpen] = useState(false); const [selectedConnection, setSelectedConnection] = useState(null); // 데이터 로딩 const loadConnections = async () => { try { setLoading(true); const filter: ExternalDbConnectionFilter = { search: searchTerm.trim() || undefined, db_type: dbTypeFilter === "ALL" ? undefined : dbTypeFilter, is_active: activeStatusFilter === "ALL" ? undefined : activeStatusFilter, }; const data = await ExternalDbConnectionAPI.getConnections(filter); setConnections(data); } catch (error) { console.error("연결 목록 로딩 오류:", error); toast({ title: "오류", description: "연결 목록을 불러오는데 실패했습니다.", variant: "destructive", }); } finally { setLoading(false); } }; // 지원되는 DB 타입 로딩 const loadSupportedDbTypes = async () => { try { const types = await ExternalDbConnectionAPI.getSupportedTypes(); setSupportedDbTypes([{ value: "ALL", label: "전체" }, ...types]); } catch (error) { console.error("지원 DB 타입 로딩 오류:", error); setSupportedDbTypes([ { value: "ALL", label: "전체" }, { value: "mysql", label: "MySQL" }, { value: "postgresql", label: "PostgreSQL" }, { value: "oracle", label: "Oracle" }, { value: "mssql", label: "SQL Server" }, { value: "sqlite", label: "SQLite" }, ]); } }; useEffect(() => { loadConnections(); loadSupportedDbTypes(); }, []); useEffect(() => { loadConnections(); }, [searchTerm, dbTypeFilter, activeStatusFilter]); const handleAddConnection = () => { setEditingConnection(undefined); setIsModalOpen(true); }; const handleEditConnection = (connection: ExternalDbConnection) => { setEditingConnection(connection); setIsModalOpen(true); }; const handleDeleteConnection = (connection: ExternalDbConnection) => { setConnectionToDelete(connection); setDeleteDialogOpen(true); }; const confirmDeleteConnection = async () => { if (!connectionToDelete?.id) return; try { await ExternalDbConnectionAPI.deleteConnection(connectionToDelete.id); toast({ title: "성공", description: "연결이 삭제되었습니다." }); loadConnections(); } catch (error) { console.error("연결 삭제 오류:", error); toast({ title: "오류", description: error instanceof Error ? error.message : "연결 삭제에 실패했습니다.", variant: "destructive", }); } finally { setDeleteDialogOpen(false); setConnectionToDelete(null); } }; const cancelDeleteConnection = () => { setDeleteDialogOpen(false); setConnectionToDelete(null); }; const handleTestConnection = async (connection: ExternalDbConnection) => { if (!connection.id) return; setTestingConnections((prev) => new Set(prev).add(connection.id!)); try { const result = await ExternalDbConnectionAPI.testConnection(connection.id); setTestResults((prev) => new Map(prev).set(connection.id!, result.success)); if (result.success) { toast({ title: "연결 성공", description: `${connection.connection_name} 연결이 성공했습니다.` }); } else { toast({ title: "연결 실패", description: result.message || `${connection.connection_name} 연결에 실패했습니다.`, variant: "destructive", }); } } catch (error) { console.error("연결 테스트 오류:", error); setTestResults((prev) => new Map(prev).set(connection.id!, false)); toast({ title: "연결 테스트 오류", description: "연결 테스트 중 오류가 발생했습니다.", variant: "destructive" }); } finally { setTestingConnections((prev) => { const newSet = new Set(prev); newSet.delete(connection.id!); return newSet; }); } }; const handleModalSave = () => { setIsModalOpen(false); setEditingConnection(undefined); loadConnections(); }; const handleModalCancel = () => { setIsModalOpen(false); setEditingConnection(undefined); }; // 테이블 컬럼 정의 const columns: RDVColumn[] = [ { key: "connection_name", label: "연결명", render: (v) => {v} }, { key: "company_code", label: "회사", width: "100px", render: (_v, row) => (row as any).company_name || row.company_code }, { key: "db_type", label: "DB 타입", width: "120px", render: (v) => {DB_TYPE_LABELS[v] || v} }, { key: "host", label: "호스트:포트", width: "180px", hideOnMobile: true, render: (_v, row) => {row.host}:{row.port} }, { key: "database_name", label: "데이터베이스", width: "140px", hideOnMobile: true, render: (v) => {v} }, { key: "username", label: "사용자", width: "100px", hideOnMobile: true, render: (v) => {v} }, { key: "is_active", label: "상태", width: "80px", render: (v) => ( {v === "Y" ? "활성" : "비활성"} ) }, { key: "created_date", label: "생성일", width: "100px", hideOnMobile: true, render: (v) => ( {v ? new Date(v).toLocaleDateString() : "N/A"} ) }, { key: "id", label: "연결 테스트", width: "150px", hideOnMobile: true, render: (_v, row) => (
{testResults.has(row.id!) && ( {testResults.get(row.id!) ? "성공" : "실패"} )}
) }, ]; // 모바일 카드 필드 정의 const cardFields: RDVCardField[] = [ { label: "DB 타입", render: (c) => {DB_TYPE_LABELS[c.db_type] || c.db_type} }, { label: "호스트", render: (c) => {c.host}:{c.port} }, { label: "데이터베이스", render: (c) => {c.database_name} }, { label: "상태", render: (c) => ( {c.is_active === "Y" ? "활성" : "비활성"} ) }, ]; return (
{/* 페이지 헤더 */}

외부 커넥션 관리

외부 데이터베이스 및 REST API 연결 정보를 관리합니다

{/* 탭 */} setActiveTab(value as ConnectionTabType)}> 데이터베이스 연결 REST API 연결 {/* 데이터베이스 연결 탭 */} {/* 검색 및 필터 */}
setSearchTerm(e.target.value)} className="h-10 pl-10 text-sm" />
{/* 연결 목록 - ResponsiveDataView */} String(c.id || c.connection_name)} isLoading={loading} emptyMessage="등록된 연결이 없습니다" skeletonCount={5} cardTitle={(c) => c.connection_name} cardSubtitle={(c) => {c.host}:{c.port}/{c.database_name}} cardHeaderRight={(c) => ( {c.is_active === "Y" ? "활성" : "비활성"} )} cardFields={cardFields} renderActions={(c) => ( <> )} actionsLabel="작업" actionsWidth="180px" /> {/* 연결 설정 모달 */} {isModalOpen && ( type.value !== "ALL")} /> )} {/* 삭제 확인 다이얼로그 */} 연결 삭제 확인 “{connectionToDelete?.connection_name}” 연결을 삭제하시겠습니까?
이 작업은 되돌릴 수 없습니다.
취소 삭제
{/* SQL 쿼리 모달 */} {selectedConnection && ( { setSqlModalOpen(false); setSelectedConnection(null); }} connectionId={selectedConnection.id!} connectionName={selectedConnection.connection_name} /> )}
{/* REST API 연결 탭 */}
); }