2025-10-21 10:59:15 +09:00
|
|
|
"use client";
|
|
|
|
|
|
|
|
|
|
import React, { useState, useEffect } from "react";
|
|
|
|
|
import { Plus, Search, Pencil, Trash2, TestTube } from "lucide-react";
|
|
|
|
|
import { Button } from "@/components/ui/button";
|
|
|
|
|
import { Input } from "@/components/ui/input";
|
|
|
|
|
import { Card, CardContent } from "@/components/ui/card";
|
|
|
|
|
import { Badge } from "@/components/ui/badge";
|
|
|
|
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
|
|
|
|
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";
|
|
|
|
|
import {
|
|
|
|
|
AlertDialog,
|
|
|
|
|
AlertDialogAction,
|
|
|
|
|
AlertDialogCancel,
|
|
|
|
|
AlertDialogContent,
|
|
|
|
|
AlertDialogDescription,
|
|
|
|
|
AlertDialogFooter,
|
|
|
|
|
AlertDialogHeader,
|
|
|
|
|
AlertDialogTitle,
|
|
|
|
|
} from "@/components/ui/alert-dialog";
|
|
|
|
|
import { useToast } from "@/hooks/use-toast";
|
|
|
|
|
import {
|
|
|
|
|
ExternalRestApiConnectionAPI,
|
|
|
|
|
ExternalRestApiConnection,
|
|
|
|
|
ExternalRestApiConnectionFilter,
|
|
|
|
|
} from "@/lib/api/externalRestApiConnection";
|
|
|
|
|
import { RestApiConnectionModal } from "./RestApiConnectionModal";
|
|
|
|
|
|
|
|
|
|
// 인증 타입 라벨
|
|
|
|
|
const AUTH_TYPE_LABELS: Record<string, string> = {
|
|
|
|
|
none: "인증 없음",
|
|
|
|
|
"api-key": "API Key",
|
|
|
|
|
bearer: "Bearer",
|
|
|
|
|
basic: "Basic Auth",
|
|
|
|
|
oauth2: "OAuth 2.0",
|
2025-11-27 17:14:24 +09:00
|
|
|
"db-token": "DB 토큰",
|
2025-10-21 10:59:15 +09:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// 활성 상태 옵션
|
|
|
|
|
const ACTIVE_STATUS_OPTIONS = [
|
|
|
|
|
{ value: "ALL", label: "전체" },
|
|
|
|
|
{ value: "Y", label: "활성" },
|
|
|
|
|
{ value: "N", label: "비활성" },
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
export function RestApiConnectionList() {
|
|
|
|
|
const { toast } = useToast();
|
|
|
|
|
|
|
|
|
|
// 상태 관리
|
|
|
|
|
const [connections, setConnections] = useState<ExternalRestApiConnection[]>([]);
|
|
|
|
|
const [loading, setLoading] = useState(true);
|
|
|
|
|
const [searchTerm, setSearchTerm] = useState("");
|
|
|
|
|
const [authTypeFilter, setAuthTypeFilter] = useState("ALL");
|
|
|
|
|
const [activeStatusFilter, setActiveStatusFilter] = useState("ALL");
|
|
|
|
|
const [isModalOpen, setIsModalOpen] = useState(false);
|
|
|
|
|
const [editingConnection, setEditingConnection] = useState<ExternalRestApiConnection | undefined>();
|
|
|
|
|
const [supportedAuthTypes, setSupportedAuthTypes] = useState<Array<{ value: string; label: string }>>([]);
|
|
|
|
|
const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
|
|
|
|
|
const [connectionToDelete, setConnectionToDelete] = useState<ExternalRestApiConnection | null>(null);
|
|
|
|
|
const [testingConnections, setTestingConnections] = useState<Set<number>>(new Set());
|
|
|
|
|
const [testResults, setTestResults] = useState<Map<number, boolean>>(new Map());
|
|
|
|
|
|
|
|
|
|
// 데이터 로딩
|
|
|
|
|
const loadConnections = async () => {
|
|
|
|
|
try {
|
|
|
|
|
setLoading(true);
|
|
|
|
|
|
|
|
|
|
const filter: ExternalRestApiConnectionFilter = {
|
|
|
|
|
search: searchTerm.trim() || undefined,
|
|
|
|
|
auth_type: authTypeFilter === "ALL" ? undefined : authTypeFilter,
|
|
|
|
|
is_active: activeStatusFilter === "ALL" ? undefined : activeStatusFilter,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const data = await ExternalRestApiConnectionAPI.getConnections(filter);
|
|
|
|
|
setConnections(data);
|
|
|
|
|
} catch (error) {
|
|
|
|
|
toast({
|
|
|
|
|
title: "오류",
|
|
|
|
|
description: "연결 목록을 불러오는데 실패했습니다.",
|
|
|
|
|
variant: "destructive",
|
|
|
|
|
});
|
|
|
|
|
} finally {
|
|
|
|
|
setLoading(false);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// 지원되는 인증 타입 로딩
|
|
|
|
|
const loadSupportedAuthTypes = () => {
|
|
|
|
|
const types = ExternalRestApiConnectionAPI.getSupportedAuthTypes();
|
|
|
|
|
setSupportedAuthTypes([{ value: "ALL", label: "전체" }, ...types]);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// 초기 데이터 로딩
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
loadConnections();
|
|
|
|
|
loadSupportedAuthTypes();
|
|
|
|
|
}, []);
|
|
|
|
|
|
|
|
|
|
// 필터 변경 시 데이터 재로딩
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
loadConnections();
|
|
|
|
|
}, [searchTerm, authTypeFilter, activeStatusFilter]);
|
|
|
|
|
|
|
|
|
|
// 새 연결 추가
|
|
|
|
|
const handleAddConnection = () => {
|
|
|
|
|
setEditingConnection(undefined);
|
|
|
|
|
setIsModalOpen(true);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// 연결 편집
|
|
|
|
|
const handleEditConnection = (connection: ExternalRestApiConnection) => {
|
|
|
|
|
setEditingConnection(connection);
|
|
|
|
|
setIsModalOpen(true);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// 연결 삭제 확인 다이얼로그 열기
|
|
|
|
|
const handleDeleteConnection = (connection: ExternalRestApiConnection) => {
|
|
|
|
|
setConnectionToDelete(connection);
|
|
|
|
|
setDeleteDialogOpen(true);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// 연결 삭제 실행
|
|
|
|
|
const confirmDeleteConnection = async () => {
|
|
|
|
|
if (!connectionToDelete?.id) return;
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
await ExternalRestApiConnectionAPI.deleteConnection(connectionToDelete.id);
|
|
|
|
|
toast({
|
|
|
|
|
title: "성공",
|
|
|
|
|
description: "연결이 삭제되었습니다.",
|
|
|
|
|
});
|
|
|
|
|
loadConnections();
|
|
|
|
|
} catch (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: ExternalRestApiConnection) => {
|
|
|
|
|
if (!connection.id) return;
|
|
|
|
|
|
|
|
|
|
setTestingConnections((prev) => new Set(prev).add(connection.id!));
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
const result = await ExternalRestApiConnectionAPI.testConnectionById(connection.id);
|
|
|
|
|
|
|
|
|
|
setTestResults((prev) => new Map(prev).set(connection.id!, result.success));
|
|
|
|
|
|
2025-11-27 17:11:30 +09:00
|
|
|
// 현재 행의 "마지막 테스트" 정보만 낙관적으로 업데이트하여
|
|
|
|
|
// 전체 목록 리로딩 없이도 UI를 즉시 반영한다.
|
|
|
|
|
const nowIso = new Date().toISOString();
|
|
|
|
|
setConnections((prev) =>
|
|
|
|
|
prev.map((c) =>
|
|
|
|
|
c.id === connection.id
|
|
|
|
|
? {
|
|
|
|
|
...c,
|
|
|
|
|
last_test_date: nowIso as any,
|
|
|
|
|
last_test_result: result.success ? "Y" : "N",
|
|
|
|
|
last_test_message: result.message,
|
|
|
|
|
}
|
|
|
|
|
: c
|
|
|
|
|
)
|
|
|
|
|
);
|
|
|
|
|
|
2025-10-21 10:59:15 +09:00
|
|
|
if (result.success) {
|
|
|
|
|
toast({
|
|
|
|
|
title: "연결 성공",
|
|
|
|
|
description: `${connection.connection_name} 연결이 성공했습니다.`,
|
|
|
|
|
});
|
|
|
|
|
} else {
|
|
|
|
|
toast({
|
|
|
|
|
title: "연결 실패",
|
|
|
|
|
description: result.message || `${connection.connection_name} 연결에 실패했습니다.`,
|
|
|
|
|
variant: "destructive",
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
} catch (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);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<>
|
|
|
|
|
{/* 검색 및 필터 */}
|
2025-10-22 14:52:13 +09:00
|
|
|
<div className="flex flex-col gap-4 lg:flex-row lg:items-center lg:justify-between">
|
|
|
|
|
<div className="flex flex-col gap-4 sm:flex-row sm:items-center">
|
|
|
|
|
{/* 검색 */}
|
|
|
|
|
<div className="relative w-full sm:w-[300px]">
|
2025-10-27 09:39:11 +09:00
|
|
|
<Search className="text-muted-foreground absolute top-1/2 left-3 h-4 w-4 -translate-y-1/2" />
|
2025-10-22 14:52:13 +09:00
|
|
|
<Input
|
|
|
|
|
placeholder="연결명 또는 URL로 검색..."
|
|
|
|
|
value={searchTerm}
|
|
|
|
|
onChange={(e) => setSearchTerm(e.target.value)}
|
|
|
|
|
className="h-10 pl-10 text-sm"
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
2025-10-21 10:59:15 +09:00
|
|
|
|
2025-10-22 14:52:13 +09:00
|
|
|
{/* 인증 타입 필터 */}
|
|
|
|
|
<Select value={authTypeFilter} onValueChange={setAuthTypeFilter}>
|
|
|
|
|
<SelectTrigger className="h-10 w-full sm:w-[160px]">
|
|
|
|
|
<SelectValue placeholder="인증 타입" />
|
|
|
|
|
</SelectTrigger>
|
|
|
|
|
<SelectContent>
|
|
|
|
|
{supportedAuthTypes.map((type) => (
|
|
|
|
|
<SelectItem key={type.value} value={type.value}>
|
|
|
|
|
{type.label}
|
|
|
|
|
</SelectItem>
|
|
|
|
|
))}
|
|
|
|
|
</SelectContent>
|
|
|
|
|
</Select>
|
2025-10-21 10:59:15 +09:00
|
|
|
|
2025-10-22 14:52:13 +09:00
|
|
|
{/* 활성 상태 필터 */}
|
|
|
|
|
<Select value={activeStatusFilter} onValueChange={setActiveStatusFilter}>
|
|
|
|
|
<SelectTrigger className="h-10 w-full sm:w-[120px]">
|
|
|
|
|
<SelectValue placeholder="상태" />
|
|
|
|
|
</SelectTrigger>
|
|
|
|
|
<SelectContent>
|
|
|
|
|
{ACTIVE_STATUS_OPTIONS.map((option) => (
|
|
|
|
|
<SelectItem key={option.value} value={option.value}>
|
|
|
|
|
{option.label}
|
|
|
|
|
</SelectItem>
|
|
|
|
|
))}
|
|
|
|
|
</SelectContent>
|
|
|
|
|
</Select>
|
|
|
|
|
</div>
|
2025-10-21 10:59:15 +09:00
|
|
|
|
2025-10-22 14:52:13 +09:00
|
|
|
{/* 추가 버튼 */}
|
|
|
|
|
<Button onClick={handleAddConnection} className="h-10 gap-2 text-sm font-medium">
|
2025-10-27 09:39:11 +09:00
|
|
|
<Plus className="h-4 w-4" />새 연결 추가
|
2025-10-22 14:52:13 +09:00
|
|
|
</Button>
|
|
|
|
|
</div>
|
2025-10-21 10:59:15 +09:00
|
|
|
|
|
|
|
|
{/* 연결 목록 */}
|
|
|
|
|
{loading ? (
|
2025-10-30 17:55:55 +09:00
|
|
|
<div className="flex h-64 items-center justify-center bg-card">
|
|
|
|
|
<div className="text-sm text-muted-foreground">로딩 중...</div>
|
2025-10-21 10:59:15 +09:00
|
|
|
</div>
|
|
|
|
|
) : connections.length === 0 ? (
|
2025-10-30 17:55:55 +09:00
|
|
|
<div className="flex h-64 flex-col items-center justify-center bg-card">
|
2025-10-22 14:52:13 +09:00
|
|
|
<div className="flex flex-col items-center gap-2 text-center">
|
2025-10-30 17:55:55 +09:00
|
|
|
<p className="text-sm text-muted-foreground">등록된 REST API 연결이 없습니다</p>
|
2025-10-22 14:52:13 +09:00
|
|
|
</div>
|
|
|
|
|
</div>
|
2025-10-21 10:59:15 +09:00
|
|
|
) : (
|
2025-10-30 17:55:55 +09:00
|
|
|
<div className="bg-card">
|
2025-10-22 14:52:13 +09:00
|
|
|
<Table>
|
2025-10-27 09:39:11 +09:00
|
|
|
<TableHeader>
|
2025-10-30 17:55:55 +09:00
|
|
|
<TableRow className="bg-background">
|
|
|
|
|
<TableHead className="h-12 px-6 py-3 text-sm font-semibold">연결명</TableHead>
|
2025-12-02 17:36:28 +09:00
|
|
|
<TableHead className="h-12 px-6 py-3 text-sm font-semibold">회사</TableHead>
|
2025-10-30 17:55:55 +09:00
|
|
|
<TableHead className="h-12 px-6 py-3 text-sm font-semibold">기본 URL</TableHead>
|
|
|
|
|
<TableHead className="h-12 px-6 py-3 text-sm font-semibold">인증 타입</TableHead>
|
|
|
|
|
<TableHead className="h-12 px-6 py-3 text-sm font-semibold">헤더 수</TableHead>
|
|
|
|
|
<TableHead className="h-12 px-6 py-3 text-sm font-semibold">상태</TableHead>
|
|
|
|
|
<TableHead className="h-12 px-6 py-3 text-sm font-semibold">마지막 테스트</TableHead>
|
|
|
|
|
<TableHead className="h-12 px-6 py-3 text-sm font-semibold">연결 테스트</TableHead>
|
|
|
|
|
<TableHead className="h-12 px-6 py-3 text-right text-sm font-semibold">작업</TableHead>
|
2025-10-27 09:39:11 +09:00
|
|
|
</TableRow>
|
|
|
|
|
</TableHeader>
|
|
|
|
|
<TableBody>
|
|
|
|
|
{connections.map((connection) => (
|
2025-10-30 17:55:55 +09:00
|
|
|
<TableRow key={connection.id} className="bg-background transition-colors hover:bg-muted/50">
|
|
|
|
|
<TableCell className="h-16 px-6 py-3 text-sm">
|
2025-10-27 09:39:11 +09:00
|
|
|
<div className="max-w-[200px]">
|
|
|
|
|
<div className="truncate font-medium" title={connection.connection_name}>
|
|
|
|
|
{connection.connection_name}
|
|
|
|
|
</div>
|
2025-10-21 10:59:15 +09:00
|
|
|
{connection.description && (
|
2025-10-27 09:39:11 +09:00
|
|
|
<div className="text-muted-foreground mt-1 truncate text-xs" title={connection.description}>
|
|
|
|
|
{connection.description}
|
2025-10-21 10:59:15 +09:00
|
|
|
</div>
|
|
|
|
|
)}
|
2025-10-27 09:39:11 +09:00
|
|
|
</div>
|
|
|
|
|
</TableCell>
|
2025-12-02 17:36:28 +09:00
|
|
|
<TableCell className="h-16 px-6 py-3 text-sm">
|
|
|
|
|
{(connection as any).company_name || connection.company_code}
|
|
|
|
|
</TableCell>
|
2025-10-30 17:55:55 +09:00
|
|
|
<TableCell className="h-16 px-6 py-3 font-mono text-sm">
|
2025-10-27 09:39:11 +09:00
|
|
|
<div className="max-w-[300px] truncate" title={connection.base_url}>
|
|
|
|
|
{connection.base_url}
|
|
|
|
|
</div>
|
|
|
|
|
</TableCell>
|
2025-10-30 17:55:55 +09:00
|
|
|
<TableCell className="h-16 px-6 py-3 text-sm">
|
2025-10-27 09:39:11 +09:00
|
|
|
<Badge variant="outline">{AUTH_TYPE_LABELS[connection.auth_type] || connection.auth_type}</Badge>
|
|
|
|
|
</TableCell>
|
2025-10-30 17:55:55 +09:00
|
|
|
<TableCell className="h-16 px-6 py-3 text-center text-sm">
|
2025-10-27 09:39:11 +09:00
|
|
|
{Object.keys(connection.default_headers || {}).length}
|
|
|
|
|
</TableCell>
|
2025-10-30 17:55:55 +09:00
|
|
|
<TableCell className="h-16 px-6 py-3 text-sm">
|
2025-10-27 09:39:11 +09:00
|
|
|
<Badge variant={connection.is_active === "Y" ? "default" : "secondary"}>
|
|
|
|
|
{connection.is_active === "Y" ? "활성" : "비활성"}
|
|
|
|
|
</Badge>
|
|
|
|
|
</TableCell>
|
2025-10-30 17:55:55 +09:00
|
|
|
<TableCell className="h-16 px-6 py-3 text-sm">
|
2025-10-27 09:39:11 +09:00
|
|
|
{connection.last_test_date ? (
|
|
|
|
|
<div>
|
|
|
|
|
<div>{new Date(connection.last_test_date).toLocaleDateString()}</div>
|
|
|
|
|
<Badge
|
|
|
|
|
variant={connection.last_test_result === "Y" ? "default" : "destructive"}
|
|
|
|
|
className="mt-1"
|
2025-10-21 10:59:15 +09:00
|
|
|
>
|
2025-10-27 09:39:11 +09:00
|
|
|
{connection.last_test_result === "Y" ? "성공" : "실패"}
|
|
|
|
|
</Badge>
|
2025-10-21 10:59:15 +09:00
|
|
|
</div>
|
2025-10-27 09:39:11 +09:00
|
|
|
) : (
|
|
|
|
|
<span className="text-muted-foreground">-</span>
|
|
|
|
|
)}
|
|
|
|
|
</TableCell>
|
2025-10-30 17:55:55 +09:00
|
|
|
<TableCell className="h-16 px-6 py-3 text-sm">
|
2025-10-27 09:39:11 +09:00
|
|
|
<div className="flex items-center gap-2">
|
|
|
|
|
<Button
|
|
|
|
|
variant="outline"
|
|
|
|
|
size="sm"
|
|
|
|
|
onClick={() => handleTestConnection(connection)}
|
|
|
|
|
disabled={testingConnections.has(connection.id!)}
|
|
|
|
|
className="h-9 text-sm"
|
|
|
|
|
>
|
|
|
|
|
{testingConnections.has(connection.id!) ? "테스트 중..." : "테스트"}
|
|
|
|
|
</Button>
|
|
|
|
|
{testResults.has(connection.id!) && (
|
|
|
|
|
<Badge variant={testResults.get(connection.id!) ? "default" : "destructive"}>
|
|
|
|
|
{testResults.get(connection.id!) ? "성공" : "실패"}
|
|
|
|
|
</Badge>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
</TableCell>
|
2025-10-30 17:55:55 +09:00
|
|
|
<TableCell className="h-16 px-6 py-3 text-right">
|
2025-10-27 09:39:11 +09:00
|
|
|
<div className="flex justify-end gap-2">
|
|
|
|
|
<Button
|
|
|
|
|
variant="ghost"
|
|
|
|
|
size="icon"
|
|
|
|
|
onClick={() => handleEditConnection(connection)}
|
|
|
|
|
className="h-8 w-8"
|
|
|
|
|
>
|
|
|
|
|
<Pencil className="h-4 w-4" />
|
|
|
|
|
</Button>
|
|
|
|
|
<Button
|
|
|
|
|
variant="ghost"
|
|
|
|
|
size="icon"
|
|
|
|
|
onClick={() => handleDeleteConnection(connection)}
|
|
|
|
|
className="text-destructive hover:bg-destructive/10 h-8 w-8"
|
|
|
|
|
>
|
|
|
|
|
<Trash2 className="h-4 w-4" />
|
|
|
|
|
</Button>
|
|
|
|
|
</div>
|
|
|
|
|
</TableCell>
|
|
|
|
|
</TableRow>
|
|
|
|
|
))}
|
|
|
|
|
</TableBody>
|
|
|
|
|
</Table>
|
|
|
|
|
</div>
|
2025-10-21 10:59:15 +09:00
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
{/* 연결 설정 모달 */}
|
|
|
|
|
{isModalOpen && (
|
|
|
|
|
<RestApiConnectionModal
|
|
|
|
|
isOpen={isModalOpen}
|
|
|
|
|
onClose={handleModalCancel}
|
|
|
|
|
onSave={handleModalSave}
|
|
|
|
|
connection={editingConnection}
|
|
|
|
|
/>
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
{/* 삭제 확인 다이얼로그 */}
|
|
|
|
|
<AlertDialog open={deleteDialogOpen} onOpenChange={setDeleteDialogOpen}>
|
2025-10-22 14:52:13 +09:00
|
|
|
<AlertDialogContent className="max-w-[95vw] sm:max-w-[500px]">
|
2025-10-21 10:59:15 +09:00
|
|
|
<AlertDialogHeader>
|
2025-10-22 14:52:13 +09:00
|
|
|
<AlertDialogTitle className="text-base sm:text-lg">연결 삭제 확인</AlertDialogTitle>
|
|
|
|
|
<AlertDialogDescription className="text-xs sm:text-sm">
|
2025-10-21 10:59:15 +09:00
|
|
|
"{connectionToDelete?.connection_name}" 연결을 삭제하시겠습니까?
|
2025-10-27 09:39:11 +09:00
|
|
|
<br />이 작업은 되돌릴 수 없습니다.
|
2025-10-21 10:59:15 +09:00
|
|
|
</AlertDialogDescription>
|
|
|
|
|
</AlertDialogHeader>
|
2025-10-22 14:52:13 +09:00
|
|
|
<AlertDialogFooter className="gap-2 sm:gap-0">
|
|
|
|
|
<AlertDialogCancel
|
|
|
|
|
onClick={cancelDeleteConnection}
|
|
|
|
|
className="h-8 flex-1 text-xs sm:h-10 sm:flex-none sm:text-sm"
|
|
|
|
|
>
|
|
|
|
|
취소
|
|
|
|
|
</AlertDialogCancel>
|
2025-10-21 10:59:15 +09:00
|
|
|
<AlertDialogAction
|
|
|
|
|
onClick={confirmDeleteConnection}
|
2025-10-27 09:39:11 +09:00
|
|
|
className="bg-destructive hover:bg-destructive/90 h-8 flex-1 text-xs sm:h-10 sm:flex-none sm:text-sm"
|
2025-10-21 10:59:15 +09:00
|
|
|
>
|
|
|
|
|
삭제
|
|
|
|
|
</AlertDialogAction>
|
|
|
|
|
</AlertDialogFooter>
|
|
|
|
|
</AlertDialogContent>
|
|
|
|
|
</AlertDialog>
|
|
|
|
|
</>
|
|
|
|
|
);
|
|
|
|
|
}
|