Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
f2f097e
feat: add init lgos
ogzhanolguncu Sep 18, 2025
b429b4c
feat: fix prefixes and filters
ogzhanolguncu Sep 18, 2025
ebccfd3
fix: add animated logdetails
ogzhanolguncu Sep 18, 2025
e201e77
refactor: get rid of duplicated components
ogzhanolguncu Sep 18, 2025
2e94f1f
fix: colors
ogzhanolguncu Sep 18, 2025
d5584b8
refactor: get rid of unsued
ogzhanolguncu Sep 18, 2025
3ed66b0
refactor: use common component
ogzhanolguncu Sep 18, 2025
144ffc5
Merge branch 'main' into feat/gateway-logs
ogzhanolguncu Sep 18, 2025
580f9e6
fix: live switch
ogzhanolguncu Sep 18, 2025
5efaf68
Merge branch 'main' of github.com:unkeyed/unkey into feat/gateway-logs
ogzhanolguncu Sep 22, 2025
6afe351
fix: table read source
ogzhanolguncu Sep 22, 2025
fa06c31
refactor: use common schema for logs
ogzhanolguncu Sep 22, 2025
38b4875
refactor: use same filters
ogzhanolguncu Sep 22, 2025
c3c5b95
refactor: make logs denser
ogzhanolguncu Sep 22, 2025
275d209
fix: make it denser
ogzhanolguncu Sep 22, 2025
a4c9765
fix: add missing hostname to table
ogzhanolguncu Sep 22, 2025
c1c29d4
refactor: table columns and text contrasts
ogzhanolguncu Sep 22, 2025
b06fb52
fix: memo issue and add gateway to logs to deployments
ogzhanolguncu Sep 22, 2025
9e9a9b6
fix: color inconsistency
ogzhanolguncu Sep 22, 2025
959a913
Merge branch 'main' into feat/gateway-logs
chronark Sep 23, 2025
63f0c1e
fix: test and add tooltip
ogzhanolguncu Sep 23, 2025
525669e
fix: border-color
ogzhanolguncu Sep 23, 2025
c10f881
fix: missing params
ogzhanolguncu Sep 23, 2025
1f9c975
fix: truncate long req and resp body
ogzhanolguncu Sep 23, 2025
941eedc
fix: comments
ogzhanolguncu Sep 23, 2025
de2a298
Merge branch 'main' of github.com:unkeyed/unkey into feat/gateway-logs
ogzhanolguncu Sep 24, 2025
96408a4
fix: props
ogzhanolguncu Sep 24, 2025
718c682
fix: conflict
ogzhanolguncu Sep 24, 2025
7ef9430
fix: small ui issue
ogzhanolguncu Sep 24, 2025
989197b
fix: make rollback and promote consistent
ogzhanolguncu Sep 24, 2025
453049a
fix: remove all animated props
ogzhanolguncu Sep 25, 2025
739b33a
Merge branch 'main' into feat/gateway-logs
ogzhanolguncu Sep 25, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,83 +1,57 @@
"use client";
import { DEFAULT_DRAGGABLE_WIDTH } from "@/app/(app)/logs/constants";
import { ResizablePanel } from "@/components/logs/details/resizable-panel";
import { LogDetails } from "@/components/logs/details/log-details";
import type { KeysOverviewLog } from "@unkey/clickhouse/src/keys/keys";
import { TimestampInfo } from "@unkey/ui";
import { TimestampInfo, toast } from "@unkey/ui";
import Link from "next/link";
import { useMemo } from "react";
import { useEffect, useState } from "react";
import { LogHeader } from "./components/log-header";
import { OutcomeDistributionSection } from "./components/log-outcome-distribution-section";
import { LogSection } from "./components/log-section";
import { PermissionsSection, RolesSection } from "./components/roles-permissions";

type StyleObject = {
top: string;
width: string;
height: string;
paddingBottom: string;
};

const createPanelStyle = (distanceToTop: number): StyleObject => ({
top: `${distanceToTop}px`,
width: `${DEFAULT_DRAGGABLE_WIDTH}px`,
height: `calc(100vh - ${distanceToTop}px)`,
paddingBottom: "1rem",
});
const ANIMATION_DELAY = 350;

