mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-09 22:28:25 +00:00
Handle body
This commit is contained in:
@@ -24,20 +24,19 @@ export const HttpBaseEventSchema = z.object({
|
|||||||
timestamp: z.coerce.date(),
|
timestamp: z.coerce.date(),
|
||||||
requestId: z.string(),
|
requestId: z.string(),
|
||||||
eventType: TerminalEventTypeSchema,
|
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({
|
export const HttpRequestEventSchema = HttpBaseEventSchema.extend({
|
||||||
eventType: z.literal(HttpEventTypeSchema.Values.request),
|
eventType: z.literal(HttpEventTypeSchema.Values.request),
|
||||||
method: z.string(),
|
method: z.string(),
|
||||||
url: z.string()
|
url: z.string()
|
||||||
// TODO: optional for now
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export const HttpResponseEventSchema = HttpBaseEventSchema.extend({
|
export const HttpResponseEventSchema = HttpBaseEventSchema.extend({
|
||||||
eventType: z.literal(HttpEventTypeSchema.Values.response),
|
eventType: z.literal(HttpEventTypeSchema.Values.response),
|
||||||
status: z.string()
|
status: z.string()
|
||||||
// TODO: optional for now
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export const HttpEventSchema = z.discriminatedUnion("eventType", [HttpRequestEventSchema, HttpResponseEventSchema]);
|
export const HttpEventSchema = z.discriminatedUnion("eventType", [HttpRequestEventSchema, HttpResponseEventSchema]);
|
||||||
|
|||||||
@@ -16,6 +16,45 @@ export const HttpEventView = ({ events }: Props) => {
|
|||||||
Record<string, { headers: boolean; body: boolean }>
|
Record<string, { headers: boolean; body: boolean }>
|
||||||
>({});
|
>({});
|
||||||
|
|
||||||
|
const getContentType = (headers: Record<string, string[]>): 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, string[]>
|
||||||
|
): 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(
|
const filteredEvents = useMemo(
|
||||||
() =>
|
() =>
|
||||||
events.filter((event) => {
|
events.filter((event) => {
|
||||||
@@ -23,6 +62,7 @@ export const HttpEventView = ({ events }: Props) => {
|
|||||||
if (!searchValue) return true;
|
if (!searchValue) return true;
|
||||||
|
|
||||||
if (event.eventType === "request") {
|
if (event.eventType === "request") {
|
||||||
|
const bodyForSearch = parseBodyForSearch(event.body, event.headers);
|
||||||
return (
|
return (
|
||||||
event.method.toLowerCase().includes(searchValue) ||
|
event.method.toLowerCase().includes(searchValue) ||
|
||||||
event.url.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.keys(event.headers).some((key) => key.toLowerCase().includes(searchValue)) ||
|
||||||
Object.values(event.headers).some((values) =>
|
Object.values(event.headers).some((values) =>
|
||||||
values.some((value) => value.toLowerCase().includes(searchValue))
|
values.some((value) => value.toLowerCase().includes(searchValue))
|
||||||
)
|
) ||
|
||||||
|
bodyForSearch.toLowerCase().includes(searchValue)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
@@ -51,6 +92,31 @@ export const HttpEventView = ({ events }: Props) => {
|
|||||||
.join("\n");
|
.join("\n");
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const formatBody = (body: string | undefined, headers: Record<string, string[]>): 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<string, string[]>) => {
|
const getKubectlCommand = (headers: Record<string, string[]>) => {
|
||||||
const headerKey = Object.keys(headers).find((key) => key.toLowerCase() === "kubectl-command");
|
const headerKey = Object.keys(headers).find((key) => key.toLowerCase() === "kubectl-command");
|
||||||
return headerKey ? headers[headerKey]?.[0] : undefined;
|
return headerKey ? headers[headerKey]?.[0] : undefined;
|
||||||
@@ -156,28 +222,29 @@ export const HttpEventView = ({ events }: Props) => {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="mt-2 pt-2 border-t border-mineshaft-700">
|
{isRequest && event.body && (
|
||||||
<button
|
<div className="mt-2 pt-2 border-t border-mineshaft-700">
|
||||||
type="button"
|
<button
|
||||||
onClick={() => toggleSection(eventKey, "body")}
|
type="button"
|
||||||
className="flex items-center gap-2 w-full text-left text-bunker-400 text-xs mb-1 hover:text-bunker-300 transition-colors"
|
onClick={() => toggleSection(eventKey, "body")}
|
||||||
>
|
className="flex items-center gap-2 w-full text-left text-bunker-400 text-xs mb-1 hover:text-bunker-300 transition-colors"
|
||||||
<FontAwesomeIcon
|
>
|
||||||
icon={isSectionExpanded(eventKey, "body") ? faChevronUp : faChevronDown}
|
<FontAwesomeIcon
|
||||||
className="text-xs"
|
icon={isSectionExpanded(eventKey, "body") ? faChevronUp : faChevronDown}
|
||||||
/>
|
className="text-xs"
|
||||||
<span>Body:</span>
|
/>
|
||||||
</button>
|
<span>Body:</span>
|
||||||
{isSectionExpanded(eventKey, "body") && (
|
</button>
|
||||||
<div className="font-mono whitespace-pre-wrap text-bunker-300 text-xs">
|
{isSectionExpanded(eventKey, "body") && (
|
||||||
<div className="text-bunker-500 italic">
|
<div className="font-mono whitespace-pre-wrap text-bunker-300 text-xs">
|
||||||
{isRequest
|
<HighlightText
|
||||||
? "Request body content will be displayed here when available."
|
text={formatBody(event.body, event.headers)}
|
||||||
: "Response body content will be displayed here when available."}
|
highlight={search}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
)}
|
||||||
)}
|
</div>
|
||||||
</div>
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user