100 lines
3.2 KiB
TypeScript
100 lines
3.2 KiB
TypeScript
import { Component, createSignal, For, Show } from "solid-js";
|
|
import type { RequestLog } from "../types";
|
|
import { Button } from "./ui/Button";
|
|
import { CardContent, CardFooter, CardHeader } from "./ui/Card";
|
|
import { Modal, ModalContent } from "./ui/Modal";
|
|
|
|
interface TreeViewProps {
|
|
data: any;
|
|
name: string;
|
|
isRoot?: boolean;
|
|
}
|
|
|
|
const TreeView: Component<TreeViewProps> = (props) => {
|
|
const [isOpen, setIsOpen] = createSignal(props.isRoot ?? false);
|
|
const isObject = typeof props.data === "object" && props.data !== null;
|
|
|
|
const renderValue = (value: any) => {
|
|
switch (typeof value) {
|
|
case "string":
|
|
return <span class="text-green-600">"{value}"</span>;
|
|
case "number":
|
|
return <span class="text-blue-600">{value}</span>;
|
|
case "boolean":
|
|
return <span class="text-purple-600">{String(value)}</span>;
|
|
case "object":
|
|
if (value === null) return <span class="text-gray-500">null</span>;
|
|
// This case is handled by recursive TreeView
|
|
default:
|
|
return <span class="text-gray-500">{String(value)}</span>;
|
|
}
|
|
};
|
|
|
|
return (
|
|
<div class="font-mono text-sm">
|
|
<div
|
|
class="flex items-center cursor-pointer hover:bg-gray-100 rounded px-1"
|
|
onClick={() => setIsOpen(!isOpen())}
|
|
>
|
|
<span class="w-4 inline-block text-gray-500">{isObject ? (isOpen() ? "▼" : "►") : ""}</span>
|
|
<span class="font-semibold text-gray-800">{props.name}:</span>
|
|
<Show when={!isObject}>
|
|
<span class="ml-2">{renderValue(props.data)}</span>
|
|
</Show>
|
|
</div>
|
|
<Show when={isOpen() && isObject}>
|
|
<div class="pl-4 border-l border-gray-200 ml-3">
|
|
<For each={Object.entries(props.data)}>
|
|
{([key, value]) => <TreeView name={key} data={value} />}
|
|
</For>
|
|
</div>
|
|
</Show>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
interface RequestDetailsProps {
|
|
log: RequestLog;
|
|
onClose: () => void;
|
|
}
|
|
|
|
const RequestDetails: Component<RequestDetailsProps> = (props) => {
|
|
return (
|
|
<Modal>
|
|
<ModalContent class="max-w-3xl h-auto max-h-[90vh]">
|
|
<CardHeader>
|
|
<h3 class="text-lg font-medium text-gray-900">Request Details</h3>
|
|
<p class="text-sm text-gray-500">
|
|
{props.log.method} at {new Date(props.log.created_at).toLocaleString()}
|
|
</p>
|
|
</CardHeader>
|
|
|
|
<CardContent class="max-h-[70vh] overflow-y-auto">
|
|
<div class="grid grid-cols-1 gap-4">
|
|
<div>
|
|
<h4 class="font-semibold mb-2">Request</h4>
|
|
<div class="bg-gray-50 p-3 rounded overflow-x-auto">
|
|
<TreeView name="body" data={props.log.request_body} isRoot={true} />
|
|
</div>
|
|
</div>
|
|
<div>
|
|
<h4 class="font-semibold mb-2">Response</h4>
|
|
<div class="bg-gray-50 p-3 rounded overflow-x-auto">
|
|
<TreeView name="body" data={props.log.response_body} isRoot={true} />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</CardContent>
|
|
|
|
<CardFooter class="flex justify-end pt-4">
|
|
<Button type="button" variant="secondary" onClick={props.onClose}>
|
|
Close
|
|
</Button>
|
|
</CardFooter>
|
|
</ModalContent>
|
|
</Modal>
|
|
);
|
|
};
|
|
|
|
export default RequestDetails;
|