feat: introduce trigger functionality (#27644)
Signed-off-by: lyzno1 <yuanyouhuilyz@gmail.com> Co-authored-by: Stream <Stream_2@qq.com> Co-authored-by: lyzno1 <92089059+lyzno1@users.noreply.github.com> Co-authored-by: zhsama <torvalds@linux.do> Co-authored-by: Harry <xh001x@hotmail.com> Co-authored-by: lyzno1 <yuanyouhuilyz@gmail.com> Co-authored-by: yessenia <yessenia.contact@gmail.com> Co-authored-by: hjlarry <hjlarry@163.com> Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> Co-authored-by: WTW0313 <twwu@dify.ai> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -0,0 +1,193 @@
|
||||
'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
|
||||
}
|
||||
|
||||
enum LogTypeEnum {
|
||||
REQUEST = 'request',
|
||||
RESPONSE = 'response',
|
||||
}
|
||||
|
||||
const LogViewer = ({ logs, className }: Props) => {
|
||||
const { t } = useTranslation()
|
||||
const [expandedLogs, setExpandedLogs] = useState<Set<string>>(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 = (originalData: any, title: LogTypeEnum) => {
|
||||
const parsedData = title === LogTypeEnum.REQUEST ? { headers: originalData.headers, data: parseRequestData(originalData.data) } : originalData
|
||||
const isJsonObject = typeof parsedData === 'object'
|
||||
|
||||
if (isJsonObject) {
|
||||
return (
|
||||
<CodeEditor
|
||||
readOnly
|
||||
title={<div className="system-xs-semibold-uppercase text-text-secondary">{title}</div>}
|
||||
language={CodeLanguage.json}
|
||||
value={parsedData}
|
||||
isJSONStringifyBeauty
|
||||
nodeId=""
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='rounded-md bg-components-input-bg-normal'>
|
||||
<div className='flex items-center justify-between px-2 py-1'>
|
||||
<div className='system-xs-semibold-uppercase text-text-secondary'>
|
||||
{title}
|
||||
</div>
|
||||
<button
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
navigator.clipboard.writeText(String(parsedData))
|
||||
Toast.notify({
|
||||
type: 'success',
|
||||
message: t('common.actionMsg.copySuccessfully'),
|
||||
})
|
||||
}}
|
||||
className='rounded-md p-0.5 hover:bg-components-panel-border'
|
||||
>
|
||||
<RiFileCopyLine className='h-4 w-4 text-text-tertiary' />
|
||||
</button>
|
||||
</div>
|
||||
<div className='px-2 pb-2 pt-1'>
|
||||
<pre className='code-xs-regular whitespace-pre-wrap break-all text-text-secondary'>
|
||||
{String(parsedData)}
|
||||
</pre>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
if (!logs || logs.length === 0)
|
||||
return null
|
||||
|
||||
return (
|
||||
<div className={cn('flex flex-col gap-1', className)}>
|
||||
{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 (
|
||||
<div
|
||||
key={logId}
|
||||
className={cn(
|
||||
'relative overflow-hidden rounded-lg border bg-components-panel-on-panel-item-bg shadow-sm hover:bg-components-panel-on-panel-item-bg-hover',
|
||||
isError && 'border-state-destructive-border',
|
||||
!isError && isExpanded && 'border-components-panel-border',
|
||||
!isError && !isExpanded && 'border-components-panel-border-subtle',
|
||||
)}
|
||||
>
|
||||
{isError && (
|
||||
<div className='pointer-events-none absolute left-0 top-0 h-7 w-[179px]'>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="179" height="28" viewBox="0 0 179 28" fill="none" className='h-full w-full'>
|
||||
<g filter="url(#filter0_f_error_glow)">
|
||||
<circle cx="27" cy="14" r="32" fill="#F04438" fillOpacity="0.25" />
|
||||
</g>
|
||||
<defs>
|
||||
<filter id="filter0_f_error_glow" x="-125" y="-138" width="304" height="304" filterUnits="userSpaceOnUse" colorInterpolationFilters="sRGB">
|
||||
<feFlood floodOpacity="0" result="BackgroundImageFix" />
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape" />
|
||||
<feGaussianBlur stdDeviation="60" result="effect1_foregroundBlur" />
|
||||
</filter>
|
||||
</defs>
|
||||
</svg>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<button
|
||||
onClick={() => toggleLogExpansion(logId)}
|
||||
className={cn(
|
||||
'flex w-full items-center justify-between px-2 py-1.5 text-left',
|
||||
isExpanded ? 'pb-1 pt-2' : 'min-h-7',
|
||||
)}
|
||||
>
|
||||
<div className='flex items-center gap-0'>
|
||||
{isExpanded ? (
|
||||
<RiArrowDownSLine className='h-4 w-4 text-text-tertiary' />
|
||||
) : (
|
||||
<RiArrowRightSLine className='h-4 w-4 text-text-tertiary' />
|
||||
)}
|
||||
<div className='system-xs-semibold-uppercase text-text-secondary'>
|
||||
{t(`pluginTrigger.modal.manual.logs.${LogTypeEnum.REQUEST}`)} #{index + 1}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='flex items-center gap-1'>
|
||||
<div className='system-xs-regular text-text-tertiary'>
|
||||
{dayjs(log.created_at).format('HH:mm:ss')}
|
||||
</div>
|
||||
<div className='h-3.5 w-3.5'>
|
||||
{isSuccess ? (
|
||||
<RiCheckboxCircleFill className='h-full w-full text-text-success' />
|
||||
) : (
|
||||
<RiErrorWarningFill className='h-full w-full text-text-destructive' />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
|
||||
{isExpanded && (
|
||||
<div className='flex flex-col gap-1 px-1 pb-1'>
|
||||
{renderJsonContent(log.request, LogTypeEnum.REQUEST)}
|
||||
{renderJsonContent(log.response, LogTypeEnum.RESPONSE)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default LogViewer
|
||||
Reference in New Issue
Block a user