diff --git a/ui/litellm-dashboard/src/components/key_team_helpers/key_list.tsx b/ui/litellm-dashboard/src/components/key_team_helpers/key_list.tsx index f410a04976..6b3c65aaf7 100644 --- a/ui/litellm-dashboard/src/components/key_team_helpers/key_list.tsx +++ b/ui/litellm-dashboard/src/components/key_team_helpers/key_list.tsx @@ -53,6 +53,7 @@ export interface KeyResponse { organization_id: string | null; org_id?: string | null; created_at: string; + created_by?: string; updated_at: string; last_active: string | null; team_spend: number; diff --git a/ui/litellm-dashboard/src/components/team/TeamVirtualKeysTable.tsx b/ui/litellm-dashboard/src/components/team/TeamVirtualKeysTable.tsx index a7e48ff12b..cfc08e8c4e 100644 --- a/ui/litellm-dashboard/src/components/team/TeamVirtualKeysTable.tsx +++ b/ui/litellm-dashboard/src/components/team/TeamVirtualKeysTable.tsx @@ -25,7 +25,8 @@ import { Text, } from "@tremor/react"; import { InfoCircleOutlined } from "@ant-design/icons"; -import { Popover, Skeleton, Tooltip } from "antd"; +import { Popover, Skeleton, Tooltip, Typography } from "antd"; +import DefaultProxyAdminTag from "../common_components/DefaultProxyAdminTag"; import React, { useCallback, useEffect, useMemo, useState } from "react"; import { getModelDisplayName } from "../key_team_helpers/fetch_available_models_team_key"; import { KeyResponse, Team } from "../key_team_helpers/key_list"; @@ -339,18 +340,59 @@ export function TeamVirtualKeysTable({ teamId, teamAlias, organization }: TeamVi size: 70, enableSorting: false, cell: (info) => { - const value = info.getValue() as string | null; - const displayValue = value === "default_user_id" ? "Default Proxy Admin" : value; + 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 width = info.cell.column.getSize(); + + 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 ( + + + + + + ); + } + return ( - + - {displayValue ?? "-"} + {displayValue} - + ); }, }, diff --git a/ui/litellm-dashboard/src/components/templates/KeyInfoHeader.test.tsx b/ui/litellm-dashboard/src/components/templates/KeyInfoHeader.test.tsx index c9c5856129..1475078760 100644 --- a/ui/litellm-dashboard/src/components/templates/KeyInfoHeader.test.tsx +++ b/ui/litellm-dashboard/src/components/templates/KeyInfoHeader.test.tsx @@ -8,10 +8,12 @@ const MOCK_DATA: KeyInfoData = { keyId: "sk-1234567890abcdef", userId: "user-abc-123", userEmail: "test@example.com", + userAlias: null, createdBy: "admin@example.com", createdAt: "Oct 29, 2025 at 1:26 AM", lastUpdated: "Oct 29, 2025 at 1:47 AM", lastActive: "Oct 29, 2025 at 2:00 AM", + expires: "Never", }; describe("KeyInfoHeader", () => { @@ -28,12 +30,11 @@ describe("KeyInfoHeader", () => { it("should render all metadata fields", () => { render(); - expect(screen.getByText("User Email")).toBeInTheDocument(); + expect(screen.getByText("User")).toBeInTheDocument(); expect(screen.getByText("test@example.com")).toBeInTheDocument(); - expect(screen.getByText("User ID")).toBeInTheDocument(); - expect(screen.getByText("user-abc-123")).toBeInTheDocument(); expect(screen.getByText("Created At")).toBeInTheDocument(); expect(screen.getByText("Created By")).toBeInTheDocument(); + expect(screen.getByText("Expires")).toBeInTheDocument(); expect(screen.getByText("Last Updated")).toBeInTheDocument(); expect(screen.getByText("Last Active")).toBeInTheDocument(); }); @@ -122,8 +123,8 @@ describe("KeyInfoHeader", () => { }); describe("default_user_id handling", () => { - it("should show Default Proxy Admin tag for User ID when value is default_user_id", () => { - const data = { ...MOCK_DATA, userId: "default_user_id" }; + it("should show Default Proxy Admin tag for User when userId is default_user_id and no alias/email", () => { + const data = { ...MOCK_DATA, userId: "default_user_id", userEmail: "", userAlias: null }; render(); expect(screen.getAllByText("Default Proxy Admin").length).toBeGreaterThanOrEqual(1); }); @@ -135,9 +136,27 @@ describe("KeyInfoHeader", () => { }); }); - describe("empty value handling", () => { - it("should show '-' for User Email when value is empty", () => { - const data = { ...MOCK_DATA, userEmail: "" }; + describe("User field fallbacks", () => { + it("should display userAlias as primary when set, overriding email and userId", () => { + const data = { ...MOCK_DATA, userAlias: "alice" }; + render(); + expect(screen.getByText("alice")).toBeInTheDocument(); + expect(screen.queryByText("test@example.com")).not.toBeInTheDocument(); + }); + + it("should display userEmail when alias is null", () => { + render(); + expect(screen.getByText("test@example.com")).toBeInTheDocument(); + }); + + it("should fall back to userId when alias and email are missing", () => { + const data = { ...MOCK_DATA, userEmail: "", userAlias: null }; + render(); + expect(screen.getByText("user-abc-123")).toBeInTheDocument(); + }); + + it("should show '-' when alias, email, and userId are all empty", () => { + const data = { ...MOCK_DATA, userId: "", userEmail: "", userAlias: null }; render(); expect(screen.getByText("-")).toBeInTheDocument(); }); diff --git a/ui/litellm-dashboard/src/components/templates/KeyInfoHeader.tsx b/ui/litellm-dashboard/src/components/templates/KeyInfoHeader.tsx index 93ebae9c4b..d39b46a5cd 100644 --- a/ui/litellm-dashboard/src/components/templates/KeyInfoHeader.tsx +++ b/ui/litellm-dashboard/src/components/templates/KeyInfoHeader.tsx @@ -1,19 +1,20 @@ import React from "react"; -import { Button, Typography, Tooltip, Space, Divider, Flex } from "antd"; +import { Button, Typography, Tooltip, Space, Divider, Flex, Popover } from "antd"; import { ArrowLeftOutlined, SyncOutlined, DeleteOutlined, PlusOutlined, UserOutlined, - MailOutlined, CalendarOutlined, ClockCircleOutlined, ThunderboltOutlined, SafetyCertificateOutlined, TransactionOutlined, + FieldTimeOutlined, } from "@ant-design/icons"; import LabeledField from "../common_components/LabeledField"; +import DefaultProxyAdminTag from "../common_components/DefaultProxyAdminTag"; const { Title, Text } = Typography; @@ -22,10 +23,12 @@ export interface KeyInfoData { keyId: string; userId: string; userEmail: string; + userAlias?: string | null; createdBy: string; createdAt: string; lastUpdated: string; lastActive: string; + expires: string; } interface KeyInfoHeaderProps { @@ -41,6 +44,94 @@ interface KeyInfoHeaderProps { regenerateTooltip?: string; } +function UserField({ + userAlias, + userEmail, + userId, +}: { + userAlias?: string | null; + userEmail: string; + userId: string; +}) { + const labelEl = ( + + + + User + + + ); + + const isEmpty = !userAlias && !userEmail && !userId; + if (isEmpty) { + return ( +
+ {labelEl} +
-
+
+ ); + } + + const isDefaultAdmin = userId === "default_user_id"; + const displayValue = userAlias || userEmail || userId; + + const popoverContent = ( +
+ {[ + { label: "User Alias", value: userAlias ?? null }, + { label: "User Email", value: userEmail || null }, + { label: "User ID", value: userId || null }, + ].map(({ label, value }) => ( +
+ {label} + {value ? ( + + {value} + + ) : ( + - + )} +
+ ))} +
+ ); + + if (isDefaultAdmin && !userAlias && !userEmail) { + return ( +
+ {labelEl} +
+ + + +
+
+ ); + } + + return ( +
+ {labelEl} +
+ + + {displayValue} + + +
+
+ ); +} + export function KeyInfoHeader({ data, onBack, @@ -101,15 +192,8 @@ export function KeyInfoHeader({ - } /> - } - truncate - copyable - defaultUserIdCheck - /> + + } /> diff --git a/ui/litellm-dashboard/src/components/templates/key_info_view.tsx b/ui/litellm-dashboard/src/components/templates/key_info_view.tsx index 65bd9d9eb9..f889778009 100644 --- a/ui/litellm-dashboard/src/components/templates/key_info_view.tsx +++ b/ui/litellm-dashboard/src/components/templates/key_info_view.tsx @@ -403,10 +403,16 @@ export default function KeyInfoView({ keyId: currentKeyData.token_id || currentKeyData.token, userId: currentKeyData.user_id || "", userEmail: currentKeyData.user_email || "", - createdBy: currentKeyData.user_email || currentKeyData.user_id || "", + userAlias: currentKeyData.user?.user_alias ?? null, + createdBy: + currentKeyData.created_by_user?.user_alias || + currentKeyData.created_by_user?.user_email || + currentKeyData.created_by || + "", createdAt: currentKeyData.created_at ? formatTimestamp(currentKeyData.created_at) : "", lastUpdated: currentKeyData.updated_at ? formatTimestamp(currentKeyData.updated_at) : "", lastActive: currentKeyData.last_active ? formatTimestamp(currentKeyData.last_active) : "Never", + expires: currentKeyData.expires ? formatTimestamp(currentKeyData.expires) : "Never", }} onBack={onClose} onRegenerate={() => setIsRegenerateModalOpen(true)}