146 lines
5.1 KiB
TypeScript
146 lines
5.1 KiB
TypeScript
import { Component, createMemo, createResource, createSignal, For, Show } from "solid-js";
|
|
import { logsApi } from "../api/client";
|
|
import type { RequestLog as RequestLogType } from "../types";
|
|
import { Button } from "./ui/Button";
|
|
import { Card } from "./ui/Card";
|
|
import { Input } from "./ui/Input";
|
|
import { Select } from "./ui/Select";
|
|
import RequestDetails from "./RequestDetails";
|
|
|
|
const RequestLogs: Component = () => {
|
|
const [search, setSearch] = createSignal("");
|
|
const [inputValue, setInputValue] = createSignal("");
|
|
const [method, setMethod] = createSignal("");
|
|
const [selectedLog, setSelectedLog] = createSignal<RequestLogType | null>(null);
|
|
|
|
let debounceTimer: number;
|
|
const handleInput = (e: { currentTarget: { value: string } }) => {
|
|
const value = e.currentTarget.value;
|
|
setInputValue(value);
|
|
clearTimeout(debounceTimer);
|
|
debounceTimer = window.setTimeout(() => setSearch(value), 300);
|
|
};
|
|
|
|
const [logs, { refetch: refetchLogs }] = createResource(
|
|
() => ({ search: search(), method: method() }),
|
|
async (filters) => {
|
|
return logsApi.list(filters);
|
|
},
|
|
);
|
|
const [allLogs, { refetch: refetchAllLogs }] = createResource(async () => {
|
|
return await logsApi.list();
|
|
});
|
|
const methods = createMemo(() => {
|
|
return allLogs.loading ? [] : [...new Set(allLogs()!.map((log) => log.method))];
|
|
});
|
|
const refetch = () => {
|
|
refetchLogs();
|
|
refetchAllLogs();
|
|
};
|
|
|
|
const handleLogClick = (log: RequestLogType) => {
|
|
setSelectedLog(log);
|
|
};
|
|
|
|
const handleCloseDetails = () => {
|
|
setSelectedLog(null);
|
|
};
|
|
|
|
const formatDate = (dateStr: string) => {
|
|
return new Date(dateStr).toLocaleString();
|
|
};
|
|
|
|
return (
|
|
<>
|
|
<div class="space-y-4">
|
|
<div class="flex items-center justify-between">
|
|
<h2 class="text-2xl font-semibold">
|
|
Request Logs
|
|
<Show when={logs() && !logs.loading && !allLogs.loading}>
|
|
<span class="text-gray-500 text-base ml-2">
|
|
({logs()?.length} of {allLogs()?.length} logs)
|
|
</span>
|
|
</Show>
|
|
</h2>
|
|
<Button onClick={() => refetch()} variant="secondary">
|
|
Refresh
|
|
</Button>
|
|
</div>
|
|
|
|
<Card>
|
|
<div class="p-4 flex flex-col sm:flex-row items-center space-y-4 sm:space-x-4 sm:space-y-0 bg-gray-50 border-b">
|
|
<div class="flex-1 w-full">
|
|
<Input
|
|
type="text"
|
|
placeholder="Search in request/response..."
|
|
value={inputValue()}
|
|
onInput={handleInput}
|
|
/>
|
|
</div>
|
|
<div class="w-full sm:w-1/4">
|
|
<Select value={method()} onChange={(e) => setMethod(e.currentTarget.value)} class="w-full">
|
|
<option value="">All Methods</option>
|
|
<For each={methods()}>{(m) => <option value={m}>{m}</option>}</For>
|
|
</Select>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="overflow-x-auto">
|
|
<table class="min-w-full divide-y divide-gray-200">
|
|
<thead class="bg-gray-50">
|
|
<tr>
|
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">Method</th>
|
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">Time</th>
|
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">
|
|
Request Body
|
|
</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody class="bg-white divide-y divide-gray-200">
|
|
<Show
|
|
when={!logs.loading}
|
|
fallback={
|
|
<tr>
|
|
<td colspan="3" class="text-center py-4">
|
|
Loading...
|
|
</td>
|
|
</tr>
|
|
}
|
|
>
|
|
<For
|
|
each={logs()}
|
|
fallback={
|
|
<tr>
|
|
<td colspan="3" class="text-center py-8 text-gray-500">
|
|
No logs found.
|
|
</td>
|
|
</tr>
|
|
}
|
|
>
|
|
{(log) => (
|
|
<tr class="hover:bg-gray-50 cursor-pointer" onClick={() => handleLogClick(log)}>
|
|
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900">
|
|
{log.method}
|
|
</td>
|
|
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
|
|
{formatDate(log.created_at)}
|
|
</td>
|
|
<td class="px-6 py-4 text-sm text-gray-500 font-mono truncate max-w-lg">
|
|
{JSON.stringify(log.request_body)}
|
|
</td>
|
|
</tr>
|
|
)}
|
|
</For>
|
|
</Show>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</Card>
|
|
</div>
|
|
<Show when={selectedLog()}>{(log) => <RequestDetails log={log()} onClose={handleCloseDetails} />}</Show>
|
|
</>
|
|
);
|
|
};
|
|
|
|
export default RequestLogs;
|