From 101635d73553353a20bed89beafa413254bb016a Mon Sep 17 00:00:00 2001 From: hejl Date: Mon, 23 Sep 2024 09:26:06 +0800 Subject: [PATCH] add import from cURL command of http request node --- .../nodes/http/components/curl-panel.tsx | 125 ++++++++++++++++++ .../components/workflow/nodes/http/panel.tsx | 42 ++++-- .../workflow/nodes/http/use-config.ts | 22 +++ 3 files changed, 180 insertions(+), 9 deletions(-) create mode 100644 web/app/components/workflow/nodes/http/components/curl-panel.tsx diff --git a/web/app/components/workflow/nodes/http/components/curl-panel.tsx b/web/app/components/workflow/nodes/http/components/curl-panel.tsx new file mode 100644 index 0000000000..1806167cd0 --- /dev/null +++ b/web/app/components/workflow/nodes/http/components/curl-panel.tsx @@ -0,0 +1,125 @@ +'use client' +import type { FC } from 'react' +import React, { useCallback, useState } from 'react' +import { useTranslation } from 'react-i18next' +import { BodyType, type HttpNodeType, Method } from '../types' +import Modal from '@/app/components/base/modal' +import Button from '@/app/components/base/button' +import { useNodesInteractions } from '@/app/components/workflow/hooks' + +type Props = { + nodeId: string + isShow: boolean + onHide: () => void + handleCurlImport: (node: HttpNodeType) => void +} + +const parseCurl = (curlCommand: string): HttpNodeType => { + const node: Partial = { + title: 'HTTP Request', + desc: 'Imported from cURL', + method: Method.get, + url: '', + headers: '', + params: '', + body: { type: BodyType.none, data: '' }, + } + const args = curlCommand.match(/(?:[^\s"']+|"[^"]*"|'[^']*')+/g) || [] + + for (let i = 1; i < args.length; i++) { + const arg = args[i].replace(/^['"]|['"]$/g, '') + switch (arg) { + case '-X': + case '--request': + node.method = (args[++i].replace(/^['"]|['"]$/g, '') as Method) || Method.get + break + case '-H': + case '--header': + node.headers += (node.headers ? '\n' : '') + args[++i].replace(/^['"]|['"]$/g, '') + break + case '-d': + case '--data': + case '--data-raw': + case '--data-binary': + node.body = { type: BodyType.rawText, data: args[++i].replace(/^['"]|['"]$/g, '') } + break + case '-F': + case '--form': { + if (node.body?.type !== BodyType.formData) + node.body = { type: BodyType.formData, data: '' } + const formData = args[++i].replace(/^['"]|['"]$/g, '') + const [key, ...valueParts] = formData.split('=') + let value = valueParts.join('=') + + // To support command like `curl -F "file=@/path/to/file;type=application/zip"` + // the `;type=application/zip` should translate to `Content-Type: application/zip` + const typeMatch = value.match(/^(.+?);type=(.+)$/) + if (typeMatch) { + const [, actualValue, mimeType] = typeMatch + value = actualValue + node.headers += `${node.headers ? '\n' : ''}Content-Type: ${mimeType}` + } + + node.body.data += `${node.body.data ? '\n' : ''}${key}:${value}` + break + } + case '--json': + node.body = { type: BodyType.json, data: args[++i].replace(/^['"]|['"]$/g, '') } + break + default: + if (arg.startsWith('http') && !node.url) + node.url = arg + break + } + } + + // Extract query params from URL + const urlParts = node.url?.split('?') || [] + if (urlParts.length > 1) { + node.url = urlParts[0] + node.params = urlParts[1].replace(/&/g, '\n').replace(/=/g, ': ') + } + + return node as HttpNodeType +} + +const CurlPanel: FC = ({ nodeId, isShow, onHide, handleCurlImport }) => { + const [inputString, setInputString] = useState('') + const { handleNodeSelect } = useNodesInteractions() + const { t } = useTranslation() + + const handleSave = useCallback(() => { + onHide() + const node = parseCurl(inputString) + handleCurlImport(node) + // Close the panel then open it again to make the panel re-render + handleNodeSelect(nodeId, true) + setTimeout(() => { + handleNodeSelect(nodeId) + }, 0) + }, [onHide, nodeId, inputString, handleNodeSelect, handleCurlImport]) + + return ( + +
+