SPB Git forge

spb/fetcha

Public
11commits 1branches 0releases
1.5 MBsize
maindefault branch
16 days agolast push
TypeScript 97.5% SQL 1.4% Python 0.8%
4.3 KB · 93 lines tsx
Raw Blame History
1import Link from "next/link";2import { ShieldCheck } from "lucide-react";3import { getWorkspace } from "@/lib/session";4import { listApiKeys } from "@/lib/queries/account";5import { PageHeader } from "@/components/ui/page-header";6import { Alert } from "@/components/ui/alert";7import { cn } from "@/lib/utils";8import { KeysTable } from "@/components/dashboard/api-keys/keys-table";9import { CreateKeyDialog } from "@/components/dashboard/api-keys/create-key-dialog";1011export const dynamic = "force-dynamic";1213export const metadata = { title: "API keys · Fetcha" };1415export default async function ApiKeysPage({ searchParams }: { searchParams: Promise<{ project?: string; new?: string }> }) {16  const sp = await searchParams;17  const ws = await getWorkspace();1819  // ?project=all → every project of the org; ?project=<id> → that project; default → current project.20  const allProjects = sp.project === "all";21  const requested = !allProjects && sp.project ? ws.projects.find((p) => p.id === sp.project) : null;22  const scopeProject = allProjects ? null : requested ?? ws.project;2324  const keys = await listApiKeys(ws.organization.id, scopeProject?.id ?? null);25  const projectOptions = ws.projects.map((p) => ({ id: p.id, name: p.name, environment: p.environment }));26  const active = keys.filter((k) => k.status === "active").length;2728  return (29    <div className="grid gap-6">30      <PageHeader31        title="API keys"32        description="Authenticate requests with a Bearer token. Keys belong to a project and inherit its limits and defaults."33        actions={<CreateKeyDialog projects={projectOptions} defaultProjectId={scopeProject?.id ?? ws.project.id} defaultOpen={sp.new === "1"} />}34      />3536      <div className="flex flex-wrap items-center justify-between gap-3">37        <nav className="inline-flex h-9 items-center gap-0.5 rounded-md bg-bg-muted p-1 text-[13px] font-medium text-fg-muted" aria-label="Project filter">38          <FilterLink href={`/dashboard/api-keys?project=${ws.project.id}`} active={!allProjects && scopeProject?.id === ws.project.id}>39            {ws.project.name}40            <span className="ml-1 text-[11px] font-normal text-fg-subtle">current</span>41          </FilterLink>42          {requested && requested.id !== ws.project.id ? (43            <FilterLink href={`/dashboard/api-keys?project=${requested.id}`} active>44              {requested.name}45            </FilterLink>46          ) : null}47          <FilterLink href="/dashboard/api-keys?project=all" active={allProjects}>48            All projects49          </FilterLink>50        </nav>51        <p className="text-[12.5px] text-fg-subtle tabular">52          {active} active · {keys.length - active} inactive53        </p>54      </div>5556      <KeysTable keys={keys} projects={projectOptions} defaultProjectId={scopeProject?.id ?? ws.project.id} showProject={allProjects} />5758      <Alert59        variant="info"60        title="Keep your keys secret"61        action={62          <Link href="/docs/authentication" className="text-[13px] font-medium text-accent underline-offset-4 hover:underline">63            Auth docs64          </Link>65        }66      >67        <ul className="mt-1 grid gap-1 text-[13px]">68          <li className="flex gap-2">69            <ShieldCheck className="mt-0.5 size-3.5 shrink-0 text-info" aria-hidden />70            Keys are hashed at rest — we cannot recover a lost secret, only rotate it.71          </li>72          <li className="flex gap-2">73            <ShieldCheck className="mt-0.5 size-3.5 shrink-0 text-info" aria-hidden />74            Rotate regularly and give every service, environment and contractor its own key with the minimum scopes.75          </li>76          <li className="flex gap-2">77            <ShieldCheck className="mt-0.5 size-3.5 shrink-0 text-info" aria-hidden />78            Never commit keys to source control or ship them in browser code. Load them from environment variables on your server.79          </li>80        </ul>81      </Alert>82    </div>83  );84}8586function FilterLink({ href, active, children }: { href: string; active: boolean; children: React.ReactNode }) {87  return (88    <Link href={href} aria-current={active ? "page" : undefined} className={cn("inline-flex h-7 items-center rounded-[5px] px-3 transition-colors", active ? "bg-bg text-fg shadow-xs" : "hover:text-fg")}>89      {children}90    </Link>91  );92}93