mirror of
https://github.com/langgenius/dify.git
synced 2026-05-03 08:58:09 +08:00
feat: add editing support for trigger subscriptions (#29957)
Co-authored-by: yyh <yuanyouhuilyz@gmail.com>
This commit is contained in:
@ -46,7 +46,7 @@ const PluginDetailPanel: FC<Props> = ({
|
||||
name: detail.name,
|
||||
id: detail.id,
|
||||
})
|
||||
}, [detail])
|
||||
}, [detail, setDetail])
|
||||
|
||||
if (!detail)
|
||||
return null
|
||||
@ -69,7 +69,7 @@ const PluginDetailPanel: FC<Props> = ({
|
||||
<div className="flex-1">
|
||||
{detail.declaration.category === PluginCategoryEnum.trigger && (
|
||||
<>
|
||||
<SubscriptionList />
|
||||
<SubscriptionList pluginDetail={detail} />
|
||||
<TriggerEventsList />
|
||||
</>
|
||||
)}
|
||||
|
||||
@ -20,7 +20,7 @@ import {
|
||||
useCreateTriggerSubscriptionBuilder,
|
||||
useTriggerSubscriptionBuilderLogs,
|
||||
useUpdateTriggerSubscriptionBuilder,
|
||||
useVerifyTriggerSubscriptionBuilder,
|
||||
useVerifyAndUpdateTriggerSubscriptionBuilder,
|
||||
} from '@/service/use-triggers'
|
||||
import { parsePluginErrorMessage } from '@/utils/error-parser'
|
||||
import { isPrivateOrLocalAddress } from '@/utils/urlValidation'
|
||||
@ -40,6 +40,15 @@ const CREDENTIAL_TYPE_MAP: Record<SupportedCreationMethods, TriggerCredentialTyp
|
||||
[SupportedCreationMethods.MANUAL]: TriggerCredentialTypeEnum.Unauthorized,
|
||||
}
|
||||
|
||||
const MODAL_TITLE_KEY_MAP: Record<
|
||||
SupportedCreationMethods,
|
||||
'pluginTrigger.modal.apiKey.title' | 'pluginTrigger.modal.oauth.title' | 'pluginTrigger.modal.manual.title'
|
||||
> = {
|
||||
[SupportedCreationMethods.APIKEY]: 'pluginTrigger.modal.apiKey.title',
|
||||
[SupportedCreationMethods.OAUTH]: 'pluginTrigger.modal.oauth.title',
|
||||
[SupportedCreationMethods.MANUAL]: 'pluginTrigger.modal.manual.title',
|
||||
}
|
||||
|
||||
enum ApiKeyStep {
|
||||
Verify = 'verify',
|
||||
Configuration = 'configuration',
|
||||
@ -104,7 +113,7 @@ export const CommonCreateModal = ({ onClose, createType, builder }: Props) => {
|
||||
const [subscriptionBuilder, setSubscriptionBuilder] = useState<TriggerSubscriptionBuilder | undefined>(builder)
|
||||
const isInitializedRef = useRef(false)
|
||||
|
||||
const { mutate: verifyCredentials, isPending: isVerifyingCredentials } = useVerifyTriggerSubscriptionBuilder()
|
||||
const { mutate: verifyCredentials, isPending: isVerifyingCredentials } = useVerifyAndUpdateTriggerSubscriptionBuilder()
|
||||
const { mutateAsync: createBuilder /* isPending: isCreatingBuilder */ } = useCreateTriggerSubscriptionBuilder()
|
||||
const { mutate: buildSubscription, isPending: isBuilding } = useBuildTriggerSubscription()
|
||||
const { mutate: updateBuilder } = useUpdateTriggerSubscriptionBuilder()
|
||||
@ -117,13 +126,13 @@ export const CommonCreateModal = ({ onClose, createType, builder }: Props) => {
|
||||
const autoCommonParametersSchema = detail?.declaration.trigger?.subscription_constructor?.parameters || [] // apikey and oauth
|
||||
const autoCommonParametersFormRef = React.useRef<FormRefObject>(null)
|
||||
|
||||
const rawApiKeyCredentialsSchema = detail?.declaration.trigger?.subscription_constructor?.credentials_schema || []
|
||||
const apiKeyCredentialsSchema = useMemo(() => {
|
||||
return rawApiKeyCredentialsSchema.map(schema => ({
|
||||
const rawSchema = detail?.declaration?.trigger?.subscription_constructor?.credentials_schema || []
|
||||
return rawSchema.map(schema => ({
|
||||
...schema,
|
||||
tooltip: schema.help,
|
||||
}))
|
||||
}, [rawApiKeyCredentialsSchema])
|
||||
}, [detail?.declaration?.trigger?.subscription_constructor?.credentials_schema])
|
||||
const apiKeyCredentialsFormRef = React.useRef<FormRefObject>(null)
|
||||
|
||||
const { data: logData } = useTriggerSubscriptionBuilderLogs(
|
||||
@ -163,7 +172,7 @@ export const CommonCreateModal = ({ onClose, createType, builder }: Props) => {
|
||||
if (form)
|
||||
form.setFieldValue('callback_url', subscriptionBuilder.endpoint)
|
||||
if (isPrivateOrLocalAddress(subscriptionBuilder.endpoint)) {
|
||||
console.log('isPrivateOrLocalAddress', isPrivateOrLocalAddress(subscriptionBuilder.endpoint))
|
||||
console.warn('callback_url is private or local address', subscriptionBuilder.endpoint)
|
||||
subscriptionFormRef.current?.setFields([{
|
||||
name: 'callback_url',
|
||||
warnings: [t('pluginTrigger.modal.form.callbackUrl.privateAddressWarning')],
|
||||
@ -179,7 +188,7 @@ export const CommonCreateModal = ({ onClose, createType, builder }: Props) => {
|
||||
}, [subscriptionBuilder?.endpoint, currentStep, t])
|
||||
|
||||
const debouncedUpdate = useMemo(
|
||||
() => debounce((provider: string, builderId: string, properties: Record<string, any>) => {
|
||||
() => debounce((provider: string, builderId: string, properties: Record<string, unknown>) => {
|
||||
updateBuilder(
|
||||
{
|
||||
provider,
|
||||
@ -187,11 +196,12 @@ export const CommonCreateModal = ({ onClose, createType, builder }: Props) => {
|
||||
properties,
|
||||
},
|
||||
{
|
||||
onError: (error: any) => {
|
||||
onError: async (error: unknown) => {
|
||||
const errorMessage = await parsePluginErrorMessage(error) || t('pluginTrigger.modal.errors.updateFailed')
|
||||
console.error('Failed to update subscription builder:', error)
|
||||
Toast.notify({
|
||||
type: 'error',
|
||||
message: error?.message || t('pluginTrigger.modal.errors.updateFailed'),
|
||||
message: errorMessage,
|
||||
})
|
||||
},
|
||||
},
|
||||
@ -246,7 +256,7 @@ export const CommonCreateModal = ({ onClose, createType, builder }: Props) => {
|
||||
})
|
||||
setCurrentStep(ApiKeyStep.Configuration)
|
||||
},
|
||||
onError: async (error: any) => {
|
||||
onError: async (error: unknown) => {
|
||||
const errorMessage = await parsePluginErrorMessage(error) || t('pluginTrigger.modal.apiKey.verify.error')
|
||||
apiKeyCredentialsFormRef.current?.setFields([{
|
||||
name: Object.keys(credentials)[0],
|
||||
@ -303,7 +313,7 @@ export const CommonCreateModal = ({ onClose, createType, builder }: Props) => {
|
||||
onClose()
|
||||
refetch?.()
|
||||
},
|
||||
onError: async (error: any) => {
|
||||
onError: async (error: unknown) => {
|
||||
const errorMessage = await parsePluginErrorMessage(error) || t('pluginTrigger.subscription.createFailed')
|
||||
Toast.notify({
|
||||
type: 'error',
|
||||
@ -328,14 +338,17 @@ export const CommonCreateModal = ({ onClose, createType, builder }: Props) => {
|
||||
}])
|
||||
}
|
||||
|
||||
const confirmButtonText = useMemo(() => {
|
||||
if (currentStep === ApiKeyStep.Verify)
|
||||
return isVerifyingCredentials ? t('pluginTrigger.modal.common.verifying') : t('pluginTrigger.modal.common.verify')
|
||||
|
||||
return isBuilding ? t('pluginTrigger.modal.common.creating') : t('pluginTrigger.modal.common.create')
|
||||
}, [currentStep, isVerifyingCredentials, isBuilding, t])
|
||||
|
||||
return (
|
||||
<Modal
|
||||
title={t(`pluginTrigger.modal.${createType === SupportedCreationMethods.APIKEY ? 'apiKey' : createType.toLowerCase()}.title` as any)}
|
||||
confirmButtonText={
|
||||
currentStep === ApiKeyStep.Verify
|
||||
? isVerifyingCredentials ? t('pluginTrigger.modal.common.verifying') : t('pluginTrigger.modal.common.verify')
|
||||
: isBuilding ? t('pluginTrigger.modal.common.creating') : t('pluginTrigger.modal.common.create')
|
||||
}
|
||||
title={t(MODAL_TITLE_KEY_MAP[createType])}
|
||||
confirmButtonText={confirmButtonText}
|
||||
onClose={onClose}
|
||||
onCancel={onClose}
|
||||
onConfirm={handleConfirm}
|
||||
|
||||
@ -19,7 +19,7 @@ import {
|
||||
useConfigureTriggerOAuth,
|
||||
useDeleteTriggerOAuth,
|
||||
useInitiateTriggerOAuth,
|
||||
useVerifyTriggerSubscriptionBuilder,
|
||||
useVerifyAndUpdateTriggerSubscriptionBuilder,
|
||||
} from '@/service/use-triggers'
|
||||
import { usePluginStore } from '../../store'
|
||||
|
||||
@ -65,10 +65,29 @@ export const OAuthClientSettingsModal = ({ oauthConfig, onClose, showOAuthCreate
|
||||
|
||||
const providerName = detail?.provider || ''
|
||||
const { mutate: initiateOAuth } = useInitiateTriggerOAuth()
|
||||
const { mutate: verifyBuilder } = useVerifyTriggerSubscriptionBuilder()
|
||||
const { mutate: verifyBuilder } = useVerifyAndUpdateTriggerSubscriptionBuilder()
|
||||
const { mutate: configureOAuth } = useConfigureTriggerOAuth()
|
||||
const { mutate: deleteOAuth } = useDeleteTriggerOAuth()
|
||||
|
||||
const confirmButtonText = useMemo(() => {
|
||||
if (authorizationStatus === AuthorizationStatusEnum.Pending)
|
||||
return t('pluginTrigger.modal.common.authorizing')
|
||||
if (authorizationStatus === AuthorizationStatusEnum.Success)
|
||||
return t('pluginTrigger.modal.oauth.authorization.waitingJump')
|
||||
return t('plugin.auth.saveAndAuth')
|
||||
}, [authorizationStatus, t])
|
||||
|
||||
const getErrorMessage = (error: unknown, fallback: string) => {
|
||||
if (error instanceof Error && error.message)
|
||||
return error.message
|
||||
if (typeof error === 'object' && error && 'message' in error) {
|
||||
const message = (error as { message?: string }).message
|
||||
if (typeof message === 'string' && message)
|
||||
return message
|
||||
}
|
||||
return fallback
|
||||
}
|
||||
|
||||
const handleAuthorization = () => {
|
||||
setAuthorizationStatus(AuthorizationStatusEnum.Pending)
|
||||
initiateOAuth(providerName, {
|
||||
@ -130,10 +149,10 @@ export const OAuthClientSettingsModal = ({ oauthConfig, onClose, showOAuthCreate
|
||||
message: t('pluginTrigger.modal.oauth.remove.success'),
|
||||
})
|
||||
},
|
||||
onError: (error: any) => {
|
||||
onError: (error: unknown) => {
|
||||
Toast.notify({
|
||||
type: 'error',
|
||||
message: error?.message || t('pluginTrigger.modal.oauth.remove.failed'),
|
||||
message: getErrorMessage(error, t('pluginTrigger.modal.oauth.remove.failed')),
|
||||
})
|
||||
},
|
||||
})
|
||||
@ -179,9 +198,7 @@ export const OAuthClientSettingsModal = ({ oauthConfig, onClose, showOAuthCreate
|
||||
return (
|
||||
<Modal
|
||||
title={t('pluginTrigger.modal.oauth.title')}
|
||||
confirmButtonText={authorizationStatus === AuthorizationStatusEnum.Pending
|
||||
? t('pluginTrigger.modal.common.authorizing')
|
||||
: authorizationStatus === AuthorizationStatusEnum.Success ? t('pluginTrigger.modal.oauth.authorization.waitingJump') : t('plugin.auth.saveAndAuth')}
|
||||
confirmButtonText={confirmButtonText}
|
||||
cancelButtonText={t('plugin.auth.saveOnly')}
|
||||
extraButtonText={t('common.operation.cancel')}
|
||||
showExtraButton
|
||||
|
||||
@ -0,0 +1,349 @@
|
||||
'use client'
|
||||
import type { FormRefObject, FormSchema } from '@/app/components/base/form/types'
|
||||
import type { ParametersSchema, PluginDetail } from '@/app/components/plugins/types'
|
||||
import type { TriggerSubscription } from '@/app/components/workflow/block-selector/types'
|
||||
import { isEqual } from 'lodash-es'
|
||||
import { useMemo, useRef, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { EncryptedBottom } from '@/app/components/base/encrypted-bottom'
|
||||
import { BaseForm } from '@/app/components/base/form/components/base'
|
||||
import { FormTypeEnum } from '@/app/components/base/form/types'
|
||||
import Modal from '@/app/components/base/modal/modal'
|
||||
import Toast from '@/app/components/base/toast'
|
||||
import { ReadmeEntrance } from '@/app/components/plugins/readme-panel/entrance'
|
||||
import { useUpdateTriggerSubscription, useVerifyTriggerSubscription } from '@/service/use-triggers'
|
||||
import { parsePluginErrorMessage } from '@/utils/error-parser'
|
||||
import { ReadmeShowType } from '../../../readme-panel/store'
|
||||
import { usePluginStore } from '../../store'
|
||||
import { useSubscriptionList } from '../use-subscription-list'
|
||||
|
||||
type Props = {
|
||||
onClose: () => void
|
||||
subscription: TriggerSubscription
|
||||
pluginDetail?: PluginDetail
|
||||
}
|
||||
|
||||
enum EditStep {
|
||||
EditCredentials = 'edit_credentials',
|
||||
EditConfiguration = 'edit_configuration',
|
||||
}
|
||||
|
||||
const normalizeFormType = (type: string): FormTypeEnum => {
|
||||
switch (type) {
|
||||
case 'string':
|
||||
case 'text':
|
||||
return FormTypeEnum.textInput
|
||||
case 'password':
|
||||
case 'secret':
|
||||
return FormTypeEnum.secretInput
|
||||
case 'number':
|
||||
case 'integer':
|
||||
return FormTypeEnum.textNumber
|
||||
case 'boolean':
|
||||
return FormTypeEnum.boolean
|
||||
case 'select':
|
||||
return FormTypeEnum.select
|
||||
default:
|
||||
if (Object.values(FormTypeEnum).includes(type as FormTypeEnum))
|
||||
return type as FormTypeEnum
|
||||
return FormTypeEnum.textInput
|
||||
}
|
||||
}
|
||||
|
||||
const HIDDEN_SECRET_VALUE = '[__HIDDEN__]'
|
||||
|
||||
// Check if all credential values are hidden (meaning nothing was changed)
|
||||
const areAllCredentialsHidden = (credentials: Record<string, unknown>): boolean => {
|
||||
return Object.values(credentials).every(value => value === HIDDEN_SECRET_VALUE)
|
||||
}
|
||||
|
||||
const StatusStep = ({ isActive, text, onClick, clickable }: {
|
||||
isActive: boolean
|
||||
text: string
|
||||
onClick?: () => void
|
||||
clickable?: boolean
|
||||
}) => {
|
||||
return (
|
||||
<div
|
||||
className={`system-2xs-semibold-uppercase flex items-center gap-1 ${isActive
|
||||
? 'text-state-accent-solid'
|
||||
: 'text-text-tertiary'} ${clickable ? 'cursor-pointer hover:text-text-secondary' : ''}`}
|
||||
onClick={clickable ? onClick : undefined}
|
||||
>
|
||||
{isActive && (
|
||||
<div className="h-1 w-1 rounded-full bg-state-accent-solid"></div>
|
||||
)}
|
||||
{text}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const MultiSteps = ({ currentStep, onStepClick }: { currentStep: EditStep, onStepClick?: (step: EditStep) => void }) => {
|
||||
const { t } = useTranslation()
|
||||
return (
|
||||
<div className="mb-6 flex w-1/3 items-center gap-2">
|
||||
<StatusStep
|
||||
isActive={currentStep === EditStep.EditCredentials}
|
||||
text={t('pluginTrigger.modal.steps.verify')}
|
||||
onClick={() => onStepClick?.(EditStep.EditCredentials)}
|
||||
clickable={currentStep === EditStep.EditConfiguration}
|
||||
/>
|
||||
<div className="h-px w-3 shrink-0 bg-divider-deep"></div>
|
||||
<StatusStep
|
||||
isActive={currentStep === EditStep.EditConfiguration}
|
||||
text={t('pluginTrigger.modal.steps.configuration')}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export const ApiKeyEditModal = ({ onClose, subscription, pluginDetail }: Props) => {
|
||||
const { t } = useTranslation()
|
||||
const detail = usePluginStore(state => state.detail)
|
||||
const { refetch } = useSubscriptionList()
|
||||
|
||||
const [currentStep, setCurrentStep] = useState<EditStep>(EditStep.EditCredentials)
|
||||
const [verifiedCredentials, setVerifiedCredentials] = useState<Record<string, unknown> | null>(null)
|
||||
|
||||
const { mutate: updateSubscription, isPending: isUpdating } = useUpdateTriggerSubscription()
|
||||
const { mutate: verifyCredentials, isPending: isVerifying } = useVerifyTriggerSubscription()
|
||||
|
||||
const parametersSchema = useMemo<ParametersSchema[]>(
|
||||
() => detail?.declaration?.trigger?.subscription_constructor?.parameters || [],
|
||||
[detail?.declaration?.trigger?.subscription_constructor?.parameters],
|
||||
)
|
||||
|
||||
const apiKeyCredentialsSchema = useMemo(() => {
|
||||
const rawSchema = detail?.declaration?.trigger?.subscription_constructor?.credentials_schema || []
|
||||
return rawSchema.map(schema => ({
|
||||
...schema,
|
||||
tooltip: schema.help,
|
||||
}))
|
||||
}, [detail?.declaration?.trigger?.subscription_constructor?.credentials_schema])
|
||||
|
||||
const basicFormRef = useRef<FormRefObject>(null)
|
||||
const parametersFormRef = useRef<FormRefObject>(null)
|
||||
const credentialsFormRef = useRef<FormRefObject>(null)
|
||||
|
||||
const handleVerifyCredentials = () => {
|
||||
const credentialsFormValues = credentialsFormRef.current?.getFormValues({
|
||||
needTransformWhenSecretFieldIsPristine: true,
|
||||
}) || { values: {}, isCheckValidated: false }
|
||||
|
||||
if (!credentialsFormValues.isCheckValidated)
|
||||
return
|
||||
|
||||
const credentials = credentialsFormValues.values
|
||||
|
||||
verifyCredentials(
|
||||
{
|
||||
provider: subscription.provider,
|
||||
subscriptionId: subscription.id,
|
||||
credentials,
|
||||
},
|
||||
{
|
||||
onSuccess: () => {
|
||||
Toast.notify({
|
||||
type: 'success',
|
||||
message: t('pluginTrigger.modal.apiKey.verify.success'),
|
||||
})
|
||||
// Only save credentials if any field was modified (not all hidden)
|
||||
setVerifiedCredentials(areAllCredentialsHidden(credentials) ? null : credentials)
|
||||
setCurrentStep(EditStep.EditConfiguration)
|
||||
},
|
||||
onError: async (error: unknown) => {
|
||||
const errorMessage = await parsePluginErrorMessage(error) || t('pluginTrigger.modal.apiKey.verify.error')
|
||||
Toast.notify({
|
||||
type: 'error',
|
||||
message: errorMessage,
|
||||
})
|
||||
},
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
const handleUpdate = () => {
|
||||
const basicFormValues = basicFormRef.current?.getFormValues({})
|
||||
if (!basicFormValues?.isCheckValidated)
|
||||
return
|
||||
|
||||
const name = basicFormValues.values.subscription_name as string
|
||||
|
||||
let parameters: Record<string, unknown> | undefined
|
||||
|
||||
if (parametersSchema.length > 0) {
|
||||
const paramsFormValues = parametersFormRef.current?.getFormValues({
|
||||
needTransformWhenSecretFieldIsPristine: true,
|
||||
})
|
||||
if (!paramsFormValues?.isCheckValidated)
|
||||
return
|
||||
|
||||
// Only send parameters if changed
|
||||
const hasChanged = !isEqual(paramsFormValues.values, subscription.parameters || {})
|
||||
parameters = hasChanged ? paramsFormValues.values : undefined
|
||||
}
|
||||
|
||||
updateSubscription(
|
||||
{
|
||||
subscriptionId: subscription.id,
|
||||
name,
|
||||
parameters,
|
||||
credentials: verifiedCredentials || undefined,
|
||||
},
|
||||
{
|
||||
onSuccess: () => {
|
||||
Toast.notify({
|
||||
type: 'success',
|
||||
message: t('pluginTrigger.subscription.list.item.actions.edit.success'),
|
||||
})
|
||||
refetch?.()
|
||||
onClose()
|
||||
},
|
||||
onError: async (error: unknown) => {
|
||||
const errorMessage = await parsePluginErrorMessage(error) || t('pluginTrigger.subscription.list.item.actions.edit.error')
|
||||
Toast.notify({
|
||||
type: 'error',
|
||||
message: errorMessage,
|
||||
})
|
||||
},
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
const handleConfirm = () => {
|
||||
if (currentStep === EditStep.EditCredentials)
|
||||
handleVerifyCredentials()
|
||||
else
|
||||
handleUpdate()
|
||||
}
|
||||
|
||||
const basicFormSchemas: FormSchema[] = useMemo(() => [
|
||||
{
|
||||
name: 'subscription_name',
|
||||
label: t('pluginTrigger.modal.form.subscriptionName.label'),
|
||||
placeholder: t('pluginTrigger.modal.form.subscriptionName.placeholder'),
|
||||
type: FormTypeEnum.textInput,
|
||||
required: true,
|
||||
default: subscription.name,
|
||||
},
|
||||
{
|
||||
name: 'callback_url',
|
||||
label: t('pluginTrigger.modal.form.callbackUrl.label'),
|
||||
placeholder: t('pluginTrigger.modal.form.callbackUrl.placeholder'),
|
||||
type: FormTypeEnum.textInput,
|
||||
required: false,
|
||||
default: subscription.endpoint || '',
|
||||
disabled: true,
|
||||
tooltip: t('pluginTrigger.modal.form.callbackUrl.tooltip'),
|
||||
showCopy: true,
|
||||
},
|
||||
], [t, subscription.name, subscription.endpoint])
|
||||
|
||||
const credentialsFormSchemas: FormSchema[] = useMemo(() => {
|
||||
return apiKeyCredentialsSchema.map(schema => ({
|
||||
...schema,
|
||||
type: normalizeFormType(schema.type as string),
|
||||
tooltip: schema.help,
|
||||
default: subscription.credentials?.[schema.name] || schema.default,
|
||||
}))
|
||||
}, [apiKeyCredentialsSchema, subscription.credentials])
|
||||
|
||||
const parametersFormSchemas: FormSchema[] = useMemo(() => {
|
||||
return parametersSchema.map((schema: ParametersSchema) => {
|
||||
const normalizedType = normalizeFormType(schema.type as string)
|
||||
return {
|
||||
...schema,
|
||||
type: normalizedType,
|
||||
tooltip: schema.description,
|
||||
default: subscription.parameters?.[schema.name] || schema.default,
|
||||
dynamicSelectParams: normalizedType === FormTypeEnum.dynamicSelect
|
||||
? {
|
||||
plugin_id: detail?.plugin_id || '',
|
||||
provider: detail?.provider || '',
|
||||
action: 'provider',
|
||||
parameter: schema.name,
|
||||
credential_id: subscription.id,
|
||||
credentials: verifiedCredentials || undefined,
|
||||
}
|
||||
: undefined,
|
||||
fieldClassName: schema.type === FormTypeEnum.boolean ? 'flex items-center justify-between' : undefined,
|
||||
labelClassName: schema.type === FormTypeEnum.boolean ? 'mb-0' : undefined,
|
||||
}
|
||||
})
|
||||
}, [parametersSchema, subscription.parameters, subscription.id, detail?.plugin_id, detail?.provider, verifiedCredentials])
|
||||
|
||||
const getConfirmButtonText = () => {
|
||||
if (currentStep === EditStep.EditCredentials)
|
||||
return isVerifying ? t('pluginTrigger.modal.common.verifying') : t('pluginTrigger.modal.common.verify')
|
||||
|
||||
return isUpdating ? t('common.operation.saving') : t('common.operation.save')
|
||||
}
|
||||
|
||||
const handleBack = () => {
|
||||
setCurrentStep(EditStep.EditCredentials)
|
||||
setVerifiedCredentials(null)
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal
|
||||
title={t('pluginTrigger.subscription.list.item.actions.edit.title')}
|
||||
confirmButtonText={getConfirmButtonText()}
|
||||
onClose={onClose}
|
||||
onCancel={onClose}
|
||||
onConfirm={handleConfirm}
|
||||
disabled={isUpdating || isVerifying}
|
||||
showExtraButton={currentStep === EditStep.EditConfiguration}
|
||||
extraButtonText={t('pluginTrigger.modal.common.back')}
|
||||
extraButtonVariant="secondary"
|
||||
onExtraButtonClick={handleBack}
|
||||
clickOutsideNotClose
|
||||
wrapperClassName="!z-[101]"
|
||||
bottomSlot={currentStep === EditStep.EditCredentials ? <EncryptedBottom /> : null}
|
||||
>
|
||||
{pluginDetail && (
|
||||
<ReadmeEntrance pluginDetail={pluginDetail} showType={ReadmeShowType.modal} />
|
||||
)}
|
||||
|
||||
{/* Multi-step indicator */}
|
||||
<MultiSteps currentStep={currentStep} onStepClick={handleBack} />
|
||||
|
||||
{/* Step 1: Edit Credentials */}
|
||||
{currentStep === EditStep.EditCredentials && (
|
||||
<div className="mb-4">
|
||||
{credentialsFormSchemas.length > 0 && (
|
||||
<BaseForm
|
||||
formSchemas={credentialsFormSchemas}
|
||||
ref={credentialsFormRef}
|
||||
labelClassName="system-sm-medium mb-2 flex items-center gap-1 text-text-primary"
|
||||
formClassName="space-y-4"
|
||||
preventDefaultSubmit={true}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Step 2: Edit Configuration */}
|
||||
{currentStep === EditStep.EditConfiguration && (
|
||||
<div className="max-h-[70vh]">
|
||||
{/* Basic form: subscription name and callback URL */}
|
||||
<BaseForm
|
||||
formSchemas={basicFormSchemas}
|
||||
ref={basicFormRef}
|
||||
labelClassName="system-sm-medium mb-2 flex items-center gap-1 text-text-primary"
|
||||
formClassName="space-y-4 mb-4"
|
||||
/>
|
||||
|
||||
{/* Parameters */}
|
||||
{parametersFormSchemas.length > 0 && (
|
||||
<BaseForm
|
||||
formSchemas={parametersFormSchemas}
|
||||
ref={parametersFormRef}
|
||||
labelClassName="system-sm-medium mb-2 flex items-center gap-1 text-text-primary"
|
||||
formClassName="space-y-4"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
@ -0,0 +1,28 @@
|
||||
'use client'
|
||||
import type { PluginDetail } from '@/app/components/plugins/types'
|
||||
import type { TriggerSubscription } from '@/app/components/workflow/block-selector/types'
|
||||
import { TriggerCredentialTypeEnum } from '@/app/components/workflow/block-selector/types'
|
||||
import { ApiKeyEditModal } from './apikey-edit-modal'
|
||||
import { ManualEditModal } from './manual-edit-modal'
|
||||
import { OAuthEditModal } from './oauth-edit-modal'
|
||||
|
||||
type Props = {
|
||||
onClose: () => void
|
||||
subscription: TriggerSubscription
|
||||
pluginDetail?: PluginDetail
|
||||
}
|
||||
|
||||
export const EditModal = ({ onClose, subscription, pluginDetail }: Props) => {
|
||||
const credentialType = subscription.credential_type
|
||||
|
||||
switch (credentialType) {
|
||||
case TriggerCredentialTypeEnum.Unauthorized:
|
||||
return <ManualEditModal onClose={onClose} subscription={subscription} pluginDetail={pluginDetail} />
|
||||
case TriggerCredentialTypeEnum.Oauth2:
|
||||
return <OAuthEditModal onClose={onClose} subscription={subscription} pluginDetail={pluginDetail} />
|
||||
case TriggerCredentialTypeEnum.ApiKey:
|
||||
return <ApiKeyEditModal onClose={onClose} subscription={subscription} pluginDetail={pluginDetail} />
|
||||
default:
|
||||
return null
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,164 @@
|
||||
'use client'
|
||||
import type { FormRefObject, FormSchema } from '@/app/components/base/form/types'
|
||||
import type { ParametersSchema, PluginDetail } from '@/app/components/plugins/types'
|
||||
import type { TriggerSubscription } from '@/app/components/workflow/block-selector/types'
|
||||
import { isEqual } from 'lodash-es'
|
||||
import { useMemo, useRef } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { BaseForm } from '@/app/components/base/form/components/base'
|
||||
import { FormTypeEnum } from '@/app/components/base/form/types'
|
||||
import Modal from '@/app/components/base/modal/modal'
|
||||
import Toast from '@/app/components/base/toast'
|
||||
import { ReadmeEntrance } from '@/app/components/plugins/readme-panel/entrance'
|
||||
import { useUpdateTriggerSubscription } from '@/service/use-triggers'
|
||||
import { ReadmeShowType } from '../../../readme-panel/store'
|
||||
import { usePluginStore } from '../../store'
|
||||
import { useSubscriptionList } from '../use-subscription-list'
|
||||
|
||||
type Props = {
|
||||
onClose: () => void
|
||||
subscription: TriggerSubscription
|
||||
pluginDetail?: PluginDetail
|
||||
}
|
||||
|
||||
const normalizeFormType = (type: string): FormTypeEnum => {
|
||||
switch (type) {
|
||||
case 'string':
|
||||
case 'text':
|
||||
return FormTypeEnum.textInput
|
||||
case 'password':
|
||||
case 'secret':
|
||||
return FormTypeEnum.secretInput
|
||||
case 'number':
|
||||
case 'integer':
|
||||
return FormTypeEnum.textNumber
|
||||
case 'boolean':
|
||||
return FormTypeEnum.boolean
|
||||
case 'select':
|
||||
return FormTypeEnum.select
|
||||
default:
|
||||
if (Object.values(FormTypeEnum).includes(type as FormTypeEnum))
|
||||
return type as FormTypeEnum
|
||||
return FormTypeEnum.textInput
|
||||
}
|
||||
}
|
||||
|
||||
export const ManualEditModal = ({ onClose, subscription, pluginDetail }: Props) => {
|
||||
const { t } = useTranslation()
|
||||
const detail = usePluginStore(state => state.detail)
|
||||
const { refetch } = useSubscriptionList()
|
||||
|
||||
const { mutate: updateSubscription, isPending: isUpdating } = useUpdateTriggerSubscription()
|
||||
|
||||
const getErrorMessage = (error: unknown, fallback: string) => {
|
||||
if (error instanceof Error && error.message)
|
||||
return error.message
|
||||
if (typeof error === 'object' && error && 'message' in error) {
|
||||
const message = (error as { message?: string }).message
|
||||
if (typeof message === 'string' && message)
|
||||
return message
|
||||
}
|
||||
return fallback
|
||||
}
|
||||
|
||||
const propertiesSchema = useMemo<ParametersSchema[]>(
|
||||
() => detail?.declaration?.trigger?.subscription_schema || [],
|
||||
[detail?.declaration?.trigger?.subscription_schema],
|
||||
)
|
||||
|
||||
const formRef = useRef<FormRefObject>(null)
|
||||
|
||||
const handleConfirm = () => {
|
||||
const formValues = formRef.current?.getFormValues({
|
||||
needTransformWhenSecretFieldIsPristine: true,
|
||||
})
|
||||
if (!formValues?.isCheckValidated)
|
||||
return
|
||||
|
||||
const name = formValues.values.subscription_name as string
|
||||
|
||||
// Extract properties (exclude subscription_name and callback_url)
|
||||
const newProperties = { ...formValues.values }
|
||||
delete newProperties.subscription_name
|
||||
delete newProperties.callback_url
|
||||
|
||||
// Only send properties if changed
|
||||
const hasChanged = !isEqual(newProperties, subscription.properties || {})
|
||||
const properties = hasChanged ? newProperties : undefined
|
||||
|
||||
updateSubscription(
|
||||
{
|
||||
subscriptionId: subscription.id,
|
||||
name,
|
||||
properties,
|
||||
},
|
||||
{
|
||||
onSuccess: () => {
|
||||
Toast.notify({
|
||||
type: 'success',
|
||||
message: t('pluginTrigger.subscription.list.item.actions.edit.success'),
|
||||
})
|
||||
refetch?.()
|
||||
onClose()
|
||||
},
|
||||
onError: (error: unknown) => {
|
||||
Toast.notify({
|
||||
type: 'error',
|
||||
message: getErrorMessage(error, t('pluginTrigger.subscription.list.item.actions.edit.error')),
|
||||
})
|
||||
},
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
const formSchemas: FormSchema[] = useMemo(() => [
|
||||
{
|
||||
name: 'subscription_name',
|
||||
label: t('pluginTrigger.modal.form.subscriptionName.label'),
|
||||
placeholder: t('pluginTrigger.modal.form.subscriptionName.placeholder'),
|
||||
type: FormTypeEnum.textInput,
|
||||
required: true,
|
||||
default: subscription.name,
|
||||
},
|
||||
{
|
||||
name: 'callback_url',
|
||||
label: t('pluginTrigger.modal.form.callbackUrl.label'),
|
||||
placeholder: t('pluginTrigger.modal.form.callbackUrl.placeholder'),
|
||||
type: FormTypeEnum.textInput,
|
||||
required: false,
|
||||
default: subscription.endpoint || '',
|
||||
disabled: true,
|
||||
tooltip: t('pluginTrigger.modal.form.callbackUrl.tooltip'),
|
||||
showCopy: true,
|
||||
},
|
||||
...propertiesSchema.map((schema: ParametersSchema) => ({
|
||||
...schema,
|
||||
type: normalizeFormType(schema.type as string),
|
||||
tooltip: schema.description,
|
||||
default: subscription.properties?.[schema.name] || schema.default,
|
||||
})),
|
||||
], [t, subscription.name, subscription.endpoint, subscription.properties, propertiesSchema])
|
||||
|
||||
return (
|
||||
<Modal
|
||||
title={t('pluginTrigger.subscription.list.item.actions.edit.title')}
|
||||
confirmButtonText={isUpdating ? t('common.operation.saving') : t('common.operation.save')}
|
||||
onClose={onClose}
|
||||
onCancel={onClose}
|
||||
onConfirm={handleConfirm}
|
||||
disabled={isUpdating}
|
||||
clickOutsideNotClose
|
||||
wrapperClassName="!z-[101]"
|
||||
>
|
||||
{pluginDetail && (
|
||||
<ReadmeEntrance pluginDetail={pluginDetail} showType={ReadmeShowType.modal} />
|
||||
)}
|
||||
<BaseForm
|
||||
formSchemas={formSchemas}
|
||||
ref={formRef}
|
||||
labelClassName="system-sm-medium mb-2 flex items-center gap-1 text-text-primary"
|
||||
formClassName="space-y-4"
|
||||
/>
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
@ -0,0 +1,178 @@
|
||||
'use client'
|
||||
import type { FormRefObject, FormSchema } from '@/app/components/base/form/types'
|
||||
import type { ParametersSchema, PluginDetail } from '@/app/components/plugins/types'
|
||||
import type { TriggerSubscription } from '@/app/components/workflow/block-selector/types'
|
||||
import { isEqual } from 'lodash-es'
|
||||
import { useMemo, useRef } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { BaseForm } from '@/app/components/base/form/components/base'
|
||||
import { FormTypeEnum } from '@/app/components/base/form/types'
|
||||
import Modal from '@/app/components/base/modal/modal'
|
||||
import Toast from '@/app/components/base/toast'
|
||||
import { ReadmeEntrance } from '@/app/components/plugins/readme-panel/entrance'
|
||||
import { useUpdateTriggerSubscription } from '@/service/use-triggers'
|
||||
import { ReadmeShowType } from '../../../readme-panel/store'
|
||||
import { usePluginStore } from '../../store'
|
||||
import { useSubscriptionList } from '../use-subscription-list'
|
||||
|
||||
type Props = {
|
||||
onClose: () => void
|
||||
subscription: TriggerSubscription
|
||||
pluginDetail?: PluginDetail
|
||||
}
|
||||
|
||||
const normalizeFormType = (type: string): FormTypeEnum => {
|
||||
switch (type) {
|
||||
case 'string':
|
||||
case 'text':
|
||||
return FormTypeEnum.textInput
|
||||
case 'password':
|
||||
case 'secret':
|
||||
return FormTypeEnum.secretInput
|
||||
case 'number':
|
||||
case 'integer':
|
||||
return FormTypeEnum.textNumber
|
||||
case 'boolean':
|
||||
return FormTypeEnum.boolean
|
||||
case 'select':
|
||||
return FormTypeEnum.select
|
||||
default:
|
||||
if (Object.values(FormTypeEnum).includes(type as FormTypeEnum))
|
||||
return type as FormTypeEnum
|
||||
return FormTypeEnum.textInput
|
||||
}
|
||||
}
|
||||
|
||||
export const OAuthEditModal = ({ onClose, subscription, pluginDetail }: Props) => {
|
||||
const { t } = useTranslation()
|
||||
const detail = usePluginStore(state => state.detail)
|
||||
const { refetch } = useSubscriptionList()
|
||||
|
||||
const { mutate: updateSubscription, isPending: isUpdating } = useUpdateTriggerSubscription()
|
||||
|
||||
const getErrorMessage = (error: unknown, fallback: string) => {
|
||||
if (error instanceof Error && error.message)
|
||||
return error.message
|
||||
if (typeof error === 'object' && error && 'message' in error) {
|
||||
const message = (error as { message?: string }).message
|
||||
if (typeof message === 'string' && message)
|
||||
return message
|
||||
}
|
||||
return fallback
|
||||
}
|
||||
|
||||
const parametersSchema = useMemo<ParametersSchema[]>(
|
||||
() => detail?.declaration?.trigger?.subscription_constructor?.parameters || [],
|
||||
[detail?.declaration?.trigger?.subscription_constructor?.parameters],
|
||||
)
|
||||
|
||||
const formRef = useRef<FormRefObject>(null)
|
||||
|
||||
const handleConfirm = () => {
|
||||
const formValues = formRef.current?.getFormValues({
|
||||
needTransformWhenSecretFieldIsPristine: true,
|
||||
})
|
||||
if (!formValues?.isCheckValidated)
|
||||
return
|
||||
|
||||
const name = formValues.values.subscription_name as string
|
||||
|
||||
// Extract parameters (exclude subscription_name and callback_url)
|
||||
const newParameters = { ...formValues.values }
|
||||
delete newParameters.subscription_name
|
||||
delete newParameters.callback_url
|
||||
|
||||
// Only send parameters if changed
|
||||
const hasChanged = !isEqual(newParameters, subscription.parameters || {})
|
||||
const parameters = hasChanged ? newParameters : undefined
|
||||
|
||||
updateSubscription(
|
||||
{
|
||||
subscriptionId: subscription.id,
|
||||
name,
|
||||
parameters,
|
||||
},
|
||||
{
|
||||
onSuccess: () => {
|
||||
Toast.notify({
|
||||
type: 'success',
|
||||
message: t('pluginTrigger.subscription.list.item.actions.edit.success'),
|
||||
})
|
||||
refetch?.()
|
||||
onClose()
|
||||
},
|
||||
onError: (error: unknown) => {
|
||||
Toast.notify({
|
||||
type: 'error',
|
||||
message: getErrorMessage(error, t('pluginTrigger.subscription.list.item.actions.edit.error')),
|
||||
})
|
||||
},
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
const formSchemas: FormSchema[] = useMemo(() => [
|
||||
{
|
||||
name: 'subscription_name',
|
||||
label: t('pluginTrigger.modal.form.subscriptionName.label'),
|
||||
placeholder: t('pluginTrigger.modal.form.subscriptionName.placeholder'),
|
||||
type: FormTypeEnum.textInput,
|
||||
required: true,
|
||||
default: subscription.name,
|
||||
},
|
||||
{
|
||||
name: 'callback_url',
|
||||
label: t('pluginTrigger.modal.form.callbackUrl.label'),
|
||||
placeholder: t('pluginTrigger.modal.form.callbackUrl.placeholder'),
|
||||
type: FormTypeEnum.textInput,
|
||||
required: false,
|
||||
default: subscription.endpoint || '',
|
||||
disabled: true,
|
||||
tooltip: t('pluginTrigger.modal.form.callbackUrl.tooltip'),
|
||||
showCopy: true,
|
||||
},
|
||||
...parametersSchema.map((schema: ParametersSchema) => {
|
||||
const normalizedType = normalizeFormType(schema.type as string)
|
||||
return {
|
||||
...schema,
|
||||
type: normalizedType,
|
||||
tooltip: schema.description,
|
||||
default: subscription.parameters?.[schema.name] || schema.default,
|
||||
dynamicSelectParams: normalizedType === FormTypeEnum.dynamicSelect
|
||||
? {
|
||||
plugin_id: detail?.plugin_id || '',
|
||||
provider: detail?.provider || '',
|
||||
action: 'provider',
|
||||
parameter: schema.name,
|
||||
credential_id: subscription.id,
|
||||
}
|
||||
: undefined,
|
||||
fieldClassName: schema.type === FormTypeEnum.boolean ? 'flex items-center justify-between' : undefined,
|
||||
labelClassName: schema.type === FormTypeEnum.boolean ? 'mb-0' : undefined,
|
||||
}
|
||||
}),
|
||||
], [t, subscription.name, subscription.endpoint, subscription.parameters, subscription.id, parametersSchema, detail?.plugin_id, detail?.provider])
|
||||
|
||||
return (
|
||||
<Modal
|
||||
title={t('pluginTrigger.subscription.list.item.actions.edit.title')}
|
||||
confirmButtonText={isUpdating ? t('common.operation.saving') : t('common.operation.save')}
|
||||
onClose={onClose}
|
||||
onCancel={onClose}
|
||||
onConfirm={handleConfirm}
|
||||
disabled={isUpdating}
|
||||
clickOutsideNotClose
|
||||
wrapperClassName="!z-[101]"
|
||||
>
|
||||
{pluginDetail && (
|
||||
<ReadmeEntrance pluginDetail={pluginDetail} showType={ReadmeShowType.modal} />
|
||||
)}
|
||||
<BaseForm
|
||||
formSchemas={formSchemas}
|
||||
ref={formRef}
|
||||
labelClassName="system-sm-medium mb-2 flex items-center gap-1 text-text-primary"
|
||||
formClassName="space-y-4"
|
||||
/>
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
@ -1,31 +1,27 @@
|
||||
import type { SimpleSubscription } from './types'
|
||||
import type { PluginDetail } from '@/app/components/plugins/types'
|
||||
import { withErrorBoundary } from '@/app/components/base/error-boundary'
|
||||
import Loading from '@/app/components/base/loading'
|
||||
import { SubscriptionListView } from './list-view'
|
||||
import { SubscriptionSelectorView } from './selector-view'
|
||||
import { SubscriptionListMode } from './types'
|
||||
import { useSubscriptionList } from './use-subscription-list'
|
||||
|
||||
export enum SubscriptionListMode {
|
||||
PANEL = 'panel',
|
||||
SELECTOR = 'selector',
|
||||
}
|
||||
|
||||
export type SimpleSubscription = {
|
||||
id: string
|
||||
name: string
|
||||
}
|
||||
|
||||
type SubscriptionListProps = {
|
||||
mode?: SubscriptionListMode
|
||||
selectedId?: string
|
||||
onSelect?: (v: SimpleSubscription, callback?: () => void) => void
|
||||
pluginDetail?: PluginDetail
|
||||
}
|
||||
|
||||
export { SubscriptionSelectorEntry } from './selector-entry'
|
||||
export type { SimpleSubscription } from './types'
|
||||
|
||||
export const SubscriptionList = withErrorBoundary(({
|
||||
mode = SubscriptionListMode.PANEL,
|
||||
selectedId,
|
||||
onSelect,
|
||||
pluginDetail,
|
||||
}: SubscriptionListProps) => {
|
||||
const { isLoading, refetch } = useSubscriptionList()
|
||||
if (isLoading) {
|
||||
@ -47,5 +43,5 @@ export const SubscriptionList = withErrorBoundary(({
|
||||
)
|
||||
}
|
||||
|
||||
return <SubscriptionListView />
|
||||
return <SubscriptionListView pluginDetail={pluginDetail} />
|
||||
})
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
'use client'
|
||||
import type { PluginDetail } from '@/app/components/plugins/types'
|
||||
import * as React from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Tooltip from '@/app/components/base/tooltip'
|
||||
@ -9,10 +10,12 @@ import { useSubscriptionList } from './use-subscription-list'
|
||||
|
||||
type SubscriptionListViewProps = {
|
||||
showTopBorder?: boolean
|
||||
pluginDetail?: PluginDetail
|
||||
}
|
||||
|
||||
export const SubscriptionListView: React.FC<SubscriptionListViewProps> = ({
|
||||
showTopBorder = false,
|
||||
pluginDetail,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
const { subscriptions } = useSubscriptionList()
|
||||
@ -41,6 +44,7 @@ export const SubscriptionListView: React.FC<SubscriptionListViewProps> = ({
|
||||
<SubscriptionCard
|
||||
key={subscription.id}
|
||||
data={subscription}
|
||||
pluginDetail={pluginDetail}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
'use client'
|
||||
import type { SimpleSubscription } from '@/app/components/plugins/plugin-detail-panel/subscription-list'
|
||||
import type { SimpleSubscription } from './types'
|
||||
import { RiArrowDownSLine, RiWebhookLine } from '@remixicon/react'
|
||||
import { useMemo, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
@ -8,8 +8,9 @@ import {
|
||||
PortalToFollowElemContent,
|
||||
PortalToFollowElemTrigger,
|
||||
} from '@/app/components/base/portal-to-follow-elem'
|
||||
import { SubscriptionList, SubscriptionListMode } from '@/app/components/plugins/plugin-detail-panel/subscription-list'
|
||||
import { SubscriptionList } from '@/app/components/plugins/plugin-detail-panel/subscription-list'
|
||||
import { cn } from '@/utils/classnames'
|
||||
import { SubscriptionListMode } from './types'
|
||||
import { useSubscriptionList } from './use-subscription-list'
|
||||
|
||||
type SubscriptionTriggerButtonProps = {
|
||||
|
||||
@ -1,7 +1,9 @@
|
||||
'use client'
|
||||
import type { PluginDetail } from '@/app/components/plugins/types'
|
||||
import type { TriggerSubscription } from '@/app/components/workflow/block-selector/types'
|
||||
import {
|
||||
RiDeleteBinLine,
|
||||
RiEditLine,
|
||||
RiWebhookLine,
|
||||
} from '@remixicon/react'
|
||||
import { useBoolean } from 'ahooks'
|
||||
@ -10,17 +12,23 @@ import ActionButton from '@/app/components/base/action-button'
|
||||
import Tooltip from '@/app/components/base/tooltip'
|
||||
import { cn } from '@/utils/classnames'
|
||||
import { DeleteConfirm } from './delete-confirm'
|
||||
import { EditModal } from './edit'
|
||||
|
||||
type Props = {
|
||||
data: TriggerSubscription
|
||||
pluginDetail?: PluginDetail
|
||||
}
|
||||
|
||||
const SubscriptionCard = ({ data }: Props) => {
|
||||
const SubscriptionCard = ({ data, pluginDetail }: Props) => {
|
||||
const { t } = useTranslation()
|
||||
const [isShowDeleteModal, {
|
||||
setTrue: showDeleteModal,
|
||||
setFalse: hideDeleteModal,
|
||||
}] = useBoolean(false)
|
||||
const [isShowEditModal, {
|
||||
setTrue: showEditModal,
|
||||
setFalse: hideEditModal,
|
||||
}] = useBoolean(false)
|
||||
|
||||
return (
|
||||
<>
|
||||
@ -40,12 +48,20 @@ const SubscriptionCard = ({ data }: Props) => {
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<ActionButton
|
||||
onClick={showDeleteModal}
|
||||
className="subscription-delete-btn hidden transition-colors hover:bg-state-destructive-hover hover:text-text-destructive group-hover:block"
|
||||
>
|
||||
<RiDeleteBinLine className="h-4 w-4" />
|
||||
</ActionButton>
|
||||
<div className="hidden items-center gap-1 group-hover:flex">
|
||||
<ActionButton
|
||||
onClick={showEditModal}
|
||||
className="transition-colors hover:bg-state-base-hover"
|
||||
>
|
||||
<RiEditLine className="h-4 w-4" />
|
||||
</ActionButton>
|
||||
<ActionButton
|
||||
onClick={showDeleteModal}
|
||||
className="subscription-delete-btn transition-colors hover:bg-state-destructive-hover hover:text-text-destructive"
|
||||
>
|
||||
<RiDeleteBinLine className="h-4 w-4" />
|
||||
</ActionButton>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-1 flex items-center justify-between">
|
||||
@ -78,6 +94,14 @@ const SubscriptionCard = ({ data }: Props) => {
|
||||
workflowsInUse={data.workflows_in_use}
|
||||
/>
|
||||
)}
|
||||
|
||||
{isShowEditModal && (
|
||||
<EditModal
|
||||
onClose={hideEditModal}
|
||||
subscription={data}
|
||||
pluginDetail={pluginDetail}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@ -0,0 +1,9 @@
|
||||
export enum SubscriptionListMode {
|
||||
PANEL = 'panel',
|
||||
SELECTOR = 'selector',
|
||||
}
|
||||
|
||||
export type SimpleSubscription = {
|
||||
id: string
|
||||
name: string
|
||||
}
|
||||
@ -131,7 +131,7 @@ export type ParametersSchema = {
|
||||
scope: any
|
||||
required: boolean
|
||||
multiple: boolean
|
||||
default?: string[]
|
||||
default?: string | string[]
|
||||
min: any
|
||||
max: any
|
||||
precision: any
|
||||
|
||||
Reference in New Issue
Block a user