TypeScript 97.5%
SQL 1.4%
Python 0.8%
1import Link from "next/link";2import { Table, TableBody, TableCell, TableEmpty, TableHead, TableHeader, TableRow } from "@/components/ui/table";3import { StatusBadge } from "@/components/ui/badge";4import { CopyButton } from "@/components/ui/copy-button";5import { formatDate, formatNumber, timeAgo } from "@/lib/format";6import type { SessionRow } from "@/lib/queries/dashboard";7import { CloseSessionButton } from "./close-session-button";89function expiresIn(d: Date): string {10 const diff = d.getTime() - Date.now();11 if (diff <= 0) return "expired";12 const s = Math.round(diff / 1000);13 if (s < 60) return `in ${s}s`;14 const m = Math.round(s / 60);15 if (m < 60) return `in ${m}m`;16 return `in ${Math.round(m / 60)}h`;17}1819export function SessionsTable({ rows }: { rows: SessionRow[] }) {20 return (21 <Table>22 <TableHeader>23 <TableRow className="hover:bg-transparent">24 <TableHead>Session</TableHead>25 <TableHead>Label</TableHead>26 <TableHead>Project</TableHead>27 <TableHead>Created</TableHead>28 <TableHead>Last used</TableHead>29 <TableHead>Country</TableHead>30 <TableHead>Network</TableHead>31 <TableHead className="text-right">Requests</TableHead>32 <TableHead>Status</TableHead>33 <TableHead>Expires</TableHead>34 <TableHead className="text-right"> </TableHead>35 </TableRow>36 </TableHeader>37 <TableBody>38 {rows.length === 0 ? (39 <TableEmpty colSpan={11}>No sessions yet.</TableEmpty>40 ) : (41 rows.map((s) => (42 <TableRow key={s.id}>43 <TableCell>44 <span className="inline-flex items-center gap-1">45 <code className="font-mono text-[12.5px]">{s.id}</code>46 <CopyButton value={s.id} className="size-6" />47 </span>48 </TableCell>49 <TableCell className="max-w-[160px] truncate text-fg-muted" title={s.label ?? undefined}>50 {s.label ?? <span className="text-fg-subtle">—</span>}51 </TableCell>52 <TableCell className="text-fg-muted">{s.projectName}</TableCell>53 <TableCell className="whitespace-nowrap text-fg-muted" title={formatDate(s.createdAt, { timeStyle: "medium" })}>54 {timeAgo(s.createdAt)}55 </TableCell>56 <TableCell className="whitespace-nowrap text-fg-muted" title={s.lastUsedAt ? formatDate(s.lastUsedAt, { timeStyle: "medium" }) : undefined}>57 {s.lastUsedAt ? timeAgo(s.lastUsedAt) : <span className="text-fg-subtle">never</span>}58 </TableCell>59 <TableCell className="font-mono tabular text-fg-muted">{[s.country, s.region, s.city].filter(Boolean).join(" · ") || "any"}</TableCell>60 <TableCell className="text-fg-muted">{s.network === "isp" ? "ISP" : s.network}</TableCell>61 <TableCell className="text-right font-mono tabular">62 {s.requestCount > 0 ? (63 <Link href={`/dashboard/requests?range=30d`} className="underline-offset-4 hover:underline">64 {formatNumber(s.requestCount)}65 </Link>66 ) : (67 "0"68 )}69 </TableCell>70 <TableCell>71 <StatusBadge status={s.status} />72 </TableCell>73 <TableCell className="whitespace-nowrap font-mono tabular text-fg-muted" title={formatDate(s.expiresAt, { timeStyle: "medium" })}>74 {s.status === "active" ? expiresIn(s.expiresAt) : formatDate(s.expiresAt, { dateStyle: "short", timeStyle: "short" })}75 </TableCell>76 <TableCell className="text-right">{s.status === "active" ? <CloseSessionButton id={s.id} /> : null}</TableCell>77 </TableRow>78 ))79 )}80 </TableBody>81 </Table>82 );83}84