diff --git a/ui/litellm-dashboard/src/components/VirtualKeysPage/keyTableColumns.tsx b/ui/litellm-dashboard/src/components/VirtualKeysPage/keyTableColumns.tsx
index 9dd4b1c2d59..6eea77ae827 100644
--- a/ui/litellm-dashboard/src/components/VirtualKeysPage/keyTableColumns.tsx
+++ b/ui/litellm-dashboard/src/components/VirtualKeysPage/keyTableColumns.tsx
@@ -9,17 +9,17 @@ import { HoverCard, HoverCardContent, HoverCardTrigger } from "@/components/ui/h
import { Skeleton } from "@/components/ui/skeleton";
import {
DateCell,
+ ENTITY_CELL_TITLE_CLASSES,
IdCell,
IdentityCell,
ModelsCell,
SpendBudgetCell,
StatusBadge,
+ UserPopoverCell,
type StatusTone,
} from "@/components/shared/table_cells";
-import { orgDetailHref, teamDetailHref, userDetailHref } from "@/utils/entityLinks";
-import { DEFAULT_PROXY_ADMIN_USER_ID } from "@/utils/sentinels";
+import { orgDetailHref, teamDetailHref } from "@/utils/entityLinks";
-import DefaultProxyAdminTag from "../common_components/DefaultProxyAdminTag";
import { KeyResponse, Team } from "../key_team_helpers/key_list";
import { Organization } from "../networking";
@@ -29,8 +29,6 @@ interface KeyStatus {
tooltip?: string;
}
-const ENTITY_CELL_TITLE_CLASSES = "font-mono text-xs font-normal";
-
const SPEND_BUDGET_SORT_FIELDS: DataTableSortField[] = [
{ id: "spend", label: "Spend" },
{ id: "max_budget", label: "Budget" },
@@ -66,60 +64,6 @@ const getKeyStatus = (key: KeyResponse): KeyStatus => {
};
};
-const UserPopoverCell = ({
- userAlias,
- userEmail,
- userId,
- width,
-}: {
- userAlias: string | null;
- userEmail: string | null;
- userId: string | null;
- width: number;
-}) => {
- const displayValue = userAlias || userEmail || userId;
- const isDefaultAdmin = userId === DEFAULT_PROXY_ADMIN_USER_ID;
-
- const popoverContent = (
-
- {[
- { label: "User Alias", value: userAlias },
- { label: "User Email", value: userEmail },
- { label: "User ID", value: userId },
- ].map(({ label, value }) => (
-
- {label}
- {value ? (
-
- ) : (
- -
- )}
-
- ))}
-
- );
-
- const trigger =
- isDefaultAdmin && !userAlias && !userEmail ? (
-
- ) : (
-
- );
-
- return (
-
- }>
- {trigger}
-
- {popoverContent}
-
- );
-};
-
const InfoHeader = ({ label, tooltip }: { label: string; tooltip: string }) => (
{label}
diff --git a/ui/litellm-dashboard/src/components/shared/table_cells/UserPopoverCell.tsx b/ui/litellm-dashboard/src/components/shared/table_cells/UserPopoverCell.tsx
new file mode 100644
index 00000000000..eb786e45541
--- /dev/null
+++ b/ui/litellm-dashboard/src/components/shared/table_cells/UserPopoverCell.tsx
@@ -0,0 +1,62 @@
+"use client";
+
+import DefaultProxyAdminTag from "@/components/common_components/DefaultProxyAdminTag";
+import { HoverCard, HoverCardContent, HoverCardTrigger } from "@/components/ui/hover-card";
+import { userDetailHref } from "@/utils/entityLinks";
+import { DEFAULT_PROXY_ADMIN_USER_ID } from "@/utils/sentinels";
+
+import { IdCell } from "./id_cell";
+import { IdentityCell } from "./identity_cell";
+
+export const ENTITY_CELL_TITLE_CLASSES = "font-mono text-xs font-normal";
+
+interface UserPopoverCellProps {
+ userAlias: string | null;
+ userEmail: string | null;
+ userId: string | null;
+ width: number;
+}
+
+export function UserPopoverCell({ userAlias, userEmail, userId, width }: UserPopoverCellProps) {
+ const displayValue = userAlias || userEmail || userId;
+ const isDefaultAdmin = userId === DEFAULT_PROXY_ADMIN_USER_ID;
+
+ const popoverContent = (
+
+ {[
+ { label: "User Alias", value: userAlias },
+ { label: "User Email", value: userEmail },
+ { label: "User ID", value: userId },
+ ].map(({ label, value }) => (
+
+ {label}
+ {value ? (
+
+ ) : (
+ -
+ )}
+
+ ))}
+
+ );
+
+ const trigger =
+ isDefaultAdmin && !userAlias && !userEmail ? (
+
+ ) : (
+
+ );
+
+ return (
+
+ }>
+ {trigger}
+
+ {popoverContent}
+
+ );
+}
diff --git a/ui/litellm-dashboard/src/components/shared/table_cells/id_cell.test.tsx b/ui/litellm-dashboard/src/components/shared/table_cells/id_cell.test.tsx
index 395c1815dd0..c715210fdde 100644
--- a/ui/litellm-dashboard/src/components/shared/table_cells/id_cell.test.tsx
+++ b/ui/litellm-dashboard/src/components/shared/table_cells/id_cell.test.tsx
@@ -71,6 +71,14 @@ describe("IdCell", () => {
expect(rowClick).not.toHaveBeenCalled();
});
+ it("names the copy button after the field it copies", async () => {
+ const user = userEvent.setup();
+ render();
+ expect(screen.queryByRole("button", { name: "Copy ID" })).not.toBeInTheDocument();
+ await user.click(screen.getByRole("button", { name: "Copy User Email" }));
+ expect(copyToClipboardMock).toHaveBeenCalledWith("alice@example.com");
+ });
+
it("passes dataTestId through to the id element", () => {
render();
expect(screen.getByTestId("key-id-cell")).toHaveTextContent("k-1");
diff --git a/ui/litellm-dashboard/src/components/shared/table_cells/id_cell.tsx b/ui/litellm-dashboard/src/components/shared/table_cells/id_cell.tsx
index 1b109a86106..33c7f835e64 100644
--- a/ui/litellm-dashboard/src/components/shared/table_cells/id_cell.tsx
+++ b/ui/litellm-dashboard/src/components/shared/table_cells/id_cell.tsx
@@ -15,6 +15,7 @@ interface IdCellProps {
variant?: IdCellVariant;
onClick?: (value: string) => void;
copyable?: boolean;
+ copyLabel?: string;
truncate?: boolean;
fallback?: string;
tooltip?: React.ReactNode;
@@ -39,6 +40,7 @@ export function IdCell({
variant = "pill",
onClick,
copyable = false,
+ copyLabel = "Copy ID",
truncate = true,
fallback = "-",
tooltip,
@@ -80,7 +82,7 @@ export function IdCell({
{withTooltip}