- {Array.isArray(details)
- ? details.map((header) => {
- const [key, ...valueParts] = header.split(":");
- const value = valueParts.join(":").trim();
-
- // Check if this is a timestamp field we should enhance
- const keyLower = key.toLowerCase();
- const isTimeField = TIME_KEYWORDS.some((keyword) => keyLower.includes(keyword));
- const shouldEnhance = isTimeField && isTimeValue(value);
-
+ {typeof details === "object"
+ ? Object.entries(details).map((detail) => {
+ const [key, value] = detail;
return (
{key}
{value ? ":" : ""}
- {shouldEnhance ? (
-
-
-
- ) : (
- {value}
- )}
+ {value}
);
})
@@ -94,55 +60,3 @@ export const LogSection = ({
);
};
-
-const getFormattedContent = (details: string | string[]) => {
- if (Array.isArray(details)) {
- return details
- .map((header) => {
- const [key, ...valueParts] = header.split(":");
- const value = valueParts.join(":").trim();
- return `${key}: ${value}`;
- })
- .join("\n");
- }
- return details;
-};
-
-const isTimeValue = (value: string): boolean => {
- // Skip non-timestamp values
- if (
- value === "N/A" ||
- value === "Invalid Date" ||
- value.startsWith("Less than") ||
- /^\d+ (day|hour|minute|second)s?$/.test(value)
- ) {
- return false;
- }
-
- try {
- // Handle ISO format strings
- if (/^\d{4}-\d{2}-\d{2}/.test(value)) {
- return isValid(parseISO(value));
- }
-
- // Handle common localized formats
- if (/\d{1,2}\/\d{1,2}\/\d{4}/.test(value)) {
- // Try US format first: MM/DD/YYYY
- const datePart = value.split(",")[0];
- const parsedDate = parse(datePart, "M/d/yyyy", new Date());
- return isValid(parsedDate);
- }
-
- // Handle month name formats
- if (/[A-Za-z]{3}\s\d{1,2},\s\d{4}/.test(value)) {
- const parsedDate = parse(value, "MMM d, yyyy", new Date());
- return isValid(parsedDate);
- }
-
- // Fallback to standard JS date parsing
- const date = new Date(value);
- return !Number.isNaN(date.getTime());
- } catch {
- return false;
- }
-};
diff --git a/apps/dashboard/app/(app)/apis/[apiId]/_overview/components/table/components/log-details/components/roles-permissions.tsx b/apps/dashboard/app/(app)/apis/[apiId]/_overview/components/table/components/log-details/components/roles-permissions.tsx
index 86767718f0..f81baef930 100644
--- a/apps/dashboard/app/(app)/apis/[apiId]/_overview/components/table/components/log-details/components/roles-permissions.tsx
+++ b/apps/dashboard/app/(app)/apis/[apiId]/_overview/components/table/components/log-details/components/roles-permissions.tsx
@@ -85,7 +85,7 @@ type PermissionsSectionProps = {
permissions: Permission[];
};
-export const PermissionsSection: React.FC = ({ permissions }) => {
+export const PermissionsSection = ({ permissions }: PermissionsSectionProps) => {
const handleCopy = (permission: Permission) => {
const content = `${permission.name}${
permission.description ? `\n${permission.description}` : ""
diff --git a/apps/dashboard/app/(app)/apis/[apiId]/_overview/components/table/components/log-details/index.tsx b/apps/dashboard/app/(app)/apis/[apiId]/_overview/components/table/components/log-details/index.tsx
index 5856352a7d..78c729fed8 100644
--- a/apps/dashboard/app/(app)/apis/[apiId]/_overview/components/table/components/log-details/index.tsx
+++ b/apps/dashboard/app/(app)/apis/[apiId]/_overview/components/table/components/log-details/index.tsx
@@ -1,7 +1,9 @@
"use client";
import { DEFAULT_DRAGGABLE_WIDTH } from "@/app/(app)/logs/constants";
import { ResizablePanel } from "@/components/logs/details/resizable-panel";
+import { TimestampInfo } from "@/components/timestamp-info";
import type { KeysOverviewLog } from "@unkey/clickhouse/src/keys/keys";
+import Link from "next/link";
import { useMemo } from "react";
import { LogHeader } from "./components/log-header";
import { OutcomeDistributionSection } from "./components/log-outcome-distribution-section";
@@ -25,6 +27,7 @@ const createPanelStyle = (distanceToTop: number): StyleObject => ({
type KeysOverviewLogDetailsProps = {
distanceToTop: number;
log: KeysOverviewLog | null;
+ apiId: string;
setSelectedLog: (data: KeysOverviewLog | null) => void;
};
@@ -32,6 +35,7 @@ export const KeysOverviewLogDetails = ({
distanceToTop,
log,
setSelectedLog,
+ apiId,
}: KeysOverviewLogDetailsProps) => {
const panelStyle = useMemo(() => createPanelStyle(distanceToTop), [distanceToTop]);
@@ -59,32 +63,45 @@ export const KeysOverviewLogDetails = ({
// Process key details data
const metaData = formatMeta(log.key_details.meta);
- const createdAt = metaData?.createdAt
- ? formatDate(metaData.createdAt.replace(/3NZ$/, "3Z"))
- : "N/A";
-
- const identifiers = [`ID: ${log.key_details.id}`, `Name: ${log.key_details.name || "N/A"}`];
+ const identifiers = {
+ "Key ID": (
+
+ {log.key_id}
+
+ ),
+ Name: log.key_details.name || "N/A",
+ };
- const usage = [`Created: ${createdAt}`, `Last Used: ${log.time ? formatDate(log.time) : "N/A"}`];
+ const usage = {
+ Created: metaData?.createdAt ? metaData.createdAt : "N/A",
+ "Last Used": log.time ? (
+
+ ) : (
+ "N/A"
+ ),
+ };
- const limits = [
- `Status: ${log.key_details.enabled ? "Enabled" : "Disabled"}`,
- `Remaining: ${
- log.key_details.remaining_requests !== null ? log.key_details.remaining_requests : "Unlimited"
- }`,
- `Rate Limit: ${
- log.key_details.ratelimit_limit
- ? `${log.key_details.ratelimit_limit} per ${log.key_details.ratelimit_duration || "N/A"}s`
- : "No limit"
- }`,
- `Async: ${log.key_details.ratelimit_async ? "Yes" : "No"}`,
- ];
+ const limits = {
+ Status: log.key_details.enabled ? "Enabled" : "Disabled",
+ Remaining:
+ log.key_details.remaining_requests !== null
+ ? log.key_details.remaining_requests
+ : "Unlimited",
+ "Rate Limit": log.key_details.ratelimit_limit
+ ? `${log.key_details.ratelimit_limit} per ${log.key_details.ratelimit_duration || "N/A"}s`
+ : "No limit",
+ Async: log.key_details.ratelimit_async ? "Yes" : "No",
+ };
const identity = log.key_details.identity
- ? [`External ID: ${log.key_details.identity.external_id || "N/A"}`]
- : ["No identity connected"];
+ ? { "External ID": log.key_details.identity.external_id || "N/A" }
+ : { "No identity connected": null };
- const metaString = metaData ? JSON.stringify(metaData, null, 2) : "";
+ const metaString = metaData ? JSON.stringify(metaData, null, 2) : { "No meta available": "" };
return (
-
);
};
-const formatDate = (date: string | number | Date | null): string => {
- if (!date) {
- return "N/A";
- }
- try {
- if (date instanceof Date) {
- return date.toLocaleString();
- }
- return new Date(date).toLocaleString();
- } catch {
- return "Invalid Date";
- }
-};
-
const formatMeta = (meta: string | null): Record | null => {
if (!meta) {
return null;
diff --git a/apps/dashboard/app/(app)/apis/[apiId]/_overview/components/table/components/override-indicator.tsx b/apps/dashboard/app/(app)/apis/[apiId]/_overview/components/table/components/override-indicator.tsx
index 9a39d3b130..ff95f96a86 100644
--- a/apps/dashboard/app/(app)/apis/[apiId]/_overview/components/table/components/override-indicator.tsx
+++ b/apps/dashboard/app/(app)/apis/[apiId]/_overview/components/table/components/override-indicator.tsx
@@ -3,6 +3,7 @@ import { cn } from "@/lib/utils";
import type { KeysOverviewLog } from "@unkey/clickhouse/src/keys/keys";
import { TriangleWarning2 } from "@unkey/icons";
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@unkey/ui";
+import Link from "next/link";
import { getErrorPercentage, getErrorSeverity } from "../utils/calculate-blocked-percentage";
export const KeyTooltip = ({
@@ -29,6 +30,7 @@ export const KeyTooltip = ({
type KeyIdentifierColumnProps = {
log: KeysOverviewLog;
+ apiId: string;
};
// Get warning icon based on error severity
@@ -59,7 +61,7 @@ const getWarningMessage = (severity: string, errorRate: number) => {
}
};
-export const KeyIdentifierColumn = ({ log }: KeyIdentifierColumnProps) => {
+export const KeyIdentifierColumn = ({ log, apiId }: KeyIdentifierColumnProps) => {
const errorPercentage = getErrorPercentage(log);
const severity = getErrorSeverity(log);
const hasErrors = severity !== "none";
@@ -73,10 +75,16 @@ export const KeyIdentifierColumn = ({ log }: KeyIdentifierColumnProps) => {
{getWarningIcon(severity)}
-
- {log.key_id.substring(0, 8)}...
- {log.key_id.substring(log.key_id.length - 4)}
-
+
+
+ {log.key_id.substring(0, 8)}...
+ {log.key_id.substring(log.key_id.length - 4)}
+
+
);
};
diff --git a/apps/dashboard/app/(app)/apis/[apiId]/_overview/components/table/logs-table.tsx b/apps/dashboard/app/(app)/apis/[apiId]/_overview/components/table/logs-table.tsx
index 2ced272439..e7c5b0a59f 100644
--- a/apps/dashboard/app/(app)/apis/[apiId]/_overview/components/table/logs-table.tsx
+++ b/apps/dashboard/app/(app)/apis/[apiId]/_overview/components/table/logs-table.tsx
@@ -36,7 +36,7 @@ export const KeysOverviewLogsTable = ({ apiId, setSelectedLog, log: selectedLog
header: "ID",
width: "15%",
headerClassName: "pl-11",
- render: (log) => ,
+ render: (log) => ,
},
{
key: "name",
@@ -154,7 +154,7 @@ export const KeysOverviewLogsTable = ({ apiId, setSelectedLog, log: selectedLog
direction: getSortDirection("time"),
sortable: true,
onSort() {
- toggleSort("time", false);
+ toggleSort("time", false, "asc");
},
},
render: (log) => (
diff --git a/apps/dashboard/app/(app)/apis/[apiId]/_overview/logs-client.tsx b/apps/dashboard/app/(app)/apis/[apiId]/_overview/logs-client.tsx
index ad75503e23..6d72e5fb62 100644
--- a/apps/dashboard/app/(app)/apis/[apiId]/_overview/logs-client.tsx
+++ b/apps/dashboard/app/(app)/apis/[apiId]/_overview/logs-client.tsx
@@ -27,6 +27,7 @@ export const LogsClient = ({ apiId }: { apiId: string }) => {
(
diff --git a/apps/dashboard/components/logs/hooks/use-sort.tsx b/apps/dashboard/components/logs/hooks/use-sort.tsx
index c01bd710a5..66c29e480a 100644
--- a/apps/dashboard/components/logs/hooks/use-sort.tsx
+++ b/apps/dashboard/components/logs/hooks/use-sort.tsx
@@ -27,7 +27,7 @@ export function useSort(paramName = "sorts") {
);
const toggleSort = useCallback(
- (columnKey: TSortFields, multiSort = false) => {
+ (columnKey: TSortFields, multiSort = false, order: "asc" | "desc" = "desc") => {
const currentSort = sortParams?.find((sort) => sort.column === columnKey);
const otherSorts = sortParams?.filter((sort) => sort.column !== columnKey) ?? [];
let newSorts: SortUrlValue[];
@@ -35,8 +35,8 @@ export function useSort(paramName = "sorts") {
if (!currentSort) {
// Add new sort
newSorts = multiSort
- ? [...(sortParams ?? []), { column: columnKey, direction: "asc" }]
- : [{ column: columnKey, direction: "asc" }];
+ ? [...(sortParams ?? []), { column: columnKey, direction: order }]
+ : [{ column: columnKey, direction: order }];
} else if (currentSort.direction === "asc") {
// Toggle to desc
newSorts = multiSort
diff --git a/apps/dashboard/lib/trpc/routers/utils/granularity.ts b/apps/dashboard/lib/trpc/routers/utils/granularity.ts
index f58b86b60f..3b91d096b9 100644
--- a/apps/dashboard/lib/trpc/routers/utils/granularity.ts
+++ b/apps/dashboard/lib/trpc/routers/utils/granularity.ts
@@ -89,7 +89,7 @@ export const getTimeseriesGranularity = (
} else if (timeRange >= MONTH_IN_MS) {
granularity = "per3Days";
} else if (timeRange >= WEEK_IN_MS * 2) {
- granularity = "perHour";
+ granularity = "per6Hours";
} else if (timeRange >= WEEK_IN_MS) {
granularity = "perHour";
} else {