Skip to content

Commit

Permalink
refactor: log details section
Browse files Browse the repository at this point in the history
  • Loading branch information
ogzhanolguncu committed Sep 28, 2024
1 parent 4344d3a commit 42e481d
Show file tree
Hide file tree
Showing 6 changed files with 303 additions and 258 deletions.
46 changes: 46 additions & 0 deletions apps/dashboard/app/(app)/logs/log-details/components/log-body.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { Button } from "@/components/ui/button";
import { Card, CardContent } from "@/components/ui/card";
import { toast } from "@/components/ui/toaster";
import { Copy } from "lucide-react";
import { highlighter } from "..";
import type { Log } from "../../data";
import { getObjectsFromLogs } from "../../utils";

type Props = {
log: Log;
};

export const LogBody = ({ log }: Props) => {
return (
<div className="p-2">
<Card className="rounded-[5px] relative">
<CardContent
className="whitespace-pre-wrap text-[12px]"
dangerouslySetInnerHTML={{
__html: highlighter.codeToHtml(getObjectsFromLogs(log), {
lang: "json",
themes: {
dark: "github-dark",
light: "github-light",
},
mergeWhitespaces: true,
}),
}}
/>
<div className="absolute bottom-2 right-3">
<Button
size="block"
variant="primary"
className="bg-background border-border text-current"
onClick={() => {
navigator.clipboard.writeText(getObjectsFromLogs(log));
toast.success("Copied to clipboard");
}}
>
<Copy className="w-4 h-4" />
</Button>
</div>
</Card>
</div>
);
};
132 changes: 132 additions & 0 deletions apps/dashboard/app/(app)/logs/log-details/components/log-footer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
import { Badge } from "@/components/ui/badge";
import { Card, CardContent } from "@/components/ui/card";
import { cn } from "@/lib/utils";
import { format } from "date-fns";
import { highlighter } from "..";
import { RED_STATES, YELLOW_STATES } from "../../constants";
import type { Log } from "../../data";
import { getRequestHeader, getResponseBodyFieldOutcome } from "../../utils";
import { RequestResponseDetails } from "./request-response-details";

