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..223d66d7421
--- /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/index.ts b/ui/litellm-dashboard/src/components/shared/table_cells/index.ts
index 99b54f9aad0..34e3701eed4 100644
--- a/ui/litellm-dashboard/src/components/shared/table_cells/index.ts
+++ b/ui/litellm-dashboard/src/components/shared/table_cells/index.ts
@@ -13,3 +13,4 @@ export { ModelsCell } from "./models_cell";
export { MoneyCell } from "./money_cell";
export { SpendBudgetCell } from "./spend_budget_cell";
export { StatusBadge, type StatusTone } from "./status_badge";
+export { UserPopoverCell, ENTITY_CELL_TITLE_CLASSES } from "./UserPopoverCell";
diff --git a/ui/litellm-dashboard/src/components/team/TeamVirtualKeysTable.test.tsx b/ui/litellm-dashboard/src/components/team/TeamVirtualKeysTable.test.tsx
index 0d9d0988aa1..76df0d12539 100644
--- a/ui/litellm-dashboard/src/components/team/TeamVirtualKeysTable.test.tsx
+++ b/ui/litellm-dashboard/src/components/team/TeamVirtualKeysTable.test.tsx
@@ -10,6 +10,8 @@ vi.mock("@/app/(dashboard)/hooks/keys/useKeys", () => ({
useKeys: vi.fn(),
}));
+vi.mock("next/navigation", () => ({ useRouter: () => ({ push: vi.fn() }) }));
+
vi.mock("../key_team_helpers/fetch_available_models_team_key", () => ({
getModelDisplayName: vi.fn((model: string) => model),
}));
@@ -384,4 +386,64 @@ describe("TeamVirtualKeysTable", () => {
expect(screen.getByText("Key Info View")).toBeInTheDocument();
});
});
+
+ describe("entity links out of the key rows", () => {
+ const renderRow = async (key: KeyResponse, organization: Organization | null = null) => {
+ mockUseKeys.mockReturnValue({
+ data: { keys: [key], total_count: 1, current_page: 1, total_pages: 1 } as KeysResponse,
+ isPending: false,
+ isFetching: false,
+ refetch: vi.fn(),
+ } as any);
+ renderWithProviders();
+ return (await screen.findByText(key.key_alias as string)).closest("tr") as HTMLElement;
+ };
+
+ it("points the Organization ID cell at the org's detail page", async () => {
+ const row = await renderRow(createMockKey({ organization_id: null }), mockOrganization);
+ expect(within(row).getByRole("link", { name: "org-123" })).toHaveAttribute(
+ "href",
+ "/ui/organizations?org=org-123",
+ );
+ });
+
+ it("points the User Email and User ID cells at the owning user's detail page", async () => {
+ const row = await renderRow(
+ createMockKey({ user_id: "user-1", user: { user_id: "user-1", user_email: "alice@example.com" } }),
+ );
+ expect(within(row).getByRole("link", { name: "alice@example.com" })).toHaveAttribute(
+ "href",
+ "/ui/users?user=user-1",
+ );
+ expect(within(row).getByRole("link", { name: "user-1" })).toHaveAttribute("href", "/ui/users?user=user-1");
+ });
+
+ it("points the Created By cell at the creator's detail page", async () => {
+ const row = await renderRow(
+ createMockKey({
+ created_by: "creator-1",
+ created_by_user: { user_id: "creator-1", user_email: "creator@example.com", user_alias: "The Creator" },
+ }),
+ );
+ expect(within(row).getByRole("link", { name: "The Creator" })).toHaveAttribute(
+ "href",
+ "/ui/users?user=creator-1",
+ );
+ });
+
+ it("leaves the default_user_id placeholder unlinked in the User ID and Created By cells", async () => {
+ const placeholder = { user_id: "default_user_id", user_email: "admin@example.com", user_alias: "Proxy Admin" };
+ const row = await renderRow(
+ createMockKey({
+ user_id: placeholder.user_id,
+ user: placeholder,
+ created_by: placeholder.user_id,
+ created_by_user: placeholder,
+ }),
+ );
+ expect(within(row).getByText("Default Proxy Admin")).toBeInTheDocument();
+ expect(within(row).getByText("Proxy Admin")).toBeInTheDocument();
+ expect(within(row).queryByRole("link", { name: "Proxy Admin" })).not.toBeInTheDocument();
+ });
+ });
});
diff --git a/ui/litellm-dashboard/src/components/team/TeamVirtualKeysTable.tsx b/ui/litellm-dashboard/src/components/team/TeamVirtualKeysTable.tsx
index b0380255b95..5b1b71e060e 100644
--- a/ui/litellm-dashboard/src/components/team/TeamVirtualKeysTable.tsx
+++ b/ui/litellm-dashboard/src/components/team/TeamVirtualKeysTable.tsx
@@ -1,8 +1,14 @@
"use client";
import { useKeys } from "@/app/(dashboard)/hooks/keys/useKeys";
import { SimpleTooltip } from "@/components/ui/tooltip";
-import CopyButton from "@/components/shared/CopyButton";
-import { DateCell, IdCell, MoneyCell } from "@/components/shared/table_cells";
+import {
+ DateCell,
+ ENTITY_CELL_TITLE_CLASSES,
+ IdCell,
+ IdentityCell,
+ MoneyCell,
+ UserPopoverCell,
+} from "@/components/shared/table_cells";
import {
DataTable,
DataTableFilterDrawer,
@@ -11,8 +17,9 @@ import {
DataTableToolbar,
} from "@/components/shared/DataTable";
import { Badge } from "@/components/ui/badge";
-import { HoverCard, HoverCardContent, HoverCardTrigger } from "@/components/ui/hover-card";
import { Input } from "@/components/ui/input";
+import { orgDetailHref, userDetailHref } from "@/utils/entityLinks";
+import { DEFAULT_PROXY_ADMIN_USER_ID } from "@/utils/sentinels";
import { DEBOUNCE_WAIT_MS } from "@/utils/debounceConstants";
import { useDebouncedValue } from "@tanstack/react-pacer/debouncer";
import { ColumnDef, ColumnFiltersState, OnChangeFn, PaginationState, SortingState } from "@tanstack/react-table";
@@ -168,7 +175,15 @@ export function TeamVirtualKeysTable({ teamId, teamAlias, organization }: TeamVi
header: "Organization ID",
size: 140,
enableSorting: false,
- cell: (info) => (info.getValue() ? info.renderValue() : "-"),
+ cell: (info) => {
+ const orgId = info.getValue() as string | null;
+ if (!orgId) return "-";
+ return (
+
+
+
+ );
+ },
},
{
id: "user_email",
@@ -179,9 +194,14 @@ export function TeamVirtualKeysTable({ teamId, teamAlias, organization }: TeamVi
cell: (info) => {
const user = info.getValue() as { user_email?: string } | undefined;
const value = user?.user_email;
+ const userId = info.row.original.user_id;
return (
- {value ?? "-"}
+
);
},
@@ -194,10 +214,16 @@ export function TeamVirtualKeysTable({ teamId, teamAlias, organization }: TeamVi
enableSorting: false,
cell: (info) => {
const userId = info.getValue() as string | null;
- const displayValue = userId === "default_user_id" ? "Default Proxy Admin" : userId;
+ if (userId === DEFAULT_PROXY_ADMIN_USER_ID) {
+ return ;
+ }
return (
-
- {displayValue ?? "-"}
+
+
);
},
@@ -221,53 +247,13 @@ export function TeamVirtualKeysTable({ teamId, teamAlias, organization }: TeamVi
const userId = info.getValue() as string | null;
if (!userId) return "-";
const { created_by_user } = info.row.original;
- const userAlias = created_by_user?.user_alias ?? null;
- const userEmail = created_by_user?.user_email ?? null;
- const isDefaultAdmin = userId === "default_user_id";
- const displayValue = userAlias || userEmail || userId;
-
- const popoverContent = (
-
- {[
- { label: "User Alias", value: userAlias },
- { label: "User Email", value: userEmail },
- { label: "User ID", value: userId },
- ].map(({ label, value }) => (
-
- {label}
- {value ? (
-
- {value}
-
-
- ) : (
- -
- )}
-
- ))}
-
- );
-
- if (isDefaultAdmin && !userAlias && !userEmail) {
- return (
-
- }>
-
-
- {popoverContent}
-
- );
- }
-
return (
-
- }
- >
- {displayValue}
-
- {popoverContent}
-
+
);
},
},