From 91e5e334402fcf75ac7319a2815b0e0ed3398cc4 Mon Sep 17 00:00:00 2001 From: yessenia Date: Thu, 11 Sep 2025 16:44:18 +0800 Subject: [PATCH] feat: add modal style opt --- .../subscription-list/add-type-dropdown.tsx | 150 +++----- .../subscription-list/index.tsx | 61 +-- .../subscription-list/log-viewer.tsx | 177 +++++++++ .../subscription-list/manual-add-modal.tsx | 351 +++--------------- .../subscription-list/oauth-add-modal.tsx | 283 +++++--------- .../subscription-list/subscription-card.tsx | 2 +- .../workflow/block-selector/types.ts | 47 +++ web/i18n/en-US/plugin-trigger.ts | 6 +- web/i18n/zh-Hans/plugin-trigger.ts | 10 +- web/service/use-triggers.ts | 9 +- 10 files changed, 456 insertions(+), 640 deletions(-) create mode 100644 web/app/components/plugins/plugin-detail-panel/subscription-list/log-viewer.tsx diff --git a/web/app/components/plugins/plugin-detail-panel/subscription-list/add-type-dropdown.tsx b/web/app/components/plugins/plugin-detail-panel/subscription-list/add-type-dropdown.tsx index a6bc7249d5..604cd7b5ee 100644 --- a/web/app/components/plugins/plugin-detail-panel/subscription-list/add-type-dropdown.tsx +++ b/web/app/components/plugins/plugin-detail-panel/subscription-list/add-type-dropdown.tsx @@ -1,21 +1,25 @@ 'use client' import React, { useEffect, useRef } from 'react' import { useTranslation } from 'react-i18next' -import { - RiQuestionLine, - RiSettings4Line, -} from '@remixicon/react' +import { RiEqualizer2Line } from '@remixicon/react' import cn from '@/utils/classnames' +import Tooltip from '@/app/components/base/tooltip' +import { ActionButton } from '@/app/components/base/action-button' -type SubscriptionAddType = 'api-key' | 'oauth' | 'manual' - -type Props = { - onSelect: (type: SubscriptionAddType) => void - onClose: () => void - position?: 'bottom' | 'right' +enum SubscriptionAddTypeEnum { + OAuth = 'oauth', + APIKey = 'api-key', + Manual = 'manual', } -const AddTypeDropdown = ({ onSelect, onClose, position = 'bottom' }: Props) => { +type Props = { + onSelect: (type: SubscriptionAddTypeEnum) => void + onClose: () => void + position?: 'bottom' | 'right' + className?: string +} + +const AddTypeDropdown = ({ onSelect, onClose, position = 'bottom', className }: Props) => { const { t } = useTranslation() const dropdownRef = useRef(null) @@ -29,28 +33,28 @@ const AddTypeDropdown = ({ onSelect, onClose, position = 'bottom' }: Props) => { return () => document.removeEventListener('mousedown', handleClickOutside) }, [onClose]) + const onClickClientSettings = () => { + // todo: show client settings + } + const options = [ { - key: 'oauth' as const, + key: SubscriptionAddTypeEnum.OAuth, title: t('pluginTrigger.subscription.addType.options.oauth.title'), - rightIcon: RiSettings4Line, - hasRightIcon: true, + extraContent: , }, { - key: 'api-key' as const, + key: SubscriptionAddTypeEnum.APIKey, title: t('pluginTrigger.subscription.addType.options.apiKey.title'), - hasRightIcon: false, }, { - key: 'manual' as const, + key: SubscriptionAddTypeEnum.Manual, title: t('pluginTrigger.subscription.addType.options.manual.description'), // 使用 description 作为标题 - rightIcon: RiQuestionLine, - hasRightIcon: true, tooltip: t('pluginTrigger.subscription.addType.options.manual.tip'), }, ] - const handleOptionClick = (type: SubscriptionAddType) => { + const handleOptionClick = (type: SubscriptionAddTypeEnum) => { onSelect(type) } @@ -58,89 +62,41 @@ const AddTypeDropdown = ({ onSelect, onClose, position = 'bottom' }: Props) => {
- {/* Context Menu Content */} -
- {/* First Group - OAuth & API Key */} -
- {options.slice(0, 2).map((option, index) => { - const RightIconComponent = option.rightIcon - return ( - - ) - })} -
- - {/* Separator */} -
- - {/* Second Group - Manual */} -
- {options.slice(2).map((option) => { - const RightIconComponent = option.rightIcon - return ( - - ) - })} -
-
- - {/* Border overlay */} -
+ {options.map((option, index) => { + return ( + <> + {index === options.length - 1 &&
} + + + ) + })}
) } diff --git a/web/app/components/plugins/plugin-detail-panel/subscription-list/index.tsx b/web/app/components/plugins/plugin-detail-panel/subscription-list/index.tsx index 9c7ae0c7cc..42856ec768 100644 --- a/web/app/components/plugins/plugin-detail-panel/subscription-list/index.tsx +++ b/web/app/components/plugins/plugin-detail-panel/subscription-list/index.tsx @@ -2,12 +2,7 @@ import React from 'react' import { useTranslation } from 'react-i18next' import { useBoolean } from 'ahooks' -import { - RiAddLine, - RiBookOpenLine, - RiWebhookLine, -} from '@remixicon/react' -import { useDocLink } from '@/context/i18n' +import { RiAddLine } from '@remixicon/react' import SubscriptionCard from './subscription-card' import SubscriptionAddModal from './subscription-add-modal' import AddTypeDropdown from './add-type-dropdown' @@ -26,13 +21,10 @@ type SubscriptionAddType = 'api-key' | 'oauth' | 'manual' export const SubscriptionList = ({ detail }: Props) => { const { t } = useTranslation() - const docLink = useDocLink() const showTopBorder = detail.declaration.tool || detail.declaration.endpoint - // Fetch subscriptions const { data: subscriptions, isLoading, refetch } = useTriggerSubscriptions(`${detail.plugin_id}/${detail.declaration.name}`) - // Modal states const [isShowAddModal, { setTrue: showAddModal, setFalse: hideAddModal, @@ -40,7 +32,6 @@ export const SubscriptionList = ({ detail }: Props) => { const [selectedAddType, setSelectedAddType] = React.useState(null) - // Dropdown state for add button const [isShowAddDropdown, { setTrue: showAddDropdown, setFalse: hideAddDropdown, @@ -90,55 +81,27 @@ export const SubscriptionList = ({ detail }: Props) => { )}
) : ( - // List state with header and secondary add button <> -
+
{t('pluginTrigger.subscription.listNum', { num: subscriptions?.length || 0 })} - -
- -
-
- {t('pluginTrigger.subscription.list.tooltip')} -
- -
- - {t('pluginTrigger.subscription.list.tooltip.viewDocument')} -
-
-
- } + +
+ + + + {isShowAddDropdown && ( + -
-
- - - - {isShowAddDropdown && ( - - )} -
+ )}
diff --git a/web/app/components/plugins/plugin-detail-panel/subscription-list/log-viewer.tsx b/web/app/components/plugins/plugin-detail-panel/subscription-list/log-viewer.tsx new file mode 100644 index 0000000000..278b70a2ec --- /dev/null +++ b/web/app/components/plugins/plugin-detail-panel/subscription-list/log-viewer.tsx @@ -0,0 +1,177 @@ +'use client' +import React, { useState } from 'react' +import { useTranslation } from 'react-i18next' +import { + RiArrowDownSLine, + RiArrowRightSLine, + RiCheckboxCircleFill, + RiErrorWarningFill, + RiFileCopyLine, +} from '@remixicon/react' +import cn from '@/utils/classnames' +import Toast from '@/app/components/base/toast' +import CodeEditor from '@/app/components/workflow/nodes/_base/components/editor/code-editor' +import { CodeLanguage } from '@/app/components/workflow/nodes/code/types' +import type { TriggerLogEntity } from '@/app/components/workflow/block-selector/types' +import dayjs from 'dayjs' + +type Props = { + logs: TriggerLogEntity[] + className?: string +} + +const LogViewer = ({ logs, className }: Props) => { + const { t } = useTranslation() + const [expandedLogs, setExpandedLogs] = useState>(new Set()) + + const toggleLogExpansion = (logId: string) => { + const newExpanded = new Set(expandedLogs) + if (newExpanded.has(logId)) + newExpanded.delete(logId) + else + newExpanded.add(logId) + + setExpandedLogs(newExpanded) + } + + const parseRequestData = (data: any) => { + if (typeof data === 'string' && data.startsWith('payload=')) { + try { + const urlDecoded = decodeURIComponent(data.substring(8)) // Remove 'payload=' + return JSON.parse(urlDecoded) + } + catch { + return data + } + } + + if (typeof data === 'object') + return data + + try { + return JSON.parse(data) + } + catch { + return data + } + } + + const renderJsonContent = (data: any, title: string) => { + const parsedData = title === 'REQUEST' ? parseRequestData(data) : data + const isJsonObject = typeof parsedData === 'object' + + if (isJsonObject) { + return ( + {title}
} + language={CodeLanguage.json} + value={parsedData} + isJSONStringifyBeauty + nodeId="" + /> + ) + } + + return ( +
+
+
+ {title} +
+ +
+
+
+            {String(parsedData)}
+          
+
+
+ ) + } + + if (!logs || logs.length === 0) + return null + + return ( +
+ {logs.map((log, index) => { + const logId = log.id || index.toString() + const isExpanded = expandedLogs.has(logId) + const isSuccess = log.response.status_code === 200 + const isError = log.response.status_code >= 400 + + return ( +
+ {isError && ( +
+
+
+ )} + + + + {isExpanded && ( +
+ {renderJsonContent(log.request.data, 'REQUEST')} + {renderJsonContent(log.response.data, 'RESPONSE')} +
+ )} +
+ ) + })} +
+ ) +} + +export default LogViewer diff --git a/web/app/components/plugins/plugin-detail-panel/subscription-list/manual-add-modal.tsx b/web/app/components/plugins/plugin-detail-panel/subscription-list/manual-add-modal.tsx index e7fb9592d4..71f3a7f209 100644 --- a/web/app/components/plugins/plugin-detail-panel/subscription-list/manual-add-modal.tsx +++ b/web/app/components/plugins/plugin-detail-panel/subscription-list/manual-add-modal.tsx @@ -2,12 +2,8 @@ import React, { useEffect, useState } from 'react' import { useTranslation } from 'react-i18next' import { - RiArrowDownSLine, - RiArrowRightSLine, - RiCheckboxCircleLine, RiCloseLine, - RiErrorWarningLine, - RiFileCopyLine, + RiLoader2Line, } from '@remixicon/react' import Modal from '@/app/components/base/modal' import Button from '@/app/components/base/button' @@ -16,16 +12,16 @@ import Toast from '@/app/components/base/toast' import { useBuildTriggerSubscription, useCreateTriggerSubscriptionBuilder, - // useTriggerSubscriptionBuilderLogs, + useTriggerSubscriptionBuilderLogs, } from '@/service/use-triggers' import type { PluginDetail } from '@/app/components/plugins/types' -import cn from '@/utils/classnames' import type { TriggerSubscriptionBuilder } from '@/app/components/workflow/block-selector/types' import { TriggerCredentialTypeEnum } from '@/app/components/workflow/block-selector/types' -// import { BaseForm } from '@/app/components/base/form/components/base' -// import type { FormRefObject } from '@/app/components/base/form/types' +import { BaseForm } from '@/app/components/base/form/components/base' import ActionButton from '@/app/components/base/action-button' import { CopyFeedbackNew } from '@/app/components/base/copy-feedback' +import type { FormRefObject } from '@/app/components/base/form/types' +import LogViewer from './log-viewer' type Props = { pluginDetail: PluginDetail @@ -33,110 +29,30 @@ type Props = { onSuccess: () => void } -// type LogEntry = { -// timestamp: string -// method: string -// path: string -// status: number -// headers: Record -// body: any -// response: any -// } - const ManualAddModal = ({ pluginDetail, onClose, onSuccess }: Props) => { const { t } = useTranslation() - // Form state const [subscriptionName, setSubscriptionName] = useState('') const [subscriptionBuilder, setSubscriptionBuilder] = useState() - const [expandedLogs, setExpandedLogs] = useState>(new Set()) - // const formRef = React.useRef(null) - // API mutations const { mutate: createBuilder /* isPending: isCreatingBuilder */ } = useCreateTriggerSubscriptionBuilder() const { mutate: buildSubscription, isPending: isBuilding } = useBuildTriggerSubscription() - // Get provider name const providerName = `${pluginDetail.plugin_id}/${pluginDetail.declaration.name}` + const propertiesSchema = pluginDetail.declaration.trigger.subscription_schema.properties_schema || [] + const propertiesFormRef = React.useRef(null) - // const { data: logs, isLoading: isLoadingLogs } = useTriggerSubscriptionBuilderLogs( - // providerName, - // subscriptionBuilder?.id || '', - // { - // enabled: !!subscriptionBuilder?.id, - // refetchInterval: 3000, // Poll every 3 seconds - // }, - // ) - - // Mock data for demonstration - const mockLogs = [ + const { data: logData } = useTriggerSubscriptionBuilderLogs( + providerName, + subscriptionBuilder?.id || '', { - id: '1', - timestamp: '2024-01-15T18:09:14Z', - method: 'POST', - path: '/webhook', - status: 500, - headers: { - 'Content-Type': 'application/json', - 'User-Agent': 'Slack-Hooks/1.0', - 'X-Slack-Signature': 'v0=a2114d57b48eac39b9ad189dd8316235a7b4a8d21a10bd27519666489c69b503', - }, - body: { - verification_token: 'secret_tMrlL1qK5vuQAhCh', - event: { - type: 'message', - text: 'Hello world', - user: 'U1234567890', - }, - }, - response: { - error: 'Internal server error', - message: 'Failed to process webhook', - }, + enabled: !!subscriptionBuilder?.id, + refetchInterval: 3000, }, - { - id: '2', - timestamp: '2024-01-15T18:09:14Z', - method: 'POST', - path: '/webhook', - status: 200, - headers: { - 'Content-Type': 'application/json', - 'User-Agent': 'Slack-Hooks/1.0', - }, - body: { - verification_token: 'secret_tMrlL1qK5vuQAhCh', - }, - response: { - success: true, - }, - }, - { - id: '3', - timestamp: '2024-01-15T18:09:14Z', - method: 'POST', - path: '/webhook', - status: 200, - headers: { - 'Content-Type': 'application/json', - 'User-Agent': 'Slack-Hooks/1.0', - }, - body: { - verification_token: 'secret_tMrlL1qK5vuQAhCh', - }, - response: { - output: { - output: 'I am the GPT-3 model from OpenAI, an artificial intelligence assistant.', - }, - raw_output: 'I am the GPT-3 model from OpenAI, an artificial intelligence assistant.', - }, - }, - ] + ) - const logs = mockLogs - const isLoadingLogs = false + const logs = logData?.logs || [] - // Create subscription builder on mount useEffect(() => { if (!subscriptionBuilder) { createBuilder( @@ -173,13 +89,23 @@ const ManualAddModal = ({ pluginDetail, onClose, onSuccess }: Props) => { if (!subscriptionBuilder) return - // Get form values using the ref (for future use if needed) - // const formValues = formRef.current?.getFormValues({}) || { values: {}, isCheckValidated: false } + const formValues = propertiesFormRef.current?.getFormValues({}) || { values: {}, isCheckValidated: false } + if (!formValues.isCheckValidated) { + Toast.notify({ + type: 'error', + message: t('pluginTrigger.modal.form.properties.required'), + }) + return + } buildSubscription( { provider: providerName, subscriptionBuilderId: subscriptionBuilder.id, + params: { + name: subscriptionName, + properties: formValues.values, + }, }, { onSuccess: () => { @@ -200,16 +126,6 @@ const ManualAddModal = ({ pluginDetail, onClose, onSuccess }: Props) => { ) } - const toggleLogExpansion = (logId: string) => { - const newExpanded = new Set(expandedLogs) - if (newExpanded.has(logId)) - newExpanded.delete(logId) - else - newExpanded.add(logId) - - setExpandedLogs(newExpanded) - } - return ( {
- {/* Subscription Name */}
- {/* Callback URL */}
- - {/* Dynamic Parameters Form */} - {/* {parametersSchema.length > 0 && ( + {propertiesSchema.length > 0 && (
-
- Subscription Parameters -
- )} */} + )} - {/* Request Logs */} - {subscriptionBuilder && ( -
- {/* Divider with Title */} -
-
- REQUESTS HISTORY -
-
+
+
+
+ REQUESTS HISTORY
+
+
- {/* Request List */} -
- {isLoadingLogs && ( -
-
- - - - - - -
-
- Awaiting request from Slack... -
-
- )} - - {!isLoadingLogs && logs && logs.length > 0 && ( - <> - {logs.map((log, index) => { - const logId = log.id || index.toString() - const isExpanded = expandedLogs.has(logId) - const isSuccess = log.status >= 200 && log.status < 300 - const isError = log.status >= 400 - - return ( -
- {/* Error background decoration */} - {isError && ( -
-
-
- )} - - {/* Request Header */} - - - {/* Expanded Content */} - {isExpanded && ( -
- {/* Request Block */} -
-
-
- REQUEST -
- -
-
-
-
- {JSON.stringify(log.body, null, 2).split('\n').map((_, i) => ( -
{String(i + 1).padStart(2, '0')}
- ))} -
-
-
-
-                                    {JSON.stringify(log.body, null, 2)}
-                                  
-
-
-
- - {/* Response Block */} -
-
-
- RESPONSE -
- -
-
-
-
- {JSON.stringify(log.response, null, 2).split('\n').map((_, i) => ( -
{String(i + 1).padStart(2, '0')}
- ))} -
-
-
-
-                                    {JSON.stringify(log.response, null, 2)}
-                                  
-
-
-
-
- )} -
- ) - })} - - )} - - {!isLoadingLogs && (!logs || logs.length === 0) && ( -
-
- - - - - - -
-
- Awaiting request from Slack... -
-
- )} +
+
+ +
+
+ Awaiting request from {pluginDetail.declaration.name}...
- )} + + +
- {/* Footer */} -
+
diff --git a/web/app/components/plugins/plugin-detail-panel/subscription-list/oauth-add-modal.tsx b/web/app/components/plugins/plugin-detail-panel/subscription-list/oauth-add-modal.tsx index 2ee068dcd1..29d36dafdc 100644 --- a/web/app/components/plugins/plugin-detail-panel/subscription-list/oauth-add-modal.tsx +++ b/web/app/components/plugins/plugin-detail-panel/subscription-list/oauth-add-modal.tsx @@ -2,8 +2,9 @@ import React, { useEffect, useState } from 'react' import { useTranslation } from 'react-i18next' import { + RiClipboardLine, RiCloseLine, - RiExternalLinkLine, + RiInformation2Fill, } from '@remixicon/react' import Modal from '@/app/components/base/modal' import Button from '@/app/components/base/button' @@ -13,12 +14,13 @@ import Form from '@/app/components/base/form/form-scenarios/auth' import type { FormRefObject } from '@/app/components/base/form/types' import { useBuildTriggerSubscription, - useConfigureTriggerOAuth, useInitiateTriggerOAuth, + useTriggerOAuthConfig, useVerifyTriggerSubscriptionBuilder, } from '@/service/use-triggers' import type { PluginDetail } from '@/app/components/plugins/types' -import { CopyFeedbackNew } from '@/app/components/base/copy-feedback' +import ActionButton from '@/app/components/base/action-button' +import type { TriggerSubscriptionBuilder } from '@/app/components/workflow/block-selector/types' type Props = { pluginDetail: PluginDetail @@ -26,37 +28,56 @@ type Props = { onSuccess: () => void } -type OAuthStep = 'setup' | 'authorize' | 'configuration' +enum OAuthStepEnum { + Setup = 'setup', + Configuration = 'configuration', +} + +enum AuthorizationStatusEnum { + Pending = 'pending', + Success = 'success', + Failed = 'failed', +} const OAuthAddModal = ({ pluginDetail, onClose, onSuccess }: Props) => { const { t } = useTranslation() - // State - const [currentStep, setCurrentStep] = useState('setup') + const [currentStep, setCurrentStep] = useState(OAuthStepEnum.Setup) const [subscriptionName, setSubscriptionName] = useState('') const [authorizationUrl, setAuthorizationUrl] = useState('') - const [subscriptionBuilder, setSubscriptionBuilder] = useState(null) - const [redirectUrl, setRedirectUrl] = useState('') - const [authorizationStatus, setAuthorizationStatus] = useState<'pending' | 'success' | 'failed'>('pending') + const [subscriptionBuilder, setSubscriptionBuilder] = useState() + const [authorizationStatus, setAuthorizationStatus] = useState() - // Form refs const clientFormRef = React.useRef(null) const parametersFormRef = React.useRef(null) - // API mutations - const { mutate: initiateOAuth, isPending: isInitiating } = useInitiateTriggerOAuth() - const { mutate: configureOAuth, isPending: isConfiguring } = useConfigureTriggerOAuth() - const { mutate: verifyBuilder } = useVerifyTriggerSubscriptionBuilder() - const { mutate: buildSubscription, isPending: isBuilding } = useBuildTriggerSubscription() - - // Get provider name and schemas const providerName = `${pluginDetail.plugin_id}/${pluginDetail.declaration.name}` const clientSchema = pluginDetail.declaration.trigger?.oauth_schema?.client_schema || [] const parametersSchema = pluginDetail.declaration.trigger?.subscription_schema?.parameters_schema || [] - // Poll for authorization status + const { mutate: initiateOAuth } = useInitiateTriggerOAuth() + const { mutate: verifyBuilder } = useVerifyTriggerSubscriptionBuilder() + const { mutate: buildSubscription, isPending: isBuilding } = useBuildTriggerSubscription() + + const { data: oauthConfig } = useTriggerOAuthConfig(providerName) + useEffect(() => { - if (currentStep === 'authorize' && subscriptionBuilder && authorizationStatus === 'pending') { + initiateOAuth(providerName, { + onSuccess: (response) => { + setAuthorizationUrl(response.authorization_url) + setSubscriptionBuilder(response.subscription_builder) + }, + onError: (error: any) => { + Toast.notify({ + type: 'error', + message: error?.message || t('pluginTrigger.modal.errors.authFailed'), + }) + }, + }) + }, [initiateOAuth, providerName, t]) + + useEffect(() => { + if (currentStep === OAuthStepEnum.Setup && subscriptionBuilder && authorizationStatus === AuthorizationStatusEnum.Pending) { const pollInterval = setInterval(() => { verifyBuilder( { @@ -65,12 +86,13 @@ const OAuthAddModal = ({ pluginDetail, onClose, onSuccess }: Props) => { }, { onSuccess: () => { - setAuthorizationStatus('success') - setCurrentStep('configuration') + setAuthorizationStatus(AuthorizationStatusEnum.Success) + setCurrentStep(OAuthStepEnum.Configuration) Toast.notify({ type: 'success', message: t('pluginTrigger.modal.oauth.authorization.authSuccess'), }) + clearInterval(pollInterval) }, onError: () => { // Continue polling - auth might still be in progress @@ -83,7 +105,7 @@ const OAuthAddModal = ({ pluginDetail, onClose, onSuccess }: Props) => { } }, [currentStep, subscriptionBuilder, authorizationStatus, verifyBuilder, providerName, t]) - const handleSetupOAuth = () => { + const handleAuthorize = () => { const clientFormValues = clientFormRef.current?.getFormValues({}) || { values: {}, isCheckValidated: false } const clientParams = clientFormValues.values @@ -94,48 +116,7 @@ const OAuthAddModal = ({ pluginDetail, onClose, onSuccess }: Props) => { }) return } - - // First configure OAuth client - configureOAuth( - { - provider: providerName, - client_params: clientParams as any, - enabled: true, - }, - { - onSuccess: () => { - // Then get redirect URL and initiate OAuth - const baseUrl = window.location.origin - const redirectPath = `/plugins/oauth/callback/${providerName}` - const fullRedirectUrl = `${baseUrl}${redirectPath}` - setRedirectUrl(fullRedirectUrl) - - // Initiate OAuth flow - initiateOAuth(providerName, { - onSuccess: (response) => { - setAuthorizationUrl(response.authorization_url) - setSubscriptionBuilder(response.subscription_builder) - setCurrentStep('authorize') - }, - onError: (error: any) => { - Toast.notify({ - type: 'error', - message: error?.message || t('pluginTrigger.modal.errors.authFailed'), - }) - }, - }) - }, - onError: (error: any) => { - Toast.notify({ - type: 'error', - message: error?.message || t('pluginTrigger.modal.errors.authFailed'), - }) - }, - }, - ) - } - - const handleAuthorize = () => { + setAuthorizationStatus(AuthorizationStatusEnum.Pending) if (authorizationUrl) { // Open authorization URL in new window window.open(authorizationUrl, '_blank', 'width=500,height=600') @@ -154,10 +135,16 @@ const OAuthAddModal = ({ pluginDetail, onClose, onSuccess }: Props) => { if (!subscriptionBuilder) return + const parameters = parametersFormRef.current?.getFormValues({})?.values + buildSubscription( { provider: providerName, subscriptionBuilderId: subscriptionBuilder.id, + params: { + name: subscriptionName, + parameters, + } as Record, }, { onSuccess: () => { @@ -182,121 +169,61 @@ const OAuthAddModal = ({ pluginDetail, onClose, onSuccess }: Props) => { -
+

{t('pluginTrigger.modal.oauth.title')}

- +
-
- {currentStep === 'setup' && ( -
-
-

- {t('pluginTrigger.modal.oauth.authorization.title')} -

-

- {t('pluginTrigger.modal.oauth.authorization.description')} -

-
+
+ {currentStep === OAuthStepEnum.Setup && ( + <> + {oauthConfig?.redirect_uri && ( +
+
+ +
+
+
+ {t('pluginTrigger.modal.oauthRedirectInfo')} +
+
+ {oauthConfig.redirect_uri} +
+ +
+
+ )} {clientSchema.length > 0 && ( -
-
-
+ )} -
+ )} - {currentStep === 'authorize' && ( + {currentStep === OAuthStepEnum.Configuration && (
-
-

- {t('pluginTrigger.modal.oauth.authorization.title')} -

-

- {t('pluginTrigger.modal.oauth.authorization.description')} -

-
- - {/* Redirect URL */} - {redirectUrl && ( -
- -
- - -
-
- {t('pluginTrigger.modal.oauth.authorization.redirectUrlHelp')} -
-
- )} - - {/* Authorization Status */} -
- {authorizationStatus === 'pending' && ( -
- {t('pluginTrigger.modal.oauth.authorization.waitingAuth')} -
- )} - {authorizationStatus === 'success' && ( -
- {t('pluginTrigger.modal.oauth.authorization.authSuccess')} -
- )} - {authorizationStatus === 'failed' && ( -
- {t('pluginTrigger.modal.oauth.authorization.authFailed')} -
- )} -
- - {/* Authorize Button */} - {authorizationStatus === 'pending' && ( - - )} -
- )} - - {currentStep === 'configuration' && ( -
-
-

- {t('pluginTrigger.modal.oauth.configuration.title')} -

-

- {t('pluginTrigger.modal.oauth.configuration.description')} -

-
- - {/* Subscription Name */}
- {/* Callback URL (read-only) */} {subscriptionBuilder?.endpoint && (
)} - {/* Dynamic Parameters Form */} {parametersSchema.length > 0 && ( -
- -
+ )}
)}
- {/* Footer */} -
+
- {currentStep === 'setup' && ( + {currentStep === OAuthStepEnum.Setup && ( )} - {currentStep === 'configuration' && ( + {currentStep === OAuthStepEnum.Configuration && (
·
- {isActive ? 'Used by 3 workflows' : 'No workflow used'} + {data.workflows_in_use > 0 ? t('pluginTrigger.subscription.list.item.usedByNum', { num: data.workflows_in_use }) : t('pluginTrigger.subscription.list.item.noUsed')}
diff --git a/web/app/components/workflow/block-selector/types.ts b/web/app/components/workflow/block-selector/types.ts index ec7ea9492e..873e4335e3 100644 --- a/web/app/components/workflow/block-selector/types.ts +++ b/web/app/components/workflow/block-selector/types.ts @@ -184,6 +184,7 @@ type TriggerSubscriptionStructure = { endpoint: string parameters: TriggerSubParameters properties: TriggerSubProperties + workflows_in_use: number } export type TriggerSubscription = TriggerSubscriptionStructure @@ -212,6 +213,7 @@ export type TriggerOAuthConfig = { configured: boolean custom_configured: boolean custom_enabled: boolean + redirect_uri: string params: { client_id: string client_secret: string @@ -230,3 +232,48 @@ export type TriggerOAuthResponse = { authorization_url: string subscription_builder: TriggerSubscriptionBuilder } + +export type TriggerLogEntity = { + id: string + endpoint: string + request: LogRequest + response: LogResponse + created_at: string +} + +export type LogRequest = { + method: string + url: string + headers: LogRequestHeaders + data: string +} + +export type LogRequestHeaders = { + 'Host': string + 'User-Agent': string + 'Content-Length': string + 'Accept': string + 'Content-Type': string + 'X-Forwarded-For': string + 'X-Forwarded-Host': string + 'X-Forwarded-Proto': string + 'X-Github-Delivery': string + 'X-Github-Event': string + 'X-Github-Hook-Id': string + 'X-Github-Hook-Installation-Target-Id': string + 'X-Github-Hook-Installation-Target-Type': string + 'Accept-Encoding': string + [key: string]: string +} + +export type LogResponse = { + status_code: number + headers: LogResponseHeaders + data: string +} + +export type LogResponseHeaders = { + 'Content-Type': string + 'Content-Length': string + [key: string]: string +} diff --git a/web/i18n/en-US/plugin-trigger.ts b/web/i18n/en-US/plugin-trigger.ts index a7689dec7b..61d2ecb247 100644 --- a/web/i18n/en-US/plugin-trigger.ts +++ b/web/i18n/en-US/plugin-trigger.ts @@ -10,6 +10,7 @@ const translation = { list: { title: 'Subscriptions', addButton: 'Add', + tip: 'Receive events via Subscription', item: { enabled: 'Enabled', disabled: 'Disabled', @@ -32,6 +33,8 @@ const translation = { active: 'Active', inactive: 'Inactive', }, + usedByNum: 'Used by {{num}} workflows', + noUsed: 'No workflow used', }, }, addType: { @@ -48,7 +51,7 @@ const translation = { }, manual: { title: 'Manual Setup', - description: 'Manually configure webhook URL and settings', + description: 'Paste URL to create a new subscription', tip: 'Configure URL on third-party platform manually', }, }, @@ -70,6 +73,7 @@ const translation = { verifying: 'Verifying...', authorizing: 'Authorizing...', }, + oauthRedirectInfo: 'As no system client secrets found for this tool provider, setup it manually is required, for redirect_uri, please use', apiKey: { title: 'Create via API Key', verify: { diff --git a/web/i18n/zh-Hans/plugin-trigger.ts b/web/i18n/zh-Hans/plugin-trigger.ts index 6d0534f553..0daddae271 100644 --- a/web/i18n/zh-Hans/plugin-trigger.ts +++ b/web/i18n/zh-Hans/plugin-trigger.ts @@ -10,6 +10,7 @@ const translation = { list: { title: '订阅列表', addButton: '添加', + tip: '通过订阅接收事件', item: { enabled: '已启用', disabled: '已禁用', @@ -32,6 +33,8 @@ const translation = { active: '活跃', inactive: '非活跃', }, + usedByNum: '被 {{num}} 个工作流使用', + noUsed: '未被工作流使用', }, }, addType: { @@ -39,16 +42,16 @@ const translation = { description: '选择创建触发器订阅的方式', options: { apiKey: { - title: '通过API密钥', + title: '通过 API Key', description: '使用API凭据自动创建订阅', }, oauth: { - title: '通过OAuth', + title: '通过 OAuth', description: '与第三方平台授权以创建订阅', }, manual: { title: '手动设置', - description: '手动配置Webhook URL和设置', + description: '粘贴 URL 以创建新订阅', tip: '手动配置 URL 到第三方平台', }, }, @@ -70,6 +73,7 @@ const translation = { verifying: '验证中...', authorizing: '授权中...', }, + oauthRedirectInfo: '由于未找到此工具提供方的系统客户端密钥,需要手动设置,对于 redirect_uri,请使用', apiKey: { title: '通过API密钥创建', verify: { diff --git a/web/service/use-triggers.ts b/web/service/use-triggers.ts index f3aa280247..1097e81400 100644 --- a/web/service/use-triggers.ts +++ b/web/service/use-triggers.ts @@ -1,6 +1,7 @@ import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query' import { del, get, post } from './base' import type { + TriggerLogEntity, TriggerOAuthClientParams, TriggerOAuthConfig, TriggerProviderApiEntity, @@ -169,10 +170,12 @@ export const useBuildTriggerSubscription = () => { mutationFn: (payload: { provider: string subscriptionBuilderId: string + params?: Record }) => { - const { provider, subscriptionBuilderId } = payload + const { provider, subscriptionBuilderId, ...body } = payload return post( `/workspaces/current/trigger-provider/${provider}/subscriptions/builder/build/${subscriptionBuilderId}`, + { body }, ) }, }) @@ -199,7 +202,7 @@ export const useTriggerSubscriptionBuilderLogs = ( ) => { const { enabled = true, refetchInterval = false } = options - return useQuery[]>({ + return useQuery<{ logs: TriggerLogEntity[] }>({ queryKey: [NAME_SPACE, 'subscription-builder-logs', provider, subscriptionBuilderId], queryFn: () => get( `/workspaces/current/trigger-provider/${provider}/subscriptions/builder/logs/${subscriptionBuilderId}`, @@ -250,7 +253,7 @@ export const useInitiateTriggerOAuth = () => { return useMutation({ mutationKey: [NAME_SPACE, 'initiate-oauth'], mutationFn: (provider: string) => { - return get<{ authorization_url: string; subscription_builder: any }>( + return get<{ authorization_url: string; subscription_builder: TriggerSubscriptionBuilder }>( `/workspaces/current/trigger-provider/${provider}/subscriptions/oauth/authorize`, ) },