refactor(logs): refactored to show the submodule and stack as well to make it more easy to watch
This commit is contained in:
49
frontend/src/components/ui/badge.tsx
Normal file
49
frontend/src/components/ui/badge.tsx
Normal file
@@ -0,0 +1,49 @@
|
||||
import * as React from "react"
|
||||
import { cva, type VariantProps } from "class-variance-authority"
|
||||
import { Slot } from "radix-ui"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
const badgeVariants = cva(
|
||||
"group/badge inline-flex h-5 w-fit shrink-0 items-center justify-center gap-1 overflow-hidden rounded-4xl border border-transparent px-2 py-0.5 text-xs font-medium whitespace-nowrap transition-all focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 [&>svg]:pointer-events-none [&>svg]:size-3!",
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
default: "bg-primary text-primary-foreground [a]:hover:bg-primary/80",
|
||||
secondary:
|
||||
"bg-secondary text-secondary-foreground [a]:hover:bg-secondary/80",
|
||||
destructive:
|
||||
"bg-destructive/10 text-destructive focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:focus-visible:ring-destructive/40 [a]:hover:bg-destructive/20",
|
||||
outline:
|
||||
"border-border text-foreground [a]:hover:bg-muted [a]:hover:text-muted-foreground",
|
||||
ghost:
|
||||
"hover:bg-muted hover:text-muted-foreground dark:hover:bg-muted/50",
|
||||
link: "text-primary underline-offset-4 hover:underline",
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: "default",
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
function Badge({
|
||||
className,
|
||||
variant = "default",
|
||||
asChild = false,
|
||||
...props
|
||||
}: React.ComponentProps<"span"> &
|
||||
VariantProps<typeof badgeVariants> & { asChild?: boolean }) {
|
||||
const Comp = asChild ? Slot.Root : "span"
|
||||
|
||||
return (
|
||||
<Comp
|
||||
data-slot="badge"
|
||||
data-variant={variant}
|
||||
className={cn(badgeVariants({ variant }), className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export { Badge, badgeVariants }
|
||||
@@ -1,6 +1,16 @@
|
||||
import { createFileRoute, redirect } from "@tanstack/react-router";
|
||||
import { createColumnHelper } from "@tanstack/react-table";
|
||||
import { format } from "date-fns-tz";
|
||||
import { useSocketRoom } from "@/hooks/socket.io.hook";
|
||||
import { authClient } from "@/lib/auth-client";
|
||||
import { Badge } from "../../components/ui/badge";
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipTrigger,
|
||||
} from "../../components/ui/tooltip";
|
||||
import LstTable from "../../lib/tableStuff/LstTable";
|
||||
import SearchableHeader from "../../lib/tableStuff/SearchableHeader";
|
||||
|
||||
export const Route = createFileRoute("/admin/logs")({
|
||||
beforeLoad: async ({ location }) => {
|
||||
@@ -37,117 +47,152 @@ interface LogEntry {
|
||||
[key: string]: any; // catch any extra fields
|
||||
}
|
||||
|
||||
function LevelBadge({ level }: { level: number }) {
|
||||
const config: Record<number, { label: string; className: string }> = {
|
||||
10: { label: "TRACE", className: "bg-gray-100 text-gray-600" },
|
||||
20: { label: "DEBUG", className: "bg-blue-100 text-blue-700" },
|
||||
30: { label: "INFO", className: "bg-green-100 text-green-700" },
|
||||
40: { label: "WARN", className: "bg-yellow-100 text-yellow-700" },
|
||||
50: { label: "ERROR", className: "bg-red-100 text-red-700" },
|
||||
60: { label: "FATAL", className: "bg-purple-100 text-purple-700" },
|
||||
function LevelBadge({ level }: { level: string }) {
|
||||
const config: Record<string, { label: string; className: string }> = {
|
||||
trace: { label: "TRACE", className: "bg-gray-100 text-gray-600" },
|
||||
debug: { label: "DEBUG", className: "bg-blue-50 text-blue-700" },
|
||||
info: { label: "INFO", className: "bg-green-50 text-green-700" },
|
||||
warn: { label: "WARN", className: "bg-yellow-100 text-yellow-700" },
|
||||
error: { label: "ERROR", className: "bg-red-50 text-red-700" },
|
||||
fatal: { label: "FATAL", className: "bg-purple-100 text-purple-700" },
|
||||
};
|
||||
|
||||
const { label, className } = config[level] ?? {
|
||||
label: String(level),
|
||||
className: "bg-gray-100",
|
||||
};
|
||||
const badge = config[level];
|
||||
|
||||
return (
|
||||
<span className={`px-2 py-0.5 rounded text-xs font-medium ${className}`}>
|
||||
{label}
|
||||
</span>
|
||||
<Badge
|
||||
className={`rounded px-2 py-0.5 text-xs font-medium ${
|
||||
badge?.className ?? "bg-gray-100 text-gray-600"
|
||||
}`}
|
||||
>
|
||||
{badge?.label ?? level}
|
||||
</Badge>
|
||||
);
|
||||
}
|
||||
|
||||
function RouteComponent() {
|
||||
const { data: logs, info: logsInfo } = useSocketRoom<LogEntry>("logs");
|
||||
//const { user } = Route.useRouteContext();
|
||||
//const router = useRouter();
|
||||
// const [logs, setLogs] = useState<LogEntry[]>([]);
|
||||
// const [logsInfo, setLogInfo] = useState(
|
||||
// "No logs yet — join the room to start receiving",
|
||||
// );
|
||||
const { data: logs } = useSocketRoom<LogEntry>("logs");
|
||||
const columnHelper = createColumnHelper<any>();
|
||||
const column = [
|
||||
columnHelper.accessor("createdAt", {
|
||||
header: ({ column }) => <SearchableHeader column={column} title="Time" />,
|
||||
filterFn: "includesString",
|
||||
cell: (i) => format(i.getValue(), "MM/dd/yyyy HH:mm:ss"),
|
||||
}),
|
||||
columnHelper.accessor("level", {
|
||||
header: ({ column }) => (
|
||||
<SearchableHeader column={column} title="Level" searchable={true} />
|
||||
),
|
||||
filterFn: "includesString",
|
||||
cell: (i) => <LevelBadge level={i.getValue()} />,
|
||||
}),
|
||||
columnHelper.accessor("module", {
|
||||
header: ({ column }) => (
|
||||
<SearchableHeader column={column} title="Module" />
|
||||
),
|
||||
filterFn: "includesString",
|
||||
cell: (i) => i.getValue(),
|
||||
}),
|
||||
columnHelper.accessor("subModule", {
|
||||
header: ({ column }) => (
|
||||
<SearchableHeader column={column} title="Submodule" />
|
||||
),
|
||||
filterFn: "includesString",
|
||||
cell: (i) => i.getValue(),
|
||||
}),
|
||||
columnHelper.accessor("hostname", {
|
||||
header: ({ column }) => (
|
||||
<SearchableHeader column={column} title="Client" />
|
||||
),
|
||||
filterFn: "includesString",
|
||||
cell: (i) => i.getValue(),
|
||||
}),
|
||||
columnHelper.accessor("message", {
|
||||
header: ({ column }) => (
|
||||
<SearchableHeader column={column} title="Message" />
|
||||
),
|
||||
filterFn: "includesString",
|
||||
cell: (i) => i.getValue(),
|
||||
}),
|
||||
columnHelper.accessor("stack", {
|
||||
header: ({ column }) => (
|
||||
<SearchableHeader column={column} title="Stack" />
|
||||
),
|
||||
filterFn: "includesString",
|
||||
cell: (i) => {
|
||||
const stack = i.row.original.stack;
|
||||
if (!stack) return <span className="text-muted-foreground">—</span>;
|
||||
|
||||
// useEffect(() => {
|
||||
// // Connect if not already connected
|
||||
// if (!socket.connected) {
|
||||
// socket.connect();
|
||||
// }
|
||||
|
||||
// socket.on("connect", () => {
|
||||
// socket.emit("join-room", "logs");
|
||||
// });
|
||||
|
||||
// socket.emit("join-room", "logs");
|
||||
// socket.on(
|
||||
// "room-update",
|
||||
// (data: { payloads: LogEntry[]; roomId: string }) => {
|
||||
// setLogs((prev) => [...data.payloads, ...prev]);
|
||||
// },
|
||||
// );
|
||||
|
||||
// socket.on("room-error", (data) => {
|
||||
// setLogInfo(data.message);
|
||||
// });
|
||||
|
||||
// // socket.on("logs", (data) => {
|
||||
// // console.log(data);
|
||||
// // setLogs((prev) => [...data.payloads, ...prev]);
|
||||
// // });
|
||||
|
||||
// // Cleanup listeners on unmount
|
||||
// return () => {
|
||||
// socket.emit("leave-room", "logs");
|
||||
// socket.off("room-update");
|
||||
// socket.off("room-error");
|
||||
// socket.off("logs");
|
||||
// };
|
||||
// }, []);
|
||||
return (
|
||||
<Tooltip>
|
||||
<TooltipTrigger>
|
||||
<button
|
||||
type="button"
|
||||
className="max-w-[350px] truncate text-left font-mono text-xs text-muted-foreground hover:text-foreground"
|
||||
>
|
||||
{stack.length !== 0 ? "Hover to see stack error" : "-"}
|
||||
</button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent
|
||||
side="left"
|
||||
align="start"
|
||||
className="max-w-[900px] bg-zinc-950 text-zinc-50"
|
||||
>
|
||||
<pre className="max-h-[500px] overflow-auto whitespace-pre-wrap rounded-md p-3 text-xs leading-relaxed">
|
||||
<code>{JSON.stringify(stack, null, 2)}</code>
|
||||
</pre>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
);
|
||||
},
|
||||
}),
|
||||
];
|
||||
return (
|
||||
<div>
|
||||
{/* Log Table */}
|
||||
<div className="rounded border overflow-auto max-h-[600px]">
|
||||
<table className="w-full text-sm">
|
||||
<thead className="bg-muted sticky top-0">
|
||||
<tr>
|
||||
<th className="text-left px-3 py-2">Time</th>
|
||||
<th className="text-left px-3 py-2">Level</th>
|
||||
<th className="text-left px-3 py-2">Module</th>
|
||||
<th className="text-left px-3 py-2">Host</th>
|
||||
<th className="text-left px-3 py-2">Message</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{logs.length === 0 ? (
|
||||
<tr>
|
||||
<td
|
||||
colSpan={6}
|
||||
className="text-center py-6 text-muted-foreground"
|
||||
>
|
||||
{logsInfo}
|
||||
</td>
|
||||
</tr>
|
||||
) : (
|
||||
logs.map((log, i) => (
|
||||
<tr
|
||||
key={`${log.id}-${i}`}
|
||||
className="border-t hover:bg-muted/50"
|
||||
>
|
||||
<td className="px-3 py-2 whitespace-nowrap">
|
||||
{new Date(log.createdAt).toLocaleTimeString()}
|
||||
</td>
|
||||
<td className="px-3 py-2">
|
||||
<LevelBadge level={log.level} />
|
||||
</td>
|
||||
<td className="px-3 py-2">{log.module}</td>
|
||||
<td className="px-3 py-2">{log.hostname}</td>
|
||||
<td className="px-3 py-2 max-w-sm truncate">{log.message}</td>
|
||||
</tr>
|
||||
))
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
// <div>
|
||||
// {/* Log Table */}
|
||||
// <div className="rounded border overflow-auto max-h-[600px]">
|
||||
// <table className="w-full text-sm">
|
||||
// <thead className="bg-muted sticky top-0">
|
||||
// <tr>
|
||||
// <th className="text-left px-3 py-2">Time</th>
|
||||
// <th className="text-left px-3 py-2">Level</th>
|
||||
// <th className="text-left px-3 py-2">Module</th>
|
||||
// <th className="text-left px-3 py-2">Host</th>
|
||||
// <th className="text-left px-3 py-2">Message</th>
|
||||
// </tr>
|
||||
// </thead>
|
||||
// <tbody>
|
||||
// {logs.length === 0 ? (
|
||||
// <tr>
|
||||
// <td
|
||||
// colSpan={6}
|
||||
// className="text-center py-6 text-muted-foreground"
|
||||
// >
|
||||
// {logsInfo}
|
||||
// </td>
|
||||
// </tr>
|
||||
// ) : (
|
||||
// logs.map((log, i) => (
|
||||
// <tr
|
||||
// key={`${log.id}-${i}`}
|
||||
// className="border-t hover:bg-muted/50"
|
||||
// >
|
||||
// <td className="px-3 py-2 whitespace-nowrap">
|
||||
// {new Date(log.createdAt).toLocaleTimeString()}
|
||||
// </td>
|
||||
// <td className="px-3 py-2">
|
||||
// <LevelBadge level={log.level} />
|
||||
// </td>
|
||||
// <td className="px-3 py-2">{log.module}</td>
|
||||
// <td className="px-3 py-2">{log.hostname}</td>
|
||||
// <td className="px-3 py-2 max-w-sm truncate">{log.message}</td>
|
||||
// </tr>
|
||||
// ))
|
||||
// )}
|
||||
// </tbody>
|
||||
// </table>
|
||||
// </div>
|
||||
// </div>
|
||||
<LstTable data={logs} columns={column} pageSize={50} />
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user