diff --git a/apps/dashboard/app/(app)/@breadcrumb/apis/[apiId]/keys/[keyAuthId]/[keyId]/page.tsx b/apps/dashboard/app/(app)/@breadcrumb/apis/[apiId]/keys/[keyAuthId]/[keyId]/page.tsx
deleted file mode 100644
index 26b3c9085f..0000000000
--- a/apps/dashboard/app/(app)/@breadcrumb/apis/[apiId]/keys/[keyAuthId]/[keyId]/page.tsx
+++ /dev/null
@@ -1,76 +0,0 @@
-import {
- Breadcrumb,
- BreadcrumbItem,
- BreadcrumbLink,
- BreadcrumbList,
- BreadcrumbPage,
- BreadcrumbSeparator,
-} from "@/components/ui/breadcrumb";
-
-import { getTenantId } from "@/lib/auth";
-import { db } from "@/lib/db";
-import { unstable_cache as cache } from "next/cache";
-import { Suspense } from "react";
-
-export const dynamic = "force-dynamic";
-export const runtime = "edge";
-
-type PageProps = {
- params: { apiId: string; keyAuthId: string; keyId: string };
-};
-
-async function AsyncPageBreadcrumb(props: PageProps) {
- const tenantId = getTenantId();
-
- const getApiById = cache(
- async (apiId: string) =>
- db.query.apis.findFirst({
- where: (table, { eq, and, isNull }) => and(eq(table.id, apiId), isNull(table.deletedAt)),
-
- with: {
- workspace: true,
- },
- }),
- ["apiById"],
- );
-
- const api = await getApiById(props.params.apiId);
- if (!api || api.workspace.tenantId !== tenantId) {
- return null;
- }
-
- return (
-
- {key.id}
-
-
-
-
-
+ {key.id}
+
+
+
+
+
- {api.id}
-
+
+
+ }>
+ APIs
+
+ {api.name}
+
+
+ Settings
+
+
+
+
+
+ {api.id}
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ API ID
+ This is your api id. It's used in some API calls.
+
+
+
+ {api.id}
+
+
+
+
+
+
+
+
+
+
);
}
diff --git a/apps/dashboard/app/(app)/apis/client.tsx b/apps/dashboard/app/(app)/apis/client.tsx
index a2633aec8c..eee7cdd620 100644
--- a/apps/dashboard/app/(app)/apis/client.tsx
+++ b/apps/dashboard/app/(app)/apis/client.tsx
@@ -1,7 +1,6 @@
"use client";
import { EmptyPlaceholder } from "@/components/dashboard/empty-placeholder";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
-import { Separator } from "@/components/ui/separator";
import { PostHogIdentify } from "@/providers/PostHogProvider";
import { useUser } from "@clerk/nextjs";
import { Button } from "@unkey/ui";
@@ -19,19 +18,22 @@ type ApiWithKeys = {
export function ApiList({ apis }: { apis: ApiWithKeys }) {
const { user, isLoaded } = useUser();
+
+ const [localData, setLocalData] = useState(apis);
+
+ if (isLoaded && user) {
+ PostHogIdentify({ user });
+ }
+
useEffect(() => {
if (apis.length) {
setLocalData(apis);
}
}, [apis]);
- const [localData, setLocalData] = useState(apis);
- if (isLoaded && user) {
- PostHogIdentify({ user });
- }
+
return (
-
-
+
-
{apis.length ? (
diff --git a/apps/dashboard/app/(app)/apis/loading.tsx b/apps/dashboard/app/(app)/apis/loading.tsx
index eb0de19b6f..cde2adf520 100644
--- a/apps/dashboard/app/(app)/apis/loading.tsx
+++ b/apps/dashboard/app/(app)/apis/loading.tsx
@@ -1,8 +1,9 @@
+import { PageContent } from "@/components/page-content";
import { Skeleton } from "@/components/ui/skeleton";
export default function Loading() {
return (
-
+
@@ -38,6 +39,6 @@ export default function Loading() {
-
+
);
}
diff --git a/apps/dashboard/app/(app)/apis/page.tsx b/apps/dashboard/app/(app)/apis/page.tsx
index 173ab966b0..d3ccf44c40 100644
--- a/apps/dashboard/app/(app)/apis/page.tsx
+++ b/apps/dashboard/app/(app)/apis/page.tsx
@@ -1,9 +1,10 @@
-import { PageHeader } from "@/components/dashboard/page-header";
import { CreateApiButton } from "./create-api-button";
-import { Separator } from "@/components/ui/separator";
+import { Navbar } from "@/components/navbar";
+import { PageContent } from "@/components/page-content";
import { getTenantId } from "@/lib/auth";
import { and, db, eq, isNull, schema, sql } from "@/lib/db";
+import { Nodes } from "@unkey/icons";
import { Search } from "lucide-react";
import Link from "next/link";
import { redirect } from "next/navigation";
@@ -42,40 +43,49 @@ export default async function ApisOverviewPage() {
const unpaid = workspace.tenantId.startsWith("org_") && workspace.plan === "free";
return (
-
- {unpaid ? (
-
-
-
-
-
-
-
+
+
+ }>
+ APIs
+
+
+
+
+
+
+ {unpaid ? (
+
+
+
+
+
+
+
+
+
+
+ Upgrade your plan
+
+
+ Team workspaces is a paid feature. Please switch to a paid plan to continue using
+ it.
+
+
+ Subscribe
+
-
-
-
-
- Upgrade your plan
-
-
- Team workspaces is a paid feature. Please switch to a paid plan to continue using it.
-
-
- Subscribe
-
-
- ) : (
-
- )}
+ ) : (
+
+ )}
+
);
}
diff --git a/apps/dashboard/app/(app)/audit/[bucket]/page.tsx b/apps/dashboard/app/(app)/audit/[bucket]/page.tsx
index 9e851efc5c..efc0cde3a0 100644
--- a/apps/dashboard/app/(app)/audit/[bucket]/page.tsx
+++ b/apps/dashboard/app/(app)/audit/[bucket]/page.tsx
@@ -1,12 +1,14 @@
import { EmptyPlaceholder } from "@/components/dashboard/empty-placeholder";
import { Loading } from "@/components/dashboard/loading";
-import { PageHeader } from "@/components/dashboard/page-header";
+import { Navbar } from "@/components/navbar";
+import { PageContent } from "@/components/page-content";
import { Table, TableBody, TableHead, TableHeader, TableRow } from "@/components/ui/table";
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 { InputSearch } from "@unkey/icons";
import { unkeyAuditLogEvents } from "@unkey/schema/src/auditlog";
import { Button } from "@unkey/ui";
import { Box, X } from "lucide-react";
@@ -33,7 +35,9 @@ type Props = {
};
};
-type AuditLogWithTargets = SelectAuditLog & { targets: Array };
+type AuditLogWithTargets = SelectAuditLog & {
+ targets: Array;
+};
/**
* Parse searchParam string arrays
@@ -116,79 +120,87 @@ export default async function AuditPage(props: Props) {
return (
-
-
-
-
- ({
- value,
- label: value,
- }))
- : [
- { value: "ratelimit.success", label: "Ratelimit success" },
- { value: "ratelimit.denied", label: "Ratelimit denied" },
- ]
- }
- />
+
+ }>
+ Audit
+
+
+
+
+
+
+ ({
+ value,
+ label: value,
+ }))
+ : [
+ {
+ value: "ratelimit.success",
+ label: "Ratelimit success",
+ },
+ { value: "ratelimit.denied", label: "Ratelimit denied" },
+ ]
+ }
+ />
- {props.params.bucket === "unkey_mutations" ? (
- }>
-
+ {props.params.bucket === "unkey_mutations" ? (
+ }>
+
+
+ ) : null}
+ }>
+
- ) : null}
- }>
-
+ {selectedEvents.length > 0 ||
+ selectedUsers.length > 0 ||
+ selectedRootKeys.length > 0 ? (
+
+
+
+ ) : null}
+
+
+
+
+
+
+ }
+ >
+ {!bucket ? (
+
+
+
+
+ Bucket Not Found
+
+ The specified audit log bucket does not exist or you do not have access to it.
+
+
+ ) : (
+
+ )}
- {selectedEvents.length > 0 || selectedUsers.length > 0 || selectedRootKeys.length > 0 ? (
-
-
-
- ) : null}
-
-
-
-
-
-
- }
- >
- {!bucket ? (
-
-
-
-
- Bucket Not Found
-
- The specified audit log bucket does not exist or you do not have access to it.
-
-
- ) : (
-
- )}
-
-
+
+
);
}
diff --git a/apps/dashboard/app/(app)/authorization/constants.ts b/apps/dashboard/app/(app)/authorization/constants.ts
new file mode 100644
index 0000000000..6739597796
--- /dev/null
+++ b/apps/dashboard/app/(app)/authorization/constants.ts
@@ -0,0 +1,12 @@
+export const navigation = [
+ {
+ label: "Roles",
+ href: "/authorization/roles",
+ segment: "roles",
+ },
+ {
+ label: "Permissions",
+ href: "/authorization/permissions",
+ segment: "permissions",
+ },
+];
diff --git a/apps/dashboard/app/(app)/authorization/layout.tsx b/apps/dashboard/app/(app)/authorization/layout.tsx
index 70fcb4677b..029be01d9a 100644
--- a/apps/dashboard/app/(app)/authorization/layout.tsx
+++ b/apps/dashboard/app/(app)/authorization/layout.tsx
@@ -1,6 +1,5 @@
import type * as React from "react";
-import { Navbar } from "@/components/dashboard/navbar";
import { getTenantId } from "@/lib/auth";
import { db } from "@/lib/db";
import { redirect } from "next/navigation";
@@ -21,24 +20,5 @@ export default async function AuthorizationLayout({
return redirect("/auth/sign-in");
}
- const navigation = [
- {
- label: "Roles",
- href: "/authorization/roles",
- segment: "roles",
- },
- {
- label: "Permissions",
- href: "/authorization/permissions",
- segment: "permissions",
- },
- ];
-
- return (
-
-
-
- {children}
-
- );
+ return children;
}
diff --git a/apps/dashboard/app/(app)/authorization/permissions/[permissionId]/page.tsx b/apps/dashboard/app/(app)/authorization/permissions/[permissionId]/page.tsx
index 44259688a7..8bdc3a1ade 100644
--- a/apps/dashboard/app/(app)/authorization/permissions/[permissionId]/page.tsx
+++ b/apps/dashboard/app/(app)/authorization/permissions/[permissionId]/page.tsx
@@ -1,18 +1,14 @@
import { CopyButton } from "@/components/dashboard/copy-button";
-import { PageHeader } from "@/components/dashboard/page-header";
+import { Navbar } from "@/components/navbar";
+import { PageContent } from "@/components/page-content";
import { Badge } from "@/components/ui/badge";
+import { Metric } from "@/components/ui/metric";
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
import { getTenantId } from "@/lib/auth";
import { db } from "@/lib/db";
+import { ShieldKey } from "@unkey/icons";
import { Button } from "@unkey/ui";
-import {
- Activity,
- CalendarPlus,
- KeySquare,
- type LucideIcon,
- Minus,
- SquareStack,
-} from "lucide-react";
+import { format } from "date-fns";
import { notFound, redirect } from "next/navigation";
import { Client } from "./client";
import { DeletePermission } from "./delete-permission";
@@ -73,83 +69,96 @@ export default async function RolesPage(props: Props) {
const shouldShowTooltip = permission.name.length > 16;
return (
-
- {permission.name}}
- description={permission.description ?? undefined}
- actions={[
-
+
+ }>
+
+ Authorization
+
+
+ Permissions
+
+
-
-
-
- {permission.name}
-
-
+ {props.params.permissionId}
+
+
+
+
+
+
+
+
+ {permission.name}
+
+
+
-
-
- {shouldShowTooltip && (
-
- {permission.name}
-
- )}
-
- ,
-
- {permission.id}
-
- ,
- Delete}
- permission={permission}
- />,
- ]}
- />
-
-
-
-
-
-
-
+
+ {shouldShowTooltip && (
+
+ {permission.name}
+
+ )}
+
+
+
+ {permission.id}
+
+
+ Delete}
+ permission={permission}
+ />{" "}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
+
);
}
-
-const Metric: React.FC<{ label: string; value?: string; Icon: LucideIcon }> = ({
- label,
- value,
- Icon,
-}) => {
- return (
-
-
-
- {label}
-
- {value ?? }
-
-
-
- );
-};
diff --git a/apps/dashboard/app/(app)/authorization/permissions/page.tsx b/apps/dashboard/app/(app)/authorization/permissions/page.tsx
index 140b96b733..6e2a3738eb 100644
--- a/apps/dashboard/app/(app)/authorization/permissions/page.tsx
+++ b/apps/dashboard/app/(app)/authorization/permissions/page.tsx
@@ -1,12 +1,17 @@
import { EmptyPlaceholder } from "@/components/dashboard/empty-placeholder";
+import { Navbar as SubMenu } from "@/components/dashboard/navbar";
+import { Navbar } from "@/components/navbar";
+import { PageContent } from "@/components/page-content";
import { Badge } from "@/components/ui/badge";
import { getTenantId } from "@/lib/auth";
import { asc, db } from "@/lib/db";
import { permissions } from "@unkey/db/src/schema";
+import { ShieldKey } from "@unkey/icons";
import { Button } from "@unkey/ui";
import { ChevronRight, Scan } from "lucide-react";
import Link from "next/link";
import { redirect } from "next/navigation";
+import { navigation } from "../constants";
import { CreateNewPermission } from "./create-new-permission";
export const revalidate = 0;
@@ -51,61 +56,89 @@ export default async function RolesPage() {
return permission;
});
return (
-
-
- Permissions
-
-
- {Intl.NumberFormat().format(workspace.permissions.length)} /{" "}
- {Intl.NumberFormat().format(Number.POSITIVE_INFINITY)} used{" "}
-
- Create New Permission} />
-
-
- {workspace.permissions.length === 0 ? (
-
-
-
-
- No permissions found
- Create your first permission
- Create New Permission} />
-
- ) : (
-
- {workspace.permissions.map((p) => (
-
-
- {p.name}
- {p.description}
-
+
+
+ }>
+
+ Authorization
+
+
+ Permissions
+
+
+
+
+
+ {Intl.NumberFormat().format(workspace.permissions.length)} /{" "}
+ {Intl.NumberFormat().format(Number.POSITIVE_INFINITY)} used{" "}
+
+ Create New Permission}
+ />
+
+
+
+
+
+
+
+
+ {workspace.permissions.length === 0 ? (
+
+
+
+
+ No permissions found
+
+ Create your first permission
+
+ Create New Permission}
+ />
+
+ ) : (
+
+ {workspace.permissions.map((p) => (
+
+
+ {p.name}
+ {p.description}
+
-
-
- {Intl.NumberFormat(undefined, { notation: "compact" }).format(p.roles.length)}{" "}
- Role
- {p.roles.length !== 1 ? "s" : ""}
-
+
+
+ {Intl.NumberFormat(undefined, {
+ notation: "compact",
+ }).format(p.roles.length)}{" "}
+ Role
+ {p.roles.length !== 1 ? "s" : ""}
+
-
- {Intl.NumberFormat(undefined, { notation: "compact" }).format(p.keys.length)} Key
- {p.keys.length !== 1 ? "s" : ""}
-
-
+
+ {Intl.NumberFormat(undefined, {
+ notation: "compact",
+ }).format(p.keys.length)}{" "}
+ Key
+ {p.keys.length !== 1 ? "s" : ""}
+
+
-
-
-
-
- ))}
-
- )}
+
+
+
+
+ ))}
+
+ )}
+
+
+
);
}
diff --git a/apps/dashboard/app/(app)/authorization/roles/[roleId]/page.tsx b/apps/dashboard/app/(app)/authorization/roles/[roleId]/page.tsx
index 222c4b8c71..65667ba019 100644
--- a/apps/dashboard/app/(app)/authorization/roles/[roleId]/page.tsx
+++ b/apps/dashboard/app/(app)/authorization/roles/[roleId]/page.tsx
@@ -1,5 +1,8 @@
+import { Navbar } from "@/components/navbar";
+import { PageContent } from "@/components/page-content";
import { getTenantId } from "@/lib/auth";
import { db } from "@/lib/db";
+import { ShieldKey } from "@unkey/icons";
import { Button } from "@unkey/ui";
import { notFound, redirect } from "next/navigation";
import { DeleteRole } from "./delete-role";
@@ -106,23 +109,47 @@ export default async function RolesPage(props: Props) {
const sortedNestedPermissions = sortNestedPermissions(nested);
return (
-
-
-
+
+
+ }>
+
+ Authorization
+
+ Roles
+
+ {props.params.roleId}
+
+
+
-
- {role.name}
-
+
+ Update Role} />
+ Delete Role}
+ />
+
- {role.description}
-
-
- Update Role} />
- Delete Role} />
+
+
+
+
+
+
+
+ {role.name}
+
+ {role.description}
+
+
+
-
-
-
+
);
}
diff --git a/apps/dashboard/app/(app)/authorization/roles/[roleId]/tree.tsx b/apps/dashboard/app/(app)/authorization/roles/[roleId]/tree.tsx
index 8e5746694d..eafe23b387 100644
--- a/apps/dashboard/app/(app)/authorization/roles/[roleId]/tree.tsx
+++ b/apps/dashboard/app/(app)/authorization/roles/[roleId]/tree.tsx
@@ -87,7 +87,7 @@ export const RecursivePermission: React.FC<
);
}
return (
-
+
{k}
diff --git a/apps/dashboard/app/(app)/authorization/roles/page.tsx b/apps/dashboard/app/(app)/authorization/roles/page.tsx
index 77a5fb9739..89d9862c98 100644
--- a/apps/dashboard/app/(app)/authorization/roles/page.tsx
+++ b/apps/dashboard/app/(app)/authorization/roles/page.tsx
@@ -1,11 +1,16 @@
import { EmptyPlaceholder } from "@/components/dashboard/empty-placeholder";
+import { Navbar as SubMenu } from "@/components/dashboard/navbar";
+import { Navbar } from "@/components/navbar";
+import { PageContent } from "@/components/page-content";
import { Badge } from "@/components/ui/badge";
import { getTenantId } from "@/lib/auth";
import { db } from "@/lib/db";
+import { ShieldKey } from "@unkey/icons";
import { Button } from "@unkey/ui";
import { ChevronRight, Scan } from "lucide-react";
import Link from "next/link";
import { redirect } from "next/navigation";
+import { navigation } from "../constants";
import { CreateNewRole } from "./create-new-role";
export const revalidate = 0;
@@ -51,70 +56,91 @@ export default async function RolesPage() {
});
return (
-
-
- Roles
-
-
- {Intl.NumberFormat().format(workspace.roles.length)} /{" "}
- {Intl.NumberFormat().format(Number.POSITIVE_INFINITY)} used{" "}
-
- Create New Role}
- permissions={workspace.permissions}
- />
-
-
+
+
+ }>
+
+ Authorization
+
+
+ Roles
+
+
+
+
+
+ {Intl.NumberFormat().format(workspace.roles.length)} /{" "}
+ {Intl.NumberFormat().format(Number.POSITIVE_INFINITY)} used{" "}
+
+ Create New Role}
+ permissions={workspace.permissions}
+ />
+
+
+
- {workspace.roles.length === 0 ? (
-
-
-
-
- No roles found
- Create your first role
- Create New Role}
- permissions={workspace.permissions}
- />
-
- ) : (
-
- {workspace.roles.map((r) => (
-
-
- {r.name}
- {r.description}
-
+
+
+
+
+ {workspace.roles.length === 0 ? (
+
+
+
+
+ No roles found
+ Create your first role
+ Create New Role}
+ permissions={workspace.permissions}
+ />
+
+ ) : (
+
+ {workspace.roles.map((r) => (
+
+
+ {r.name}
+
+ {r.description}
+
+
-
-
- {Intl.NumberFormat(undefined, { notation: "compact" }).format(
- r.permissions.length,
- )}{" "}
- Permission
- {r.permissions.length !== 1 ? "s" : ""}
-
+
+
+ {Intl.NumberFormat(undefined, {
+ notation: "compact",
+ }).format(r.permissions.length)}{" "}
+ Permission
+ {r.permissions.length !== 1 ? "s" : ""}
+
-
- {Intl.NumberFormat(undefined, { notation: "compact" }).format(r.keys.length)} Key
- {r.keys.length !== 1 ? "s" : ""}
-
-
+
+ {Intl.NumberFormat(undefined, {
+ notation: "compact",
+ }).format(r.keys.length)}{" "}
+ Key
+ {r.keys.length !== 1 ? "s" : ""}
+
+
-
-
-
-
- ))}
-
- )}
+
+
+
+
+ ))}
+
+ )}
+
+
+
);
}
diff --git a/apps/dashboard/app/(app)/layout.tsx b/apps/dashboard/app/(app)/layout.tsx
index 18ec728800..38f84c79ee 100644
--- a/apps/dashboard/app/(app)/layout.tsx
+++ b/apps/dashboard/app/(app)/layout.tsx
@@ -43,7 +43,7 @@ export default async function Layout({ children, breadcrumb }: LayoutProps) {
/>
-
+
{workspace.enabled ? (
<>
{/* Hacky way to make the breadcrumbs line up with the Teamswitcher on the left, because that also has h12 */}
diff --git a/apps/dashboard/app/(app)/logs/logs-page.tsx b/apps/dashboard/app/(app)/logs/logs-page.tsx
index 67b1c40bc1..d9aa08ae77 100644
--- a/apps/dashboard/app/(app)/logs/logs-page.tsx
+++ b/apps/dashboard/app/(app)/logs/logs-page.tsx
@@ -1,5 +1,8 @@
"use client";
+import { Navbar } from "@/components/navbar";
+import { PageContent } from "@/components/page-content";
import type { Log, LogsTimeseriesDataPoint } from "@unkey/clickhouse/src/logs";
+import { Layers3 } from "@unkey/icons";
import { LogsChart } from "./components/charts";
import { LogsFilters } from "./components/filters";
import { LogsTable } from "./components/table/logs-table";
@@ -12,13 +15,22 @@ export function LogsPage({
initialTimeseries: LogsTimeseriesDataPoint[];
}) {
return (
-
-
-
-
- {/* Chart is using more space than it should to be able to display tooltip correctly so we can -margin that empty space */}
-
-
+
+
+ }>
+ Logs
+
+
+
+
+
+
+
+ {/* Chart is using more space than it should to be able to display tooltip correctly so we can -margin that empty space */}
+
+
+
+
);
}
diff --git a/apps/dashboard/app/(app)/ratelimits/[namespaceId]/constants.ts b/apps/dashboard/app/(app)/ratelimits/[namespaceId]/constants.ts
new file mode 100644
index 0000000000..361fdb74a1
--- /dev/null
+++ b/apps/dashboard/app/(app)/ratelimits/[namespaceId]/constants.ts
@@ -0,0 +1,23 @@
+export const navigation = (namespaceId: string) => [
+ {
+ label: "Overview",
+ href: `/ratelimits/${namespaceId}`,
+ segment: "overview",
+ },
+
+ {
+ label: "Settings",
+ href: `/ratelimits/${namespaceId}/settings`,
+ segment: "settings",
+ },
+ {
+ label: "Logs",
+ href: `/ratelimits/${namespaceId}/logs`,
+ segment: "logs",
+ },
+ {
+ label: "Overrides",
+ href: `/ratelimits/${namespaceId}/overrides`,
+ segment: "overrides",
+ },
+];
diff --git a/apps/dashboard/app/(app)/ratelimits/[namespaceId]/layout.tsx b/apps/dashboard/app/(app)/ratelimits/[namespaceId]/layout.tsx
index 7eebd35dde..994772e653 100644
--- a/apps/dashboard/app/(app)/ratelimits/[namespaceId]/layout.tsx
+++ b/apps/dashboard/app/(app)/ratelimits/[namespaceId]/layout.tsx
@@ -1,7 +1,3 @@
-import { CopyButton } from "@/components/dashboard/copy-button";
-import { Navbar } from "@/components/dashboard/navbar";
-import { PageHeader } from "@/components/dashboard/page-header";
-import { Badge } from "@/components/ui/badge";
import { getTenantId } from "@/lib/auth";
import { db } from "@/lib/db";
import { notFound } from "next/navigation";
@@ -26,53 +22,10 @@ export default async function RatelimitNamespacePageLayout(props: Props) {
workspace: true,
},
});
+
if (!namespace || namespace.workspace.tenantId !== tenantId) {
return notFound();
}
- const navigation = [
- {
- label: "Overview",
- href: `/ratelimits/${namespace.id}`,
- segment: null,
- },
-
- {
- label: "Settings",
- href: `/ratelimits/${namespace.id}/settings`,
- segment: "settings",
- },
- {
- label: "Logs",
- href: `/ratelimits/${namespace.id}/logs`,
- segment: "logs",
- },
- {
- label: "Overrides",
- href: `/ratelimits/${namespace.id}/overrides`,
- segment: "overrides",
- },
- ];
-
- return (
-
-
- {namespace.id}
-
- ,
- ]}
- />
-
-
- {props.children}
-
- );
+ return props.children;
}
diff --git a/apps/dashboard/app/(app)/ratelimits/[namespaceId]/logs/page.tsx b/apps/dashboard/app/(app)/ratelimits/[namespaceId]/logs/page.tsx
index 49b3931962..c48ff7559d 100644
--- a/apps/dashboard/app/(app)/ratelimits/[namespaceId]/logs/page.tsx
+++ b/apps/dashboard/app/(app)/ratelimits/[namespaceId]/logs/page.tsx
@@ -2,8 +2,12 @@ import { getTenantId } from "@/lib/auth";
import { db } from "@/lib/db";
import { notFound } from "next/navigation";
+import { CopyButton } from "@/components/dashboard/copy-button";
import { EmptyPlaceholder } from "@/components/dashboard/empty-placeholder";
import { Loading } from "@/components/dashboard/loading";
+import { Navbar as SubMenu } from "@/components/dashboard/navbar";
+import { Navbar } from "@/components/navbar";
+import { PageContent } from "@/components/page-content";
import { TimestampInfo } from "@/components/timestamp-info";
import { Badge } from "@/components/ui/badge";
import {
@@ -15,11 +19,14 @@ import {
TableRow,
} from "@/components/ui/table";
import { clickhouse } from "@/lib/clickhouse";
+import { Gauge } from "@unkey/icons";
import { Box, Check, X } from "lucide-react";
import { parseAsArrayOf, parseAsBoolean, parseAsIsoDateTime, parseAsString } from "nuqs/server";
import { Suspense } from "react";
+import { navigation } from "../constants";
import { Filters } from "./filter";
import { Menu } from "./menu";
+
export const dynamic = "force-dynamic";
export const runtime = "edge";
@@ -67,25 +74,49 @@ export default async function AuditPage(props: Props) {
return (
-
-
+
+ }>
+ Ratelimits
+
+ {namespace.name}
+
+
+ Logs{" "}
+
+
+
+
+ {props.params.namespaceId}
+
+
+
+
+
+
-
-
-
-
-
- }
- >
-
-
-
+
+
+
+
+
+
+
+ }
+ >
+
+
+
+
);
}
diff --git a/apps/dashboard/app/(app)/ratelimits/[namespaceId]/overrides/[overrideId]/page.tsx b/apps/dashboard/app/(app)/ratelimits/[namespaceId]/overrides/[overrideId]/page.tsx
index 5c3763568f..7f0af67713 100644
--- a/apps/dashboard/app/(app)/ratelimits/[namespaceId]/overrides/[overrideId]/page.tsx
+++ b/apps/dashboard/app/(app)/ratelimits/[namespaceId]/overrides/[overrideId]/page.tsx
@@ -1,8 +1,11 @@
-import { BackLink } from "@/components/back";
import { CopyButton } from "@/components/dashboard/copy-button";
+import { PageHeader } from "@/components/dashboard/page-header";
+import { Navbar } from "@/components/navbar";
+import { PageContent } from "@/components/page-content";
import { Badge } from "@/components/ui/badge";
import { getTenantId } from "@/lib/auth";
import { db, schema } from "@/lib/db";
+import { Gauge } from "@unkey/icons";
import { notFound } from "next/navigation";
import { UpdateCard } from "./settings";
@@ -28,6 +31,7 @@ export default async function OverrideSettings(props: Props) {
),
with: {
workspace: true,
+ namespace: true,
},
});
if (!override || override.workspace.tenantId !== tenantId) {
@@ -35,23 +39,61 @@ export default async function OverrideSettings(props: Props) {
}
return (
-
-
-
- {override.identifier}
-
-
-
+
+
+ }>
+ Ratelimits
+
+ {override.namespace.name}
+
+
+ Overrides
+
+
+ {override.identifier}
+
+
+
+
+ {override.namespace.id}
+
+
+
+
+
+
+
+ {override.identifier}
+
+ ,
+ ]}
+ />
+
+
+
);
}
diff --git a/apps/dashboard/app/(app)/ratelimits/[namespaceId]/overrides/page.tsx b/apps/dashboard/app/(app)/ratelimits/[namespaceId]/overrides/page.tsx
index 521dcfdd70..e98849d81c 100644
--- a/apps/dashboard/app/(app)/ratelimits/[namespaceId]/overrides/page.tsx
+++ b/apps/dashboard/app/(app)/ratelimits/[namespaceId]/overrides/page.tsx
@@ -1,11 +1,16 @@
+import { CopyButton } from "@/components/dashboard/copy-button";
import { EmptyPlaceholder } from "@/components/dashboard/empty-placeholder";
+import { Navbar as SubMenu } from "@/components/dashboard/navbar";
import { PageHeader } from "@/components/dashboard/page-header";
+import { Navbar } from "@/components/navbar";
+import { PageContent } from "@/components/page-content";
import { Badge } from "@/components/ui/badge";
-import { Separator } from "@/components/ui/separator";
import { getTenantId } from "@/lib/auth";
import { db } from "@/lib/db";
+import { Gauge } from "@unkey/icons";
import { Scan } from "lucide-react";
import { notFound } from "next/navigation";
+import { navigation } from "../constants";
import { CreateNewOverride } from "./create-new-override";
import { Overrides } from "./table";
@@ -49,37 +54,67 @@ export default async function OverridePage(props: Props) {
}
return (
- <>
-
- {Intl.NumberFormat().format(namespace.overrides.length)} /{" "}
- {Intl.NumberFormat().format(namespace.workspace.features.ratelimitOverrides ?? 5)} used{" "}
- ,
- ]}
- />
-
+
+
+ }>
+ Ratelimits
+
+ {namespace.name}
+
+
+ Overrides
+
+
+
+
+ {namespace.id}
+
+
+
+
+
+
-
+
+
+ {Intl.NumberFormat().format(namespace.overrides.length)} /{" "}
+ {Intl.NumberFormat().format(namespace.workspace.features.ratelimitOverrides ?? 5)}{" "}
+ used{" "}
+ ,
+ ]}
+ />
- {namespace.overrides.length === 0 ? (
-
-
-
-
- No custom ratelimits found
-
- Create your first override below
-
-
- ) : (
-
- )}
- >
+
+ {namespace.overrides.length === 0 ? (
+
+
+
+
+ No custom ratelimits found
+
+ Create your first override below
+
+
+ ) : (
+
+ )}
+
+
+
);
}
diff --git a/apps/dashboard/app/(app)/ratelimits/[namespaceId]/page.tsx b/apps/dashboard/app/(app)/ratelimits/[namespaceId]/page.tsx
index b3e181d4fe..8188fc2c94 100644
--- a/apps/dashboard/app/(app)/ratelimits/[namespaceId]/page.tsx
+++ b/apps/dashboard/app/(app)/ratelimits/[namespaceId]/page.tsx
@@ -1,6 +1,10 @@
import { StackedColumnChart } from "@/components/dashboard/charts";
import { CopyButton } from "@/components/dashboard/copy-button";
import { EmptyPlaceholder } from "@/components/dashboard/empty-placeholder";
+import { Navbar as SubMenu } from "@/components/dashboard/navbar";
+import { Navbar } from "@/components/navbar";
+import { PageContent } from "@/components/page-content";
+import { Badge } from "@/components/ui/badge";
import { Card, CardContent, CardHeader } from "@/components/ui/card";
import { Code } from "@/components/ui/code";
import { Metric } from "@/components/ui/metric";
@@ -9,10 +13,12 @@ import { getTenantId } from "@/lib/auth";
import { clickhouse } from "@/lib/clickhouse";
import { db, eq, schema, sql } from "@/lib/db";
import { formatNumber } from "@/lib/fmt";
+import { Gauge } from "@unkey/icons";
import { BarChart } from "lucide-react";
import ms from "ms";
import { redirect } from "next/navigation";
import { parseAsArrayOf, parseAsString, parseAsStringEnum } from "nuqs/server";
+import { navigation } from "./constants";
import { Filters, type Interval } from "./filters";
export const dynamic = "force-dynamic";
@@ -95,13 +101,6 @@ export default async function RatelimitNamespacePage(props: {
ratelimitedOverTime.push({ x, y: d.total - d.passed });
}
- // const dataOverTime = [
- // ...ratelimitedOverTime.map((d) => ({ ...d, category: "Ratelimited" })),
- // ...successOverTime.map((d) => ({
- // ...d,
- // category: "Successful",
- // })),
- // ];
const dataOverTime = ratelimitEvents.flatMap((d) => [
{
x: new Date(d.time).toISOString(),
@@ -115,11 +114,6 @@ export default async function RatelimitNamespacePage(props: {
},
]);
- // const activeKeysOverTime = activeKeys.data.map(({ time, keys }) => ({
- // x: new Date(time).toISOString(),
- // y: keys,
- // }));
-
const snippet = `curl -XPOST 'https://api.unkey.dev/v1/ratelimits.limit' \\
-H 'Content-Type: application/json' \\
-H 'Authorization: Bearer ' \\
@@ -130,100 +124,122 @@ export default async function RatelimitNamespacePage(props: {
"duration": 10000
}'`;
return (
-
-
-
-
- sum + day.passed, 0))}
- />
-
-
- {/* */}
-
-
-
-
-
-
-
- Requests
-
-
-
-
-
-
- {dataOverTime.some((d) => d.y > 0) ? (
-
-
-
- sum + day.passed, 0))}
- />
+
+
+ }>
+ Ratelimits
+
+ {namespace.name}
+
+
+
+
+ {props.params.namespaceId}
+
+
+
+
+
+
+
+
+
+
sum + (day.total - day.passed), 0),
+ ratelimitsInBillingCycle.reduce((sum, day) => sum + day.passed, 0),
)}
/>
sum + day.total, 0))}
- />
- sum + day.passed, 0) /
- ratelimitEvents.reduce((sum, day) => sum + day.total, 0)) *
- 100,
- )}%`}
+ label="Last used"
+ value={lastUsed ? `${ms(Date.now() - lastUsed)} ago` : "never"}
/>
+
+
+
+
+
+
+
+ Requests
+
-
-
- = 1000 * 60 * 60 * 24 * 30
- ? "month"
- : granularity >= 1000 * 60 * 60 * 24
- ? "day"
- : granularity >= 1000 * 60 * 60
- ? "hour"
- : "minute"
- }
- />
-
-
- ) : (
-
-
-
-
- No usage
-
- Ratelimit something or change the range
-
-
- {snippet}
-
-
-
- )}
+
+
+
+
+ {dataOverTime.some((d) => d.y > 0) ? (
+
+
+
+ sum + day.passed, 0))}
+ />
+ sum + (day.total - day.passed), 0),
+ )}
+ />
+ sum + day.total, 0))}
+ />
+ sum + day.passed, 0) /
+ ratelimitEvents.reduce((sum, day) => sum + day.total, 0)) *
+ 100,
+ )}%`}
+ />
+
+
+
+ = 1000 * 60 * 60 * 24 * 30
+ ? "month"
+ : granularity >= 1000 * 60 * 60 * 24
+ ? "day"
+ : granularity >= 1000 * 60 * 60
+ ? "hour"
+ : "minute"
+ }
+ />
+
+
+ ) : (
+
+
+
+
+ No usage
+
+ Ratelimit something or change the range
+
+
+ {snippet}
+
+
+
+ )}
+
+
);
}
diff --git a/apps/dashboard/app/(app)/ratelimits/[namespaceId]/settings/page.tsx b/apps/dashboard/app/(app)/ratelimits/[namespaceId]/settings/page.tsx
index 0db9450eaf..cc50addaf7 100644
--- a/apps/dashboard/app/(app)/ratelimits/[namespaceId]/settings/page.tsx
+++ b/apps/dashboard/app/(app)/ratelimits/[namespaceId]/settings/page.tsx
@@ -1,9 +1,15 @@
import { CopyButton } from "@/components/dashboard/copy-button";
+import { Navbar as SubMenu } from "@/components/dashboard/navbar";
+import { Navbar } from "@/components/navbar";
+import { PageContent } from "@/components/page-content";
+import { Badge } from "@/components/ui/badge";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
import { Code } from "@/components/ui/code";
import { getTenantId } from "@/lib/auth";
import { db } from "@/lib/db";
+import { Gauge } from "@unkey/icons";
import { notFound, redirect } from "next/navigation";
+import { navigation } from "../constants";
import { DeleteNamespace } from "./delete-namespace";
import { UpdateNamespaceName } from "./update-namespace-name";
@@ -27,34 +33,66 @@ export default async function SettingsPage(props: Props) {
},
},
});
+
if (!workspace || workspace.tenantId !== tenantId) {
return redirect("/new");
}
+
const namespace = workspace.ratelimitNamespaces.find(
(namespace) => namespace.id === props.params.namespaceId,
);
+
if (!namespace) {
return notFound();
}
return (
-
-
-
-
- Namespace ID
- This is your namespace id. It's used in some API calls.
-
-
-
- {namespace.id}
-
-
-
-
-
-
-
+
+
+ }>
+ Ratelimits
+
+ {namespace.name}
+
+
+ Settings{" "}
+
+
+
+
+ {props.params.namespaceId}
+
+
+
+
+
+
+
+
+
+
+
+ Namespace ID
+
+ This is your namespace id. It's used in some API calls.
+
+
+
+
+ {namespace.id}
+
+
+
+
+
+
+
+
+
);
}
diff --git a/apps/dashboard/app/(app)/ratelimits/loading.tsx b/apps/dashboard/app/(app)/ratelimits/loading.tsx
index eb0de19b6f..cde2adf520 100644
--- a/apps/dashboard/app/(app)/ratelimits/loading.tsx
+++ b/apps/dashboard/app/(app)/ratelimits/loading.tsx
@@ -1,8 +1,9 @@
+import { PageContent } from "@/components/page-content";
import { Skeleton } from "@/components/ui/skeleton";
export default function Loading() {
return (
-
+
@@ -38,6 +39,6 @@ export default function Loading() {
-
+
);
}
diff --git a/apps/dashboard/app/(app)/ratelimits/page.tsx b/apps/dashboard/app/(app)/ratelimits/page.tsx
index 2e55b47e65..6b87aa2cda 100644
--- a/apps/dashboard/app/(app)/ratelimits/page.tsx
+++ b/apps/dashboard/app/(app)/ratelimits/page.tsx
@@ -1,11 +1,11 @@
-import { PageHeader } from "@/components/dashboard/page-header";
-
import { CopyButton } from "@/components/dashboard/copy-button";
import { EmptyPlaceholder } from "@/components/dashboard/empty-placeholder";
+import { Navbar } from "@/components/navbar";
+import { PageContent } from "@/components/page-content";
import { Code } from "@/components/ui/code";
-import { Separator } from "@/components/ui/separator";
import { getTenantId } from "@/lib/auth";
import { db } from "@/lib/db";
+import { Gauge } from "@unkey/icons";
import { Button } from "@unkey/ui";
import { BookOpen, Scan } from "lucide-react";
import Link from "next/link";
@@ -49,47 +49,50 @@ export default async function RatelimitOverviewPage() {
return (
- ]}
- />
-
-
- {workspace.ratelimitNamespaces.length > 0 ? (
-
- {workspace.ratelimitNamespaces.map((namespace) => (
-
-
-
-
-
- ))}
-
- ) : (
-
-
-
-
- No Namespaces found
-
- You haven't created any Namespaces yet. Create one by performing a limit request as
- shown below.
-
-
- {snippet}
-
-
-
-
-
-
-
-
- )}
+
+ }>
+ Ratelimits
+
+
+
+
+
+
+ {workspace.ratelimitNamespaces.length > 0 ? (
+
+ {workspace.ratelimitNamespaces.map((namespace) => (
+
+
+
+
+
+ ))}
+
+ ) : (
+
+
+
+
+ No Namespaces found
+
+ You haven't created any Namespaces yet. Create one by performing a limit request
+ as shown below.
+
+
+ {snippet}
+
+
+
+
+
+
+
+
+ )}
+
);
}
diff --git a/apps/dashboard/app/(app)/settings/constants.ts b/apps/dashboard/app/(app)/settings/constants.ts
new file mode 100644
index 0000000000..2d58f6c7d8
--- /dev/null
+++ b/apps/dashboard/app/(app)/settings/constants.ts
@@ -0,0 +1,32 @@
+export const navigation = [
+ {
+ label: "General",
+ href: "/settings/general",
+ segment: "general",
+ },
+ {
+ label: "Team",
+ href: "/settings/team",
+ segment: "team",
+ },
+ {
+ label: "Root Keys",
+ href: "/settings/root-keys",
+ segment: "root-keys",
+ },
+ {
+ label: "Billing",
+ href: "/settings/billing",
+ segment: "billing",
+ },
+ {
+ label: "Vercel Integration",
+ href: "/settings/vercel",
+ segment: "vercel",
+ },
+ {
+ label: "User",
+ href: "/settings/user",
+ segment: "user",
+ },
+];
diff --git a/apps/dashboard/app/(app)/settings/general/page.tsx b/apps/dashboard/app/(app)/settings/general/page.tsx
index d6ef5e5bee..84b6ff3622 100644
--- a/apps/dashboard/app/(app)/settings/general/page.tsx
+++ b/apps/dashboard/app/(app)/settings/general/page.tsx
@@ -1,11 +1,17 @@
import { CopyButton } from "@/components/dashboard/copy-button";
+import { Navbar as SubMenu } from "@/components/dashboard/navbar";
+import { Navbar } from "@/components/navbar";
+import { PageContent } from "@/components/page-content";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
import { Code } from "@/components/ui/code";
import { getTenantId } from "@/lib/auth";
import { db } from "@/lib/db";
+import { Gear } from "@unkey/icons";
import { redirect } from "next/navigation";
+import { navigation } from "../constants";
import { UpdateWorkspaceImage } from "./update-workspace-image";
import { UpdateWorkspaceName } from "./update-workspace-name";
+
export const dynamic = "force-dynamic";
export default async function SettingsPage() {
@@ -20,23 +26,37 @@ export default async function SettingsPage() {
}
return (
-
-
-
-
-
- Workspace ID
- This is your workspace id. It's used in some API calls.
-
-
-
- {workspace.id}
-
-
-
-
-
-
+
+
+ }>
+
+ Settings
+
+
+
+
+
+
+
+
+
+
+ Workspace ID
+
+ This is your workspace id. It's used in some API calls.
+
+
+
+
+ {workspace.id}
+
+
+
+
+
+
+
+
);
}
diff --git a/apps/dashboard/app/(app)/settings/layout.tsx b/apps/dashboard/app/(app)/settings/layout.tsx
deleted file mode 100644
index 5cbbb76e90..0000000000
--- a/apps/dashboard/app/(app)/settings/layout.tsx
+++ /dev/null
@@ -1,63 +0,0 @@
-import type * as React from "react";
-
-import { Navbar } from "@/components/dashboard/navbar";
-
-export const dynamic = "force-dynamic";
-export const runtime = "edge";
-
-export default function SettingsLayout({
- children,
-}: {
- children: React.ReactNode;
-}) {
- const navigation = [
- {
- label: "General",
- href: "/settings/general",
- segment: "general",
- },
- {
- label: "Team",
- href: "/settings/team",
- segment: "team",
- },
- {
- label: "Root Keys",
- href: "/settings/root-keys",
- segment: "root-keys",
- },
- {
- label: "Billing",
- href: "/settings/billing",
- segment: "billing",
- },
- {
- label: "Vercel Integration",
- href: "/settings/vercel",
- segment: "vercel",
- },
- // {
- // label: "Webhooks",
- // href: "/settings/webhooks",
- // segment: "webhooks",
- // },
- {
- label: "User",
- href: "/settings/user",
- segment: "user",
- },
- ];
-
- return (
-
-
- Settings
- Manage your workspace settings.
-
-
-
-
- {children}
-
- );
-}
diff --git a/apps/dashboard/app/(app)/settings/loading.tsx b/apps/dashboard/app/(app)/settings/loading.tsx
index b04687a5ae..70879711de 100644
--- a/apps/dashboard/app/(app)/settings/loading.tsx
+++ b/apps/dashboard/app/(app)/settings/loading.tsx
@@ -1,8 +1,9 @@
+import { PageContent } from "@/components/page-content";
import { Skeleton } from "@/components/ui/skeleton";
export default function Loading() {
return (
-
+
@@ -27,6 +28,6 @@ export default function Loading() {
-
+
);
}
diff --git a/apps/dashboard/app/(app)/settings/root-keys/[keyId]/history/loading.tsx b/apps/dashboard/app/(app)/settings/root-keys/[keyId]/history/loading.tsx
deleted file mode 100644
index 853422fa66..0000000000
--- a/apps/dashboard/app/(app)/settings/root-keys/[keyId]/history/loading.tsx
+++ /dev/null
@@ -1,10 +0,0 @@
-import { Card } from "@/components/ui/card";
-import { Loader2 } from "lucide-react";
-
-export default function Loading() {
- return (
-
-
-
- );
-}
diff --git a/apps/dashboard/app/(app)/settings/root-keys/[keyId]/history/page.tsx b/apps/dashboard/app/(app)/settings/root-keys/[keyId]/history/page.tsx
deleted file mode 100644
index 3e8995320f..0000000000
--- a/apps/dashboard/app/(app)/settings/root-keys/[keyId]/history/page.tsx
+++ /dev/null
@@ -1,51 +0,0 @@
-import { getTenantId } from "@/lib/auth";
-import { clickhouse } from "@/lib/clickhouse";
-import { db } from "@/lib/db";
-import { env } from "@/lib/env";
-import { notFound } from "next/navigation";
-import { AccessTable } from "./access-table";
-
-export const runtime = "edge";
-
-export default async function HistoryPage(props: {
- params: { keyId: string };
-}) {
- const tenantId = getTenantId();
-
- const workspace = await db.query.workspaces.findFirst({
- where: (table, { eq }) => eq(table.tenantId, tenantId),
- });
- if (!workspace) {
- return notFound();
- }
- const { UNKEY_WORKSPACE_ID } = env();
-
- const key = await db.query.keys.findFirst({
- where: (table, { and, eq, isNull }) =>
- and(
- eq(table.workspaceId, UNKEY_WORKSPACE_ID),
- eq(table.forWorkspaceId, workspace.id),
- eq(table.id, props.params.keyId),
- isNull(table.deletedAt),
- ),
- with: {
- keyAuth: {
- with: {
- api: true,
- },
- },
- },
- });
- if (!key?.keyAuth?.api) {
- return notFound();
- }
- const history = await clickhouse.verifications
- .logs({
- workspaceId: UNKEY_WORKSPACE_ID,
- keySpaceId: key.keyAuthId,
- keyId: key.id,
- })
- .then((res) => res.val!);
-
- return ;
-}
diff --git a/apps/dashboard/app/(app)/settings/root-keys/[keyId]/page.tsx b/apps/dashboard/app/(app)/settings/root-keys/[keyId]/page.tsx
index bb1de70698..dc82fe05c5 100644
--- a/apps/dashboard/app/(app)/settings/root-keys/[keyId]/page.tsx
+++ b/apps/dashboard/app/(app)/settings/root-keys/[keyId]/page.tsx
@@ -1,9 +1,12 @@
+import { Navbar } from "@/components/navbar";
+import { PageContent } from "@/components/page-content";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { DialogTrigger } from "@/components/ui/dialog";
import { getTenantId } from "@/lib/auth";
import { clickhouse } from "@/lib/clickhouse";
import { type Permission, db, eq, schema } from "@/lib/db";
import { env } from "@/lib/env";
+import { Gear } from "@unkey/icons";
import { Button } from "@unkey/ui";
import { notFound } from "next/navigation";
import { AccessTable } from "./history/access-table";
@@ -123,45 +126,64 @@ export default async function RootKeyPage(props: {
const apisWithoutActivePermissions = apis.filter((api) => !api.hasActivePermissions);
return (
-
- {permissions.some((p) => p.name === "*") ? (
-
- ) : null}
-
-
-
-
-
- {apisWithActivePermissions.map((api) => (
-
- ))}
-
-
- {apisWithoutActivePermissions.length > 0 && (
-
-
-
-
-
- )}
-
-
-
+
+
+ }>
+
+ Settings
+
+
+
+
+
+ {permissions.some((p) => p.name === "*") ? (
+
+ ) : null}
+
+
+
+
+
+ {apisWithActivePermissions.map((api) => (
+
+ ))}
+
+
+ {apisWithoutActivePermissions.length > 0 && (
+
+
+
+
+
+ )}
+
+
+
+
+
);
}
-function UsageHistoryCard(props: { accessTableProps: React.ComponentProps }) {
+function UsageHistoryCard(props: {
+ accessTableProps: React.ComponentProps;
+}) {
return (
diff --git a/apps/dashboard/app/(app)/settings/root-keys/[keyId]/permissions/loading.tsx b/apps/dashboard/app/(app)/settings/root-keys/[keyId]/permissions/loading.tsx
deleted file mode 100644
index 853422fa66..0000000000
--- a/apps/dashboard/app/(app)/settings/root-keys/[keyId]/permissions/loading.tsx
+++ /dev/null
@@ -1,10 +0,0 @@
-import { Card } from "@/components/ui/card";
-import { Loader2 } from "lucide-react";
-
-export default function Loading() {
- return (
-
-
-
- );
-}
diff --git a/apps/dashboard/app/(app)/settings/root-keys/new/page.tsx b/apps/dashboard/app/(app)/settings/root-keys/new/page.tsx
index 64c38b1682..4acfce0b3e 100644
--- a/apps/dashboard/app/(app)/settings/root-keys/new/page.tsx
+++ b/apps/dashboard/app/(app)/settings/root-keys/new/page.tsx
@@ -1,6 +1,9 @@
import { PageHeader } from "@/components/dashboard/page-header";
+import { Navbar } from "@/components/navbar";
+import { PageContent } from "@/components/page-content";
import { getTenantId } from "@/lib/auth";
import { db } from "@/lib/db";
+import { Gear } from "@unkey/icons";
import { redirect } from "next/navigation";
import { Client } from "./client";
@@ -25,13 +28,23 @@ export default async function SettingsKeysPage(_props: {
}
return (
-
-
+
+
+ }>
+ Settings
+
+ Create new key
+
+
+
+
+
-
+
+
);
}
diff --git a/apps/dashboard/app/(app)/settings/root-keys/page.tsx b/apps/dashboard/app/(app)/settings/root-keys/page.tsx
index 6dc6d1c548..2fb584f3a8 100644
--- a/apps/dashboard/app/(app)/settings/root-keys/page.tsx
+++ b/apps/dashboard/app/(app)/settings/root-keys/page.tsx
@@ -1,10 +1,15 @@
+import { Navbar as SubMenu } from "@/components/dashboard/navbar";
import { PageHeader } from "@/components/dashboard/page-header";
import { RootKeyTable } from "@/components/dashboard/root-key-table";
+import { Navbar } from "@/components/navbar";
+import { PageContent } from "@/components/page-content";
import { getTenantId } from "@/lib/auth";
import { db } from "@/lib/db";
+import { Gear } from "@unkey/icons";
import { Button } from "@unkey/ui";
import Link from "next/link";
import { redirect } from "next/navigation";
+import { navigation } from "../constants";
export const revalidate = 0;
@@ -32,19 +37,31 @@ export default async function SettingsKeysPage(_props: {
});
return (
-
-
+
+ }>
+
+ Settings
+
+
+
- ,
- ]}
- />
-
-
-
+
+
+
+
+
+
+
+
+
+
+
);
}
diff --git a/apps/dashboard/app/(app)/settings/team/page.tsx b/apps/dashboard/app/(app)/settings/team/page.tsx
index 3697973dcb..479f4ad365 100644
--- a/apps/dashboard/app/(app)/settings/team/page.tsx
+++ b/apps/dashboard/app/(app)/settings/team/page.tsx
@@ -20,6 +20,9 @@ import {
import { useAuth, useClerk, useOrganization } from "@clerk/nextjs";
import { Loading } from "@/components/dashboard/loading";
+import { Navbar as SubMenu } from "@/components/dashboard/navbar";
+import { Navbar } from "@/components/navbar";
+import { PageContent } from "@/components/page-content";
import {
Select,
SelectContent,
@@ -30,7 +33,9 @@ import {
} from "@/components/ui/select";
import { toast } from "@/components/ui/toaster";
import type { MembershipRole } from "@clerk/types";
+import { Gear } from "@unkey/icons";
import Link from "next/link";
+import { navigation } from "../constants";
type Member = {
id: string;
@@ -45,16 +50,30 @@ export default function TeamPage() {
if (!organization) {
return (
-
- This is a personal account
-
- You can only manage teams in paid workspaces.
-
+
+
+ }>
+
+ Settings
+
+
+
+
+
+
+
+ This is a personal account
+
+ You can only manage teams in paid workspaces.
+
-
-
-
-
+
+
+
+
+
+
+
);
}
@@ -89,9 +108,21 @@ export default function TeamPage() {
return (
-
+
+ }>
+
+ Settings
+
+
+
+
+
+
+
- {tab === "members" ? : }
+ {tab === "members" ? : }
+
+
);
}
diff --git a/apps/dashboard/components/dashboard/navbar.tsx b/apps/dashboard/components/dashboard/navbar.tsx
index 3d7d4f1104..19e750ae89 100644
--- a/apps/dashboard/components/dashboard/navbar.tsx
+++ b/apps/dashboard/components/dashboard/navbar.tsx
@@ -8,17 +8,35 @@ import { cn } from "@/lib/utils";
import { useRouter, useSelectedLayoutSegment } from "next/navigation";
type Props = {
- navigation: { label: string; href: string; segment: string | null; tag?: string }[];
+ navigation: {
+ label: string;
+ href: string;
+ segment: string | null;
+ tag?: string;
+ isActive?: boolean;
+ }[];
+ segment?: string;
className?: string;
};
-export const Navbar: React.FC> = ({ navigation, className }) => {
+export const Navbar: React.FC> = ({
+ navigation,
+ className,
+ segment,
+}) => {
return (