-
Notifications
You must be signed in to change notification settings - Fork 531
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* feat: add new layout for logs page * feat: add charts * fix: import paths * fix: colors * refactor: colors of timestamp info * feat: finish table rows * feat: fix log details to chart * refactor: clean up log details * feat: add new log details header section * refactor: improve feel of virtual table * feat: finalize log details * refactor: redundant null check * refactor: adjust texts * refactor: remove old virtual table and make it more readable * feat: add new chart tooltip * chore: fix build step * chore: run formatter * chore: run formatter * feat: update icons * fix: style issue due to virtual table changes * [autofix.ci] apply automated fixes * feat: add feature flog for logs-v2 --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
- Loading branch information
1 parent
8a1ccc3
commit 2cb3b62
Showing
46 changed files
with
2,419 additions
and
425 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
155 changes: 155 additions & 0 deletions
155
apps/dashboard/app/(app)/logs-v2/components/charts/index.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,155 @@ | ||
"use client"; | ||
import { | ||
type ChartConfig, | ||
ChartContainer, | ||
ChartTooltip, | ||
ChartTooltipContent, | ||
} from "@/components/ui/chart"; | ||
import { Grid } from "@unkey/icons"; | ||
import { addMinutes, format } from "date-fns"; | ||
import { useEffect, useRef } from "react"; | ||
import { Bar, BarChart, ResponsiveContainer, YAxis } from "recharts"; | ||
import { generateMockLogsData } from "./util"; | ||
|
||
const chartConfig = { | ||
success: { | ||
label: "Success", | ||
subLabel: "2xx", | ||
color: "hsl(var(--accent-4))", | ||
}, | ||
warning: { | ||
label: "Warning", | ||
subLabel: "4xx", | ||
color: "hsl(var(--warning-9))", | ||
}, | ||
error: { | ||
label: "Error", | ||
subLabel: "5xx", | ||
color: "hsl(var(--error-9))", | ||
}, | ||
} satisfies ChartConfig; | ||
|
||
const formatTimestampTooltip = (value: string | number) => { | ||
const date = new Date(value); | ||
const offset = new Date().getTimezoneOffset() * -1; | ||
const localDate = addMinutes(date, offset); | ||
return format(localDate, "MMM dd HH:mm:ss.SS aa"); | ||
}; | ||
|
||
const formatTimestampLabel = (timestamp: string | number | Date) => { | ||
const date = new Date(timestamp); | ||
return format(date, "MMM dd, h:mma").toUpperCase(); | ||
}; | ||
|
||
type Timeseries = { | ||
x: number; | ||
displayX: string; | ||
originalTimestamp: string; | ||
success: number; | ||
error: number; | ||
warning: number; | ||
total: number; | ||
}; | ||
|
||
const calculateTimePoints = (timeseries: Timeseries[]) => { | ||
const startTime = timeseries[0].x; | ||
const endTime = timeseries.at(-1)?.x; | ||
const timeRange = endTime ?? 0 - startTime; | ||
const timePoints = Array.from({ length: 5 }, (_, i) => { | ||
return new Date(startTime + (timeRange * i) / 5); | ||
}); | ||
return timePoints; | ||
}; | ||
|
||
const timeseries = generateMockLogsData(24, 10); | ||
|
||
export function LogsChart({ | ||
onMount, | ||
}: { | ||
onMount: (distanceToTop: number) => void; | ||
}) { | ||
const chartRef = useRef<HTMLDivElement>(null); | ||
|
||
useEffect(() => { | ||
const distanceToTop = chartRef.current?.getBoundingClientRect().top ?? 0; | ||
onMount(distanceToTop); | ||
}, [onMount]); | ||
|
||
return ( | ||
<div className="w-full relative" ref={chartRef}> | ||
<div className="px-2 text-accent-11 font-mono absolute top-0 text-xxs w-full flex justify-between"> | ||
{calculateTimePoints(timeseries).map((time, i) => ( | ||
// biome-ignore lint/suspicious/noArrayIndexKey: use of index is acceptable here. | ||
<div key={i}>{formatTimestampLabel(time)}</div> | ||
))} | ||
</div> | ||
<ResponsiveContainer width="100%" height={50} className="border-b border-gray-4"> | ||
<ChartContainer config={chartConfig}> | ||
<BarChart data={timeseries} barGap={2} margin={{ top: 0, right: 0, bottom: 0, left: 0 }}> | ||
<YAxis domain={[0, (dataMax: number) => dataMax * 1.5]} hide /> | ||
<ChartTooltip | ||
position={{ y: 50 }} | ||
isAnimationActive | ||
wrapperStyle={{ zIndex: 1000 }} | ||
cursor={{ | ||
fill: "hsl(var(--accent-3))", | ||
strokeWidth: 1, | ||
strokeDasharray: "5 5", | ||
strokeOpacity: 0.7, | ||
}} | ||
content={({ active, payload, label }) => { | ||
if (!active || !payload?.length) { | ||
return null; | ||
} | ||
|
||
return ( | ||
<ChartTooltipContent | ||
payload={payload} | ||
label={label} | ||
active={active} | ||
bottomExplainer={ | ||
<div className="grid gap-1.5 pt-2 border-t border-gray-4"> | ||
<div className="flex w-full [&>svg]:size-4 gap-4 px-4 items-center"> | ||
<Grid className="text-gray-6" /> | ||
<div className="flex gap-4 leading-none justify-between w-full py-1 items-center"> | ||
<div className="flex gap-4 items-center min-w-[80px]"> | ||
<span className="capitalize text-accent-9 text-xs w-[2ch] inline-block"> | ||
All | ||
</span> | ||
<span className="capitalize text-accent-12 text-xs">Total</span> | ||
</div> | ||
<div className="ml-auto"> | ||
<span className="font-mono tabular-nums text-accent-12"> | ||
{payload[0]?.payload?.total} | ||
</span> | ||
</div> | ||
</div> | ||
</div> | ||
</div> | ||
} | ||
className="rounded-lg shadow-lg border border-gray-4" | ||
labelFormatter={(_, tooltipPayload) => { | ||
const originalTimestamp = tooltipPayload[0]?.payload?.originalTimestamp; | ||
return originalTimestamp ? ( | ||
<div> | ||
<span className="font-mono text-accent-9 text-xs px-4"> | ||
{formatTimestampTooltip(originalTimestamp)} | ||
</span> | ||
</div> | ||
) : ( | ||
"" | ||
); | ||
}} | ||
/> | ||
); | ||
}} | ||
/> | ||
{["success", "error", "warning"].map((key) => ( | ||
<Bar key={key} dataKey={key} stackId="a" fill={`var(--color-${key})`} /> | ||
))} | ||
</BarChart> | ||
</ChartContainer> | ||
</ResponsiveContainer> | ||
</div> | ||
); | ||
} |
31 changes: 31 additions & 0 deletions
31
apps/dashboard/app/(app)/logs-v2/components/charts/util.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
/** | ||
* Generates mock time series data for logs visualization | ||
* @param hours Number of hours of data to generate | ||
* @param intervalMinutes Interval between data points in minutes | ||
* @returns Array of LogsTimeseriesDataPoint | ||
*/ | ||
export function generateMockLogsData(hours = 24, intervalMinutes = 5) { | ||
const now = new Date(); | ||
const points = Math.floor((hours * 60) / intervalMinutes); | ||
const data = []; | ||
|
||
for (let i = points - 1; i >= 0; i--) { | ||
const timestamp = new Date(now.getTime() - i * intervalMinutes * 60 * 1000); | ||
|
||
const success = Math.floor(Math.random() * 50) + 20; | ||
const error = Math.floor(Math.random() * 30); | ||
const warning = Math.floor(Math.random() * 25); | ||
|
||
data.push({ | ||
x: Math.floor(timestamp.getTime()), | ||
displayX: timestamp.toISOString(), | ||
originalTimestamp: timestamp.toISOString(), | ||
success, | ||
error, | ||
warning, | ||
total: success + error + warning, | ||
}); | ||
} | ||
|
||
return data; | ||
} |
46 changes: 46 additions & 0 deletions
46
apps/dashboard/app/(app)/logs-v2/components/filters/index.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
import { | ||
BarsFilter, | ||
Calendar, | ||
CircleCarretRight, | ||
Magnifier, | ||
Refresh3, | ||
Sliders, | ||
} from "@unkey/icons"; | ||
|
||
export function LogsFilters() { | ||
return ( | ||
<div className="flex flex-col border-b border-gray-4 "> | ||
<div className="px-3 py-2 w-full justify-between flex items-center min-h-10"> | ||
<div className="flex gap-2"> | ||
<div className="flex gap-2 items-center px-2"> | ||
<Magnifier className="text-accent-9 size-4" /> | ||
<span className="text-accent-12 font-medium text-[13px]">Search logs...</span> | ||
</div> | ||
<div className="flex gap-2 items-center px-2"> | ||
<BarsFilter className="text-accent-9 size-4" /> | ||
<span className="text-accent-12 font-medium text-[13px]">Filter</span> | ||
</div> | ||
<div className="flex gap-2 items-center px-2"> | ||
<Calendar className="text-accent-9 size-4" /> | ||
<span className="text-accent-12 font-medium text-[13px]">Last 24 hours</span> | ||
</div> | ||
</div> | ||
|
||
<div className="flex gap-2"> | ||
<div className="flex gap-2 items-center px-2"> | ||
<CircleCarretRight className="text-accent-9 size-4" /> | ||
<span className="text-accent-12 font-medium text-[13px]">Live</span> | ||
</div> | ||
<div className="flex gap-2 items-center px-2"> | ||
<Refresh3 className="text-accent-9 size-4" /> | ||
<span className="text-accent-12 font-medium text-[13px]">Refresh</span> | ||
</div> | ||
<div className="flex gap-2 items-center px-2"> | ||
<Sliders className="text-accent-9 size-4" /> | ||
<span className="text-accent-12 font-medium text-[13px]">Display</span> | ||
</div> | ||
</div> | ||
</div> | ||
</div> | ||
); | ||
} |
34 changes: 34 additions & 0 deletions
34
apps/dashboard/app/(app)/logs-v2/components/logs-client.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
"use client"; | ||
|
||
import type { Log } from "@unkey/clickhouse/src/logs"; | ||
import { useCallback, useState } from "react"; | ||
import { LogsChart } from "./charts"; | ||
import { LogsFilters } from "./filters"; | ||
import { LogDetails } from "./table/log-details"; | ||
import { LogsTable } from "./table/logs-table"; | ||
|
||
export const LogsClient = () => { | ||
const [selectedLog, setSelectedLog] = useState<Log | null>(null); | ||
const [tableDistanceToTop, setTableDistanceToTop] = useState(0); | ||
|
||
const handleDistanceToTop = useCallback((distanceToTop: number) => { | ||
setTableDistanceToTop(distanceToTop); | ||
}, []); | ||
|
||
const handleLogSelection = useCallback((log: Log | null) => { | ||
setSelectedLog(log); | ||
}, []); | ||
|
||
return ( | ||
<> | ||
<LogsFilters /> | ||
<LogsChart onMount={handleDistanceToTop} /> | ||
<LogsTable onLogSelect={handleLogSelection} selectedLog={selectedLog} /> | ||
<LogDetails | ||
log={selectedLog} | ||
onClose={() => handleLogSelection(null)} | ||
distanceToTop={tableDistanceToTop} | ||
/> | ||
</> | ||
); | ||
}; |
Oops, something went wrong.