feat(logs): improvements based on feedback

This commit is contained in:
Nicholas Penree
2024-12-11 19:13:53 -05:00
parent 50b1de9594
commit 42f3105f69
7 changed files with 511 additions and 491 deletions

View File

@@ -1,9 +1,6 @@
import { Input } from "@/components/ui/input";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import React, { useEffect, useRef } from "react";
import { getLogType, LogLine, parseLogs } from "./utils";
import { TerminalLine } from "./terminal-line";
import { Download as DownloadIcon } from "lucide-react";
import { Input } from "@/components/ui/input";
import {
Select,
SelectContent,
@@ -11,8 +8,11 @@ import {
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { Badge } from "@/components/ui/badge";
import { api } from "@/utils/api";
import { Download as DownloadIcon } from "lucide-react";
import React, { useEffect, useRef } from "react";
import { TerminalLine } from "./terminal-line";
import { type LogLine, getLogType, parseLogs } from "./utils";
interface Props {
containerId: string;
@@ -30,7 +30,7 @@ export const DockerLogsId: React.FC<Props> = ({ containerId, serverId }) => {
},
{
enabled: !!containerId,
}
},
);
const [rawLogs, setRawLogs] = React.useState("");
@@ -132,7 +132,7 @@ export const DockerLogsId: React.FC<Props> = ({ containerId, serverId }) => {
const logContent = filteredLogs
.map(
({ timestamp, message }: { timestamp: Date | null; message: string }) =>
`${timestamp?.toISOString() || "No timestamp"} ${message}`
`${timestamp?.toISOString() || "No timestamp"} ${message}`,
)
.join("\n");
@@ -190,19 +190,13 @@ export const DockerLogsId: React.FC<Props> = ({ containerId, serverId }) => {
onChange={handleLines}
className="inline-flex h-9 text-sm text-white placeholder-gray-400 w-full sm:w-auto"
/>
<Input
type="search"
placeholder="Search logs..."
value={search}
onChange={handleSearch}
className="inline-flex h-9 text-sm text-white placeholder-gray-400 w-full sm:w-auto"
/>
<Select value={since} onValueChange={handleSince}>
<SelectTrigger className="w-[180px] h-9">
<SelectValue placeholder="Time filter" />
</SelectTrigger>
<SelectContent>
<SelectItem value="1h">Last 1 hour</SelectItem>
<SelectItem value="1h">Last hour</SelectItem>
<SelectItem value="6h">Last 6 hours</SelectItem>
<SelectItem value="24h">Last 24 hours</SelectItem>
<SelectItem value="168h">Last 7 days</SelectItem>
@@ -233,6 +227,14 @@ export const DockerLogsId: React.FC<Props> = ({ containerId, serverId }) => {
</SelectItem>
</SelectContent>
</Select>
<Input
type="search"
placeholder="Search logs..."
value={search}
onChange={handleSearch}
className="inline-flex h-9 text-sm text-white placeholder-gray-400 w-full sm:w-auto"
/>
</div>
<Button

View File

@@ -46,10 +46,7 @@ export const ShowDockerModalLogs = ({
<DialogDescription>View the logs for {containerId}</DialogDescription>
</DialogHeader>
<div className="flex flex-col gap-4 pt-2.5">
<DockerLogsId
containerId={containerId || ""}
serverId={serverId}
/>
<DockerLogsId containerId={containerId || ""} serverId={serverId} />
</div>
</DialogContent>
</Dialog>

View File

@@ -1,8 +1,14 @@
import { Badge } from "@/components/ui/badge";
import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from "@/components/ui/tooltip";
import { cn } from "@/lib/utils";
import { getLogType, type LogLine } from "./utils";
import { escapeRegExp } from "lodash";
import React from "react";
import { escapeRegExp } from 'lodash';
import { type LogLine, getLogType } from "./utils";
interface LogLineProps {
log: LogLine;
@@ -10,18 +16,17 @@ interface LogLineProps {
}
export function TerminalLine({ log, searchTerm }: LogLineProps) {
const { timestamp, message } = log;
const { timestamp, message, rawTimestamp } = log;
const { type, variant, color } = getLogType(message);
const formattedTime = timestamp
? timestamp.toLocaleString("en-GB", {
? timestamp.toLocaleString([], {
month: "2-digit",
day: "2-digit",
month: "short",
year: "numeric",
hour: "2-digit",
minute: "2-digit",
year: "2-digit",
second: "2-digit",
hour12: false,
})
: "--- No time found ---";
@@ -36,7 +41,26 @@ export function TerminalLine({ log, searchTerm }: LogLineProps) {
</span>
) : (
part
)
),
);
};
const tooltip = (color: string, timestamp: string) => {
return (
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<div
className={cn("w-2 h-full flex-shrink-0 rounded-[3px]", color)}
/>
</TooltipTrigger>
<TooltipContent>
<p className="text text-xs text-muted-foreground break-all max-w-md">
{timestamp}
</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
);
};
@@ -48,14 +72,14 @@ export function TerminalLine({ log, searchTerm }: LogLineProps) {
? "bg-red-500/10 hover:bg-red-500/15"
: type === "warning"
? "bg-yellow-500/10 hover:bg-yellow-500/15"
: "hover:bg-gray-200/50 dark:hover:bg-gray-800/50"
: "hover:bg-gray-200/50 dark:hover:bg-gray-800/50",
)}
>
{" "}
<div className="flex items-start gap-x-2">
{/* Icon to expand the log item maybe implement a colapsible later */}
{/* <Square className="size-4 text-muted-foreground opacity-0 group-hover/logitem:opacity-100 transition-opacity" /> */}
<div className={cn("w-2 h-full flex-shrink-0 rounded-[3px]", color)} />
{rawTimestamp && tooltip(color, rawTimestamp)}
<span className="select-none pl-2 text-muted-foreground w-full sm:w-40 flex-shrink-0">
{formattedTime}
</span>

View File

@@ -2,6 +2,7 @@ export type LogType = "error" | "warning" | "success" | "info";
export type LogVariant = "red" | "yellow" | "green" | "blue";
export interface LogLine {
rawTimestamp: string | null;
timestamp: Date | null;
message: string;
}
@@ -61,11 +62,12 @@ export function parseLogs(logString: string): LogLine[] {
const cleanedMessage = message
?.replace(
/\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?Z|\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\.\d{3} UTC/g,
""
"",
)
.trim();
return {
rawTimestamp: timestamp,
timestamp: timestamp ? new Date(timestamp.replace(" UTC", "Z")) : null,
message: cleanedMessage,
};
@@ -107,7 +109,7 @@ export const getLogType = (message: string): LogStyle => {
if (
/(?:successfully|complete[d]?)\s+(?:initialized|started|completed|done)/i.test(
lowerMessage
lowerMessage,
) ||
/\[(?:success|ok|done)\]/i.test(lowerMessage) ||
/(?:listening|running)\s+(?:on|at)\s+(?:port\s+)?\d+/i.test(lowerMessage) ||

View File

@@ -91,10 +91,7 @@ export const ShowModalLogs = ({ appName, children, serverId }: Props) => {
</SelectGroup>
</SelectContent>
</Select>
<DockerLogsId
containerId={containerId || ""}
serverId={serverId}
/>
<DockerLogsId containerId={containerId || ""} serverId={serverId} />
</div>
</DialogContent>
</Dialog>

View File

@@ -14,16 +14,14 @@ const badgeVariants = cva(
"border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",
destructive:
"border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80",
red:
"border-transparent select-none items-center whitespace-nowrap font-medium bg-red-500/15 text-destructive text-xs h-4 px-1 py-1 rounded-md",
red: "border-transparent select-none items-center whitespace-nowrap font-medium bg-red-500/15 text-destructive text-xs h-4 px-1 py-1 rounded-md",
yellow:
"border-transparent select-none items-center whitespace-nowrap font-medium bg-yellow-500/15 text-yellow-500 text-xs h-4 px-1 py-1 rounded-md",
orange:
"border-transparent select-none items-center whitespace-nowrap font-medium bg-orange-500/15 text-orange-500 text-xs h-4 px-1 py-1 rounded-md",
green:
"border-transparent select-none items-center whitespace-nowrap font-medium bg-emerald-500/15 text-emerald-500 text-xs h-4 px-1 py-1 rounded-md",
blue:
"border-transparent select-none items-center whitespace-nowrap font-medium bg-blue-500/15 text-blue-500 text-xs h-4 px-1 py-1 rounded-md",
blue: "border-transparent select-none items-center whitespace-nowrap font-medium bg-blue-500/15 text-blue-500 text-xs h-4 px-1 py-1 rounded-md",
blank:
"border-transparent select-none items-center whitespace-nowrap font-medium dark:bg-white/15 bg-black/15 text-foreground text-xs h-4 px-1 py-1 rounded-md",
outline: "text-foreground",