From 168a8731cef839d6540c2cc5abe0d51255c51cf7 Mon Sep 17 00:00:00 2001 From: yuneng-jiang Date: Thu, 12 Feb 2026 16:02:54 -0800 Subject: [PATCH] improve credentials messaging --- .../molecules/models/columns.test.tsx | 129 +++++++++++++++++- .../components/molecules/models/columns.tsx | 83 ++++++++--- 2 files changed, 194 insertions(+), 18 deletions(-) diff --git a/ui/litellm-dashboard/src/components/molecules/models/columns.test.tsx b/ui/litellm-dashboard/src/components/molecules/models/columns.test.tsx index 665023f1da..0a8dacbadd 100644 --- a/ui/litellm-dashboard/src/components/molecules/models/columns.test.tsx +++ b/ui/litellm-dashboard/src/components/molecules/models/columns.test.tsx @@ -200,7 +200,7 @@ describe("columns", () => { expect(screen.getByText("my-credential")).toBeInTheDocument(); }); - it("should display 'No credentials' when credential name is missing", () => { + it("should display 'Manual' when credential name is missing", () => { const cols = columns( defaultProps.userRole, defaultProps.userID, @@ -221,7 +221,132 @@ describe("columns", () => { }); render(); - expect(screen.getByText("No credentials")).toBeInTheDocument(); + expect(screen.getByText("Manual")).toBeInTheDocument(); + }); + + describe("credentials column", () => { + it("should display Credentials header with info icon", () => { + const cols = columns( + defaultProps.userRole, + defaultProps.userID, + defaultProps.premiumUser, + defaultProps.setSelectedModelId, + defaultProps.setSelectedTeamId, + defaultProps.getDisplayModelName, + defaultProps.handleEditClick, + defaultProps.handleRefreshClick, + defaultProps.expandedRows, + defaultProps.setExpandedRows, + ); + + const model = createMockModel(); + render(); + + expect(screen.getByText("Credentials")).toBeInTheDocument(); + // Info icon is in a flex container with Credentials - ant icons render as span with role="img" + const credentialsHeader = screen.getByText("Credentials").closest("span"); + expect(credentialsHeader?.parentElement?.querySelector('[role="img"]')).toBeInTheDocument(); + }); + + it("should display reusable credential with SyncOutlined icon and credential name", () => { + const cols = columns( + defaultProps.userRole, + defaultProps.userID, + defaultProps.premiumUser, + defaultProps.setSelectedModelId, + defaultProps.setSelectedTeamId, + defaultProps.getDisplayModelName, + defaultProps.handleEditClick, + defaultProps.handleRefreshClick, + defaultProps.expandedRows, + defaultProps.setExpandedRows, + ); + + const model = createMockModel({ + litellm_params: { + model: "gpt-4", + litellm_credential_name: "my-reusable-credential", + }, + }); + render(); + + expect(screen.getByText("my-reusable-credential")).toBeInTheDocument(); + const credentialCell = screen.getByText("my-reusable-credential").closest("div"); + expect(credentialCell).toHaveClass("flex"); + expect(screen.getByText("my-reusable-credential")).toHaveClass("text-blue-600"); + }); + + it("should display Manual with EditOutlined when no credential name", () => { + const cols = columns( + defaultProps.userRole, + defaultProps.userID, + defaultProps.premiumUser, + defaultProps.setSelectedModelId, + defaultProps.setSelectedTeamId, + defaultProps.getDisplayModelName, + defaultProps.handleEditClick, + defaultProps.handleRefreshClick, + defaultProps.expandedRows, + defaultProps.setExpandedRows, + ); + + const model = createMockModel({ + litellm_params: { + model: "gpt-4", + }, + }); + render(); + + expect(screen.getByText("Manual")).toBeInTheDocument(); + expect(screen.getByText("Manual")).toHaveClass("text-gray-500"); + }); + + it("should display Manual when litellm_params is undefined", () => { + const cols = columns( + defaultProps.userRole, + defaultProps.userID, + defaultProps.premiumUser, + defaultProps.setSelectedModelId, + defaultProps.setSelectedTeamId, + defaultProps.getDisplayModelName, + defaultProps.handleEditClick, + defaultProps.handleRefreshClick, + defaultProps.expandedRows, + defaultProps.setExpandedRows, + ); + + const model = createMockModel({ + litellm_params: undefined as any, + }); + render(); + + expect(screen.getByText("Manual")).toBeInTheDocument(); + }); + + it("should display Manual when litellm_credential_name is empty string", () => { + const cols = columns( + defaultProps.userRole, + defaultProps.userID, + defaultProps.premiumUser, + defaultProps.setSelectedModelId, + defaultProps.setSelectedTeamId, + defaultProps.getDisplayModelName, + defaultProps.handleEditClick, + defaultProps.handleRefreshClick, + defaultProps.expandedRows, + defaultProps.setExpandedRows, + ); + + const model = createMockModel({ + litellm_params: { + model: "gpt-4", + litellm_credential_name: "", + }, + }); + render(); + + expect(screen.getByText("Manual")).toBeInTheDocument(); + }); }); it("should display created by information for DB models", () => { diff --git a/ui/litellm-dashboard/src/components/molecules/models/columns.tsx b/ui/litellm-dashboard/src/components/molecules/models/columns.tsx index 8bb28e68b7..f6114bc9f3 100644 --- a/ui/litellm-dashboard/src/components/molecules/models/columns.tsx +++ b/ui/litellm-dashboard/src/components/molecules/models/columns.tsx @@ -1,11 +1,45 @@ -import { KeyIcon, TrashIcon } from "@heroicons/react/outline"; +import { EditOutlined, InfoCircleOutlined, SyncOutlined } from "@ant-design/icons"; +import { TrashIcon } from "@heroicons/react/outline"; import { ColumnDef } from "@tanstack/react-table"; import { Badge, Button, Icon } from "@tremor/react"; -import { Popover, Tooltip, Typography, Space, Flex } from "antd"; +import { Divider, Flex, Popover, Space, Tooltip, Typography } from "antd"; import { ModelData } from "../../model_dashboard/types"; import { ProviderLogo } from "./ProviderLogo"; -const { Text } = Typography; +const { Text, Title } = Typography; + +const credentialsInfoPopoverContent = ( + + + Credential types + + + + + + + Reusable + + + Credentials saved in LiteLLM that can be added to models repeatedly. + + + + + + + + + Manual + + + Credentials added directly during model creation or defined in the config file. + + + + + +); export const columns = ( userRole: string, @@ -127,7 +161,21 @@ export const columns = ( }, }, { - header: () => Credentials, + header: () => ( + + Credentials + + + + + ), accessorKey: "litellm_credential_name", enableSorting: false, size: 180, @@ -135,20 +183,23 @@ export const columns = ( cell: ({ row }) => { const model = row.original; const credentialName = model.litellm_params?.litellm_credential_name; + const isReusable = !!credentialName; - return credentialName ? ( - -
- - - {credentialName} - -
-
- ) : ( + return (
- - No credentials + {isReusable ? ( + <> + + + {credentialName} + + + ) : ( + <> + + Manual + + )}
); },