diff --git a/ui/litellm-dashboard/src/components/VirtualKeysPage/VirtualKeysTable.test.tsx b/ui/litellm-dashboard/src/components/VirtualKeysPage/VirtualKeysTable.test.tsx
index 9dc468c6a9f..07946c5b8c7 100644
--- a/ui/litellm-dashboard/src/components/VirtualKeysPage/VirtualKeysTable.test.tsx
+++ b/ui/litellm-dashboard/src/components/VirtualKeysPage/VirtualKeysTable.test.tsx
@@ -453,8 +453,8 @@ it("should open KeyInfoView when clicking on a key ID button", async () => {
// Wait for KeyInfoView to appear - check for unique elements that only exist in KeyInfoView
await waitFor(() => {
expect(screen.getByText("Back to Keys")).toBeInTheDocument();
- // KeyInfoView shows "Created:" or "Updated:" which is unique to it
- expect(screen.getByText(/Created:|Updated:/)).toBeInTheDocument();
+ // KeyInfoHeader shows "Created At" metadata label
+ expect(screen.getByText("Created At")).toBeInTheDocument();
});
// Verify that table-specific elements are no longer visible
diff --git a/ui/litellm-dashboard/src/components/common_components/LabeledField.test.tsx b/ui/litellm-dashboard/src/components/common_components/LabeledField.test.tsx
new file mode 100644
index 00000000000..6558fe8bad9
--- /dev/null
+++ b/ui/litellm-dashboard/src/components/common_components/LabeledField.test.tsx
@@ -0,0 +1,57 @@
+import { render, screen } from "@testing-library/react";
+import { describe, expect, it } from "vitest";
+import LabeledField from "./LabeledField";
+
+describe("LabeledField", () => {
+ it("should render the label and value", () => {
+ render();
+ expect(screen.getByText("User Email")).toBeInTheDocument();
+ expect(screen.getByText("test@example.com")).toBeInTheDocument();
+ });
+
+ it("should render the icon when provided", () => {
+ render(
+ } />,
+ );
+ expect(screen.getByTestId("test-icon")).toBeInTheDocument();
+ });
+
+ it("should show '-' when value is empty", () => {
+ render();
+ expect(screen.getByText("-")).toBeInTheDocument();
+ });
+
+ it("should show 'Default Proxy Admin' tag when value is default_user_id and defaultUserIdCheck is true", () => {
+ render(
+ ,
+ );
+ expect(screen.getByText("Default Proxy Admin")).toBeInTheDocument();
+ expect(screen.queryByText("default_user_id")).not.toBeInTheDocument();
+ });
+
+ it("should show raw value when value is default_user_id but defaultUserIdCheck is false", () => {
+ render();
+ expect(screen.getByText("default_user_id")).toBeInTheDocument();
+ expect(screen.queryByText("Default Proxy Admin")).not.toBeInTheDocument();
+ });
+
+ it("should not be copyable when value is empty", () => {
+ const { container } = render();
+ // antd adds a .ant-typography-copy element when copyable; should not be present
+ expect(container.querySelector(".ant-typography-copy")).not.toBeInTheDocument();
+ });
+
+ it("should not be copyable when value is default_user_id and defaultUserIdCheck is true", () => {
+ const { container } = render(
+ ,
+ );
+ expect(container.querySelector(".ant-typography-copy")).not.toBeInTheDocument();
+ });
+
+ it("should be copyable when copyable is true and value is present", () => {
+ const { container } = render(
+ ,
+ );
+ expect(container.querySelector(".ant-typography-copy")).toBeInTheDocument();
+ });
+});
diff --git a/ui/litellm-dashboard/src/components/common_components/LabeledField.tsx b/ui/litellm-dashboard/src/components/common_components/LabeledField.tsx
new file mode 100644
index 00000000000..75a8236a2fa
--- /dev/null
+++ b/ui/litellm-dashboard/src/components/common_components/LabeledField.tsx
@@ -0,0 +1,52 @@
+import React from "react";
+import { Typography, Space } from "antd";
+import DefaultProxyAdminTag from "./DefaultProxyAdminTag";
+
+const { Text } = Typography;
+
+interface LabeledFieldProps {
+ label: string;
+ value: string;
+ icon?: React.ReactNode;
+ truncate?: boolean;
+ copyable?: boolean;
+ defaultUserIdCheck?: boolean;
+}
+
+export default function LabeledField({
+ label,
+ value,
+ icon,
+ truncate = false,
+ copyable = false,
+ defaultUserIdCheck = false,
+}: LabeledFieldProps) {
+ const isEmpty = !value;
+ const isDefaultUser = defaultUserIdCheck && value === "default_user_id";
+ const displayValue = isEmpty ? "-" : value;
+ const isCopyable = copyable && !isEmpty && !isDefaultUser;
+
+ const valueEl = isDefaultUser ? (
+
+ ) : (
+
+ {displayValue}
+
+ );
+ return (
+
+
+ {icon}
+
+ {label}
+
+
+
{valueEl}
+
+ );
+}
diff --git a/ui/litellm-dashboard/src/components/templates/KeyInfoHeader.test.tsx b/ui/litellm-dashboard/src/components/templates/KeyInfoHeader.test.tsx
new file mode 100644
index 00000000000..0d746b1f872
--- /dev/null
+++ b/ui/litellm-dashboard/src/components/templates/KeyInfoHeader.test.tsx
@@ -0,0 +1,145 @@
+import { render, screen, waitFor } from "@testing-library/react";
+import userEvent from "@testing-library/user-event";
+import { describe, expect, it, vi } from "vitest";
+import { KeyInfoHeader, KeyInfoData } from "./KeyInfoHeader";
+
+const MOCK_DATA: KeyInfoData = {
+ keyName: "My Test Key",
+ keyId: "sk-1234567890abcdef",
+ userId: "user-abc-123",
+ userEmail: "test@example.com",
+ 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",
+};
+
+describe("KeyInfoHeader", () => {
+ it("should render", () => {
+ render();
+ expect(screen.getByText("My Test Key")).toBeInTheDocument();
+ });
+
+ it("should render the key ID with prefix", () => {
+ render();
+ expect(screen.getByText(/Key ID:/)).toBeInTheDocument();
+ expect(screen.getByText(/sk-1234567890abcdef/)).toBeInTheDocument();
+ });
+
+ it("should render all metadata fields", () => {
+ render();
+ expect(screen.getByText("User Email")).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("Last Updated")).toBeInTheDocument();
+ expect(screen.getByText("Last Active")).toBeInTheDocument();
+ });
+
+ describe("back button", () => {
+ it("should render with default text", () => {
+ render();
+ expect(screen.getByRole("button", { name: /back to keys/i })).toBeInTheDocument();
+ });
+
+ it("should render with custom text", () => {
+ render();
+ expect(screen.getByRole("button", { name: /back to dashboard/i })).toBeInTheDocument();
+ expect(screen.queryByRole("button", { name: /back to keys/i })).not.toBeInTheDocument();
+ });
+
+ it("should call onBack when clicked", async () => {
+ const onBack = vi.fn();
+ render();
+ await userEvent.click(screen.getByRole("button", { name: /back to keys/i }));
+ expect(onBack).toHaveBeenCalledTimes(1);
+ });
+ });
+
+ describe("action buttons", () => {
+ it("should show Regenerate and Delete buttons by default", () => {
+ render();
+ expect(screen.getByRole("button", { name: /regenerate key/i })).toBeInTheDocument();
+ expect(screen.getByRole("button", { name: /delete key/i })).toBeInTheDocument();
+ });
+
+ it("should show Regenerate and Delete buttons when canModifyKey is true", () => {
+ render();
+ expect(screen.getByRole("button", { name: /regenerate key/i })).toBeInTheDocument();
+ expect(screen.getByRole("button", { name: /delete key/i })).toBeInTheDocument();
+ });
+
+ it("should hide Regenerate and Delete buttons when canModifyKey is false", () => {
+ render();
+ expect(screen.queryByRole("button", { name: /regenerate key/i })).not.toBeInTheDocument();
+ expect(screen.queryByRole("button", { name: /delete key/i })).not.toBeInTheDocument();
+ });
+
+ it("should call onRegenerate when Regenerate Key is clicked", async () => {
+ const onRegenerate = vi.fn();
+ render();
+ await userEvent.click(screen.getByRole("button", { name: /regenerate key/i }));
+ expect(onRegenerate).toHaveBeenCalledTimes(1);
+ });
+
+ it("should call onDelete when Delete Key is clicked", async () => {
+ const onDelete = vi.fn();
+ render();
+ await userEvent.click(screen.getByRole("button", { name: /delete key/i }));
+ expect(onDelete).toHaveBeenCalledTimes(1);
+ });
+
+ it("should disable Regenerate button when regenerateDisabled is true", () => {
+ render();
+ expect(screen.getByRole("button", { name: /regenerate key/i })).toBeDisabled();
+ });
+
+ it("should not disable Regenerate button by default", () => {
+ render();
+ expect(screen.getByRole("button", { name: /regenerate key/i })).not.toBeDisabled();
+ });
+ });
+
+ describe("Create New Key button", () => {
+ it("should show when onCreateNew is provided", () => {
+ render();
+ expect(screen.getByRole("button", { name: /create new key/i })).toBeInTheDocument();
+ });
+
+ it("should hide when onCreateNew is not provided", () => {
+ render();
+ expect(screen.queryByRole("button", { name: /create new key/i })).not.toBeInTheDocument();
+ });
+
+ it("should call onCreateNew when clicked", async () => {
+ const onCreateNew = vi.fn();
+ render();
+ await userEvent.click(screen.getByRole("button", { name: /create new key/i }));
+ expect(onCreateNew).toHaveBeenCalledTimes(1);
+ });
+ });
+
+ 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" };
+ render();
+ expect(screen.getAllByText("Default Proxy Admin").length).toBeGreaterThanOrEqual(1);
+ });
+
+ it("should show Default Proxy Admin tag for Created By when value is default_user_id", () => {
+ const data = { ...MOCK_DATA, createdBy: "default_user_id" };
+ render();
+ expect(screen.getAllByText("Default Proxy Admin").length).toBeGreaterThanOrEqual(1);
+ });
+ });
+
+ describe("empty value handling", () => {
+ it("should show '-' for User Email when value is empty", () => {
+ const data = { ...MOCK_DATA, userEmail: "" };
+ 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
new file mode 100644
index 00000000000..1befd657843
--- /dev/null
+++ b/ui/litellm-dashboard/src/components/templates/KeyInfoHeader.tsx
@@ -0,0 +1,130 @@
+import React from "react";
+import { Button, Typography, Tooltip, Space, Divider, Flex } from "antd";
+import {
+ ArrowLeftOutlined,
+ SyncOutlined,
+ DeleteOutlined,
+ PlusOutlined,
+ UserOutlined,
+ MailOutlined,
+ CalendarOutlined,
+ ClockCircleOutlined,
+ ThunderboltOutlined,
+ SafetyCertificateOutlined,
+} from "@ant-design/icons";
+import LabeledField from "../common_components/LabeledField";
+
+const { Title, Text } = Typography;
+
+export interface KeyInfoData {
+ keyName: string;
+ keyId: string;
+ userId: string;
+ userEmail: string;
+ createdBy: string;
+ createdAt: string;
+ lastUpdated: string;
+ lastActive: string;
+}
+
+interface KeyInfoHeaderProps {
+ data: KeyInfoData;
+ onBack?: () => void;
+ onCreateNew?: () => void;
+ onRegenerate?: () => void;
+ onDelete?: () => void;
+ canModifyKey?: boolean;
+ backButtonText?: string;
+ regenerateDisabled?: boolean;
+ regenerateTooltip?: string;
+}
+
+export function KeyInfoHeader({
+ data,
+ onBack,
+ onCreateNew,
+ onRegenerate,
+ onDelete,
+ canModifyKey = true,
+ backButtonText = "Back to Keys",
+ regenerateDisabled = false,
+ regenerateTooltip,
+}: KeyInfoHeaderProps) {
+ return (
+
+ {onCreateNew && (
+
+ } onClick={onCreateNew}>
+ Create New Key
+
+
+ )}
+
+
+ } onClick={onBack}>
+ {backButtonText}
+
+
+
+
+
+
+ {data.keyName}
+
+
+ Key ID: {data.keyId}
+
+
+ {canModifyKey && (
+
+
+
+ } onClick={onRegenerate} disabled={regenerateDisabled}>
+ Regenerate Key
+
+
+
+ } onClick={onDelete}>
+ Delete Key
+
+
+ )}
+
+
+
+
+ } />
+ }
+ truncate
+ copyable
+ defaultUserIdCheck
+ />
+
+
+
+
+
+ } />
+ }
+ 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 94ca90b9630..378f5b3872b 100644
--- a/ui/litellm-dashboard/src/components/templates/key_info_view.tsx
+++ b/ui/litellm-dashboard/src/components/templates/key_info_view.tsx
@@ -1,13 +1,13 @@
import useAuthorized from "@/app/(dashboard)/hooks/useAuthorized";
import useTeams from "@/app/(dashboard)/hooks/useTeams";
-import { formatNumberWithCommas, copyToClipboard as utilCopyToClipboard } from "@/utils/dataUtils";
+import { formatNumberWithCommas } from "@/utils/dataUtils";
import { mapEmptyStringToNull } from "@/utils/keyUpdateUtils";
-import { ArrowLeftIcon, RefreshIcon, TrashIcon } from "@heroicons/react/outline";
+import { ArrowLeftIcon } from "@heroicons/react/outline";
import { Badge, Button, Card, Grid, Tab, TabGroup, TabList, TabPanel, TabPanels, Text, Title } from "@tremor/react";
-import { Button as AntdButton, Form, Tag, Tooltip } from "antd";
-import { CheckIcon, CopyIcon } from "lucide-react";
+import { Form, Tag } from "antd";
+import { KeyInfoHeader } from "./KeyInfoHeader";
import { useEffect, useState } from "react";
-import { isProxyAdminRole, isUserTeamAdminForSingleTeam, rolesWithWriteAccess } from "../../utils/roles";
+import { isProxyAdminRole, isUserTeamAdminForSingleTeam } from "../../utils/roles";
import { mapDisplayToInternalNames, mapInternalToDisplayNames } from "../callback_info_helpers";
import AutoRotationView from "../common_components/AutoRotationView";
import DeleteResourceModal from "../common_components/DeleteResourceModal";
@@ -54,8 +54,6 @@ export default function KeyInfoView({
const [deleteLoading, setDeleteLoading] = useState(false);
const [deleteConfirmInput, setDeleteConfirmInput] = useState("");
const [isRegenerateModalOpen, setIsRegenerateModalOpen] = useState(false);
- const [copiedStates, setCopiedStates] = useState>({});
-
// Add local state to maintain key data and track regeneration
const [currentKeyData, setCurrentKeyData] = useState(keyData);
const [lastRegeneratedAt, setLastRegeneratedAt] = useState(null);
@@ -284,16 +282,6 @@ export default function KeyInfoView({
}
};
- const copyToClipboard = async (text: string, key: string) => {
- const success = await utilCopyToClipboard(text);
- if (success) {
- setCopiedStates((prev) => ({ ...prev, [key]: true }));
- setTimeout(() => {
- setCopiedStates((prev) => ({ ...prev, [key]: false }));
- }, 2000);
- }
- };
-
const handleRegenerateKeyUpdate = (updatedKeyData: Partial) => {
// Update local state immediately with ALL the new data
setCurrentKeyData((prevData) => {
@@ -346,79 +334,29 @@ export default function KeyInfoView({
return (
-
-
-
-
{currentKeyData.key_alias || "Virtual Key"}
-
-
-
- Key ID
- {currentKeyData.token_id || currentKeyData.token}
-
-
:
}
- onClick={() => copyToClipboard(currentKeyData.token_id || currentKeyData.token, "key-id")}
- className={`ml-2 transition-all duration-200${copiedStates["key-id"]
- ? "text-green-600 bg-green-50 border-green-200"
- : "text-gray-500 hover:text-gray-700 hover:bg-gray-100"
- }`}
- />
-
-
- {/* Add timestamp and regeneration indicator */}
-
-
- {currentKeyData.updated_at && currentKeyData.updated_at !== currentKeyData.created_at
- ? `Updated: ${formatTimestamp(currentKeyData.updated_at)}`
- : `Created: ${formatTimestamp(currentKeyData.created_at)}`}
-
-
- {isRecentlyRegenerated && (
-
- Recently Regenerated
-
- )}
-
- {lastRegeneratedAt && (
-
- Regenerated
-
- )}
-
-
- {canModifyKey && (
-
-
-
-
-
-
-
-
- )}
-
+
setIsRegenerateModalOpen(true)}
+ onDelete={() => setIsDeleteModalOpen(true)}
+ canModifyKey={canModifyKey}
+ backButtonText={backButtonText}
+ regenerateDisabled={!premiumUser}
+ regenerateTooltip={
+ !premiumUser
+ ? "This is a LiteLLM Enterprise feature, and requires a valid key to use."
+ : undefined
+ }
+ />
{/* Add RegenerateKeyModal */}