모달관련 에러 수정 #198

Merged
hyeonsu merged 4 commits from fix/modalError into main 2025-11-11 17:42:54 +09:00
2 changed files with 63 additions and 46 deletions

View File

@ -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<SqlQueryModalProps> = ({ 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<SqlQueryModalProps> = ({ 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<SqlQueryModalProps> = ({ isOpen, onClose, c
SQL SELECT .
</ResizableDialogDescription>
</ResizableDialogHeader>
{/* 쿼리 입력 영역 */}
<div className="space-y-4">
<div className="space-y-2">
@ -220,18 +225,18 @@ export const SqlQueryModal: React.FC<SqlQueryModalProps> = ({ isOpen, onClose, c
</div>
{/* 테이블 정보 */}
<div className="rounded-md border bg-muted/50 p-4 space-y-4">
<div className="bg-muted/50 space-y-4 rounded-md border p-4">
<div>
<h3 className="mb-2 font-medium text-sm"> </h3>
<h3 className="mb-2 text-sm font-medium"> </h3>
<div className="max-h-[200px] overflow-y-auto">
<div className="space-y-2 pr-2">
{tables.map((table) => (
<div key={table.table_name} className="rounded-lg border bg-card p-3 shadow-sm">
<div key={table.table_name} className="bg-card rounded-lg border p-3 shadow-sm">
<div className="flex items-center justify-between">
<h4 className="font-mono font-bold text-sm">{table.table_name}</h4>
<Button
variant="ghost"
size="sm"
<h4 className="font-mono text-sm font-bold">{table.table_name}</h4>
<Button
variant="ghost"
size="sm"
onClick={() => {
setSelectedTable(table.table_name);
loadTableColumns(table.table_name);
@ -243,7 +248,7 @@ export const SqlQueryModal: React.FC<SqlQueryModalProps> = ({ isOpen, onClose, c
</Button>
</div>
{table.description && (
<p className="mt-1 text-sm text-muted-foreground">{table.description}</p>
<p className="text-muted-foreground mt-1 text-sm">{table.description}</p>
)}
</div>
))}
@ -254,12 +259,12 @@ export const SqlQueryModal: React.FC<SqlQueryModalProps> = ({ isOpen, onClose, c
{/* 선택된 테이블의 컬럼 정보 */}
{selectedTable && (
<div>
<h3 className="mb-2 font-medium text-sm"> : {selectedTable}</h3>
<h3 className="mb-2 text-sm font-medium"> : {selectedTable}</h3>
{loadingColumns ? (
<div className="text-sm text-muted-foreground"> ...</div>
<div className="text-muted-foreground text-sm"> ...</div>
) : selectedTableColumns.length > 0 ? (
<div className="max-h-[200px] overflow-y-auto">
<div className="rounded-lg border bg-card shadow-sm">
<div className="bg-card rounded-lg border shadow-sm">
<Table>
<TableHeader>
<TableRow>
@ -275,7 +280,7 @@ export const SqlQueryModal: React.FC<SqlQueryModalProps> = ({ isOpen, onClose, c
<TableCell className="font-mono font-medium">{column.column_name}</TableCell>
<TableCell className="text-sm">{column.data_type}</TableCell>
<TableCell className="text-sm">{column.is_nullable}</TableCell>
<TableCell className="text-sm">{column.column_default || '-'}</TableCell>
<TableCell className="text-sm">{column.column_default || "-"}</TableCell>
</TableRow>
))}
</TableBody>
@ -283,7 +288,7 @@ export const SqlQueryModal: React.FC<SqlQueryModalProps> = ({ isOpen, onClose, c
</div>
</div>
) : (
<div className="text-sm text-muted-foreground"> .</div>
<div className="text-muted-foreground text-sm"> .</div>
)}
</div>
)}
@ -316,20 +321,24 @@ export const SqlQueryModal: React.FC<SqlQueryModalProps> = ({ isOpen, onClose, c
{/* 결과 섹션 */}
<div className="space-y-4">
<div className="flex items-center justify-between">
<div className="text-sm text-muted-foreground">
{loading ? "쿼리 실행 중..." : results.length > 0 ? `${results.length}개의 결과가 있습니다.` : "실행된 쿼리가 없습니다."}
<div className="text-muted-foreground text-sm">
{loading
? "쿼리 실행 중..."
: results.length > 0
? `${results.length}개의 결과가 있습니다.`
: "실행된 쿼리가 없습니다."}
</div>
</div>
{/* 결과 그리드 */}
<div className="rounded-md border bg-card">
<div className="bg-card rounded-md border">
<div className="max-h-[300px] overflow-y-auto">
<div className="inline-block min-w-full align-middle">
<div className="overflow-x-auto">
<Table>
{results.length > 0 ? (
<>
<TableHeader className="sticky top-0 z-10 bg-card">
<TableHeader className="bg-card sticky top-0 z-10">
<TableRow>
{Object.keys(results[0]).map((key) => (
<TableHead key={key} className="font-mono font-bold">

View File

@ -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
/>
{/* 저장 성공 모달 */}
<Dialog
<ResizableDialog
open={successModalOpen}
onOpenChange={() => {
setSuccessModalOpen(false);
router.push("/admin/dashboard");
}}
>
<DialogContent className="sm:max-w-md">
<DialogHeader>
<ResizableDialogContent className="sm:max-w-md">
<ResizableDialogHeader>
<div className="bg-success/10 mx-auto flex h-12 w-12 items-center justify-center rounded-full">
<CheckCircle2 className="text-success h-6 w-6" />
</div>
<DialogTitle className="text-center"> </DialogTitle>
<DialogDescription className="text-center"> .</DialogDescription>
</DialogHeader>
<ResizableDialogTitle className="text-center"> </ResizableDialogTitle>
<ResizableDialogDescription className="text-center">
.
</ResizableDialogDescription>
</ResizableDialogHeader>
<div className="flex justify-center pt-4">
<Button
onClick={() => {
@ -635,8 +643,8 @@ export default function DashboardDesigner({ dashboardId: initialDashboardId }: D
</Button>
</div>
</DialogContent>
</Dialog>
</ResizableDialogContent>
</ResizableDialog>
{/* 초기화 확인 모달 */}
<AlertDialog open={clearConfirmOpen} onOpenChange={setClearConfirmOpen}>