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 (
-
-
-
- APIs
-
-
-
- {api.name}
-
-
-
-
- Keys
-
-
-
-
-
- ...
- {props.params.keyId.substring(props.params.keyId.length - 4, props.params.keyId.length)}
-
-
-
-
- );
-}
-
-export default function PageBreadcrumb(props: PageProps) {
- return (
-
-
-
- );
-}
diff --git a/apps/dashboard/app/(app)/@breadcrumb/apis/[apiId]/keys/[keyAuthId]/[keyId]/settings/page.tsx b/apps/dashboard/app/(app)/@breadcrumb/apis/[apiId]/keys/[keyAuthId]/[keyId]/settings/page.tsx
deleted file mode 100644
index 1c81f2c3a2..0000000000
--- a/apps/dashboard/app/(app)/@breadcrumb/apis/[apiId]/keys/[keyAuthId]/[keyId]/settings/page.tsx
+++ /dev/null
@@ -1,83 +0,0 @@
-import { BreadcrumbSkeleton } from "@/components/dashboard/breadcrumb-skeleton";
-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 (
-
-
-
- APIs
-
-
-
- {api.name}
-
-
-
-
- Keys
-
-
-
-
-
- ...
- {props.params.keyId.substring(props.params.keyId.length - 4, props.params.keyId.length)}
-
-
-
-
- Settings
-
-
-
- );
-}
-
-export default function PageBreadcrumb(props: PageProps) {
- return (
- }>
-
-
- );
-}
diff --git a/apps/dashboard/app/(app)/@breadcrumb/apis/[apiId]/keys/[keyAuthId]/new/page.tsx b/apps/dashboard/app/(app)/@breadcrumb/apis/[apiId]/keys/[keyAuthId]/new/page.tsx
deleted file mode 100644
index 8e4b4de9cd..0000000000
--- a/apps/dashboard/app/(app)/@breadcrumb/apis/[apiId]/keys/[keyAuthId]/new/page.tsx
+++ /dev/null
@@ -1,74 +0,0 @@
-import { BreadcrumbSkeleton } from "@/components/dashboard/breadcrumb-skeleton";
-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 (
-
-
-
- APIs
-
-
-
- {api.name}
-
-
-
-
- Keys
-
-
-
-
- Create new key
-
-
-
- );
-}
-
-export default function PageBreadcrumb(props: PageProps) {
- return (
- }>
-
-
- );
-}
diff --git a/apps/dashboard/app/(app)/@breadcrumb/apis/[apiId]/keys/[keyAuthId]/page.tsx b/apps/dashboard/app/(app)/@breadcrumb/apis/[apiId]/keys/[keyAuthId]/page.tsx
deleted file mode 100644
index 818cae0682..0000000000
--- a/apps/dashboard/app/(app)/@breadcrumb/apis/[apiId]/keys/[keyAuthId]/page.tsx
+++ /dev/null
@@ -1,70 +0,0 @@
-import { BreadcrumbSkeleton } from "@/components/dashboard/breadcrumb-skeleton";
-import {
- Breadcrumb,
- BreadcrumbItem,
- BreadcrumbLink,
- BreadcrumbList,
- BreadcrumbPage,
- BreadcrumbSeparator,
-} from "@/components/ui/breadcrumb";
-import { tags } from "@/lib/cache";
-
-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 };
-};
-
-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"],
- { tags: [tags.api(props.params.apiId)] },
- );
-
- const api = await getApiById(props.params.apiId);
- if (!api || api.workspace.tenantId !== tenantId) {
- return null;
- }
-
- return (
-
-
-
- APIs
-
-
-
- {api.name}
-
-
-
- Keys
-
-
-
- );
-}
-
-export default function PageBreadcrumb(props: PageProps) {
- return (
- }>
-
-
- );
-}
diff --git a/apps/dashboard/app/(app)/@breadcrumb/apis/[apiId]/page.tsx b/apps/dashboard/app/(app)/@breadcrumb/apis/[apiId]/page.tsx
deleted file mode 100644
index b586b79e24..0000000000
--- a/apps/dashboard/app/(app)/@breadcrumb/apis/[apiId]/page.tsx
+++ /dev/null
@@ -1,66 +0,0 @@
-import {
- Breadcrumb,
- BreadcrumbItem,
- BreadcrumbLink,
- BreadcrumbList,
- BreadcrumbPage,
- BreadcrumbSeparator,
-} from "@/components/ui/breadcrumb";
-import { unstable_cache as cache } from "next/cache";
-
-import { BreadcrumbSkeleton } from "@/components/dashboard/breadcrumb-skeleton";
-import { getTenantId } from "@/lib/auth";
-import { tags } from "@/lib/cache";
-import { db } from "@/lib/db";
-import { Suspense } from "react";
-
-export const dynamic = "force-dynamic";
-export const runtime = "edge";
-
-type PageProps = {
- params: { apiId: 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"],
- { tags: [tags.api(props.params.apiId)] },
- );
-
- const api = await getApiById(props.params.apiId);
- if (!api || api.workspace.tenantId !== tenantId) {
- return null;
- }
-
- return (
-
-
-
- APIs
-
-
-
- {api.name}
-
-
-
- );
-}
-
-export default function PageBreadcrumb(props: PageProps) {
- return (
- }>
-
-
- );
-}
diff --git a/apps/dashboard/app/(app)/@breadcrumb/apis/[apiId]/settings/page.tsx b/apps/dashboard/app/(app)/@breadcrumb/apis/[apiId]/settings/page.tsx
deleted file mode 100644
index dc69639b2d..0000000000
--- a/apps/dashboard/app/(app)/@breadcrumb/apis/[apiId]/settings/page.tsx
+++ /dev/null
@@ -1,67 +0,0 @@
-import { BreadcrumbSkeleton } from "@/components/dashboard/breadcrumb-skeleton";
-import {
- Breadcrumb,
- BreadcrumbItem,
- BreadcrumbLink,
- BreadcrumbList,
- BreadcrumbPage,
- BreadcrumbSeparator,
-} from "@/components/ui/breadcrumb";
-import { getTenantId } from "@/lib/auth";
-import { tags } from "@/lib/cache";
-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 } };
-
-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"],
- { tags: [tags.api(props.params.apiId)] },
- );
-
- const api = await getApiById(props.params.apiId);
- if (!api || api.workspace.tenantId !== tenantId) {
- return null;
- }
-
- return (
-
-
-
- APIs
-
-
-
- {api.name}
-
-
-
- Settings
-
-
-
- );
-}
-
-export default function PageBreadcrumb(props: PageProps) {
- return (
- }>
-
-
- );
-}
diff --git a/apps/dashboard/app/(app)/@breadcrumb/apis/page.tsx b/apps/dashboard/app/(app)/@breadcrumb/apis/page.tsx
deleted file mode 100644
index 75983528a7..0000000000
--- a/apps/dashboard/app/(app)/@breadcrumb/apis/page.tsx
+++ /dev/null
@@ -1,5 +0,0 @@
-import { PageHeader } from "@/components/dashboard/page-header";
-
-export default function PageBreadcrumb() {
- return ;
-}
diff --git a/apps/dashboard/app/(app)/@breadcrumb/audit/[...path]/page.tsx b/apps/dashboard/app/(app)/@breadcrumb/audit/[...path]/page.tsx
deleted file mode 100644
index eaa3a82cbd..0000000000
--- a/apps/dashboard/app/(app)/@breadcrumb/audit/[...path]/page.tsx
+++ /dev/null
@@ -1,4 +0,0 @@
-// No breadcrumb
-export default function NoBreadcrumb() {
- return null;
-}
diff --git a/apps/dashboard/app/(app)/@breadcrumb/audit/page.tsx b/apps/dashboard/app/(app)/@breadcrumb/audit/page.tsx
deleted file mode 100644
index eaa3a82cbd..0000000000
--- a/apps/dashboard/app/(app)/@breadcrumb/audit/page.tsx
+++ /dev/null
@@ -1,4 +0,0 @@
-// No breadcrumb
-export default function NoBreadcrumb() {
- return null;
-}
diff --git a/apps/dashboard/app/(app)/@breadcrumb/authorization/permissions/[permissionId]/page.tsx b/apps/dashboard/app/(app)/@breadcrumb/authorization/permissions/[permissionId]/page.tsx
deleted file mode 100644
index 0c031fa95e..0000000000
--- a/apps/dashboard/app/(app)/@breadcrumb/authorization/permissions/[permissionId]/page.tsx
+++ /dev/null
@@ -1,62 +0,0 @@
-import {
- Breadcrumb,
- BreadcrumbItem,
- BreadcrumbLink,
- BreadcrumbList,
- BreadcrumbPage,
- BreadcrumbSeparator,
-} from "@/components/ui/breadcrumb";
-
-import { BreadcrumbSkeleton } from "@/components/dashboard/breadcrumb-skeleton";
-import { tags } from "@/lib/cache";
-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: { permissionId: string };
-};
-
-async function AsyncPageBreadcrumb(props: PageProps) {
- const getPermissionById = cache(
- async (permissionId: string) =>
- db.query.permissions.findFirst({
- where: (table, { eq }) => eq(table.id, permissionId),
- }),
- ["permissionById"],
- { tags: [tags.permission(props.params.permissionId)] },
- );
-
- const permissions = await getPermissionById(props.params.permissionId);
- if (!permissions) {
- return null;
- }
-
- return (
-
-
-
- Authorization
-
-
-
- Permissions
-
-
-
- {permissions.name}
-
-
-
- );
-}
-
-export default function PageBreadcrumb(props: PageProps) {
- return (
- }>
-
-
- );
-}
diff --git a/apps/dashboard/app/(app)/@breadcrumb/authorization/permissions/page.tsx b/apps/dashboard/app/(app)/@breadcrumb/authorization/permissions/page.tsx
deleted file mode 100644
index e121ae339a..0000000000
--- a/apps/dashboard/app/(app)/@breadcrumb/authorization/permissions/page.tsx
+++ /dev/null
@@ -1,23 +0,0 @@
-import {
- Breadcrumb,
- BreadcrumbItem,
- BreadcrumbList,
- BreadcrumbPage,
- BreadcrumbSeparator,
-} from "@/components/ui/breadcrumb";
-
-export default function PageBreadcrumb() {
- return (
-
-
-
- Authorization
-
-
-
- Permissions
-
-
-
- );
-}
diff --git a/apps/dashboard/app/(app)/@breadcrumb/authorization/roles/[roleId]/page.tsx b/apps/dashboard/app/(app)/@breadcrumb/authorization/roles/[roleId]/page.tsx
deleted file mode 100644
index 45aae47e92..0000000000
--- a/apps/dashboard/app/(app)/@breadcrumb/authorization/roles/[roleId]/page.tsx
+++ /dev/null
@@ -1,62 +0,0 @@
-import {
- Breadcrumb,
- BreadcrumbItem,
- BreadcrumbLink,
- BreadcrumbList,
- BreadcrumbPage,
- BreadcrumbSeparator,
-} from "@/components/ui/breadcrumb";
-
-import { BreadcrumbSkeleton } from "@/components/dashboard/breadcrumb-skeleton";
-import { tags } from "@/lib/cache";
-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: { roleId: string };
-};
-
-async function AsyncPageBreadcrumb(props: PageProps) {
- const getWorkspaceByRoleId = cache(
- async (roleId: string) =>
- await db.query.roles.findFirst({
- where: (table, { eq }) => eq(table.id, roleId),
- }),
- ["roleById"],
- { tags: [tags.role(props.params.roleId)] },
- );
-
- const role = await getWorkspaceByRoleId(props.params.roleId);
- if (!role) {
- return null;
- }
-
- return (
-
-
-
- Authorization
-
-
-
- Roles
-
-
-
- {role.name}
-
-
-
- );
-}
-
-export default function PageBreadcrumb(props: PageProps) {
- return (
- }>
-
-
- );
-}
diff --git a/apps/dashboard/app/(app)/@breadcrumb/authorization/roles/page.tsx b/apps/dashboard/app/(app)/@breadcrumb/authorization/roles/page.tsx
deleted file mode 100644
index a4ddaadccf..0000000000
--- a/apps/dashboard/app/(app)/@breadcrumb/authorization/roles/page.tsx
+++ /dev/null
@@ -1,23 +0,0 @@
-import {
- Breadcrumb,
- BreadcrumbItem,
- BreadcrumbList,
- BreadcrumbPage,
- BreadcrumbSeparator,
-} from "@/components/ui/breadcrumb";
-
-export default function PageBreadcrumb() {
- return (
-
-
-
- Authorization
-
-
-
- Roles
-
-
-
- );
-}
diff --git a/apps/dashboard/app/(app)/@breadcrumb/debug/[...path]/page.tsx b/apps/dashboard/app/(app)/@breadcrumb/debug/[...path]/page.tsx
deleted file mode 100644
index eaa3a82cbd..0000000000
--- a/apps/dashboard/app/(app)/@breadcrumb/debug/[...path]/page.tsx
+++ /dev/null
@@ -1,4 +0,0 @@
-// No breadcrumb
-export default function NoBreadcrumb() {
- return null;
-}
diff --git a/apps/dashboard/app/(app)/@breadcrumb/debug/page.tsx b/apps/dashboard/app/(app)/@breadcrumb/debug/page.tsx
deleted file mode 100644
index eaa3a82cbd..0000000000
--- a/apps/dashboard/app/(app)/@breadcrumb/debug/page.tsx
+++ /dev/null
@@ -1,4 +0,0 @@
-// No breadcrumb
-export default function NoBreadcrumb() {
- return null;
-}
diff --git a/apps/dashboard/app/(app)/@breadcrumb/default.tsx b/apps/dashboard/app/(app)/@breadcrumb/default.tsx
deleted file mode 100644
index 6ddf1b76fa..0000000000
--- a/apps/dashboard/app/(app)/@breadcrumb/default.tsx
+++ /dev/null
@@ -1,3 +0,0 @@
-export default function Default() {
- return null;
-}
diff --git a/apps/dashboard/app/(app)/@breadcrumb/identities/[identityId]/page.tsx b/apps/dashboard/app/(app)/@breadcrumb/identities/[identityId]/page.tsx
deleted file mode 100644
index e2fa0b4ab9..0000000000
--- a/apps/dashboard/app/(app)/@breadcrumb/identities/[identityId]/page.tsx
+++ /dev/null
@@ -1,64 +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: { identityId: string };
-};
-
-async function AsyncPageBreadcrumb(props: PageProps) {
- const tenantId = getTenantId();
-
- const getIdentityById = cache(
- async (identityId: string) =>
- db.query.identities.findFirst({
- where: (table, { eq }) => eq(table.id, identityId),
-
- with: {
- workspace: true,
- },
- }),
- ["identityById"],
- );
-
- const identity = await getIdentityById(props.params.identityId);
- if (!identity || identity.workspace.tenantId !== tenantId) {
- return null;
- }
-
- return (
-
-
-
- Identities
-
-
-
-
- {identity.externalId}
-
-
-
- );
-}
-
-export default function PageBreadcrumb(props: PageProps) {
- return (
-
-
-
- );
-}
diff --git a/apps/dashboard/app/(app)/@breadcrumb/identities/page.tsx b/apps/dashboard/app/(app)/@breadcrumb/identities/page.tsx
deleted file mode 100644
index 72cad67391..0000000000
--- a/apps/dashboard/app/(app)/@breadcrumb/identities/page.tsx
+++ /dev/null
@@ -1,27 +0,0 @@
-import {
- Breadcrumb,
- BreadcrumbItem,
- BreadcrumbLink,
- BreadcrumbList,
-} from "@/components/ui/breadcrumb";
-
-export const dynamic = "force-dynamic";
-export const runtime = "edge";
-
-export default function PageBreadcrumb() {
- return (
-
-
-
-
- Identities
-
-
-
-
-
- Beta
-
-
- );
-}
diff --git a/apps/dashboard/app/(app)/@breadcrumb/logs/page.tsx b/apps/dashboard/app/(app)/@breadcrumb/logs/page.tsx
deleted file mode 100644
index 382fddc8c0..0000000000
--- a/apps/dashboard/app/(app)/@breadcrumb/logs/page.tsx
+++ /dev/null
@@ -1,3 +0,0 @@
-export default function NoBreadcrumb() {
- return null;
-}
diff --git a/apps/dashboard/app/(app)/@breadcrumb/monitors/[...path]/page.tsx b/apps/dashboard/app/(app)/@breadcrumb/monitors/[...path]/page.tsx
deleted file mode 100644
index eaa3a82cbd..0000000000
--- a/apps/dashboard/app/(app)/@breadcrumb/monitors/[...path]/page.tsx
+++ /dev/null
@@ -1,4 +0,0 @@
-// No breadcrumb
-export default function NoBreadcrumb() {
- return null;
-}
diff --git a/apps/dashboard/app/(app)/@breadcrumb/monitors/page.tsx b/apps/dashboard/app/(app)/@breadcrumb/monitors/page.tsx
deleted file mode 100644
index eaa3a82cbd..0000000000
--- a/apps/dashboard/app/(app)/@breadcrumb/monitors/page.tsx
+++ /dev/null
@@ -1,4 +0,0 @@
-// No breadcrumb
-export default function NoBreadcrumb() {
- return null;
-}
diff --git a/apps/dashboard/app/(app)/@breadcrumb/overview/[...path]/page.tsx b/apps/dashboard/app/(app)/@breadcrumb/overview/[...path]/page.tsx
deleted file mode 100644
index eaa3a82cbd..0000000000
--- a/apps/dashboard/app/(app)/@breadcrumb/overview/[...path]/page.tsx
+++ /dev/null
@@ -1,4 +0,0 @@
-// No breadcrumb
-export default function NoBreadcrumb() {
- return null;
-}
diff --git a/apps/dashboard/app/(app)/@breadcrumb/overview/page.tsx b/apps/dashboard/app/(app)/@breadcrumb/overview/page.tsx
deleted file mode 100644
index eaa3a82cbd..0000000000
--- a/apps/dashboard/app/(app)/@breadcrumb/overview/page.tsx
+++ /dev/null
@@ -1,4 +0,0 @@
-// No breadcrumb
-export default function NoBreadcrumb() {
- return null;
-}
diff --git a/apps/dashboard/app/(app)/@breadcrumb/page.tsx b/apps/dashboard/app/(app)/@breadcrumb/page.tsx
deleted file mode 100644
index 85911e6c31..0000000000
--- a/apps/dashboard/app/(app)/@breadcrumb/page.tsx
+++ /dev/null
@@ -1 +0,0 @@
-export { default } from "./default";
diff --git a/apps/dashboard/app/(app)/@breadcrumb/ratelimits/[namespaceId]/logs/page.tsx b/apps/dashboard/app/(app)/@breadcrumb/ratelimits/[namespaceId]/logs/page.tsx
deleted file mode 100644
index 51f11f99d6..0000000000
--- a/apps/dashboard/app/(app)/@breadcrumb/ratelimits/[namespaceId]/logs/page.tsx
+++ /dev/null
@@ -1,66 +0,0 @@
-import {
- Breadcrumb,
- BreadcrumbItem,
- BreadcrumbLink,
- BreadcrumbList,
- BreadcrumbPage,
- BreadcrumbSeparator,
-} from "@/components/ui/breadcrumb";
-
-import { BreadcrumbSkeleton } from "@/components/dashboard/breadcrumb-skeleton";
-import { tags } from "@/lib/cache";
-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: { namespaceId: string };
-};
-
-async function AsyncPageBreadcrumb(props: PageProps) {
- const getNamespaceById = cache(
- async (namespaceId: string) =>
- db.query.ratelimitNamespaces.findFirst({
- where: (table, { eq, and, isNull }) =>
- and(eq(table.id, namespaceId), isNull(table.deletedAt)),
- }),
- ["namespaceById"],
- { tags: [tags.namespace(props.params.namespaceId)] },
- );
-
- const namespace = await getNamespaceById(props.params.namespaceId);
- if (!namespace) {
- return null;
- }
-
- return (
-
-
-
- Ratelimits
-
-
-
-
- {namespace.name}
-
-
-
-
- Logs
-
-
-
- );
-}
-
-export default function PageBreadcrumb(props: PageProps) {
- return (
- }>
-
-
- );
-}
diff --git a/apps/dashboard/app/(app)/@breadcrumb/ratelimits/[namespaceId]/overrides/page.tsx b/apps/dashboard/app/(app)/@breadcrumb/ratelimits/[namespaceId]/overrides/page.tsx
deleted file mode 100644
index 858a7cd0f1..0000000000
--- a/apps/dashboard/app/(app)/@breadcrumb/ratelimits/[namespaceId]/overrides/page.tsx
+++ /dev/null
@@ -1,66 +0,0 @@
-import {
- Breadcrumb,
- BreadcrumbItem,
- BreadcrumbLink,
- BreadcrumbList,
- BreadcrumbPage,
- BreadcrumbSeparator,
-} from "@/components/ui/breadcrumb";
-
-import { BreadcrumbSkeleton } from "@/components/dashboard/breadcrumb-skeleton";
-import { tags } from "@/lib/cache";
-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: { namespaceId: string };
-};
-
-async function AsyncPageBreadcrumb(props: PageProps) {
- const getNamespaceById = cache(
- async (namespaceId: string) =>
- db.query.ratelimitNamespaces.findFirst({
- where: (table, { eq, and, isNull }) =>
- and(eq(table.id, namespaceId), isNull(table.deletedAt)),
- }),
- ["namespaceById"],
- { tags: [tags.namespace(props.params.namespaceId)] },
- );
-
- const namespace = await getNamespaceById(props.params.namespaceId);
- if (!namespace) {
- return null;
- }
-
- return (
-
-
-
- Ratelimits
-
-
-
-
- {namespace.name}
-
-
-
-
- Overrides
-
-
-
- );
-}
-
-export default function PageBreadcrumb(props: PageProps) {
- return (
- }>
-
-
- );
-}
diff --git a/apps/dashboard/app/(app)/@breadcrumb/ratelimits/[namespaceId]/page.tsx b/apps/dashboard/app/(app)/@breadcrumb/ratelimits/[namespaceId]/page.tsx
deleted file mode 100644
index 8fbce359e9..0000000000
--- a/apps/dashboard/app/(app)/@breadcrumb/ratelimits/[namespaceId]/page.tsx
+++ /dev/null
@@ -1,61 +0,0 @@
-import {
- Breadcrumb,
- BreadcrumbItem,
- BreadcrumbLink,
- BreadcrumbList,
- BreadcrumbPage,
- BreadcrumbSeparator,
-} from "@/components/ui/breadcrumb";
-
-import { BreadcrumbSkeleton } from "@/components/dashboard/breadcrumb-skeleton";
-import { tags } from "@/lib/cache";
-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: { namespaceId: string };
-};
-
-async function AsyncPageBreadcrumb(props: PageProps) {
- const getNamespaceById = cache(
- async (namespaceId: string) =>
- db.query.ratelimitNamespaces.findFirst({
- where: (table, { eq, and, isNull }) =>
- and(eq(table.id, namespaceId), isNull(table.deletedAt)),
- }),
-
- ["namespaceById"],
- { tags: [tags.namespace(props.params.namespaceId)] },
- );
-
- const namespace = await getNamespaceById(props.params.namespaceId);
- if (!namespace) {
- return null;
- }
-
- return (
-
-
-
- Ratelimits
-
-
-
- {namespace.name}
-
-
-
- );
-}
-
-export default function PageBreadcrumb(props: PageProps) {
- return (
- }>
-
-
- );
-}
diff --git a/apps/dashboard/app/(app)/@breadcrumb/ratelimits/[namespaceId]/settings/page.tsx b/apps/dashboard/app/(app)/@breadcrumb/ratelimits/[namespaceId]/settings/page.tsx
deleted file mode 100644
index 176cbbba31..0000000000
--- a/apps/dashboard/app/(app)/@breadcrumb/ratelimits/[namespaceId]/settings/page.tsx
+++ /dev/null
@@ -1,66 +0,0 @@
-import {
- Breadcrumb,
- BreadcrumbItem,
- BreadcrumbLink,
- BreadcrumbList,
- BreadcrumbPage,
- BreadcrumbSeparator,
-} from "@/components/ui/breadcrumb";
-
-import { BreadcrumbSkeleton } from "@/components/dashboard/breadcrumb-skeleton";
-import { tags } from "@/lib/cache";
-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: { namespaceId: string };
-};
-
-async function AsyncPageBreadcrumb(props: PageProps) {
- const getNamespaceById = cache(
- async (namespaceId: string) =>
- db.query.ratelimitNamespaces.findFirst({
- where: (table, { eq, and, isNull }) =>
- and(eq(table.id, namespaceId), isNull(table.deletedAt)),
- }),
- ["namespaceById"],
- { tags: [tags.namespace(props.params.namespaceId)] },
- );
-
- const namespace = await getNamespaceById(props.params.namespaceId);
- if (!namespace) {
- return null;
- }
-
- return (
-
-
-
- Ratelimits
-
-
-
-
- {namespace.name}
-
-
-
-
- Settings
-
-
-
- );
-}
-
-export default function PageBreadcrumb(props: PageProps) {
- return (
- }>
-
-
- );
-}
diff --git a/apps/dashboard/app/(app)/@breadcrumb/ratelimits/page.tsx b/apps/dashboard/app/(app)/@breadcrumb/ratelimits/page.tsx
deleted file mode 100644
index eaa3a82cbd..0000000000
--- a/apps/dashboard/app/(app)/@breadcrumb/ratelimits/page.tsx
+++ /dev/null
@@ -1,4 +0,0 @@
-// No breadcrumb
-export default function NoBreadcrumb() {
- return null;
-}
diff --git a/apps/dashboard/app/(app)/@breadcrumb/secrets/[...path]/page.tsx b/apps/dashboard/app/(app)/@breadcrumb/secrets/[...path]/page.tsx
deleted file mode 100644
index eaa3a82cbd..0000000000
--- a/apps/dashboard/app/(app)/@breadcrumb/secrets/[...path]/page.tsx
+++ /dev/null
@@ -1,4 +0,0 @@
-// No breadcrumb
-export default function NoBreadcrumb() {
- return null;
-}
diff --git a/apps/dashboard/app/(app)/@breadcrumb/secrets/page.tsx b/apps/dashboard/app/(app)/@breadcrumb/secrets/page.tsx
deleted file mode 100644
index eaa3a82cbd..0000000000
--- a/apps/dashboard/app/(app)/@breadcrumb/secrets/page.tsx
+++ /dev/null
@@ -1,4 +0,0 @@
-// No breadcrumb
-export default function NoBreadcrumb() {
- return null;
-}
diff --git a/apps/dashboard/app/(app)/@breadcrumb/semantic-cache/[...path]/page.tsx b/apps/dashboard/app/(app)/@breadcrumb/semantic-cache/[...path]/page.tsx
deleted file mode 100644
index eaa3a82cbd..0000000000
--- a/apps/dashboard/app/(app)/@breadcrumb/semantic-cache/[...path]/page.tsx
+++ /dev/null
@@ -1,4 +0,0 @@
-// No breadcrumb
-export default function NoBreadcrumb() {
- return null;
-}
diff --git a/apps/dashboard/app/(app)/@breadcrumb/semantic-cache/page.tsx b/apps/dashboard/app/(app)/@breadcrumb/semantic-cache/page.tsx
deleted file mode 100644
index eaa3a82cbd..0000000000
--- a/apps/dashboard/app/(app)/@breadcrumb/semantic-cache/page.tsx
+++ /dev/null
@@ -1,4 +0,0 @@
-// No breadcrumb
-export default function NoBreadcrumb() {
- return null;
-}
diff --git a/apps/dashboard/app/(app)/@breadcrumb/settings/[...path]/page.tsx b/apps/dashboard/app/(app)/@breadcrumb/settings/[...path]/page.tsx
deleted file mode 100644
index eaa3a82cbd..0000000000
--- a/apps/dashboard/app/(app)/@breadcrumb/settings/[...path]/page.tsx
+++ /dev/null
@@ -1,4 +0,0 @@
-// No breadcrumb
-export default function NoBreadcrumb() {
- return null;
-}
diff --git a/apps/dashboard/app/(app)/@breadcrumb/settings/page.tsx b/apps/dashboard/app/(app)/@breadcrumb/settings/page.tsx
deleted file mode 100644
index eaa3a82cbd..0000000000
--- a/apps/dashboard/app/(app)/@breadcrumb/settings/page.tsx
+++ /dev/null
@@ -1,4 +0,0 @@
-// No breadcrumb
-export default function NoBreadcrumb() {
- return null;
-}
diff --git a/apps/dashboard/app/(app)/@breadcrumb/success/page.tsx b/apps/dashboard/app/(app)/@breadcrumb/success/page.tsx
deleted file mode 100644
index eaa3a82cbd..0000000000
--- a/apps/dashboard/app/(app)/@breadcrumb/success/page.tsx
+++ /dev/null
@@ -1,4 +0,0 @@
-// No breadcrumb
-export default function NoBreadcrumb() {
- return null;
-}
diff --git a/apps/dashboard/app/(app)/apis/[apiId]/constants.ts b/apps/dashboard/app/(app)/apis/[apiId]/constants.ts
new file mode 100644
index 0000000000..9268499db4
--- /dev/null
+++ b/apps/dashboard/app/(app)/apis/[apiId]/constants.ts
@@ -0,0 +1,17 @@
+export const navigation = (apiId: string, keyAuthId: string) => [
+ {
+ label: "Overview",
+ href: `/apis/${apiId}`,
+ segment: "overview",
+ },
+ {
+ label: "Keys",
+ href: `/apis/${apiId}/keys/${keyAuthId}`,
+ segment: "keys",
+ },
+ {
+ label: "API Settings",
+ href: `/apis/${apiId}/settings`,
+ segment: "settings",
+ },
+];
diff --git a/apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/[keyId]/page.tsx b/apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/[keyId]/page.tsx
index 12f8f1468d..f94ecb0bdd 100644
--- a/apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/[keyId]/page.tsx
+++ b/apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/[keyId]/page.tsx
@@ -6,7 +6,11 @@ import { CreateNewPermission } from "@/app/(app)/authorization/permissions/creat
import type { NestedPermissions } from "@/app/(app)/authorization/roles/[roleId]/tree";
import { CreateNewRole } from "@/app/(app)/authorization/roles/create-new-role";
import { StackedColumnChart } from "@/components/dashboard/charts";
+import { CopyButton } from "@/components/dashboard/copy-button";
+import { CreateKeyButton } from "@/components/dashboard/create-key-button";
import { EmptyPlaceholder } from "@/components/dashboard/empty-placeholder";
+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 { Metric } from "@/components/ui/metric";
@@ -15,6 +19,7 @@ import { getTenantId } from "@/lib/auth";
import { clickhouse } from "@/lib/clickhouse";
import { and, db, eq, isNull, schema } from "@/lib/db";
import { formatNumber } from "@/lib/fmt";
+import { Nodes } from "@unkey/icons";
import { Button } from "@unkey/ui";
import { BarChart, Minus } from "lucide-react";
import ms from "ms";
@@ -36,6 +41,7 @@ export default async function APIKeyDetailPage(props: {
const key = await db.query.keys.findFirst({
where: and(eq(schema.keys.id, props.params.keyId), isNull(schema.keys.deletedAt)),
with: {
+ keyAuth: true,
roles: {
with: {
role: {
@@ -70,6 +76,7 @@ export default async function APIKeyDetailPage(props: {
if (!key || key.workspace.tenantId !== tenantId) {
return notFound();
}
+
const api = await db.query.apis.findFirst({
where: (table, { eq, and, isNull }) =>
and(eq(table.keyAuthId, key.keyAuthId), isNull(table.deletedAt)),
@@ -96,7 +103,11 @@ export default async function APIKeyDetailPage(props: {
keyId: key.id,
}),
clickhouse.verifications
- .latest({ workspaceId: key.workspaceId, keySpaceId: key.keyAuthId, keyId: key.id })
+ .latest({
+ workspaceId: key.workspaceId,
+ keySpaceId: key.keyAuthId,
+ keyId: key.id,
+ })
.then((res) => res.val?.at(0)?.time ?? 0),
]);
@@ -144,7 +155,10 @@ export default async function APIKeyDetailPage(props: {
...ratelimitedOverTime.map((d) => ({ ...d, category: "Ratelimited" })),
...usageExceededOverTime.map((d) => ({ ...d, category: "Usage Exceeded" })),
...disabledOverTime.map((d) => ({ ...d, category: "Disabled" })),
- ...insufficientPermissionsOverTime.map((d) => ({ ...d, category: "Insufficient Permissions" })),
+ ...insufficientPermissionsOverTime.map((d) => ({
+ ...d,
+ category: "Insufficient Permissions",
+ })),
...expiredOverTime.map((d) => ({ ...d, category: "Expired" })),
...forbiddenOverTime.map((d) => ({ ...d, category: "Forbidden" })),
];
@@ -228,124 +242,160 @@ export default async function APIKeyDetailPage(props: {
});
return (
-
-
-
-
Back to API Keys listing
-
-
-
-
-
+
+
+ }>
+ APIs
+
+ {api.name}
+
+
+
+ {key.id}
+
+
+
+
+ {key.id}
+
+
-
-
-
- }
- />
- }
- />
- }
- />
-
-
-
+
+
+
-
-
Verifications
-
-
-
+
+
+
+
+
Back to API Keys listing
+
+
+
+
-
- {verificationsData.some(({ y }) => y > 0) ? (
-
-
-
-
-
-
-
+
+
+
}
/>
-
-
+
+ }
+ />
+ }
+ />
+
+
+
+
+
+
Verifications
+
+
+
-
-
- = 1000 * 60 * 60 * 24 * 30
- ? "month"
- : granularity >= 1000 * 60 * 60 * 24
- ? "day"
- : "hour"
- }
- />
-
-
- ) : (
-
-
-
-
- Not used
-
- This key was not used in the last {interval}
-
-
- )}
+
+
+ {verificationsData.some(({ y }) => y > 0) ? (
+
+
+
+
+
+
+
+
+
+
+
+
+
+ = 1000 * 60 * 60 * 24 * 30
+ ? "month"
+ : granularity >= 1000 * 60 * 60 * 24
+ ? "day"
+ : "hour"
+ }
+ />
+
+
+ ) : (
+
+
+
+
+ Not used
+
+ This key was not used in the last {interval}
+
+
+ )}
+
+ {latestVerifications.val && latestVerifications.val.length > 0 ? (
+ <>
+
+
+ Latest Verifications
+
+
+ >
+ ) : null}
- {latestVerifications.val && latestVerifications.val.length > 0 ? (
- <>
-
- Latest Verifications
-
-
- >
- ) : null}
+
+
+
+ {Intl.NumberFormat().format(key.roles.length)} Roles{" "}
+
+
+ {Intl.NumberFormat().format(transientPermissionIds.size)} Permissions
+
+
+
+ Create New Role}
+ permissions={key.workspace.permissions}
+ />
+ Create New Permission} />
+
+
-
-
-
-
- {Intl.NumberFormat().format(key.roles.length)} Roles{" "}
-
-
- {Intl.NumberFormat().format(transientPermissionIds.size)} Permissions
-
-
-
-
Create New Role}
- permissions={key.workspace.permissions}
- />
- Create New Permission} />
+
-
-
-
+
);
}
diff --git a/apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/[keyId]/settings/page.tsx b/apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/[keyId]/settings/page.tsx
index 44dfb57fa8..4e551027c5 100644
--- a/apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/[keyId]/settings/page.tsx
+++ b/apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/[keyId]/settings/page.tsx
@@ -1,8 +1,13 @@
import { CopyButton } from "@/components/dashboard/copy-button";
+import { CreateKeyButton } from "@/components/dashboard/create-key-button";
+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 { and, db, eq, isNull, schema } from "@/lib/db";
+import { Nodes } from "@unkey/icons";
import { ArrowLeft } from "lucide-react";
import Link from "next/link";
import { notFound } from "next/navigation";
@@ -28,9 +33,9 @@ export default async function SettingsPage(props: Props) {
const key = await db.query.keys.findFirst({
where: and(eq(schema.keys.id, props.params.keyId), isNull(schema.keys.deletedAt)),
-
with: {
workspace: true,
+ keyAuth: { with: { api: true } },
},
});
if (!key || key.workspace.tenantId !== tenantId) {
@@ -38,37 +43,72 @@ export default async function SettingsPage(props: Props) {
}
return (
-
-
-
API Key Overview
-
+
+
+ }>
+ APIs
+
+ {key.keyAuth.api.name}
+
+
+
+ {key.id}
+
+
+ Settings
+
+
+
+
+ {key.id}
+
+
+
+
+
+
+
+
+
+
API Key Overview
+
-
-
-
-
-
-
-
-
-
- Key ID
- This is your key id. It's used in some API calls.
-
-
-
- {key.id}
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+ Key ID
+ This is your key id. It's used in some API calls.
+
+
+
+ {key.id}
+
+
+
+
+
+
+
+
+
);
}
-//
diff --git a/apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/new/page.tsx b/apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/new/page.tsx
index 78dbfc00f7..73dc3a6766 100644
--- a/apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/new/page.tsx
+++ b/apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/new/page.tsx
@@ -1,5 +1,10 @@
+import { CopyButton } from "@/components/dashboard/copy-button";
+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 { Nodes } from "@unkey/icons";
import { notFound } from "next/navigation";
import { CreateKey } from "./client";
@@ -24,11 +29,43 @@ export default async function CreateKeypage(props: {
}
return (
-
+
+
+ }>
+ APIs
+
+ {keyAuth.api.name}
+
+
+ Keys
+
+
+ Create new key
+
+
+
+
+ {keyAuth.api.id}
+
+
+
+
+
+
+
+
+
);
}
diff --git a/apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/page.tsx b/apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/page.tsx
index fb55ea0a09..d98dc40fd9 100644
--- a/apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/page.tsx
+++ b/apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/page.tsx
@@ -2,6 +2,14 @@ import { getTenantId } from "@/lib/auth";
import { db } from "@/lib/db";
import { notFound } from "next/navigation";
+import { CopyButton } from "@/components/dashboard/copy-button";
+import { CreateKeyButton } from "@/components/dashboard/create-key-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 { Nodes } from "@unkey/icons";
+import { navigation } from "../../constants";
import { Keys } from "./keys";
export const dynamic = "force-dynamic";
@@ -28,8 +36,37 @@ export default async function APIKeysPage(props: {
}
return (
-
-
+
+
+ }>
+ APIs
+
+ {keyAuth.api.name}
+
+
+ Keys
+
+
+
+
+ {keyAuth.api.id}
+
+
+
+
+
+
+
+
+
+
+
+
+
);
}
diff --git a/apps/dashboard/app/(app)/apis/[apiId]/layout.tsx b/apps/dashboard/app/(app)/apis/[apiId]/layout.tsx
deleted file mode 100644
index 35c2c14593..0000000000
--- a/apps/dashboard/app/(app)/apis/[apiId]/layout.tsx
+++ /dev/null
@@ -1,75 +0,0 @@
-import { CopyButton } from "@/components/dashboard/copy-button";
-import { CreateKeyButton } from "@/components/dashboard/create-key-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";
-import type { PropsWithChildren } from "react";
-
-type Props = PropsWithChildren<{
- params: {
- apiId: string;
- };
-}>;
-
-export const dynamic = "force-dynamic";
-export const runtime = "edge";
-
-export default async function ApiPageLayout(props: Props) {
- const tenantId = getTenantId();
-
- const api = await db.query.apis.findFirst({
- where: (table, { eq, and, isNull }) =>
- and(eq(table.id, props.params.apiId), isNull(table.deletedAt)),
- with: {
- workspace: true,
- },
- });
- if (!api || api.workspace.tenantId !== tenantId) {
- return notFound();
- }
- const navigation = [
- {
- label: "Overview",
- href: `/apis/${api.id}`,
- segment: null,
- },
- {
- label: "Keys",
- href: `/apis/${api.id}/keys/${api.keyAuthId}`,
- segment: "keys",
- },
- {
- label: "API Settings",
- href: `/apis/${api.id}/settings`,
- segment: "settings",
- },
- ];
-
- return (
-
-
- {api.id}
-
- ,
- ,
- ]}
- />
-
-
-
- {props.children}
-
- );
-}
diff --git a/apps/dashboard/app/(app)/apis/[apiId]/loading.tsx b/apps/dashboard/app/(app)/apis/[apiId]/loading.tsx
index 550d892be5..2f3be9eef2 100644
--- a/apps/dashboard/app/(app)/apis/[apiId]/loading.tsx
+++ b/apps/dashboard/app/(app)/apis/[apiId]/loading.tsx
@@ -1,7 +1,8 @@
+import { PageContent } from "@/components/page-content";
import { Skeleton } from "@/components/ui/skeleton";
export default async function Loading() {
return (
-
+
@@ -55,6 +56,6 @@ export default async function Loading() {
-
+
);
}
diff --git a/apps/dashboard/app/(app)/apis/[apiId]/page.tsx b/apps/dashboard/app/(app)/apis/[apiId]/page.tsx
index 2e5fb67bcc..33f2c9fc20 100644
--- a/apps/dashboard/app/(app)/apis/[apiId]/page.tsx
+++ b/apps/dashboard/app/(app)/apis/[apiId]/page.tsx
@@ -1,5 +1,11 @@
import { AreaChart, StackedColumnChart } from "@/components/dashboard/charts";
+import { CopyButton } from "@/components/dashboard/copy-button";
+import { CreateKeyButton } from "@/components/dashboard/create-key-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 { Metric } from "@/components/ui/metric";
import { Separator } from "@/components/ui/separator";
@@ -7,9 +13,12 @@ import { getTenantId } from "@/lib/auth";
import { clickhouse } from "@/lib/clickhouse";
import { db } from "@/lib/db";
import { formatNumber } from "@/lib/fmt";
+import { Nodes } from "@unkey/icons";
import { BarChart } from "lucide-react";
import { redirect } from "next/navigation";
+import { navigation } from "./constants";
import { type Interval, IntervalSelect } from "./select";
+
export const dynamic = "force-dynamic";
export const runtime = "edge";
@@ -118,7 +127,10 @@ export default async function ApiPage(props: {
...ratelimitedOverTime.map((d) => ({ ...d, category: "Ratelimited" })),
...usageExceededOverTime.map((d) => ({ ...d, category: "Usage Exceeded" })),
...disabledOverTime.map((d) => ({ ...d, category: "Disabled" })),
- ...insufficientPermissionsOverTime.map((d) => ({ ...d, category: "Insufficient Permissions" })),
+ ...insufficientPermissionsOverTime.map((d) => ({
+ ...d,
+ category: "Insufficient Permissions",
+ })),
...expiredOverTime.map((d) => ({ ...d, category: "Expired" })),
...forbiddenOverTime.map((d) => ({ ...d, category: "Forbidden" })),
].sort((a, b) => new Date(a.x).getTime() - new Date(b.x).getTime());
@@ -129,138 +141,167 @@ export default async function ApiPage(props: {
}));
return (
-
-
-
-
- sum + day.count, 0),
- )}
- />
-
-
-
-
+
+
+ }>
+ APIs
+
+ {api.name}
+
+
+
+
+ {api.id}
+
+
+
+
+
-
-
Verifications
-
-
-
-
-
+
+
- {verificationsData.some((d) => d.y > 0) ? (
-
-
-
-
sum + day.y, 0))}
- />
- sum + day.y, 0))}
- />
- sum + day.y, 0))}
- />
- sum + day.y, 0))}
- />
+
+
+
+
sum + day.y, 0),
+ verificationsInBillingCycle.reduce((sum, day) => sum + day.count, 0),
)}
/>
sum + day.y, 0))}
- />
- sum + day.y, 0))}
+ label={`Active Keys in ${new Date().toLocaleString("en-US", {
+ month: "long",
+ })}`}
+ value={formatNumber(activeKeysTotal?.keys ?? 0)}
/>
+
+
+
+
+
+
Verifications
+
+
+
-
-
- = 1000 * 60 * 60 * 24 * 30
- ? "month"
- : granularity >= 1000 * 60 * 60 * 24
- ? "day"
- : "hour"
- }
- />
-
-
- ) : (
-
-
-
-
- No usage
-
- Verify a key or change the range
-
-
- )}
+
-
-
-
Active Keys
+ {verificationsData.some((d) => d.y > 0) ? (
+
+
+
+ sum + day.y, 0))}
+ />
+ sum + day.y, 0))}
+ />
+ sum + day.y, 0))}
+ />
+ sum + day.y, 0))}
+ />
+ sum + day.y, 0),
+ )}
+ />
+ sum + day.y, 0))}
+ />
+ sum + day.y, 0))}
+ />
+
+
+
+ = 1000 * 60 * 60 * 24 * 30
+ ? "month"
+ : granularity >= 1000 * 60 * 60 * 24
+ ? "day"
+ : "hour"
+ }
+ />
+
+
+ ) : (
+
+
+
+
+ No usage
+
+ Verify a key or change the range
+
+
+ )}
-
-
-
-
- {activeKeysOverTime.some((k) => k.y > 0) ? (
-
-
-
-
+
+
+
Active Keys
+
+
+
-
-
- = 1000 * 60 * 60 * 24 * 30
- ? "month"
- : granularity >= 1000 * 60 * 60 * 24
- ? "day"
- : "hour"
- }
- />
-
-
- ) : (
-
-
-
-
- No usage
-
- Verify a key or change the range
-
-
- )}
+
+ {activeKeysOverTime.some((k) => k.y > 0) ? (
+
+
+
+
+
+
+
+ = 1000 * 60 * 60 * 24 * 30
+ ? "month"
+ : granularity >= 1000 * 60 * 60 * 24
+ ? "day"
+ : "hour"
+ }
+ />
+
+
+ ) : (
+
+
+
+
+ No usage
+
+ Verify a key or change the range
+
+
+ )}
+
+
);
}
diff --git a/apps/dashboard/app/(app)/apis/[apiId]/settings/page.tsx b/apps/dashboard/app/(app)/apis/[apiId]/settings/page.tsx
index b22d545cc7..8f563649ed 100644
--- a/apps/dashboard/app/(app)/apis/[apiId]/settings/page.tsx
+++ b/apps/dashboard/app/(app)/apis/[apiId]/settings/page.tsx
@@ -1,9 +1,16 @@
import { CopyButton } from "@/components/dashboard/copy-button";
+import { CreateKeyButton } from "@/components/dashboard/create-key-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, eq, schema } from "@/lib/db";
+import { Nodes } from "@unkey/icons";
import { notFound, redirect } from "next/navigation";
+import { navigation } from "../constants";
import { DefaultBytes } from "./default-bytes";
import { DefaultPrefix } from "./default-prefix";
import { DeleteApi } from "./delete-api";
@@ -49,27 +56,56 @@ export default async function SettingsPage(props: Props) {
}
return (
-
-
-
-
-
-
-
- API ID
- This is your api id. It's used in some API calls.
-
-
-
- {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]/bucket-select.tsx b/apps/dashboard/app/(app)/audit/[bucket]/bucket-select.tsx
index b24aa0e18d..f42f4a2424 100644
--- a/apps/dashboard/app/(app)/audit/[bucket]/bucket-select.tsx
+++ b/apps/dashboard/app/(app)/audit/[bucket]/bucket-select.tsx
@@ -10,6 +10,7 @@ import {
import { useRouter } from "next/navigation";
import type React from "react";
import { useTransition } from "react";
+
type Props = {
selected: string;
ratelimitNamespaces: { id: string; name: string }[];
diff --git a/apps/dashboard/app/(app)/audit/[bucket]/filter.tsx b/apps/dashboard/app/(app)/audit/[bucket]/filter.tsx
index ff4444902e..58e8efa5ee 100644
--- a/apps/dashboard/app/(app)/audit/[bucket]/filter.tsx
+++ b/apps/dashboard/app/(app)/audit/[bucket]/filter.tsx
@@ -17,6 +17,7 @@ import { Button } from "@unkey/ui";
import { Check, ChevronDown } from "lucide-react";
import { parseAsArrayOf, parseAsString, useQueryState } from "nuqs";
import type React from "react";
+
type Props = {
options: { value: string; label: string }[];
title: string;
@@ -33,6 +34,13 @@ export const Filter: React.FC
= ({ options, title, param }) => {
}),
);
+ const handleSelection = (optionValue: string, isSelected: boolean) => {
+ const next = isSelected
+ ? selected.filter((v) => v !== optionValue)
+ : Array.from(new Set([...selected, optionValue]));
+ setSelected(next);
+ };
+
return (
@@ -72,11 +80,12 @@ export const Filter: React.FC = ({ options, title, param }) => {
const isSelected = selected.includes(option.value);
return (
{
- const next = isSelected
- ? selected.filter((v) => v !== option.value)
- : Array.from(new Set([...selected, option.value]));
- setSelected(next);
+ onClick={() => handleSelection(option.value, isSelected)}
+ onKeyDown={(e) => {
+ if (e.key === "Enter" || e.key === " ") {
+ e.preventDefault();
+ handleSelection(option.value, isSelected);
+ }
}}
>
};
+type AuditLogWithTargets = SelectAuditLog & {
+ targets: Array;
+};
/**
* Parse searchParam string arrays
@@ -116,79 +120,91 @@ export default async function AuditPage(props: Props) {
return (
-
-
-
-
-
({
- value,
- label: value,
- }))
- : [
- { value: "ratelimit.success", label: "Ratelimit success" },
- { value: "ratelimit.denied", label: "Ratelimit denied" },
- ]
- }
- />
+
+ }>
+ Audit
+
+ {workspace.ratelimitNamespaces.find((ratelimit) => ratelimit.id === props.params.bucket)
+ ?.name ?? props.params.bucket}
+
+
+
+
+
+
+
+ ({
+ 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..773c714bff 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,11 +69,24 @@ export default async function RolesPage(props: Props) {
const shouldShowTooltip = permission.name.length > 16;
return (
-
-
{permission.name}}
- description={permission.description ?? undefined}
- actions={[
+
+
+ }>
+
+ Authorization
+
+
+ Permissions
+
+
+ {props.params.permissionId}
+
+
+
)}
- ,
+
{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..0480d246b6 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,85 @@ export default async function RolesPage() {
return permission;
});
return (
-
-
-
Permissions
-
+
+
+ }>
+
+ 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" : ""}
-
+
+
+
+
+ {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.keys.length)} Key
- {p.keys.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" : ""}
+
+
+
+
+
+
+
+ ))}
+
+ )}
+
+
+
);
}
diff --git a/apps/dashboard/app/(app)/authorization/roles/[roleId]/page.tsx b/apps/dashboard/app/(app)/authorization/roles/[roleId]/page.tsx
index 222c4b8c71..2037a639f1 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,39 @@ export default async function RolesPage(props: Props) {
const sortedNestedPermissions = sortNestedPermissions(nested);
return (
-
-
-
-
-
- {role.name}
-
-
-
{role.description}
-
-
+
+
+ }>
+
+ Authorization
+
+ Roles
+
+ {props.params.roleId}
+
+
+
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..13a7c21aa2 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,10 +56,17 @@ export default async function RolesPage() {
});
return (
-
-
-
Roles
-
+
+
+ }>
+
+ Authorization
+
+
+ Roles
+
+
+
{Intl.NumberFormat().format(workspace.roles.length)} /{" "}
{Intl.NumberFormat().format(Number.POSITIVE_INFINITY)} used{" "}
@@ -63,58 +75,70 @@ export default async function RolesPage() {
trigger={}
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)/identities/[identityId]/page.tsx b/apps/dashboard/app/(app)/identities/[identityId]/page.tsx
index 7e7c15ffd9..ebb03aa9f4 100644
--- a/apps/dashboard/app/(app)/identities/[identityId]/page.tsx
+++ b/apps/dashboard/app/(app)/identities/[identityId]/page.tsx
@@ -1,6 +1,8 @@
import { notFound } from "next/navigation";
import { CopyButton } from "@/components/dashboard/copy-button";
+import { Navbar } from "@/components/navbar";
+import { PageContent } from "@/components/page-content";
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
import { Badge } from "@/components/ui/badge";
import { Code } from "@/components/ui/code";
@@ -15,6 +17,7 @@ import {
import { getTenantId } from "@/lib/auth";
import { clickhouse } from "@/lib/clickhouse";
import { db } from "@/lib/db";
+import { Fingerprint } from "@unkey/icons";
import { Button } from "@unkey/ui";
import { ChevronRight, Minus } from "lucide-react";
import ms from "ms";
@@ -54,115 +57,140 @@ export default async function Page(props: Props) {
}
return (
-
-
-
- Identity ID:
-
+
+ }>
+ Identities
+
- {identity.id}
-
-
-
-
-
External ID:
+ {props.params.identityId}
+
+
+
+
+
+
+
+ Identity ID:
+
+ {identity.id}
+
+
+
+
+ External ID:
-
- {identity.externalId}
-
-
-
-
-
Meta
- {identity.meta ? (
-
{JSON.stringify(identity.meta, null, 2)}
- ) : (
-
- No metadata
- This identity has no metadata.
-
- )}
+
+ {identity.externalId}
+
+
+
+
+
Meta
+ {identity.meta ? (
+
{JSON.stringify(identity.meta, null, 2)}
+ ) : (
+
+ No metadata
+ This identity has no metadata.
+
+ )}
-
Ratelimits
- {identity.ratelimits.length === 0 ? (
-
- No ratelimits
- This identity has no ratelimits attached.
-
- ) : (
-
-
-
- Name
- Limit
- Duration
-
-
-
- {identity.ratelimits.map((ratelimit) => (
-
- {ratelimit.name}
-
- {Intl.NumberFormat(undefined, { notation: "compact" }).format(ratelimit.limit)}
-
- {ms(ratelimit.duration)}
-
- ))}
-
-
- )}
-
Keys
- {identity.keys.length === 0 ? (
-
- No keys
- This identity has no keys attached.
-
- ) : (
-
-
-
- ID
- Meta
- Last Used
-
-
-
-
- {identity.keys.map((key) => (
-
- {key.id}
-
- {key.meta ? (
- JSON.stringify(JSON.parse(key.meta), null, 2)
- ) : (
-
- )}
-
-
-
-
-
-
-
-
- ))}
-
-
- )}
+
Ratelimits
+ {identity.ratelimits.length === 0 ? (
+
+ No ratelimits
+ This identity has no ratelimits attached.
+
+ ) : (
+
+
+
+ Name
+ Limit
+ Duration
+
+
+
+ {identity.ratelimits.map((ratelimit) => (
+
+ {ratelimit.name}
+
+ {Intl.NumberFormat(undefined, {
+ notation: "compact",
+ }).format(ratelimit.limit)}
+
+ {ms(ratelimit.duration)}
+
+ ))}
+
+
+ )}
+
Keys
+ {identity.keys.length === 0 ? (
+
+ No keys
+ This identity has no keys attached.
+
+ ) : (
+
+
+
+ ID
+ Meta
+ Last Used
+
+
+
+
+ {identity.keys.map((key) => (
+
+ {key.id}
+
+ {key.meta ? (
+ JSON.stringify(JSON.parse(key.meta), null, 2)
+ ) : (
+
+ )}
+
+
+
+
+
+
+
+
+ ))}
+
+
+ )}
+
+
);
}
-const LastUsed: React.FC<{ workspaceId: string; keySpaceId: string; keyId: string }> = async (
- props,
-) => {
+const LastUsed: React.FC<{
+ workspaceId: string;
+ keySpaceId: string;
+ keyId: string;
+}> = async (props) => {
const lastUsed = await clickhouse.verifications
.latest({
workspaceId: props.workspaceId,
diff --git a/apps/dashboard/app/(app)/identities/layout.tsx b/apps/dashboard/app/(app)/identities/layout.tsx
deleted file mode 100644
index 165b1b041f..0000000000
--- a/apps/dashboard/app/(app)/identities/layout.tsx
+++ /dev/null
@@ -1,34 +0,0 @@
-import type * as React from "react";
-
-import { OptIn } from "@/components/opt-in";
-import { getTenantId } from "@/lib/auth";
-import { db } from "@/lib/db";
-import { redirect } from "next/navigation";
-
-export const dynamic = "force-dynamic";
-export const runtime = "edge";
-
-export default async function AuthorizationLayout({
- children,
-}: {
- children: React.ReactNode;
-}) {
- const tenantId = getTenantId();
- const workspace = await db.query.workspaces.findFirst({
- where: (table, { eq }) => eq(table.tenantId, tenantId),
- });
- if (!workspace) {
- return redirect("/auth/sign-in");
- }
-
- if (!workspace.betaFeatures.identities) {
- children = (
-
- );
- }
- return (
-
- {children}
-
- );
-}
diff --git a/apps/dashboard/app/(app)/identities/page.tsx b/apps/dashboard/app/(app)/identities/page.tsx
index 849f1cb592..a388709b19 100644
--- a/apps/dashboard/app/(app)/identities/page.tsx
+++ b/apps/dashboard/app/(app)/identities/page.tsx
@@ -1,9 +1,13 @@
import { redirect } from "next/navigation";
import { EmptyPlaceholder } from "@/components/dashboard/empty-placeholder";
+import { Navbar } from "@/components/navbar";
+import { OptIn } from "@/components/opt-in";
+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 { Fingerprint } from "@unkey/icons";
import { Loader2 } from "lucide-react";
import { unstable_cache as cache } from "next/cache";
import { parseAsInteger, parseAsString } from "nuqs/server";
@@ -21,23 +25,44 @@ export default async function Page(props: Props) {
const search = parseAsString.withDefault("").parse(props.searchParams.search ?? "");
const limit = parseAsInteger.withDefault(10).parse(props.searchParams.limit ?? "10");
- return (
-
-
+ const tenantId = getTenantId();
+ const workspace = await db.query.workspaces.findFirst({
+ where: (table, { eq }) => eq(table.tenantId, tenantId),
+ });
+
+ if (!workspace) {
+ return redirect("/auth/sign-in");
+ }
+
+ if (!workspace.betaFeatures.identities) {
+ return
;
+ }
-
-
-
-
-
-
- }
- >
-
-
-
+ return (
+
+
+ }>
+
+ Identities
+
+
+
+
+
+
+
+
+
+
+
+ }
+ >
+
+
+
+
);
}
diff --git a/apps/dashboard/app/(app)/layout.tsx b/apps/dashboard/app/(app)/layout.tsx
index 18ec728800..605f8cd54d 100644
--- a/apps/dashboard/app/(app)/layout.tsx
+++ b/apps/dashboard/app/(app)/layout.tsx
@@ -10,10 +10,9 @@ import { MobileSideBar } from "./mobile-sidebar";
interface LayoutProps {
children: React.ReactNode;
- breadcrumb: React.ReactNode;
}
-export default async function Layout({ children, breadcrumb }: LayoutProps) {
+export default async function Layout({ children }: LayoutProps) {
const tenantId = getTenantId();
const workspace = await db.query.workspaces.findFirst({
where: (table, { and, eq, isNull }) =>
@@ -43,13 +42,9 @@ 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 */}
- {breadcrumb &&
{breadcrumb}
}
- {children}
- >
+ children
) : (
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
deleted file mode 100644
index 7eebd35dde..0000000000
--- a/apps/dashboard/app/(app)/ratelimits/[namespaceId]/layout.tsx
+++ /dev/null
@@ -1,78 +0,0 @@
-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";
-import type { PropsWithChildren } from "react";
-
-type Props = PropsWithChildren<{
- params: {
- namespaceId: string;
- };
-}>;
-
-export const dynamic = "force-dynamic";
-export const runtime = "edge";
-
-export default async function RatelimitNamespacePageLayout(props: Props) {
- const tenantId = getTenantId();
-
- const namespace = await db.query.ratelimitNamespaces.findFirst({
- where: (table, { eq, and, isNull }) =>
- and(eq(table.id, props.params.namespaceId), isNull(table.deletedAt)),
- with: {
- 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}
-
- );
-}
diff --git a/apps/dashboard/app/(app)/ratelimits/[namespaceId]/logs/page.tsx b/apps/dashboard/app/(app)/ratelimits/[namespaceId]/logs/page.tsx
index 49b3931962..277f26d626 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))}
- />
-
-
- {/* */}
-
-
-
-
-
-
- {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..031660f178 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/billing/page.tsx b/apps/dashboard/app/(app)/settings/billing/page.tsx
index 8c55c290ed..23c7223ed8 100644
--- a/apps/dashboard/app/(app)/settings/billing/page.tsx
+++ b/apps/dashboard/app/(app)/settings/billing/page.tsx
@@ -1,3 +1,6 @@
+import { Navbar as SubMenu } from "@/components/dashboard/navbar";
+import { Navbar } from "@/components/navbar";
+import { PageContent } from "@/components/page-content";
import {
Card,
CardContent,
@@ -13,11 +16,13 @@ import { type Workspace, db } from "@/lib/db";
import { stripeEnv } from "@/lib/env";
import { cn } from "@/lib/utils";
import { type BillingTier, QUOTA, calculateTieredPrices } from "@unkey/billing";
+import { Gear } from "@unkey/icons";
import { Button } from "@unkey/ui";
import { Check, ExternalLink } from "lucide-react";
import Link from "next/link";
import { redirect } from "next/navigation";
import Stripe from "stripe";
+import { navigation } from "../constants";
import { UserPaymentMethod } from "./user-payment-method";
export const revalidate = 0;
@@ -29,20 +34,33 @@ export default async function BillingPage() {
where: (table, { and, eq, isNull }) =>
and(eq(table.tenantId, tenantId), isNull(table.deletedAt)),
});
+
if (!workspace) {
return redirect("/new");
}
return (
-
-
- {workspace.plan === "free" ? (
-
- ) : (
-
- )}
-
-
+
+
+ }>
+
+ Settings
+
+
+
+
+
+
+
+ {workspace.plan === "free" ? (
+
+ ) : (
+
+ )}
+
+
+
+
);
}
@@ -77,7 +95,13 @@ const FreeUsage: React.FC<{ workspace: Workspace }> = async ({ workspace }) => {
@@ -235,7 +259,10 @@ const PaidUsage: React.FC<{ workspace: Workspace }> = async ({ workspace }) => {
Current billing cycle:{" "}
- {startOfMonth.toLocaleString("en-US", { month: "long", year: "numeric" })}
+ {startOfMonth.toLocaleString("en-US", {
+ month: "long",
+ year: "numeric",
+ })}
{" "}
@@ -360,7 +387,11 @@ const MeteredLineItem: React.FC<{
.filter((tier) => props.used >= tier.firstUnit)
.map((tier, i) => (
-
+
-
-
Back to Billing
-
-
-
- {(["free", "pro", "custom"] as const).map((tier) => (
-
+
}>
+
Billing
+
+ Plans
+
+
+
+
+
+
-
-
- {tiers[tier].name}
-
-
-
- {tiers[tier].description}
-
-
- {typeof tiers[tier].price === "number" ? (
- <>
-
- {`$${tiers[tier].price}`}
-
-
- {"/month"}
-
- >
- ) : (
-
- {tiers[tier].price}
-
- )}
-
+
Back to Billing
+
+
+
+ {(["free", "pro", "custom"] as const).map((tier) => (
+
+
+
+ {tiers[tier].name}
+
+
+
+ {tiers[tier].description}
+
+
+ {typeof tiers[tier].price === "number" ? (
+ <>
+
+ {`$${tiers[tier].price}`}
+
+
+ {"/month"}
+
+ >
+ ) : (
+
+ {tiers[tier].price}
+
+ )}
+
-
-
- {tiers[tier].features.map((feature) => (
- -
-
+
+
+ {tiers[tier].features.map((feature) => (
+ -
+
- {feature}
-
- ))}
-
- {tiers[tier].footnotes && (
-
- {tiers[tier].footnotes.map((footnote, i) => (
- -
- {footnote}
-
- ))}
-
- )}
- {tier === "custom" ? (
-
-
-
- ) : (
-
- )}
-
+ {feature}
+
+ ))}
+
+ {tiers[tier].footnotes && (
+
+ {tiers[tier].footnotes.map((footnote, i) => (
+ -
+ {footnote}
+
+ ))}
+
+ )}
+ {tier === "custom" ? (
+
+
+
+ ) : (
+
+ )}
+
+
+ ))}
- ))}
-
+
+
);
}
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]/layout.tsx b/apps/dashboard/app/(app)/settings/root-keys/[keyId]/page-layout.tsx
similarity index 69%
rename from apps/dashboard/app/(app)/settings/root-keys/[keyId]/layout.tsx
rename to apps/dashboard/app/(app)/settings/root-keys/[keyId]/page-layout.tsx
index 71e512064f..de0d78d85e 100644
--- a/apps/dashboard/app/(app)/settings/root-keys/[keyId]/layout.tsx
+++ b/apps/dashboard/app/(app)/settings/root-keys/[keyId]/page-layout.tsx
@@ -1,39 +1,21 @@
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Metric } from "@/components/ui/metric";
-import { getTenantId } from "@/lib/auth";
import { clickhouse } from "@/lib/clickhouse";
-import { db } from "@/lib/db";
+import type { Key } from "@unkey/db";
import { ArrowLeft } from "lucide-react";
import ms from "ms";
import Link from "next/link";
-import { notFound } from "next/navigation";
import { Suspense } from "react";
type Props = {
params: {
keyId: string;
};
+ rootKey: Key;
children: React.ReactNode;
};
-export default async function Layout({ children, params: { keyId } }: Props) {
- const tenantId = getTenantId();
-
- const workspace = await db.query.workspaces.findFirst({
- where: (table, { and, eq, isNull }) =>
- and(eq(table.tenantId, tenantId), isNull(table.deletedAt)),
- });
- if (!workspace) {
- return notFound();
- }
-
- const key = await db.query.keys.findFirst({
- where: (table, { eq, and }) => and(eq(table.forWorkspaceId, workspace.id), eq(table.id, keyId)),
- });
- if (!key) {
- return notFound();
- }
-
+export function PageLayout({ children, rootKey: key, params: { keyId } }: Props) {
return (
= async ({
- workspaceId,
- keySpaceId,
- keyId,
-}) => {
+const LastUsed: React.FC<{
+ workspaceId: string;
+ keySpaceId: string;
+ keyId: string;
+}> = async ({ workspaceId, keySpaceId, keyId }) => {
const lastUsed = await clickhouse.verifications
.latest({ workspaceId, keySpaceId, keyId })
.then((res) => res.val?.at(0)?.time ?? 0);
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..d45eb1ce54 100644
--- a/apps/dashboard/app/(app)/settings/root-keys/[keyId]/page.tsx
+++ b/apps/dashboard/app/(app)/settings/root-keys/[keyId]/page.tsx
@@ -1,12 +1,16 @@
+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";
+import { PageLayout } from "./page-layout";
import { DialogAddPermissionsForAPI } from "./permissions/add-permission-for-api";
import { Api } from "./permissions/api";
import { Legacy } from "./permissions/legacy";
@@ -123,45 +127,73 @@ 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 && (
-
-
-
-
-
- )}
-
-
-
+
+
+ }>
+ Root Keys
+
+ {key.id}
+
+
+
+
+
+
+
+ {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..e0f858fb41 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";
@@ -20,18 +23,29 @@ export default async function SettingsKeysPage(_props: {
},
},
});
+
if (!workspace) {
return redirect("/new");
}
return (
-
-
+
+
+ }>
+ Root Keys
+
+ 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..593fb53433 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/app/(app)/settings/user/page.tsx b/apps/dashboard/app/(app)/settings/user/page.tsx
index 7dba3926b3..2f18a2b2a6 100644
--- a/apps/dashboard/app/(app)/settings/user/page.tsx
+++ b/apps/dashboard/app/(app)/settings/user/page.tsx
@@ -1,10 +1,16 @@
+import { Navbar as SubMenu } from "@/components/dashboard/navbar";
+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 { navigation } from "../constants";
import { UpdateTheme } from "./update-theme";
import { UpdateUserEmail } from "./update-user-email";
import { UpdateUserImage } from "./update-user-image";
import { UpdateUserName } from "./update-user-name";
+
export const revalidate = 0;
export default async function SettingsPage() {
@@ -19,11 +25,24 @@ export default async function SettingsPage() {
}
return (
-
-
-
-
-
+
+
+ }>
+
+ Settings
+
+
+
+
+
+
+
+
+
+
+
+
+
);
}
diff --git a/apps/dashboard/app/(app)/settings/vercel/page.tsx b/apps/dashboard/app/(app)/settings/vercel/page.tsx
index 7ab4a902a4..0100d91874 100644
--- a/apps/dashboard/app/(app)/settings/vercel/page.tsx
+++ b/apps/dashboard/app/(app)/settings/vercel/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 { Code } from "@/components/ui/code";
import { getTenantId } from "@/lib/auth";
import { type Api, type Key, type VercelBinding, db, eq, schema } from "@/lib/db";
import { clerkClient } from "@clerk/nextjs";
+import { Gear } from "@unkey/icons";
import { Button } from "@unkey/ui";
import { Vercel } from "@unkey/vercel";
import Link from "next/link";
import { notFound } from "next/navigation";
+import { navigation } from "../constants";
import { Client } from "./client";
type Props = {
searchParams: {
@@ -33,6 +38,7 @@ export default async function Page(props: Props) {
},
},
});
+
if (!workspace) {
console.warn("no workspace");
return notFound();
@@ -41,16 +47,32 @@ export default async function Page(props: Props) {
const integration = props.searchParams.configurationId
? workspace.vercelIntegrations.find((i) => i.id === props.searchParams.configurationId)
: workspace.vercelIntegrations.at(0);
+
if (!integration) {
return (
-
- Vercel is not connected to this workspace
-
-
-
-
-
-
+
+
+ }>
+
+ Settings
+
+
+
+
+
+
+
+
+ Vercel is not connected to this workspace
+
+
+
+
+
+
+
+
+
);
}
@@ -60,6 +82,7 @@ export default async function Page(props: Props) {
});
const { val: rawProjects, err } = await vercel.listProjects();
+
if (err) {
return (
@@ -145,5 +168,20 @@ export default async function Page(props: Props) {
})),
);
- return ;
+ return (
+
+
+ }>
+
+ Settings
+
+
+
+
+
+
+
+
+
+ );
}
diff --git a/apps/dashboard/components/dashboard/breadcrumb-skeleton.tsx b/apps/dashboard/components/dashboard/breadcrumb-skeleton.tsx
deleted file mode 100644
index c9f41dd66c..0000000000
--- a/apps/dashboard/components/dashboard/breadcrumb-skeleton.tsx
+++ /dev/null
@@ -1,18 +0,0 @@
-import { Breadcrumb, BreadcrumbList, BreadcrumbSeparator } from "@/components/ui/breadcrumb";
-import { Fragment } from "react";
-import { Skeleton } from "../ui/skeleton";
-
-export function BreadcrumbSkeleton(props: { levels: number }) {
- return (
-
-
- {Array.from({ length: props.levels }).map((_, idx) => (
-
-
- {idx < props.levels - 1 && }
-
- ))}
-
-
- );
-}
diff --git a/apps/dashboard/components/dashboard/navbar.tsx b/apps/dashboard/components/dashboard/navbar.tsx
index 3d7d4f1104..1547060b43 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 (