feat: refine request log

This commit is contained in:
2025-12-06 21:49:51 +08:00
parent 1941e6bcaf
commit 8be7af6815
7 changed files with 248 additions and 87 deletions

View File

@@ -1,47 +1,103 @@
import { Component, createSignal, For } from 'solid-js';
import { Component, createSignal, For, Show } from 'solid-js';
import type { RequestLog } from '../types';
import { Card } from './ui/Card';
import { Button } from './ui/Button';
import { CardContent, CardFooter, CardHeader } from './ui/Card';
import { Modal, ModalContent } from './ui/Modal';
const TreeView: Component<{ data: any; name: string }> = (props) => {
const [isOpen, setIsOpen] = createSignal(true);
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="ml-4 my-1">
<div class="flex items-center">
<span class="cursor-pointer" onClick={() => setIsOpen(!isOpen())}>
<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 ml-2">{props.name}</span>
<span class="font-semibold text-gray-800">{props.name}:</span>
<Show when={!isObject}>
<span class="ml-2">{renderValue(props.data)}</span>
</Show>
</div>
{isOpen() && isObject && (
<div class="pl-4 border-l-2 border-gray-200">
<Show when={isOpen() && isObject}>
<div class="pl-4 border-l border-gray-200 ml-3">
<For each={Object.entries(props.data)}>
{([key, value]) => {
if (typeof value === 'object' && value !== null && 'modified' in value && 'value' in value) {
return <TreeView name={key} data={value.value} />
}
return <TreeView name={key} data={value} />
}}
{([key, value]) => <TreeView name={key} data={value} />}
</For>
</div>
)}
{isOpen() && !isObject && <div class="pl-6 text-gray-700">{JSON.stringify(props.data)}</div>}
</Show>
</div>
);
};
const RequestDetails: Component<{ log: RequestLog }> = (props) => {
interface RequestDetailsProps {
log: RequestLog;
onClose: () => void;
}
const RequestDetails: Component<RequestDetailsProps> = (props) => {
return (
<Card class="p-4">
<h2 class="text-xl font-bold mb-4">Request Details</h2>
{/* TODO: interception method */}
<div>
<TreeView name="Request" data={props.log.request_body} />
<TreeView name="Response" data={props.log.response_body} />
</div>
</Card>
<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>
);
};