type Props = {
log: Log;
};
export const LogFooter = ({ log }: Props) => {
return (
<RequestResponseDetails
className="pl-3"
fields={[
{
label: "Time",
description: (content) => (
<span className="text-[13px] font-mono">{content}</span>
),
content: format(log.time, "MMM dd HH:mm:ss.SS"),
tooltipContent: "Copy Time",
tooltipSuccessMessage: "Time copied to clipboard",
},
{
label: "Host",
description: (content) => (
<span className="text-[13px] font-mono">{content}</span>
),
content: log.host,
tooltipContent: "Copy Host",
tooltipSuccessMessage: "Host copied to clipboard",
},
{
label: "Request Path",
description: (content) => (
<span className="text-[13px] font-mono">{content}</span>
),
content: log.path,
tooltipContent: "Copy Request Path",
tooltipSuccessMessage: "Request path copied to clipboard",
},
{
label: "Request ID",
description: (content) => (
<span className="text-[13px] font-mono">{content}</span>
),
content: log.request_id,
tooltipContent: "Copy Request ID",
tooltipSuccessMessage: "Request ID copied to clipboard",
},
{
label: "Request User Agent",
description: (content) => (
<span className="text-[13px] font-mono">{content}</span>
),
content: getRequestHeader(log, "user-agent") ?? "",
tooltipContent: "Copy Request User Agent",
tooltipSuccessMessage: "Request user agent copied to clipboard",
},
{
label: "Outcome",
description: (content) => {
let contentCopy = content;
if (contentCopy == null) {
contentCopy = "VALID";
}
return (
<Badge
className={cn(
{
"bg-amber-2 text-amber-11 hover:bg-amber-3":
YELLOW_STATES.includes(contentCopy),
"bg-red-2 text-red-11 hover:bg-red-3":
RED_STATES.includes(contentCopy),
},
"uppercase"
)}
>
{content}
</Badge>
);
},
content: getResponseBodyFieldOutcome(log, "code"),
tooltipContent: "Copy Outcome",
tooltipSuccessMessage: "Outcome copied to clipboard",
},
{
label: "Permissions",
description: (content) => (
<span className="text-[13px] font-mono flex gap-1">
{content.map((permission) => (
<Badge key={permission} variant="secondary">
{permission}
</Badge>
))}
</span>
),
content: getResponseBodyFieldOutcome(log, "permissions"),
tooltipContent: "Copy Permissions",
tooltipSuccessMessage: "Permissions copied to clipboard",
},
{
label: "Meta",
description: (content) => (
<Card className="rounded-[5px]">
<CardContent
className="whitespace-pre-wrap text-[12px] w-[300px]"
dangerouslySetInnerHTML={{
__html: highlighter.codeToHtml(JSON.stringify(content), {
lang: "json",
themes: {
dark: "github-dark",
light: "github-light",
},
mergeWhitespaces: true,
}),
}}
/>
</Card>
),
content: getResponseBodyFieldOutcome(log, "meta"),
tooltipContent: "Copy Meta",
tooltipSuccessMessage: "Meta copied to clipboard",
},
]}
/>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { Badge } from "@/components/ui/badge";
import { cn } from "@/lib/utils";
import { X } from "lucide-react";
import type { Log } from "../../data";

type Props = {
log: Log;
onClose: () => void;
};
export const LogHeader = ({ onClose, log }: Props) => {
return (
<div className="border-b-[1px] px-3 py-4 flex justify-between border-border items-center">
<div className="flex gap-2">
<Badge variant="secondary" className="bg-transparent">
POST
</Badge>
<p className="text-[13px] text-content/65">{log.path}</p>
</div>

<div className="flex gap-1 items-center">
<Badge
className={cn(
"bg-background border border-solid border-border text-current hover:bg-transparent",
log.response_status >= 400 && "border-red-6 text-red-11"
)}
>
{log.response_status}
</Badge>

<span className="text-content/65">|</span>
<X
onClick={onClose}
size="22"
strokeWidth="1.5"
className="text-content/65 cursor-pointer"
/>
</div>
</div>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import { toast } from "@/components/ui/toaster";
import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from "@/components/ui/tooltip";
import { cn } from "@/lib/utils";
import type { ReactNode } from "react";

type Field<T> = {
label: string;
description: (content: NonNullable<T>) => ReactNode;
content: T | null;
tooltipContent: ReactNode;
tooltipSuccessMessage: string;
className?: string;
};

type Props<T extends unknown[]> = {
fields: { [K in keyof T]: Field<T[K]> };
className?: string;
};
//This function ensures that content is not nil, and if it's an object or array, it has some content.
const isNonEmpty = (content: unknown): boolean => {
if (Array.isArray(content)) {
return content.length > 0;
}
if (typeof content === "object" && content !== null) {
return Object.keys(content).length > 0;
}
return Boolean(content);
};

export const RequestResponseDetails = <T extends unknown[]>({
fields,
className,
}: Props<T>) => {
return (
<div className={cn("font-sans", className)}>
{fields.map(
(field, index) =>
isNonEmpty(field.content) && (
// biome-ignore lint/suspicious/noArrayIndexKey: <explanation>
<TooltipProvider key={index}>
<Tooltip>
<TooltipTrigger
className={cn(
"flex w-full justify-between border-border border-solid pr-3 py-[10px] items-center",
index !== fields.length - 1 && "border-b",
field.className
)}
onClick={() => {
navigator.clipboard.writeText(
typeof field.content === "object"
? JSON.stringify(field.content)
: String(field.content)
);
toast.success(field.tooltipSuccessMessage);
}}
>
<span className="text-sm text-content/65">{field.label}</span>
{field.description(field.content as NonNullable<T[number]>)}
</TooltipTrigger>
<TooltipContent side="left">
{field.tooltipContent}
</TooltipContent>
</Tooltip>
</TooltipProvider>
)
)}
</div>
);
};
Loading

0 comments on commit 42e481d

Please sign in to comment.