From 7a9374c72b039643c9560b49bc7d919ed8bc53d4 Mon Sep 17 00:00:00 2001 From: Fang-Pen Lin Date: Thu, 4 Dec 2025 15:34:03 -0800 Subject: [PATCH] Handle body --- .../pam-session/pam-session-schemas.ts | 5 +- .../components/HttpEventView.tsx | 111 ++++++++++++++---- 2 files changed, 91 insertions(+), 25 deletions(-) diff --git a/backend/src/ee/services/pam-session/pam-session-schemas.ts b/backend/src/ee/services/pam-session/pam-session-schemas.ts index f0fe5702c..b336d15c8 100644 --- a/backend/src/ee/services/pam-session/pam-session-schemas.ts +++ b/backend/src/ee/services/pam-session/pam-session-schemas.ts @@ -24,20 +24,19 @@ export const HttpBaseEventSchema = z.object({ timestamp: z.coerce.date(), requestId: z.string(), eventType: TerminalEventTypeSchema, - headers: z.record(z.string(), z.array(z.string())) + headers: z.record(z.string(), z.array(z.string())), + body: z.string().optional() }); export const HttpRequestEventSchema = HttpBaseEventSchema.extend({ eventType: z.literal(HttpEventTypeSchema.Values.request), method: z.string(), url: z.string() - // TODO: optional for now }); export const HttpResponseEventSchema = HttpBaseEventSchema.extend({ eventType: z.literal(HttpEventTypeSchema.Values.response), status: z.string() - // TODO: optional for now }); export const HttpEventSchema = z.discriminatedUnion("eventType", [HttpRequestEventSchema, HttpResponseEventSchema]); diff --git a/frontend/src/pages/pam/PamSessionsByIDPage/components/HttpEventView.tsx b/frontend/src/pages/pam/PamSessionsByIDPage/components/HttpEventView.tsx index 5d77ff385..fd024e879 100644 --- a/frontend/src/pages/pam/PamSessionsByIDPage/components/HttpEventView.tsx +++ b/frontend/src/pages/pam/PamSessionsByIDPage/components/HttpEventView.tsx @@ -16,6 +16,45 @@ export const HttpEventView = ({ events }: Props) => { Record >({}); + const getContentType = (headers: Record): string | undefined => { + const contentTypeKey = Object.keys(headers).find((key) => key.toLowerCase() === "content-type"); + return contentTypeKey ? headers[contentTypeKey]?.[0] : undefined; + }; + + const decodeBase64Body = (body: string): string => { + try { + return atob(body); + } catch { + // If base64 decoding fails, return original body + return body; + } + }; + + const parseBodyForSearch = ( + body: string | undefined, + headers: Record + ): string => { + if (!body) return ""; + + // Decode base64 first + const decodedBody = decodeBase64Body(body); + + const contentType = getContentType(headers); + const isJson = contentType?.toLowerCase().includes("application/json"); + + if (isJson) { + try { + const parsed = JSON.parse(decodedBody); + return JSON.stringify(parsed); + } catch { + // If JSON parsing fails, fall back to decoded body + return decodedBody; + } + } + + return decodedBody; + }; + const filteredEvents = useMemo( () => events.filter((event) => { @@ -23,6 +62,7 @@ export const HttpEventView = ({ events }: Props) => { if (!searchValue) return true; if (event.eventType === "request") { + const bodyForSearch = parseBodyForSearch(event.body, event.headers); return ( event.method.toLowerCase().includes(searchValue) || event.url.toLowerCase().includes(searchValue) || @@ -30,7 +70,8 @@ export const HttpEventView = ({ events }: Props) => { Object.keys(event.headers).some((key) => key.toLowerCase().includes(searchValue)) || Object.values(event.headers).some((values) => values.some((value) => value.toLowerCase().includes(searchValue)) - ) + ) || + bodyForSearch.toLowerCase().includes(searchValue) ); } return ( @@ -51,6 +92,31 @@ export const HttpEventView = ({ events }: Props) => { .join("\n"); }; + const formatBody = (body: string | undefined, headers: Record): string => { + if (!body) { + return ""; + } + + // Decode base64 first + const decodedBody = decodeBase64Body(body); + + const contentType = getContentType(headers); + const isJson = contentType?.toLowerCase().includes("application/json"); + + if (isJson) { + try { + const parsed = JSON.parse(decodedBody); + return JSON.stringify(parsed, null, 2); + } catch { + // If JSON parsing fails, return decoded body + return decodedBody; + } + } + + // For non-JSON content, return decoded body + return decodedBody; + }; + const getKubectlCommand = (headers: Record) => { const headerKey = Object.keys(headers).find((key) => key.toLowerCase() === "kubectl-command"); return headerKey ? headers[headerKey]?.[0] : undefined; @@ -156,28 +222,29 @@ export const HttpEventView = ({ events }: Props) => { )} -
- - {isSectionExpanded(eventKey, "body") && ( -
-
- {isRequest - ? "Request body content will be displayed here when available." - : "Response body content will be displayed here when available."} + {isRequest && event.body && ( +
+ + {isSectionExpanded(eventKey, "body") && ( +
+
-
- )} -
+ )} +
+ )}
);