Skip to content

Commit

Permalink
fix(dashboard): "No bucket found" message position on Audit Logs page (
Browse files Browse the repository at this point in the history
…#2413)

* fix(dashboard): move "bucket not found" placeholder under Audit Logs filters

* fix(dashboard): filter Audit Logs by active keys and rate limits

* fix(dashboard): resolve hydration error by using consistent date formatting

Replace `toLocaleDateString()` with `date-fns` to ensure consistent date formatting between server and client. This prevents hydration mismatches caused by differing time zones or locale settings, ensuring stable rendering across environments.

* refactor(dashboard): extract audit log mapping logic for improved readability

The mapping logic within the <AuditLogTable> component is complex due to nested structures. Extracting this logic into a separate function or utility enhances readability and maintainability.

* [autofix.ci] apply automated fixes

* fix(dashboard): silence hydration mismatch warning in audit bucket row

Revert static datetime formatting using `date-fns` to `toLocaleDateString()` to better suit global users. Suppress the hydration mismatch warnings caused by differences between server and client-side rendered dates.

Co-authored-by: @chronark
PR: #2413

* [autofix.ci] apply automated fixes

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: Andreas Thomas <[email protected]>
  • Loading branch information
3 people authored Oct 28, 2024
1 parent bbe8a7f commit b652852
Show file tree
Hide file tree
Showing 3 changed files with 57 additions and 40 deletions.
88 changes: 51 additions & 37 deletions apps/dashboard/app/(app)/audit/[bucket]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { getTenantId } from "@/lib/auth";
import { db } from "@/lib/db";
import { clerkClient } from "@clerk/nextjs";
import type { User } from "@clerk/nextjs/server";
import type { SelectAuditLog, SelectAuditLogTarget } from "@unkey/db/src/schema";
import { unkeyAuditLogEvents } from "@unkey/schema/src/auditlog";
import { Box, X } from "lucide-react";
import Link from "next/link";
Expand All @@ -16,6 +17,7 @@ import { Suspense } from "react";
import { BucketSelect } from "./bucket-select";
import { Filter } from "./filter";
import { Row } from "./row";

export const dynamic = "force-dynamic";
export const runtime = "edge";

Expand All @@ -31,18 +33,42 @@ type Props = {
};
};

type AuditLogWithTargets = SelectAuditLog & { targets: Array<SelectAuditLogTarget> };

/**
* Parse searchParam string arrays
*/
const filterParser = parseAsArrayOf(parseAsString).withDefault([]);

/**
* Utility to map log with targets to log entry
*/
const toLogEntry = (l: AuditLogWithTargets) => ({
id: l.id,
event: l.event,
time: l.time,
actor: {
id: l.actorId,
name: l.actorName,
type: l.actorType,
},
location: l.remoteIp,
description: l.display,
targets: l.targets.map((t) => ({
id: t.id,
type: t.type,
name: t.name,
})),
});

export default async function AuditPage(props: Props) {
const tenantId = getTenantId();
const workspace = await db.query.workspaces.findFirst({
where: (table, { eq, and, isNull }) =>
and(eq(table.tenantId, tenantId), isNull(table.deletedAt)),
with: {
ratelimitNamespaces: {
where: (table, { isNull }) => isNull(table.deletedAt),
columns: {
id: true,
name: true,
Expand Down Expand Up @@ -87,19 +113,6 @@ export default async function AuditPage(props: Props) {
},
},
});
if (!bucket) {
return (
<EmptyPlaceholder>
<EmptyPlaceholder.Icon>
<Box />
</EmptyPlaceholder.Icon>
<EmptyPlaceholder.Title>Bucket Not Found</EmptyPlaceholder.Title>
<EmptyPlaceholder.Description>
The specified audit log bucket does not exist or you do not have access to it.
</EmptyPlaceholder.Description>
</EmptyPlaceholder>
);
}

return (
<div>
Expand Down Expand Up @@ -159,29 +172,25 @@ export default async function AuditPage(props: Props) {
</EmptyPlaceholder>
}
>
<AuditLogTable
logs={bucket.logs.map((l) => ({
id: l.id,
event: l.event,
time: l.time,
actor: {
id: l.actorId,
name: l.actorName,
type: l.actorType,
},
location: l.remoteIp,
description: l.display,
targets: l.targets.map((t) => ({
id: t.id,
type: t.type,
name: t.name,
})),
}))}
before={props.searchParams.before ? Number(props.searchParams.before) : undefined}
selectedEvents={selectedEvents}
selectedUsers={selectedUsers}
selectedRootKeys={selectedRootKeys}
/>
{!bucket ? (
<EmptyPlaceholder>
<EmptyPlaceholder.Icon>
<Box />
</EmptyPlaceholder.Icon>
<EmptyPlaceholder.Title>Bucket Not Found</EmptyPlaceholder.Title>
<EmptyPlaceholder.Description>
The specified audit log bucket does not exist or you do not have access to it.
</EmptyPlaceholder.Description>
</EmptyPlaceholder>
) : (
<AuditLogTable
logs={bucket.logs.map(toLogEntry)}
before={props.searchParams.before ? Number(props.searchParams.before) : undefined}
selectedEvents={selectedEvents}
selectedUsers={selectedUsers}
selectedRootKeys={selectedRootKeys}
/>
)}
</Suspense>
</main>
</div>
Expand Down Expand Up @@ -359,7 +368,12 @@ const UserFilter: React.FC<{ tenantId: string }> = async ({ tenantId }) => {

const RootKeyFilter: React.FC<{ workspaceId: string }> = async ({ workspaceId }) => {
const rootKeys = await db.query.keys.findMany({
where: (table, { eq }) => eq(table.forWorkspaceId, workspaceId),
where: (table, { eq, and, or, isNull, gt }) =>
and(
eq(table.forWorkspaceId, workspaceId),
isNull(table.deletedAt),
or(isNull(table.expires), gt(table.expires, new Date())),
),
columns: {
id: true,
name: true,
Expand Down
6 changes: 3 additions & 3 deletions apps/dashboard/app/(app)/audit/[bucket]/row.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -84,11 +84,11 @@ export const Row: React.FC<Props> = ({ auditLog, user }) => {
</TableCell>
<TableCell>
<div className="flex items-center gap-2">
<span className="text-sm text-content">
<span className="text-sm text-content" suppressHydrationWarning>
{new Date(auditLog.time).toLocaleDateString()}
</span>
<span className="text-xs text-content-subtle">
{new Date(auditLog.time).toLocaleTimeString()}
<span className="text-xs text-content-subtle" suppressHydrationWarning>
{new Date(auditLog.time).toLocaleDateString()}
</span>
</div>
</TableCell>
Expand Down
3 changes: 3 additions & 0 deletions internal/db/src/schema/audit_logs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -137,3 +137,6 @@ export const auditLogTargetRelations = relations(auditLogTarget, ({ one }) => ({
references: [auditLog.id],
}),
}));

export type SelectAuditLog = typeof auditLog.$inferSelect;
export type SelectAuditLogTarget = typeof auditLogTarget.$inferSelect;

0 comments on commit b652852

Please sign in to comment.