import type { FC } from 'react' import React, { useEffect, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' import type { HttpMethod, WebhookTriggerNodeType } from './types' import useConfig from './use-config' import ParameterTable from './components/parameter-table' import HeaderTable from './components/header-table' import ParagraphInput from './components/paragraph-input' import { OutputVariablesContent } from './utils/render-output-vars' import Field from '@/app/components/workflow/nodes/_base/components/field' import Split from '@/app/components/workflow/nodes/_base/components/split' import OutputVars from '@/app/components/workflow/nodes/_base/components/output-vars' import type { NodePanelProps } from '@/app/components/workflow/types' import InputWithCopy from '@/app/components/base/input-with-copy' import { InputNumber } from '@/app/components/base/input-number' import { SimpleSelect } from '@/app/components/base/select' import Toast from '@/app/components/base/toast' import Tooltip from '@/app/components/base/tooltip' import copy from 'copy-to-clipboard' import { isPrivateOrLocalAddress } from '@/utils/urlValidation' const i18nPrefix = 'workflow.nodes.triggerWebhook' const HTTP_METHODS = [ { name: 'GET', value: 'GET' }, { name: 'POST', value: 'POST' }, { name: 'PUT', value: 'PUT' }, { name: 'DELETE', value: 'DELETE' }, { name: 'PATCH', value: 'PATCH' }, { name: 'HEAD', value: 'HEAD' }, ] const CONTENT_TYPES = [ { name: 'application/json', value: 'application/json' }, { name: 'application/x-www-form-urlencoded', value: 'application/x-www-form-urlencoded' }, { name: 'text/plain', value: 'text/plain' }, { name: 'application/octet-stream', value: 'application/octet-stream' }, { name: 'multipart/form-data', value: 'multipart/form-data' }, ] const Panel: FC> = ({ id, data, }) => { const { t } = useTranslation() const [debugUrlCopied, setDebugUrlCopied] = React.useState(false) const [outputVarsCollapsed, setOutputVarsCollapsed] = useState(false) const { readOnly, inputs, handleMethodChange, handleContentTypeChange, handleHeadersChange, handleParamsChange, handleBodyChange, handleStatusCodeChange, handleStatusCodeBlur, handleResponseBodyChange, generateWebhookUrl, } = useConfig(id, data) // Ensure we only attempt to generate URL once for a newly created node without url const hasRequestedUrlRef = useRef(false) useEffect(() => { if (!readOnly && !inputs.webhook_url && !hasRequestedUrlRef.current) { hasRequestedUrlRef.current = true void generateWebhookUrl() } }, [readOnly, inputs.webhook_url, generateWebhookUrl]) return (
{/* Webhook URL Section */}
handleMethodChange(item.value as HttpMethod)} disabled={readOnly} className="h-8 pr-8 text-sm" wrapperClassName="h-8" optionWrapClassName="w-26 min-w-26 z-[5]" allowSearch={false} notClearable={true} />
{ Toast.notify({ type: 'success', message: t(`${i18nPrefix}.urlCopied`), }) }} />
{inputs.webhook_debug_url && (
{ copy(inputs.webhook_debug_url || '') setDebugUrlCopied(true) setTimeout(() => setDebugUrlCopied(false), 2000) }} >
{t(`${i18nPrefix}.debugUrlTitle`)}
{inputs.webhook_debug_url}
{isPrivateOrLocalAddress(inputs.webhook_debug_url) && (
{t(`${i18nPrefix}.debugUrlPrivateAddressWarning`)}
)}
)}
{/* Content Type */}
handleContentTypeChange(item.value as string)} disabled={readOnly} className="h-8 text-sm" wrapperClassName="h-8" optionWrapClassName="min-w-48 z-[5]" allowSearch={false} notClearable={true} />
{/* Query Parameters */} {/* Header Parameters */} {/* Request Body Parameters */} {/* Response Configuration */}
{ handleStatusCodeChange(value || 200) }} disabled={readOnly} wrapClassName="w-[120px]" className="h-8" defaultValue={200} onBlur={() => { handleStatusCodeBlur(inputs.status_code) }} />
) } export default Panel