Skip to content

Commit

Permalink
fix: audit log filters use search params (#2769)
Browse files Browse the repository at this point in the history
  • Loading branch information
chronark authored Dec 20, 2024
1 parent f9dcdc7 commit e886e0d
Show file tree
Hide file tree
Showing 20 changed files with 127 additions and 141 deletions.

This file was deleted.

46 changes: 0 additions & 46 deletions apps/dashboard/app/(app)/audit/[bucket]/page.tsx

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,12 @@ export const getWorkspace = async (tenantId: string) => {
where: (table, { eq, and, isNull }) =>
and(eq(table.tenantId, tenantId), isNull(table.deletedAt)),
with: {
ratelimitNamespaces: {
where: (table, { isNull }) => isNull(table.deletedAt),
auditLogBuckets: {
columns: {
id: true,
name: true,
},
orderBy: (table, { asc }) => asc(table.createdAt),
},
},
});
Expand All @@ -39,10 +39,7 @@ export type SearchParams = {
startTime?: string | string[];
endTime?: string | string[];
cursor?: string | string[];
};

type ParseFilterInput = SearchParams & {
bucket: string;
bucketName?: string;
};

export type ParsedParams = {
Expand All @@ -51,11 +48,11 @@ export type ParsedParams = {
selectedRootKeys: string[];
startTime: number | null;
endTime: number | null;
bucket: string | null;
bucketName: string;
cursor: string | null;
};

export const parseFilterParams = (params: ParseFilterInput): ParsedParams => {
export const parseFilterParams = (params: SearchParams): ParsedParams => {
const filterParser = parseAsArrayOf(parseAsString).withDefault([]);
const timeParser = parseAsInteger;
const bucketParser = parseAsString;
Expand All @@ -66,7 +63,7 @@ export const parseFilterParams = (params: ParseFilterInput): ParsedParams => {
selectedRootKeys: filterParser.parseServerSide(params.rootKeys),
startTime: timeParser.parseServerSide(params.startTime),
endTime: timeParser.parseServerSide(params.endTime),
bucket: bucketParser.parseServerSide(params.bucket),
bucketName: bucketParser.withDefault("unkey_mutations").parseServerSide(params.bucketName),
cursor: bucketParser.parseServerSide(params.cursor),
};
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
"use client";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";

import { parseAsString, useQueryState } from "nuqs";
import type React from "react";

type Props = {
buckets: { id: string; name: string }[];
};

export const BucketSelect: React.FC<Props> = ({ buckets }) => {
const [selected, setSelected] = useQueryState(
"bucket",
parseAsString.withDefault("unkey_mutations").withOptions({
history: "push",
shallow: false, // otherwise server components won't notice the change
clearOnDefault: true,
}),
);

return (
<div>
<Select
value={selected}
onValueChange={(value) => {
setSelected(value);
}}
>
<SelectTrigger>
<SelectValue />
</SelectTrigger>
<SelectContent>
{buckets.map((b) => (
<SelectItem key={b.id} value={b.name}>
{b.name === "unkey_mutations" ? "System" : `Ratelimit: ${b.name}`}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ export const Filter: React.FC<Props> = ({ options, title, param }) => {
</PopoverTrigger>
<PopoverContent className="w-[400px] p-0" align="start">
<Command>
<CommandInput placeholder="Events" />
<CommandInput placeholder="Search" />
<CommandList>
<CommandEmpty>No results found.</CommandEmpty>
<CommandGroup>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { DatePickerWithRange } from "@/app/(app)/logs/components/filters/components/custom-date-filter";
import { DEFAULT_BUCKET_NAME } from "@/lib/trpc/routers/audit/fetch";
import type { ratelimitNamespaces, workspaces } from "@unkey/db/src/schema";
import type { auditLogBucket, workspaces } from "@unkey/db/src/schema";
import { unkeyAuditLogEvents } from "@unkey/schema/src/auditlog";
import { Button } from "@unkey/ui";
import { Suspense } from "react";
Expand All @@ -12,29 +12,26 @@ import { RootKeyFilter } from "./root-key-filter";
import { UserFilter } from "./user-filter";

export type SelectWorkspace = typeof workspaces.$inferSelect & {
ratelimitNamespaces: Pick<typeof ratelimitNamespaces.$inferSelect, "id" | "name">[];
auditLogBuckets: Pick<typeof auditLogBucket.$inferSelect, "id" | "name">[];
};

export const Filters = ({
bucket,
selectedBucketName,
workspace,
parsedParams,
}: {
bucket: string | null;
selectedBucketName: string;
workspace: SelectWorkspace;
parsedParams: ParsedParams;
}) => {
return (
<div className="flex items-center justify-start gap-2 mb-4">
<BucketSelect
selected={bucket ?? DEFAULT_BUCKET_NAME}
ratelimitNamespaces={workspace.ratelimitNamespaces}
/>
<BucketSelect buckets={workspace.auditLogBuckets} />
<Filter
param="events"
title="Events"
options={
bucket === DEFAULT_BUCKET_NAME
selectedBucketName === DEFAULT_BUCKET_NAME
? Object.values(unkeyAuditLogEvents.Values).map((value) => ({
value,
label: value,
Expand All @@ -48,7 +45,7 @@ export const Filters = ({
]
}
/>
{bucket === DEFAULT_BUCKET_NAME ? (
{selectedBucketName === DEFAULT_BUCKET_NAME ? (
<Suspense fallback={<Filter param="users" title="Users" options={[]} />}>
<UserFilter tenantId={workspace.tenantId} />
</Suspense>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,8 @@ import { Filter } from "./filter";

export const RootKeyFilter: React.FC<{ workspaceId: string }> = async ({ workspaceId }) => {
const rootKeys = await db.query.keys.findMany({
where: (table, { eq, and, or, isNull, gt }) =>
and(
eq(table.forWorkspaceId, workspaceId),
isNull(table.deletedAt),
or(isNull(table.expires), gt(table.expires, new Date())),
),
where: (table, { eq }) => eq(table.forWorkspaceId, workspaceId),

columns: {
id: true,
name: true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export const UserFilter: React.FC<{ tenantId: string }> = async ({ tenantId }) =
if (tenantId.startsWith("user_")) {
return null;
}

const members = await clerkClient.organizations.getOrganizationMembershipList({
organizationId: tenantId,
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export const AuditLogTableClient = () => {
const { data, fetchNextPage, hasNextPage, isFetchingNextPage, isLoading, isError } =
trpc.audit.fetch.useInfiniteQuery(
{
bucket: searchParams.bucket ?? undefined,
bucketName: searchParams.bucket ?? undefined,
limit: DEFAULT_FETCH_COUNT,
users: searchParams.users,
events: searchParams.events,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import { LogSection } from "@/app/(app)/logs/components/table/log-details/components/log-section";
import { memo, useMemo, useState } from "react";
import { useDebounceCallback } from "usehooks-ts";
import ResizablePanel from "../../../../logs/components/table/log-details/resizable-panel";
import ResizablePanel from "../../../logs/components/table/log-details/resizable-panel";
import { LogFooter } from "./log-footer";
import { LogHeader } from "./log-header";
import type { Data } from "./types";
Expand Down
56 changes: 53 additions & 3 deletions apps/dashboard/app/(app)/audit/page.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,55 @@
import { redirect } from "next/navigation";
import { EmptyPlaceholder } from "@/components/dashboard/empty-placeholder";
import { Navbar } from "@/components/navbar";
import { PageContent } from "@/components/page-content";
import { getTenantId } from "@/lib/auth";
import { InputSearch, Ufo } from "@unkey/icons";
import { type SearchParams, getWorkspace, parseFilterParams } from "./actions";
import { Filters } from "./components/filters";
import { AuditLogTableClient } from "./components/table/audit-log-table-client";

export default function Page() {
return redirect("/audit/unkey_mutations");
export const dynamic = "force-dynamic";
export const runtime = "edge";

type Props = {
searchParams: SearchParams;
};

export default async function AuditPage(props: Props) {
const tenantId = getTenantId();
const workspace = await getWorkspace(tenantId);
const parsedParams = parseFilterParams(props.searchParams);

return (
<div>
<Navbar>
<Navbar.Breadcrumbs icon={<InputSearch />}>
<Navbar.Breadcrumbs.Link href="/audit">Audit</Navbar.Breadcrumbs.Link>
</Navbar.Breadcrumbs>
</Navbar>
<PageContent>
{workspace.auditLogBuckets.length > 0 ? (
<main className="mb-5">
<Filters
workspace={workspace}
parsedParams={parsedParams}
selectedBucketName={parsedParams.bucketName}
/>

<AuditLogTableClient />
</main>
) : (
<EmptyPlaceholder>
<EmptyPlaceholder.Icon>
<Ufo />
</EmptyPlaceholder.Icon>
<EmptyPlaceholder.Title>No logs</EmptyPlaceholder.Title>
<EmptyPlaceholder.Description>
There are no audit logs available yet. Create a key or another resource and come back
here.
</EmptyPlaceholder.Description>
</EmptyPlaceholder>
)}
</PageContent>
</div>
);
}
Loading

0 comments on commit e886e0d

Please sign in to comment.