type KeysOverviewLogDetailsProps = {
type Props = {
distanceToTop: number;
log: KeysOverviewLog | null;
apiId: string;
setSelectedLog: (data: KeysOverviewLog | null) => void;
};

export const KeysOverviewLogDetails = ({
distanceToTop,
log,
setSelectedLog,
apiId,
}: KeysOverviewLogDetailsProps) => {
const panelStyle = useMemo(() => createPanelStyle(distanceToTop), [distanceToTop]);
export const KeysOverviewLogDetails = ({ distanceToTop, log, setSelectedLog, apiId }: Props) => {
const [errorShown, setErrorShown] = useState(false);

if (!log) {
return null;
}
useEffect(() => {
if (!errorShown && log) {
if (!log.key_details) {
toast.error("Key Details Unavailable", {
description:
"Could not retrieve key information for this log. The key may have been deleted or is still processing.",
});
setErrorShown(true);
}
}
if (!log) {
setErrorShown(false);
}
}, [log, errorShown]);

const handleClose = (): void => {
const handleClose = () => {
setSelectedLog(null);
};

// Only process if key_details exists
if (!log) {
return null;
}

if (!log.key_details) {
return (
<ResizablePanel
onClose={handleClose}
className="absolute right-0 bg-gray-1 dark:bg-black font-mono drop-shadow-2xl overflow-y-auto z-20 p-4"
style={panelStyle}
>
<LogHeader log={log} onClose={handleClose} />
<div className="py-4 text-center text-accent-9">No key details available</div>
</ResizablePanel>
);
return null;
}

// Process key details data
const metaData = formatMeta(log.key_details.meta);
const identifiers = {
"Key ID": (
<Link
title={`View details for ${log.key_id}`}
className="font-mono underline decoration-dotted"
href={`/apis/${apiId}/keys/${log.key_details?.key_auth_id}/${log.key_id}`}
>
<div className="font-mono font-medium truncate">{log.key_id}</div>
</Link>
),
Name: log.key_details.name || "N/A",
};

const usage = {
Created: metaData?.createdAt ? metaData.createdAt : "N/A",
Created: metaData?.createdAt || "N/A",
"Last Used": log.time ? (
<TimestampInfo value={log.time} className="font-mono underline decoration-dotted" />
) : (
Expand All @@ -93,32 +67,50 @@ export const KeysOverviewLogDetails = ({
: "Unlimited",
};

const tags =
log.tags && log.tags.length > 0 ? { Tags: log.tags.join(", ") } : { "No tags": null };
const identifiers = {
"Key ID": (
<Link
title={`View details for ${log.key_id}`}
className="font-mono underline decoration-dotted"
href={`/apis/${apiId}/keys/${log.key_details.key_auth_id}/${log.key_id}`}
>
<div className="font-mono font-medium truncate">{log.key_id}</div>
</Link>
),
Name: log.key_details.name || "N/A",
};

const identity = log.key_details.identity
? { "External ID": log.key_details.identity.external_id || "N/A" }
: { "No identity connected": null };

const metaString = metaData ? JSON.stringify(metaData, null, 2) : { "No meta available": "" };
const tags =
log.tags && log.tags.length > 0 ? { Tags: log.tags.join(", ") } : { "No tags": null };

const sections = [
<LogSection key="usage" title="Usage" details={usage} />,
log.outcome_counts && (
<OutcomeDistributionSection key="outcomes" outcomeCounts={log.outcome_counts} />
),
<LogSection key="limits" title="Limits" details={limits} />,
<LogSection key="identifiers" title="Identifiers" details={identifiers} />,
<LogSection key="identity" title="Identity" details={identity} />,
<LogSection key="tags" title="Tags" details={tags} />,
<RolesSection key="roles" roles={log.key_details.roles || []} />,
<PermissionsSection key="permissions" permissions={log.key_details.permissions || []} />,
].filter(Boolean);

return (
<ResizablePanel
onClose={handleClose}
className="absolute max-md:!h-screen max-md:!w-full max-md:!top-0 right-0 bg-gray-1 dark:bg-black font-mono shadow-2xl overflow-y-auto z-20 p-4"
style={panelStyle}
>
<LogHeader log={log} onClose={handleClose} />
<LogSection title="Usage" details={usage} />
{log.outcome_counts && <OutcomeDistributionSection outcomeCounts={log.outcome_counts} />}
<LogSection title="Limits" details={limits} />
<LogSection title="Identifiers" details={identifiers} />
<LogSection title="Identity" details={identity} />
<LogSection title="Tags" details={tags} />
<RolesSection roles={log.key_details.roles || []} />
<PermissionsSection permissions={log.key_details.permissions || []} />
<LogSection title="Meta" details={metaString} />
</ResizablePanel>
<LogDetails distanceToTop={distanceToTop} log={log} onClose={handleClose}>
<LogDetails.Header onClose={handleClose}>
<LogHeader log={log} onClose={handleClose} />
</LogDetails.Header>
<LogDetails.CustomSections startDelay={150} staggerDelay={50}>
{sections}
</LogDetails.CustomSections>
<LogDetails.Spacer delay={ANIMATION_DELAY} />
<LogDetails.Meta />
</LogDetails>
);
};

Expand Down
Original file line number Diff line number Diff line change
@@ -1,31 +1,19 @@
"use client";
import { ResizablePanel } from "@/components/logs/details/resizable-panel";
import { useEffect, useMemo, useState } from "react";
import { useEffect, useState } from "react";

import { LogFooter } from "@/app/(app)/logs/components/table/log-details/components/log-footer";
import { LogHeader } from "@/app/(app)/logs/components/table/log-details/components/log-header";
import { LogSection } from "@/app/(app)/logs/components/table/log-details/components/log-section";
import { DEFAULT_DRAGGABLE_WIDTH } from "@/app/(app)/logs/constants";
import { safeParseJson } from "@/app/(app)/logs/utils";
import { LogDetails } from "@/components/logs/details/log-details";
import type { KeyDetailsLog } from "@unkey/clickhouse/src/verifications";
import { toast } from "@unkey/ui";
import { useFetchRequestDetails } from "./components/hooks/use-logs-query";

const createPanelStyle = (distanceToTop: number) => ({
top: `${distanceToTop}px`,
width: `${DEFAULT_DRAGGABLE_WIDTH}px`,
height: `calc(100vh - ${distanceToTop}px)`,
paddingBottom: "1rem",
});

const ANIMATION_DELAY = 350;
type Props = {
distanceToTop: number;
selectedLog: KeyDetailsLog | null;
onLogSelect: (log: KeyDetailsLog | null) => void;
};

export const KeyDetailsDrawer = ({ distanceToTop, onLogSelect, selectedLog }: Props) => {
const panelStyle = useMemo(() => createPanelStyle(distanceToTop), [distanceToTop]);
const { log, error } = useFetchRequestDetails({
requestId: selectedLog?.request_id,
});
Expand Down Expand Up @@ -69,38 +57,11 @@ export const KeyDetailsDrawer = ({ distanceToTop, onLogSelect, selectedLog }: Pr
}

return (
<ResizablePanel
onClose={handleClose}
className="absolute right-0 bg-gray-1 dark:bg-black font-mono drop-shadow-2xl overflow-y-auto z-20 p-4"
style={panelStyle}
>
<LogHeader log={log} onClose={handleClose} />
<LogSection
details={log.request_headers.length ? log.request_headers : "<EMPTY>"}
title="Request Header"
/>
<LogSection
details={
JSON.stringify(safeParseJson(log.request_body), null, 2) === "null"
? "<EMPTY>"
: JSON.stringify(safeParseJson(log.request_body), null, 2)
}
title="Request Body"
/>
<LogSection
details={log.response_headers.length ? log.response_headers : "<EMPTY>"}
title="Response Header"
/>
<LogSection
details={
JSON.stringify(safeParseJson(log.response_body), null, 2) === "null"
? "<EMPTY>"
: JSON.stringify(safeParseJson(log.response_body), null, 2)
}
title="Response Body"
/>
<div className="mt-3" />
<LogFooter log={log} />
</ResizablePanel>
<LogDetails distanceToTop={distanceToTop} log={log} onClose={handleClose}>
<LogDetails.Header onClose={handleClose} />
<LogDetails.Sections />
<LogDetails.Spacer delay={ANIMATION_DELAY} />
<LogDetails.Footer />
</LogDetails>
);
};
Original file line number Diff line number Diff line change
@@ -1,18 +1,17 @@
import { formatTimestampForChart } from "@/components/logs/chart/utils/format-timestamp";
import { HISTORICAL_DATA_WINDOW } from "@/components/logs/constants";
import type { TimeseriesRequestSchema } from "@/lib/schemas/logs.schema";
import { trpc } from "@/lib/trpc/client";
import { useQueryTime } from "@/providers/query-time-provider";
import { useMemo } from "react";
import type { z } from "zod";
import { useFilters } from "../../../hooks/use-filters";
import type { queryTimeseriesPayload } from "../query-timeseries.schema";

export const useFetchTimeseries = () => {
const { filters } = useFilters();

const { queryTime: timestamp } = useQueryTime();
const queryParams = useMemo(() => {
const params: z.infer<typeof queryTimeseriesPayload> = {
const params: TimeseriesRequestSchema = {
startTime: timestamp - HISTORICAL_DATA_WINDOW,
endTime: timestamp,
host: { filters: [] },
Expand Down Expand Up @@ -61,7 +60,7 @@ export const useFetchTimeseries = () => {
console.error("Host filter value type has to be 'string'");
return;
}
params.host?.filters.push({
params.host?.filters?.push({
operator: "is",
value: filter.value,
});
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { logsFilterFieldConfig } from "@/app/(app)/logs/filters.schema";
import { useFilters } from "@/app/(app)/logs/hooks/use-filters";
import { FilterOperatorInput } from "@/components/logs/filter-operator-input";
import { logsFilterFieldConfig } from "@/lib/schemas/logs.filter.schema";

export const PathsFilter = () => {
const { filters, updateFilters } = useFilters();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { QuerySearchParams } from "@/app/(app)/logs/filters.schema";
import { iconsPerField } from "@/components/logs/queries/utils";
import type { QuerySearchParams } from "@/lib/schemas/logs.filter.schema";
import { ChartActivity2 } from "@unkey/icons";
import { format } from "date-fns";
import React from "react";
Expand Down Expand Up @@ -90,7 +90,10 @@ export function formatFilterValues(

export function getFilterFieldIcon(field: string): JSX.Element {
const Icon = iconsPerField[field] || ChartActivity2;
return React.createElement(Icon, { size: "md-regular", className: "justify-center" });
return React.createElement(Icon, {
size: "md-regular",
className: "justify-center",
});
}

export const FieldsToTruncate = [
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
import { HISTORICAL_DATA_WINDOW } from "@/components/logs/constants";
import type { LogsRequestSchema } from "@/lib/schemas/logs.schema";
import { trpc } from "@/lib/trpc/client";
import { useQueryTime } from "@/providers/query-time-provider";
import type { Log } from "@unkey/clickhouse/src/logs";
import { useCallback, useEffect, useMemo, useState } from "react";
import type { z } from "zod";
import { useFilters } from "../../../hooks/use-filters";
import type { queryLogsPayload } from "../query-logs.schema";

// Duration in milliseconds for historical data fetch window (12 hours)
type UseLogsQueryParams = {
Expand Down Expand Up @@ -37,7 +36,7 @@ export function useLogsQuery({

//Required for preventing double trpc call during initial render
const queryParams = useMemo(() => {
const params: z.infer<typeof queryLogsPayload> = {
const params: LogsRequestSchema = {
limit,
startTime: timestamp - HISTORICAL_DATA_WINDOW,
endTime: timestamp,
Expand Down Expand Up @@ -88,7 +87,7 @@ export function useLogsQuery({
console.error("Host filter value type has to be 'string'");
return;
}
params.host?.filters.push({
params.host?.filters?.push({
operator: "is",
value: filter.value,
});
Expand Down
Loading