"use client"; import React, { useState, useCallback, useEffect } from "react"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Textarea } from "@/components/ui/textarea"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { Button } from "@/components/ui/button"; import { Badge } from "@/components/ui/badge"; import { Alert, AlertDescription } from "@/components/ui/alert"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/components/ui/collapsible"; import { Globe, Key, Clock, RefreshCw, ChevronDown, ChevronRight, Plus, Trash2, Copy, Eye, EyeOff, AlertCircle, CheckCircle, } from "lucide-react"; // νƒ€μž… import import { RestApiSettings as RestApiSettingsType, RestApiSettingsProps } from "@/types/external-call/ExternalCallTypes"; import { HttpMethod, AuthenticationType, COMMON_HEADER_PRESETS, JSON_BODY_TEMPLATES, DEFAULT_RETRY_POLICY, DEFAULT_TIMEOUT_CONFIG, } from "@/types/external-call/RestApiTypes"; /** * πŸ”§ REST API μ „μš© μ„€μ • μ»΄ν¬λ„ŒνŠΈ * * URL, HTTP λ©”μ„œλ“œ, 헀더, 인증, λ°”λ”” ν…œν”Œλ¦Ώ λ“± * REST API ν˜ΈμΆœμ— ν•„μš”ν•œ λͺ¨λ“  섀정을 관리 */ const RestApiSettings: React.FC = ({ settings, onSettingsChange, readonly = false }) => { // μƒνƒœ 관리 const [activeTab, setActiveTab] = useState("basic"); const [showPassword, setShowPassword] = useState(false); const [isAdvancedOpen, setIsAdvancedOpen] = useState(false); const [validationErrors, setValidationErrors] = useState([]); const [newHeaderKey, setNewHeaderKey] = useState(""); const [newHeaderValue, setNewHeaderValue] = useState(""); // URL λ³€κ²½ ν•Έλ“€λŸ¬ const handleUrlChange = useCallback( (url: string) => { onSettingsChange({ ...settings, apiUrl: url, }); }, [settings, onSettingsChange], ); // HTTP λ©”μ„œλ“œ λ³€κ²½ ν•Έλ“€λŸ¬ const handleMethodChange = useCallback( (method: HttpMethod) => { onSettingsChange({ ...settings, httpMethod: method, }); }, [settings, onSettingsChange], ); // 헀더 μΆ”κ°€ ν•Έλ“€λŸ¬ const handleAddHeader = useCallback(() => { if (newHeaderKey && newHeaderValue) { onSettingsChange({ ...settings, headers: { ...settings.headers, [newHeaderKey]: newHeaderValue, }, }); setNewHeaderKey(""); setNewHeaderValue(""); } }, [settings, onSettingsChange, newHeaderKey, newHeaderValue]); // 헀더 μ‚­μ œ ν•Έλ“€λŸ¬ const handleRemoveHeader = useCallback( (key: string) => { const newHeaders = { ...settings.headers }; delete newHeaders[key]; onSettingsChange({ ...settings, headers: newHeaders, }); }, [settings, onSettingsChange], ); // 헀더 프리셋 적용 ν•Έλ“€λŸ¬ const handleApplyHeaderPreset = useCallback( (presetName: string) => { const preset = COMMON_HEADER_PRESETS.find((p) => p.name === presetName); if (preset) { onSettingsChange({ ...settings, headers: { ...settings.headers, ...preset.headers, }, }); } }, [settings, onSettingsChange], ); // λ°”λ”” ν…œν”Œλ¦Ώ λ³€κ²½ ν•Έλ“€λŸ¬ const handleBodyTemplateChange = useCallback( (template: string) => { onSettingsChange({ ...settings, bodyTemplate: template, }); }, [settings, onSettingsChange], ); // λ°”λ”” ν…œν”Œλ¦Ώ 프리셋 적용 ν•Έλ“€λŸ¬ const handleApplyBodyPreset = useCallback( (presetKey: string) => { const preset = JSON_BODY_TEMPLATES[presetKey as keyof typeof JSON_BODY_TEMPLATES]; if (preset) { onSettingsChange({ ...settings, bodyTemplate: preset.template, }); } }, [settings, onSettingsChange], ); // 인증 μ„€μ • λ³€κ²½ ν•Έλ“€λŸ¬ const handleAuthChange = useCallback( (auth: Partial) => { onSettingsChange({ ...settings, authentication: { ...settings.authentication, ...auth, } as AuthenticationType, }); }, [settings, onSettingsChange], ); // νƒ€μž„μ•„μ›ƒ λ³€κ²½ ν•Έλ“€λŸ¬ (초 λ‹¨μœ„λ₯Ό λ°€λ¦¬μ΄ˆλ‘œ λ³€ν™˜) const handleTimeoutChange = useCallback( (timeoutInSeconds: number) => { onSettingsChange({ ...settings, timeout: timeoutInSeconds * 1000, // 초λ₯Ό λ°€λ¦¬μ΄ˆλ‘œ λ³€ν™˜ }); }, [settings, onSettingsChange], ); // μž¬μ‹œλ„ 횟수 λ³€κ²½ ν•Έλ“€λŸ¬ const handleRetryCountChange = useCallback( (retryCount: number) => { onSettingsChange({ ...settings, retryCount, }); }, [settings, onSettingsChange], ); // μ„€μ • μœ νš¨μ„± 검사 const validateSettings = useCallback(() => { const errors: string[] = []; // URL 검증 if (!settings.apiUrl) { errors.push("API URL은 ν•„μˆ˜μž…λ‹ˆλ‹€."); } else if (!settings.apiUrl.startsWith("http")) { errors.push("API URL은 http:// λ˜λŠ” https://둜 μ‹œμž‘ν•΄μ•Ό ν•©λ‹ˆλ‹€."); } // λ°”λ”” ν…œν”Œλ¦Ώ JSON 검증 (POST/PUT/PATCH λ©”μ„œλ“œμΈ 경우) if (["POST", "PUT", "PATCH"].includes(settings.httpMethod) && settings.bodyTemplate) { try { // ν…œν”Œλ¦Ώ λ³€μˆ˜λ₯Ό μž„μ‹œ κ°’μœΌλ‘œ μΉ˜ν™˜ν•˜μ—¬ JSON μœ νš¨μ„± 검사 const testTemplate = settings.bodyTemplate.replace(/\{\{[^}]+\}\}/g, '"test_value"'); JSON.parse(testTemplate); } catch { errors.push("μš”μ²­ λ°”λ”” ν…œν”Œλ¦Ώμ΄ μœ νš¨ν•œ JSON ν˜•μ‹μ΄ μ•„λ‹™λ‹ˆλ‹€."); } } // 인증 μ„€μ • 검증 if (settings.authentication?.type === "bearer" && !settings.authentication.token) { errors.push("Bearer 토큰이 ν•„μš”ν•©λ‹ˆλ‹€."); } if ( settings.authentication?.type === "basic" && (!settings.authentication.username || !settings.authentication.password) ) { errors.push("Basic μΈμ¦μ—λŠ” μ‚¬μš©μžλͺ…κ³Ό λΉ„λ°€λ²ˆν˜Έκ°€ ν•„μš”ν•©λ‹ˆλ‹€."); } if (settings.authentication?.type === "api-key" && !settings.authentication.apiKey) { errors.push("API ν‚€κ°€ ν•„μš”ν•©λ‹ˆλ‹€."); } setValidationErrors(errors); return errors.length === 0; }, [settings]); // μ„€μ • λ³€κ²½ μ‹œ μœ νš¨μ„± 검사 μ‹€ν–‰ useEffect(() => { validateSettings(); }, [validateSettings]); return (
{/* μœ νš¨μ„± 검사 였λ₯˜ ν‘œμ‹œ */} {validationErrors.length > 0 && (
{validationErrors.map((error, index) => (
β€’ {error}
))}
)} κΈ°λ³Έ μ„€μ • 헀더 μš”μ²­ λ°”λ”” 인증 {/* κΈ°λ³Έ μ„€μ • νƒ­ */} κΈ°λ³Έ μ„€μ • {/* API URL */}
handleUrlChange(e.target.value)} disabled={readonly} className={validationErrors.some((e) => e.includes("URL")) ? "border-red-500" : ""} />
ν˜ΈμΆœν•  API의 전체 URL을 μž…λ ₯ν•˜μ„Έμš”.
{/* HTTP λ©”μ„œλ“œ */}
{/* κ³ κΈ‰ μ„€μ • (접을 수 μžˆλŠ” μ„Ήμ…˜) */}
{/* νƒ€μž„μ•„μ›ƒ */}
handleTimeoutChange(parseInt(e.target.value))} disabled={readonly} />
{/* μž¬μ‹œλ„ 횟수 */}
handleRetryCountChange(parseInt(e.target.value))} disabled={readonly} />
{/* 헀더 νƒ­ */}
HTTP 헀더
{COMMON_HEADER_PRESETS.map((preset) => ( ))}
{/* κΈ°μ‘΄ 헀더 λͺ©λ‘ */}
{Object.entries(settings.headers).map(([key, value]) => (
{!readonly && ( )}
))}
{/* μƒˆ 헀더 μΆ”κ°€ */} {!readonly && (
setNewHeaderKey(e.target.value)} /> setNewHeaderValue(e.target.value)} />
)}
{/* μš”μ²­ λ°”λ”” νƒ­ */}
μš”μ²­ λ°”λ”” ν…œν”Œλ¦Ώ
{Object.entries(JSON_BODY_TEMPLATES).map(([key, template]) => ( ))}
{["POST", "PUT", "PATCH"].includes(settings.httpMethod) ? ( <>