125 lines
4.1 KiB
TypeScript
125 lines
4.1 KiB
TypeScript
|
|
"use client";
|
||
|
|
|
||
|
|
/**
|
||
|
|
* HTTP 요청 액션 노드
|
||
|
|
* REST API를 호출하는 노드
|
||
|
|
*/
|
||
|
|
|
||
|
|
import { memo } from "react";
|
||
|
|
import { Handle, Position, NodeProps } from "reactflow";
|
||
|
|
import { Globe, Lock, Unlock } from "lucide-react";
|
||
|
|
import type { HttpRequestActionNodeData } from "@/types/node-editor";
|
||
|
|
|
||
|
|
// HTTP 메서드별 색상
|
||
|
|
const METHOD_COLORS: Record<string, { bg: string; text: string }> = {
|
||
|
|
GET: { bg: "bg-green-100", text: "text-green-700" },
|
||
|
|
POST: { bg: "bg-blue-100", text: "text-blue-700" },
|
||
|
|
PUT: { bg: "bg-orange-100", text: "text-orange-700" },
|
||
|
|
PATCH: { bg: "bg-yellow-100", text: "text-yellow-700" },
|
||
|
|
DELETE: { bg: "bg-red-100", text: "text-red-700" },
|
||
|
|
HEAD: { bg: "bg-gray-100", text: "text-gray-700" },
|
||
|
|
OPTIONS: { bg: "bg-purple-100", text: "text-purple-700" },
|
||
|
|
};
|
||
|
|
|
||
|
|
export const HttpRequestActionNode = memo(({ data, selected }: NodeProps<HttpRequestActionNodeData>) => {
|
||
|
|
const methodColor = METHOD_COLORS[data.method] || METHOD_COLORS.GET;
|
||
|
|
const hasUrl = data.url && data.url.trim().length > 0;
|
||
|
|
const hasAuth = data.authentication?.type && data.authentication.type !== "none";
|
||
|
|
|
||
|
|
// URL에서 도메인 추출
|
||
|
|
const getDomain = (url: string) => {
|
||
|
|
try {
|
||
|
|
const urlObj = new URL(url);
|
||
|
|
return urlObj.hostname;
|
||
|
|
} catch {
|
||
|
|
return url;
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
return (
|
||
|
|
<div
|
||
|
|
className={`min-w-[250px] rounded-lg border-2 bg-white shadow-md transition-all ${
|
||
|
|
selected ? "border-cyan-500 shadow-lg" : "border-gray-200"
|
||
|
|
}`}
|
||
|
|
>
|
||
|
|
{/* 입력 핸들 */}
|
||
|
|
<Handle
|
||
|
|
type="target"
|
||
|
|
position={Position.Left}
|
||
|
|
className="!h-3 !w-3 !border-2 !border-white !bg-cyan-500"
|
||
|
|
/>
|
||
|
|
|
||
|
|
{/* 헤더 */}
|
||
|
|
<div className="flex items-center gap-2 rounded-t-lg bg-cyan-500 px-3 py-2 text-white">
|
||
|
|
<Globe className="h-4 w-4" />
|
||
|
|
<div className="flex-1">
|
||
|
|
<div className="text-sm font-semibold">{data.displayName || "HTTP 요청"}</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
{/* 본문 */}
|
||
|
|
<div className="space-y-2 p-3">
|
||
|
|
{/* 메서드 & 인증 */}
|
||
|
|
<div className="flex items-center gap-2">
|
||
|
|
<span className={`rounded px-2 py-0.5 text-xs font-bold ${methodColor.bg} ${methodColor.text}`}>
|
||
|
|
{data.method}
|
||
|
|
</span>
|
||
|
|
{hasAuth ? (
|
||
|
|
<span className="flex items-center gap-1 rounded bg-green-100 px-1.5 py-0.5 text-xs text-green-700">
|
||
|
|
<Lock className="h-3 w-3" />
|
||
|
|
{data.authentication?.type}
|
||
|
|
</span>
|
||
|
|
) : (
|
||
|
|
<span className="flex items-center gap-1 rounded bg-gray-100 px-1.5 py-0.5 text-xs text-gray-500">
|
||
|
|
<Unlock className="h-3 w-3" />
|
||
|
|
인증없음
|
||
|
|
</span>
|
||
|
|
)}
|
||
|
|
</div>
|
||
|
|
|
||
|
|
{/* URL */}
|
||
|
|
<div className="text-xs">
|
||
|
|
<span className="text-gray-500">URL: </span>
|
||
|
|
{hasUrl ? (
|
||
|
|
<span className="truncate text-gray-700" title={data.url}>
|
||
|
|
{getDomain(data.url)}
|
||
|
|
</span>
|
||
|
|
) : (
|
||
|
|
<span className="text-orange-500">URL 설정 필요</span>
|
||
|
|
)}
|
||
|
|
</div>
|
||
|
|
|
||
|
|
{/* 바디 타입 */}
|
||
|
|
{data.bodyType && data.bodyType !== "none" && (
|
||
|
|
<div className="text-xs">
|
||
|
|
<span className="text-gray-500">Body: </span>
|
||
|
|
<span className="rounded bg-gray-100 px-1.5 py-0.5 text-gray-600">
|
||
|
|
{data.bodyType.toUpperCase()}
|
||
|
|
</span>
|
||
|
|
</div>
|
||
|
|
)}
|
||
|
|
|
||
|
|
{/* 타임아웃 & 재시도 */}
|
||
|
|
<div className="flex gap-2 text-xs text-gray-500">
|
||
|
|
{data.options?.timeout && (
|
||
|
|
<span>타임아웃: {Math.round(data.options.timeout / 1000)}초</span>
|
||
|
|
)}
|
||
|
|
{data.options?.retryCount && data.options.retryCount > 0 && (
|
||
|
|
<span>재시도: {data.options.retryCount}회</span>
|
||
|
|
)}
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
{/* 출력 핸들 */}
|
||
|
|
<Handle
|
||
|
|
type="source"
|
||
|
|
position={Position.Right}
|
||
|
|
className="!h-3 !w-3 !border-2 !border-white !bg-cyan-500"
|
||
|
|
/>
|
||
|
|
</div>
|
||
|
|
);
|
||
|
|
});
|
||
|
|
|
||
|
|
HttpRequestActionNode.displayName = "HttpRequestActionNode";
|
||
|
|
|