rest api 연결 ui 개선
This commit is contained in:
parent
4e3dbd4bc8
commit
ef5b86cc4c
|
|
@ -73,6 +73,9 @@ export const ExternalDbConnectionModal: React.FC<ExternalDbConnectionModalProps>
|
|||
|
||||
// 연결 정보가 변경될 때 폼 데이터 업데이트
|
||||
useEffect(() => {
|
||||
// 테스트 관련 상태 초기화
|
||||
setTestResult(null);
|
||||
|
||||
if (connection) {
|
||||
setFormData({
|
||||
...connection,
|
||||
|
|
@ -304,7 +307,9 @@ export const ExternalDbConnectionModal: React.FC<ExternalDbConnectionModalProps>
|
|||
<Dialog open={isOpen} onOpenChange={onClose}>
|
||||
<DialogContent className="max-h-[90vh] max-w-[95vw] overflow-y-auto sm:max-w-2xl">
|
||||
<DialogHeader>
|
||||
<DialogTitle className="text-base sm:text-lg">{isEditMode ? "연결 정보 수정" : "새 외부 DB 연결 추가"}</DialogTitle>
|
||||
<DialogTitle className="text-base sm:text-lg">
|
||||
{isEditMode ? "연결 정보 수정" : "새 외부 DB 연결 추가"}
|
||||
</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
<div className="space-y-3 sm:space-y-4">
|
||||
|
|
@ -437,7 +442,7 @@ export const ExternalDbConnectionModal: React.FC<ExternalDbConnectionModalProps>
|
|||
type="button"
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="absolute right-0 top-0 h-full px-3 py-2 hover:bg-transparent"
|
||||
className="absolute top-0 right-0 h-full px-3 py-2 hover:bg-transparent"
|
||||
onClick={() => setShowPassword(!showPassword)}
|
||||
>
|
||||
{showPassword ? <EyeOff className="h-4 w-4" /> : <Eye className="h-4 w-4" />}
|
||||
|
|
@ -464,7 +469,7 @@ export const ExternalDbConnectionModal: React.FC<ExternalDbConnectionModalProps>
|
|||
>
|
||||
{testingConnection ? "테스트 중..." : "연결 테스트"}
|
||||
</Button>
|
||||
{testingConnection && <div className="text-sm text-gray-500">연결을 확인하고 있습니다...</div>}
|
||||
{testingConnection && <div className="text-muted-foreground text-sm">연결을 확인하고 있습니다...</div>}
|
||||
</div>
|
||||
|
||||
{/* 테스트 결과 표시 */}
|
||||
|
|
@ -492,7 +497,9 @@ export const ExternalDbConnectionModal: React.FC<ExternalDbConnectionModalProps>
|
|||
{!testResult.success && testResult.error && (
|
||||
<div className="mt-2 text-xs">
|
||||
<div>오류 코드: {testResult.error.code}</div>
|
||||
{testResult.error.details && <div className="mt-1 text-destructive">{testResult.error.details}</div>}
|
||||
{testResult.error.details && (
|
||||
<div className="text-destructive mt-1">{testResult.error.details}</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
|
@ -602,7 +609,11 @@ export const ExternalDbConnectionModal: React.FC<ExternalDbConnectionModalProps>
|
|||
>
|
||||
취소
|
||||
</Button>
|
||||
<Button onClick={handleSave} disabled={loading} className="h-8 flex-1 text-xs sm:h-10 sm:flex-none sm:text-sm">
|
||||
<Button
|
||||
onClick={handleSave}
|
||||
disabled={loading}
|
||||
className="h-8 flex-1 text-xs sm:h-10 sm:flex-none sm:text-sm"
|
||||
>
|
||||
{loading ? "저장 중..." : isEditMode ? "수정" : "생성"}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
|
|
|
|||
|
|
@ -206,7 +206,7 @@ export function RestApiConnectionList() {
|
|||
<div className="flex flex-col gap-4 sm:flex-row sm:items-center">
|
||||
{/* 검색 */}
|
||||
<div className="relative w-full sm:w-[300px]">
|
||||
<Search className="absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" />
|
||||
<Search className="text-muted-foreground absolute top-1/2 left-3 h-4 w-4 -translate-y-1/2" />
|
||||
<Input
|
||||
placeholder="연결명 또는 URL로 검색..."
|
||||
value={searchTerm}
|
||||
|
|
@ -246,118 +246,125 @@ export function RestApiConnectionList() {
|
|||
|
||||
{/* 추가 버튼 */}
|
||||
<Button onClick={handleAddConnection} className="h-10 gap-2 text-sm font-medium">
|
||||
<Plus className="h-4 w-4" />
|
||||
새 연결 추가
|
||||
<Plus className="h-4 w-4" />새 연결 추가
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* 연결 목록 */}
|
||||
{loading ? (
|
||||
<div className="flex h-64 items-center justify-center rounded-lg border bg-card shadow-sm">
|
||||
<div className="text-sm text-muted-foreground">로딩 중...</div>
|
||||
<div className="bg-card flex h-64 items-center justify-center rounded-lg border shadow-sm">
|
||||
<div className="text-muted-foreground text-sm">로딩 중...</div>
|
||||
</div>
|
||||
) : connections.length === 0 ? (
|
||||
<div className="flex h-64 flex-col items-center justify-center rounded-lg border bg-card shadow-sm">
|
||||
<div className="bg-card flex h-64 flex-col items-center justify-center rounded-lg border shadow-sm">
|
||||
<div className="flex flex-col items-center gap-2 text-center">
|
||||
<p className="text-sm text-muted-foreground">등록된 REST API 연결이 없습니다</p>
|
||||
<p className="text-muted-foreground text-sm">등록된 REST API 연결이 없습니다</p>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="rounded-lg border bg-card shadow-sm">
|
||||
<div className="bg-card rounded-lg border shadow-sm">
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow className="border-b bg-muted/50 hover:bg-muted/50">
|
||||
<TableHead className="h-12 text-sm font-semibold">연결명</TableHead>
|
||||
<TableHead className="h-12 text-sm font-semibold">기본 URL</TableHead>
|
||||
<TableHead className="h-12 text-sm font-semibold">인증 타입</TableHead>
|
||||
<TableHead className="h-12 text-sm font-semibold">헤더 수</TableHead>
|
||||
<TableHead className="h-12 text-sm font-semibold">상태</TableHead>
|
||||
<TableHead className="h-12 text-sm font-semibold">마지막 테스트</TableHead>
|
||||
<TableHead className="h-12 text-sm font-semibold">연결 테스트</TableHead>
|
||||
<TableHead className="h-12 text-right text-sm font-semibold">작업</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{connections.map((connection) => (
|
||||
<TableRow key={connection.id} className="border-b transition-colors hover:bg-muted/50">
|
||||
<TableCell className="h-16 text-sm">
|
||||
<div className="font-medium">{connection.connection_name}</div>
|
||||
<TableHeader>
|
||||
<TableRow className="bg-muted/50 hover:bg-muted/50 border-b">
|
||||
<TableHead className="h-12 text-sm font-semibold">연결명</TableHead>
|
||||
<TableHead className="h-12 text-sm font-semibold">기본 URL</TableHead>
|
||||
<TableHead className="h-12 text-sm font-semibold">인증 타입</TableHead>
|
||||
<TableHead className="h-12 text-sm font-semibold">헤더 수</TableHead>
|
||||
<TableHead className="h-12 text-sm font-semibold">상태</TableHead>
|
||||
<TableHead className="h-12 text-sm font-semibold">마지막 테스트</TableHead>
|
||||
<TableHead className="h-12 text-sm font-semibold">연결 테스트</TableHead>
|
||||
<TableHead className="h-12 text-right text-sm font-semibold">작업</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{connections.map((connection) => (
|
||||
<TableRow key={connection.id} className="hover:bg-muted/50 border-b transition-colors">
|
||||
<TableCell className="h-16 text-sm">
|
||||
<div className="max-w-[200px]">
|
||||
<div className="truncate font-medium" title={connection.connection_name}>
|
||||
{connection.connection_name}
|
||||
</div>
|
||||
{connection.description && (
|
||||
<div className="mt-1 text-xs text-muted-foreground">{connection.description}</div>
|
||||
)}
|
||||
</TableCell>
|
||||
<TableCell className="h-16 font-mono text-sm">{connection.base_url}</TableCell>
|
||||
<TableCell className="h-16 text-sm">
|
||||
<Badge variant="outline">
|
||||
{AUTH_TYPE_LABELS[connection.auth_type] || connection.auth_type}
|
||||
</Badge>
|
||||
</TableCell>
|
||||
<TableCell className="h-16 text-center text-sm">
|
||||
{Object.keys(connection.default_headers || {}).length}
|
||||
</TableCell>
|
||||
<TableCell className="h-16 text-sm">
|
||||
<Badge variant={connection.is_active === "Y" ? "default" : "secondary"}>
|
||||
{connection.is_active === "Y" ? "활성" : "비활성"}
|
||||
</Badge>
|
||||
</TableCell>
|
||||
<TableCell className="h-16 text-sm">
|
||||
{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"
|
||||
>
|
||||
{connection.last_test_result === "Y" ? "성공" : "실패"}
|
||||
</Badge>
|
||||
<div className="text-muted-foreground mt-1 truncate text-xs" title={connection.description}>
|
||||
{connection.description}
|
||||
</div>
|
||||
) : (
|
||||
<span className="text-muted-foreground">-</span>
|
||||
)}
|
||||
</TableCell>
|
||||
<TableCell className="h-16 text-sm">
|
||||
<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"
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell className="h-16 font-mono text-sm">
|
||||
<div className="max-w-[300px] truncate" title={connection.base_url}>
|
||||
{connection.base_url}
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell className="h-16 text-sm">
|
||||
<Badge variant="outline">{AUTH_TYPE_LABELS[connection.auth_type] || connection.auth_type}</Badge>
|
||||
</TableCell>
|
||||
<TableCell className="h-16 text-center text-sm">
|
||||
{Object.keys(connection.default_headers || {}).length}
|
||||
</TableCell>
|
||||
<TableCell className="h-16 text-sm">
|
||||
<Badge variant={connection.is_active === "Y" ? "default" : "secondary"}>
|
||||
{connection.is_active === "Y" ? "활성" : "비활성"}
|
||||
</Badge>
|
||||
</TableCell>
|
||||
<TableCell className="h-16 text-sm">
|
||||
{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"
|
||||
>
|
||||
{testingConnections.has(connection.id!) ? "테스트 중..." : "테스트"}
|
||||
</Button>
|
||||
{testResults.has(connection.id!) && (
|
||||
<Badge variant={testResults.get(connection.id!) ? "default" : "destructive"}>
|
||||
{testResults.get(connection.id!) ? "성공" : "실패"}
|
||||
</Badge>
|
||||
)}
|
||||
{connection.last_test_result === "Y" ? "성공" : "실패"}
|
||||
</Badge>
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell className="h-16 text-right">
|
||||
<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="h-8 w-8 text-destructive hover:bg-destructive/10"
|
||||
>
|
||||
<Trash2 className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
) : (
|
||||
<span className="text-muted-foreground">-</span>
|
||||
)}
|
||||
</TableCell>
|
||||
<TableCell className="h-16 text-sm">
|
||||
<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>
|
||||
<TableCell className="h-16 text-right">
|
||||
<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>
|
||||
)}
|
||||
|
||||
{/* 연결 설정 모달 */}
|
||||
|
|
@ -377,8 +384,7 @@ export function RestApiConnectionList() {
|
|||
<AlertDialogTitle className="text-base sm:text-lg">연결 삭제 확인</AlertDialogTitle>
|
||||
<AlertDialogDescription className="text-xs sm:text-sm">
|
||||
"{connectionToDelete?.connection_name}" 연결을 삭제하시겠습니까?
|
||||
<br />
|
||||
이 작업은 되돌릴 수 없습니다.
|
||||
<br />이 작업은 되돌릴 수 없습니다.
|
||||
</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogFooter className="gap-2 sm:gap-0">
|
||||
|
|
@ -390,7 +396,7 @@ export function RestApiConnectionList() {
|
|||
</AlertDialogCancel>
|
||||
<AlertDialogAction
|
||||
onClick={confirmDeleteConnection}
|
||||
className="h-8 flex-1 bg-destructive text-xs hover:bg-destructive/90 sm:h-10 sm:flex-none sm:text-sm"
|
||||
className="bg-destructive hover:bg-destructive/90 h-8 flex-1 text-xs sm:h-10 sm:flex-none sm:text-sm"
|
||||
>
|
||||
삭제
|
||||
</AlertDialogAction>
|
||||
|
|
|
|||
|
|
@ -46,6 +46,7 @@ export function RestApiConnectionModal({ isOpen, onClose, onSave, connection }:
|
|||
const [testEndpoint, setTestEndpoint] = useState("");
|
||||
const [testing, setTesting] = useState(false);
|
||||
const [testResult, setTestResult] = useState<RestApiTestResult | null>(null);
|
||||
const [testRequestUrl, setTestRequestUrl] = useState<string>("");
|
||||
const [saving, setSaving] = useState(false);
|
||||
|
||||
// 기존 연결 데이터 로드
|
||||
|
|
@ -77,6 +78,7 @@ export function RestApiConnectionModal({ isOpen, onClose, onSave, connection }:
|
|||
|
||||
setTestResult(null);
|
||||
setTestEndpoint("");
|
||||
setTestRequestUrl("");
|
||||
}, [connection, isOpen]);
|
||||
|
||||
// 연결 테스트
|
||||
|
|
@ -94,6 +96,10 @@ export function RestApiConnectionModal({ isOpen, onClose, onSave, connection }:
|
|||
setTesting(true);
|
||||
setTestResult(null);
|
||||
|
||||
// 사용자가 테스트하려는 실제 외부 API URL 설정
|
||||
const fullUrl = testEndpoint ? `${baseUrl}${testEndpoint}` : baseUrl;
|
||||
setTestRequestUrl(fullUrl);
|
||||
|
||||
try {
|
||||
const result = await ExternalRestApiConnectionAPI.testConnection({
|
||||
base_url: baseUrl,
|
||||
|
|
@ -220,7 +226,7 @@ export function RestApiConnectionModal({ isOpen, onClose, onSave, connection }:
|
|||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="connection-name">
|
||||
연결명 <span className="text-red-500">*</span>
|
||||
연결명 <span className="text-destructive">*</span>
|
||||
</Label>
|
||||
<Input
|
||||
id="connection-name"
|
||||
|
|
@ -243,7 +249,7 @@ export function RestApiConnectionModal({ isOpen, onClose, onSave, connection }:
|
|||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="base-url">
|
||||
기본 URL <span className="text-red-500">*</span>
|
||||
기본 URL <span className="text-destructive">*</span>
|
||||
</Label>
|
||||
<Input
|
||||
id="base-url"
|
||||
|
|
@ -283,14 +289,14 @@ export function RestApiConnectionModal({ isOpen, onClose, onSave, connection }:
|
|||
<button
|
||||
type="button"
|
||||
onClick={() => setShowAdvanced(!showAdvanced)}
|
||||
className="flex items-center space-x-2 text-sm font-semibold hover:text-blue-600"
|
||||
className="hover:text-primary flex items-center space-x-2 text-sm font-semibold transition-colors"
|
||||
>
|
||||
<span>고급 설정</span>
|
||||
{showAdvanced ? <ChevronUp className="h-4 w-4" /> : <ChevronDown className="h-4 w-4" />}
|
||||
</button>
|
||||
|
||||
{showAdvanced && (
|
||||
<div className="space-y-4 rounded-md border bg-gray-50 p-4">
|
||||
<div className="bg-muted space-y-4 rounded-md border p-4">
|
||||
<div className="grid grid-cols-3 gap-4">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="timeout">타임아웃 (ms)</Label>
|
||||
|
|
@ -342,7 +348,7 @@ export function RestApiConnectionModal({ isOpen, onClose, onSave, connection }:
|
|||
id="test-endpoint"
|
||||
value={testEndpoint}
|
||||
onChange={(e) => setTestEndpoint(e.target.value)}
|
||||
placeholder="/api/v1/test 또는 빈칸 (기본 URL만 테스트)"
|
||||
placeholder="엔드포인트 또는 빈칸(기본 URL만 테스트)"
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
|
@ -351,6 +357,41 @@ export function RestApiConnectionModal({ isOpen, onClose, onSave, connection }:
|
|||
{testing ? "테스트 중..." : "연결 테스트"}
|
||||
</Button>
|
||||
|
||||
{/* 테스트 요청 정보 표시 */}
|
||||
{testRequestUrl && (
|
||||
<div className="bg-muted/30 space-y-3 rounded-md border p-3">
|
||||
<div>
|
||||
<div className="text-muted-foreground mb-1 text-xs font-medium">테스트 요청 URL</div>
|
||||
<code className="text-foreground block text-xs break-all">GET {testRequestUrl}</code>
|
||||
</div>
|
||||
|
||||
{Object.keys(defaultHeaders).length > 0 && (
|
||||
<div>
|
||||
<div className="text-muted-foreground mb-1 text-xs font-medium">요청 헤더</div>
|
||||
<div className="space-y-1">
|
||||
{Object.entries(defaultHeaders).map(([key, value]) => (
|
||||
<code key={key} className="text-foreground block text-xs">
|
||||
{key}: {value}
|
||||
</code>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{authType !== "none" && (
|
||||
<div>
|
||||
<div className="text-muted-foreground mb-1 text-xs font-medium">인증 방식</div>
|
||||
<code className="text-foreground block text-xs">
|
||||
{authType === "api-key" && "API Key"}
|
||||
{authType === "bearer" && "Bearer Token"}
|
||||
{authType === "basic" && "Basic Auth"}
|
||||
{authType === "oauth2" && "OAuth 2.0"}
|
||||
</code>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{testResult && (
|
||||
<div
|
||||
className={`rounded-md border p-4 ${
|
||||
|
|
|
|||
Loading…
Reference in New Issue