diff --git a/frontend/components/admin/SqlQueryModal.tsx b/frontend/components/admin/SqlQueryModal.tsx index a578afd5..4c01f472 100644 --- a/frontend/components/admin/SqlQueryModal.tsx +++ b/frontend/components/admin/SqlQueryModal.tsx @@ -2,7 +2,13 @@ import { useState, useEffect, ChangeEvent } from "react"; import { Button } from "@/components/ui/button"; -import { ResizableDialog, ResizableDialogContent, ResizableDialogHeader, DialogDescription } from "@/components/ui/resizable-dialog"; +import { + ResizableDialog, + ResizableDialogContent, + ResizableDialogHeader, + ResizableDialogTitle, + ResizableDialogDescription, +} from "@/components/ui/resizable-dialog"; import { Textarea } from "@/components/ui/textarea"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; @@ -119,21 +125,20 @@ export const SqlQueryModal: React.FC = ({ isOpen, onClose, c // SELECT 쿼리만 허용하는 검증 const trimmedQuery = query.trim().toUpperCase(); - if (!trimmedQuery.startsWith('SELECT')) { + if (!trimmedQuery.startsWith("SELECT")) { toast({ title: "보안 오류", - description: "외부 데이터베이스에서는 SELECT 쿼리만 실행할 수 있습니다. INSERT, UPDATE, DELETE는 허용되지 않습니다.", + description: + "외부 데이터베이스에서는 SELECT 쿼리만 실행할 수 있습니다. INSERT, UPDATE, DELETE는 허용되지 않습니다.", variant: "destructive", }); return; } // 위험한 키워드 검사 - const dangerousKeywords = ['INSERT', 'UPDATE', 'DELETE', 'DROP', 'CREATE', 'ALTER', 'TRUNCATE', 'EXEC', 'EXECUTE']; - const hasDangerousKeyword = dangerousKeywords.some(keyword => - trimmedQuery.includes(keyword) - ); - + const dangerousKeywords = ["INSERT", "UPDATE", "DELETE", "DROP", "CREATE", "ALTER", "TRUNCATE", "EXEC", "EXECUTE"]; + const hasDangerousKeyword = dangerousKeywords.some((keyword) => trimmedQuery.includes(keyword)); + if (hasDangerousKeyword) { toast({ title: "보안 오류", @@ -161,13 +166,13 @@ export const SqlQueryModal: React.FC = ({ isOpen, onClose, c variant: "destructive", }); } - } catch (error) { - console.error("쿼리 실행 오류:", error); - toast({ - title: "오류", - description: error instanceof Error ? error.message : "쿼리 실행 중 오류가 발생했습니다.", - variant: "destructive", - }); + } catch (error) { + console.error("쿼리 실행 오류:", error); + toast({ + title: "오류", + description: error instanceof Error ? error.message : "쿼리 실행 중 오류가 발생했습니다.", + variant: "destructive", + }); } finally { setLoading(false); } @@ -182,7 +187,7 @@ export const SqlQueryModal: React.FC = ({ isOpen, onClose, c 데이터베이스에 대해 SQL SELECT 쿼리를 실행하고 결과를 확인할 수 있습니다. - + {/* 쿼리 입력 영역 */}
@@ -220,18 +225,18 @@ export const SqlQueryModal: React.FC = ({ isOpen, onClose, c
{/* 테이블 정보 */} -
+
-

사용 가능한 테이블

+

사용 가능한 테이블

{tables.map((table) => ( -
+
-

{table.table_name}

-
{table.description && ( -

{table.description}

+

{table.description}

)}
))} @@ -254,12 +259,12 @@ export const SqlQueryModal: React.FC = ({ isOpen, onClose, c {/* 선택된 테이블의 컬럼 정보 */} {selectedTable && (
-

테이블 컬럼 정보: {selectedTable}

+

테이블 컬럼 정보: {selectedTable}

{loadingColumns ? ( -
컬럼 정보 로딩 중...
+
컬럼 정보 로딩 중...
) : selectedTableColumns.length > 0 ? (
-
+
@@ -275,7 +280,7 @@ export const SqlQueryModal: React.FC = ({ isOpen, onClose, c {column.column_name} {column.data_type} {column.is_nullable} - {column.column_default || '-'} + {column.column_default || "-"} ))} @@ -283,7 +288,7 @@ export const SqlQueryModal: React.FC = ({ isOpen, onClose, c ) : ( -
컬럼 정보를 불러올 수 없습니다.
+
컬럼 정보를 불러올 수 없습니다.
)} )} @@ -316,20 +321,24 @@ export const SqlQueryModal: React.FC = ({ isOpen, onClose, c {/* 결과 섹션 */}
-
- {loading ? "쿼리 실행 중..." : results.length > 0 ? `${results.length}개의 결과가 있습니다.` : "실행된 쿼리가 없습니다."} +
+ {loading + ? "쿼리 실행 중..." + : results.length > 0 + ? `${results.length}개의 결과가 있습니다.` + : "실행된 쿼리가 없습니다."}
- + {/* 결과 그리드 */} -
+
{results.length > 0 ? ( <> - + {Object.keys(results[0]).map((key) => ( diff --git a/frontend/components/admin/dashboard/DashboardDesigner.tsx b/frontend/components/admin/dashboard/DashboardDesigner.tsx index a9f86027..5560f1cb 100644 --- a/frontend/components/admin/dashboard/DashboardDesigner.tsx +++ b/frontend/components/admin/dashboard/DashboardDesigner.tsx @@ -12,15 +12,21 @@ import { Resolution, RESOLUTIONS, detectScreenResolution } from "./ResolutionSel import { DashboardProvider } from "@/contexts/DashboardContext"; import { useMenu } from "@/contexts/MenuContext"; import { useKeyboardShortcuts } from "./hooks/useKeyboardShortcuts"; -import { ResizableDialog, ResizableDialogContent, ResizableDialogDescription, ResizableDialogHeader, DialogTitle } from "@/components/ui/dialog"; +import { + ResizableDialog, + ResizableDialogContent, + ResizableDialogDescription, + ResizableDialogHeader, + ResizableDialogTitle, +} from "@/components/ui/resizable-dialog"; import { AlertDialog, AlertDialogAction, AlertDialogCancel, - AlertResizableDialogContent, - AlertResizableDialogDescription, + AlertDialogContent, + AlertDialogDescription, AlertDialogFooter, - AlertResizableDialogHeader, + AlertDialogHeader, AlertDialogTitle, } from "@/components/ui/alert-dialog"; import { Button } from "@/components/ui/button"; @@ -610,21 +616,23 @@ export default function DashboardDesigner({ dashboardId: initialDashboardId }: D /> {/* 저장 성공 모달 */} - { setSuccessModalOpen(false); router.push("/admin/dashboard"); }} > - - + +
- 저장 완료 - 대시보드가 성공적으로 저장되었습니다. -
+ 저장 완료 + + 대시보드가 성공적으로 저장되었습니다. + +
-
-
+ + {/* 초기화 확인 모달 */}