From 33a0a60eee53d1ac08cc9accc2e96f06e56ebb52 Mon Sep 17 00:00:00 2001 From: moonrailgun Date: Tue, 1 Oct 2024 22:14:43 +0800 Subject: [PATCH] feat: add webhook playground --- src/client/components/CodeBlock.tsx | 9 +- src/client/components/WebhookPlayground.tsx | 218 ++++++++++++++++++ .../components/feed/FeedIntegration.tsx | 1 - src/client/routes/playground.tsx | 30 ++- src/server/trpc/routers/feed/integration.ts | 37 ++- src/server/ws/shared.ts | 7 +- src/types/server.ts | 11 + 7 files changed, 302 insertions(+), 11 deletions(-) create mode 100644 src/client/components/WebhookPlayground.tsx diff --git a/src/client/components/CodeBlock.tsx b/src/client/components/CodeBlock.tsx index c4d8e04..ef979dc 100644 --- a/src/client/components/CodeBlock.tsx +++ b/src/client/components/CodeBlock.tsx @@ -4,10 +4,10 @@ import { Button } from './ui/button'; import { LuCopy, LuCopyCheck } from 'react-icons/lu'; import { toast } from 'sonner'; import { useTranslation } from '@i18next-toolkit/react'; -import { ScrollBar } from './ui/scroll-area'; export const CodeBlock: React.FC<{ code: string; + height?: number; }> = React.memo((props) => { const [copied, setCopied] = useState(false); const { t } = useTranslation(); @@ -22,7 +22,12 @@ export const CodeBlock: React.FC<{ return (
-
+      
         {props.code}
       
+ ); + })} +
+ + ); + + const webhookUrl = `${window.location.origin}/open/feed/playground/${workspaceId}`; + const emptyContentFallback = ( +
+
+ + Set webhook url with , and keep this + window actived, then you can get webhook request here. + +
+ +
+ ); + + const copyBtn = ( + + + + + + cURL + + + ); + + const content = selectedRequest ? ( +
+
+
+ {selectedRequest.method} +
+ {selectedRequest.url} +
+ {copyBtn} +
+
+ {dayjs(selectedRequest.createdAt).fromNow()} +
+
+ +
+ + + {t('Request Header')} + + + + {toPairs(selectedRequest.headers).map(([key, value]) => { + return ( +
+
+ {key} +
+
{value}
+
+ ); + })} +
+
+ + + + {t('Request Body')} + + + + + + +
+
+ ) : ( + emptyContentFallback + ); + + return ( +
+ + + {list} + + + + + + {content} + + +
+ ); +}); +WebhookPlayground.displayName = 'WebhookPlayground'; diff --git a/src/client/components/feed/FeedIntegration.tsx b/src/client/components/feed/FeedIntegration.tsx index 987ce48..f08af0b 100644 --- a/src/client/components/feed/FeedIntegration.tsx +++ b/src/client/components/feed/FeedIntegration.tsx @@ -4,7 +4,6 @@ import { Popover, PopoverContent, PopoverTrigger } from '../ui/popover'; import { CodeBlock } from '../CodeBlock'; import { useTranslation } from '@i18next-toolkit/react'; import { SiSentry } from 'react-icons/si'; -import { Link } from '@tanstack/react-router'; export const FeedIntegration: React.FC<{ feedId: string; diff --git a/src/client/routes/playground.tsx b/src/client/routes/playground.tsx index 86470a9..e867a4f 100644 --- a/src/client/routes/playground.tsx +++ b/src/client/routes/playground.tsx @@ -6,6 +6,8 @@ import { } from '@/components/monitor/StatusPage/ServiceList'; import { useState } from 'react'; import { EditableText } from '@/components/EditableText'; +import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; +import { WebhookPlayground } from '@/components/WebhookPlayground'; export const Route = createFileRoute('/playground')({ beforeLoad: () => { @@ -45,13 +47,29 @@ function PageComponent() { ]); return ( -
- console.log('save')} - /> +
+ +
+ + Current + History + +
- + + + + +
+ console.log('save')} + /> + + +
+
+
); } diff --git a/src/server/trpc/routers/feed/integration.ts b/src/server/trpc/routers/feed/integration.ts index 1f80c92..113331e 100644 --- a/src/server/trpc/routers/feed/integration.ts +++ b/src/server/trpc/routers/feed/integration.ts @@ -6,9 +6,44 @@ import { OPENAPI_TAG } from '../../../utils/const.js'; import { createFeedEvent } from '../../../model/feed/event.js'; import { tencentCloudAlarmSchema } from '../../../model/_schema/feed.js'; import { logger } from '../../../utils/logger.js'; -import { compact, fromPairs, get, map } from 'lodash-es'; +import { compact, fromPairs, get, map, uniqueId } from 'lodash-es'; +import { subscribeEventBus } from '../../../ws/shared.js'; export const feedIntegrationRouter = router({ + playground: publicProcedure + .meta( + buildFeedPublicOpenapi({ + method: 'POST', + path: '/playground/{workspaceId}', + summary: 'webhook playground', + }) + ) + .input( + z + .object({ + workspaceId: z.string(), + }) + .passthrough() + ) + .output(z.string()) + .mutation(async ({ input, ctx }) => { + const headers = ctx.req.headers; + const body = ctx.req.body; + const method = ctx.req.method; + const url = ctx.req.originalUrl; + const workspaceId = input.workspaceId; + + subscribeEventBus.emit('onReceivePlaygroundWebhookRequest', workspaceId, { + id: uniqueId('req#'), + headers, + body, + method, + url, + createdAt: Date.now(), + }); + + return 'success'; + }), github: publicProcedure .meta( buildFeedPublicOpenapi({ diff --git a/src/server/ws/shared.ts b/src/server/ws/shared.ts index bf22397..b868d9a 100644 --- a/src/server/ws/shared.ts +++ b/src/server/ws/shared.ts @@ -1,6 +1,10 @@ import { EventEmitter } from 'eventemitter-strict'; import { Socket } from 'socket.io'; -import { MaybePromise, ServerStatusInfo } from '../../types/index.js'; +import { + MaybePromise, + PlaygroundWebhookRequestPayload, + ServerStatusInfo, +} from '../../types/index.js'; import { FeedEvent, MonitorData } from '@prisma/client'; import { Serialize } from '../types/utils.js'; @@ -10,6 +14,7 @@ export interface SubscribeEventMap { onServerStatusUpdate: SubscribeEventFn>; onMonitorReceiveNewData: SubscribeEventFn; onReceiveFeedEvent: SubscribeEventFn>; + onReceivePlaygroundWebhookRequest: SubscribeEventFn; onLighthouseWorkCompleted: SubscribeEventFn<{ websiteId: string }>; } diff --git a/src/types/server.ts b/src/types/server.ts index e2ded80..32e0174 100644 --- a/src/types/server.ts +++ b/src/types/server.ts @@ -1,3 +1,5 @@ +import { type IncomingHttpHeaders } from 'http'; + export interface ServerStatusInfo { workspaceId: string; name: string; @@ -52,3 +54,12 @@ export interface ServerStatusDockerContainerPort { PublicPort: number; Type: 'tcp' | 'udp'; } + +export interface PlaygroundWebhookRequestPayload { + id: string; + url: string; + method: string; + headers: IncomingHttpHeaders; + body: string; + createdAt: number; +}