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
+ >
+ )}
);
},