handleDeleteClick(value)}
+ />
))}
+ {isDeleteModalVisible && (
+
+ Are you sure you want to delete budget: {selectedBudget?.budget_id} ?
+ This action cannot be undone.
+
+ )}
How to use budget id
diff --git a/ui/litellm-dashboard/src/components/callback_info_helpers.tsx b/ui/litellm-dashboard/src/components/callback_info_helpers.tsx
index 22f7989a13a..7abe255accc 100644
--- a/ui/litellm-dashboard/src/components/callback_info_helpers.tsx
+++ b/ui/litellm-dashboard/src/components/callback_info_helpers.tsx
@@ -7,7 +7,7 @@ interface CallbackConfig {
description: string;
}
-const asset_logos_folder = '/ui/assets/logos/';
+const asset_logos_folder = "/ui/assets/logos/";
export const CALLBACK_CONFIGS: CallbackConfig[] = [
{
@@ -16,10 +16,10 @@ export const CALLBACK_CONFIGS: CallbackConfig[] = [
logo: `${asset_logos_folder}arize.png`,
supports_key_team_logging: true,
dynamic_params: {
- "arize_api_key": "password",
- "arize_space_key": "password",
+ arize_api_key: "password",
+ arize_space_key: "password",
},
- description: "Arize Logging Integration"
+ description: "Arize Logging Integration",
},
{
id: "braintrust",
@@ -27,10 +27,10 @@ export const CALLBACK_CONFIGS: CallbackConfig[] = [
logo: `${asset_logos_folder}braintrust.png`,
supports_key_team_logging: false,
dynamic_params: {
- "braintrust_api_key": "password",
- "braintrust_project_name": "text"
+ braintrust_api_key: "password",
+ braintrust_project_name: "text",
},
- description: "Braintrust Logging Integration"
+ description: "Braintrust Logging Integration",
},
{
id: "custom_callback_api",
@@ -38,10 +38,10 @@ export const CALLBACK_CONFIGS: CallbackConfig[] = [
logo: `${asset_logos_folder}custom.svg`,
supports_key_team_logging: true,
dynamic_params: {
- "custom_callback_api_url": "text",
- "custom_callback_api_headers": "text"
+ custom_callback_api_url: "text",
+ custom_callback_api_headers: "text",
},
- description: "Custom Callback API Logging Integration"
+ description: "Custom Callback API Logging Integration",
},
{
id: "datadog",
@@ -49,10 +49,10 @@ export const CALLBACK_CONFIGS: CallbackConfig[] = [
logo: `${asset_logos_folder}datadog.png`,
supports_key_team_logging: false,
dynamic_params: {
- "dd_api_key": "password",
- "dd_site": "text"
+ dd_api_key: "password",
+ dd_site: "text",
},
- description: "Datadog Logging Integration"
+ description: "Datadog Logging Integration",
},
{
id: "lago",
@@ -60,10 +60,10 @@ export const CALLBACK_CONFIGS: CallbackConfig[] = [
logo: `${asset_logos_folder}lago.svg`,
supports_key_team_logging: false,
dynamic_params: {
- "lago_api_url": "text",
- "lago_api_key": "password"
+ lago_api_url: "text",
+ lago_api_key: "password",
},
- description: "Lago Billing Logging Integration"
+ description: "Lago Billing Logging Integration",
},
{
id: "langfuse",
@@ -71,11 +71,11 @@ export const CALLBACK_CONFIGS: CallbackConfig[] = [
logo: `${asset_logos_folder}langfuse.png`,
supports_key_team_logging: true,
dynamic_params: {
- "langfuse_public_key": "text",
- "langfuse_secret_key": "password",
- "langfuse_host": "text"
+ langfuse_public_key: "text",
+ langfuse_secret_key: "password",
+ langfuse_host: "text",
},
- description: "Langfuse v2 Logging Integration"
+ description: "Langfuse v2 Logging Integration",
},
{
id: "langfuse_otel",
@@ -83,11 +83,11 @@ export const CALLBACK_CONFIGS: CallbackConfig[] = [
logo: `${asset_logos_folder}langfuse.png`,
supports_key_team_logging: true,
dynamic_params: {
- "langfuse_public_key": "text",
- "langfuse_secret_key": "password",
- "langfuse_host": "text"
+ langfuse_public_key: "text",
+ langfuse_secret_key: "password",
+ langfuse_host: "text",
},
- description: "Langfuse v3 OTEL Logging Integration"
+ description: "Langfuse v3 OTEL Logging Integration",
},
{
id: "langsmith",
@@ -95,12 +95,12 @@ export const CALLBACK_CONFIGS: CallbackConfig[] = [
logo: `${asset_logos_folder}langsmith.png`,
supports_key_team_logging: true,
dynamic_params: {
- "langsmith_api_key": "password",
- "langsmith_project": "text",
- "langsmith_base_url": "text",
- "langsmith_sampling_rate": "number"
+ langsmith_api_key: "password",
+ langsmith_project: "text",
+ langsmith_base_url: "text",
+ langsmith_sampling_rate: "number",
},
- description: "Langsmith Logging Integration"
+ description: "Langsmith Logging Integration",
},
{
id: "openmeter",
@@ -108,10 +108,10 @@ export const CALLBACK_CONFIGS: CallbackConfig[] = [
logo: `${asset_logos_folder}openmeter.png`,
supports_key_team_logging: false,
dynamic_params: {
- "openmeter_api_key": "password",
- "openmeter_base_url": "text"
+ openmeter_api_key: "password",
+ openmeter_base_url: "text",
},
- description: "OpenMeter Logging Integration"
+ description: "OpenMeter Logging Integration",
},
{
id: "otel",
@@ -119,10 +119,10 @@ export const CALLBACK_CONFIGS: CallbackConfig[] = [
logo: `${asset_logos_folder}otel.png`,
supports_key_team_logging: false,
dynamic_params: {
- "otel_endpoint": "text",
- "otel_headers": "text"
+ otel_endpoint: "text",
+ otel_headers: "text",
},
- description: "OpenTelemetry Logging Integration"
+ description: "OpenTelemetry Logging Integration",
},
{
id: "s3",
@@ -130,48 +130,70 @@ export const CALLBACK_CONFIGS: CallbackConfig[] = [
logo: `${asset_logos_folder}aws.svg`,
supports_key_team_logging: false,
dynamic_params: {
- "s3_bucket_name": "text",
- "aws_access_key_id": "password",
- "aws_secret_access_key": "password",
- "aws_region": "text"
+ s3_bucket_name: "text",
+ aws_access_key_id: "password",
+ aws_secret_access_key: "password",
+ aws_region: "text",
},
- description: "S3 Bucket (AWS) Logging Integration"
- }
+ description: "S3 Bucket (AWS) Logging Integration",
+ },
+ {
+ id: "SQS",
+ displayName: "SQS",
+ logo: `${asset_logos_folder}aws.svg`,
+ supports_key_team_logging: false,
+ dynamic_params: {
+ sqs_queue_url: "text",
+ aws_access_key_id: "password",
+ aws_secret_access_key: "password",
+ aws_region: "text",
+ },
+ description: "SQS Queue (AWS) Logging Integration",
+ },
];
// Create callbackInfo object mapping display names to config objects
-export const callbackInfo: Record = CALLBACK_CONFIGS.reduce((acc, config) => {
- acc[config.displayName] = config;
- return acc;
-}, {} as Record);
+export const callbackInfo: Record = CALLBACK_CONFIGS.reduce(
+ (acc, config) => {
+ acc[config.displayName] = config;
+ return acc;
+ },
+ {} as Record,
+);
// Create callback_map mapping display names to internal IDs
-export const callback_map: Record = CALLBACK_CONFIGS.reduce((acc, config) => {
- acc[config.displayName] = config.id;
- return acc;
-}, {} as Record);
+export const callback_map: Record = CALLBACK_CONFIGS.reduce(
+ (acc, config) => {
+ acc[config.displayName] = config.id;
+ return acc;
+ },
+ {} as Record,
+);
// create reverse_callback_map to map internal IDs to display names
-export const reverse_callback_map: Record = CALLBACK_CONFIGS.reduce((acc, config) => {
- acc[config.id] = config.displayName;
- return acc;
-}, {} as Record);
+export const reverse_callback_map: Record = CALLBACK_CONFIGS.reduce(
+ (acc, config) => {
+ acc[config.id] = config.displayName;
+ return acc;
+ },
+ {} as Record,
+);
// Function to map display names to internal names
export const mapDisplayToInternalNames = (displayNames: string[]): string[] => {
- return displayNames.map(name => callback_map[name] || name);
+ return displayNames.map((name) => callback_map[name] || name);
};
// Function to map internal names to display names
export const mapInternalToDisplayNames = (internalNames: string[]): string[] => {
- return internalNames.map(name => reverse_callback_map[name] || name);
+ return internalNames.map((name) => reverse_callback_map[name] || name);
};
// Utility functions for easy access
export const getCallbackById = (id: string): CallbackConfig | undefined => {
- return CALLBACK_CONFIGS.find(callback => callback.id === id);
+ return CALLBACK_CONFIGS.find((callback) => callback.id === id);
};
export const getCallbackByDisplayName = (displayName: string): CallbackConfig | undefined => {
- return CALLBACK_CONFIGS.find(callback => callback.displayName === displayName);
+ return CALLBACK_CONFIGS.find((callback) => callback.displayName === displayName);
};
diff --git a/ui/litellm-dashboard/src/components/chat_ui/ChatUI.test.tsx b/ui/litellm-dashboard/src/components/chat_ui/ChatUI.test.tsx
index 6670395aad5..53fa2b0916e 100644
--- a/ui/litellm-dashboard/src/components/chat_ui/ChatUI.test.tsx
+++ b/ui/litellm-dashboard/src/components/chat_ui/ChatUI.test.tsx
@@ -1,4 +1,4 @@
-import { fireEvent, render, screen, waitFor } from "@testing-library/react";
+import { act, fireEvent, render, screen, waitFor } from "@testing-library/react";
import { beforeEach, describe, expect, it, vi } from "vitest";
import ChatUI from "./ChatUI";
import * as fetchModelsModule from "./llm_calls/fetch_models";
@@ -118,18 +118,64 @@ describe("ChatUI", () => {
expect(getByText("Test Key")).toBeInTheDocument();
});
- await waitFor(() => {
- expect(getByText("Model 1")).toBeInTheDocument();
- });
+ // Open the "Select Model" dropdown (AntD renders options in a portal)
+ const selectModelLabel = getByText("Select Model");
+ const modelSelect = selectModelLabel.parentElement?.querySelector(".ant-select-selector");
+ expect(modelSelect).toBeTruthy();
- const selectComponent = container.querySelectorAll(".ant-select-selector")[1];
- expect(selectComponent).toBeTruthy();
-
- fireEvent.mouseDown(selectComponent!);
+ fireEvent.mouseDown(modelSelect!);
await waitFor(() => {
const model1Label = screen.getAllByText("Model 1");
expect(model1Label.length).toBeGreaterThan(0);
});
});
+
+ it("shows only chat-compatible models when chat endpoint is selected", async () => {
+ vi.mocked(fetchModelsModule.fetchAvailableModels).mockResolvedValueOnce([
+ { model_group: "ChatModel", mode: "chat" },
+ { model_group: "SpeechModel", mode: "audio_speech" },
+ { model_group: "ImageModel", mode: "image_generation" },
+ { model_group: "ResponsesModel", mode: "responses" },
+ ]);
+
+ const { getByText, baseElement } = render(
+ ,
+ );
+
+ await waitFor(() => {
+ expect(getByText("Test Key")).toBeInTheDocument();
+ });
+
+ // Open endpoint selector and explicitly select /v1/chat/completions
+ const endpointTypeText = getByText("Endpoint Type");
+ const endpointSelect = endpointTypeText.parentElement?.querySelector(".ant-select-selector");
+ expect(endpointSelect).toBeTruthy();
+ act(() => {
+ fireEvent.mouseDown(endpointSelect!);
+ fireEvent.click(screen.getByText("/v1/chat/completions"));
+ });
+
+ // Open model selector
+ const selectModelLabel = getByText("Select Model");
+ const modelSelect = selectModelLabel.parentElement?.querySelector(".ant-select-selector");
+ expect(modelSelect).toBeTruthy();
+ act(() => {
+ fireEvent.mouseDown(modelSelect!);
+ });
+
+ await waitFor(() => {
+ // Chat-compatible: ChatModel should be visible
+ expect(screen.getAllByText("ChatModel").length).toBeGreaterThan(0);
+ expect(screen.queryByText("SpeechModel")).toBeNull();
+ expect(screen.queryByText("ImageModel")).toBeNull();
+ expect(screen.queryByText("ResponsesModel")).toBeNull();
+ });
+ });
});
diff --git a/ui/litellm-dashboard/src/components/chat_ui/ChatUI.tsx b/ui/litellm-dashboard/src/components/chat_ui/ChatUI.tsx
index a006d6734b2..b619c755b57 100644
--- a/ui/litellm-dashboard/src/components/chat_ui/ChatUI.tsx
+++ b/ui/litellm-dashboard/src/components/chat_ui/ChatUI.tsx
@@ -48,7 +48,7 @@ import { makeOpenAIImageEditsRequest } from "./llm_calls/image_edits";
import { makeOpenAIImageGenerationRequest } from "./llm_calls/image_generation";
import { makeOpenAIResponsesRequest } from "./llm_calls/responses_api";
import MCPEventsDisplay, { MCPEvent } from "./MCPEventsDisplay";
-import { EndpointType } from "./mode_endpoint_mapping";
+import { EndpointType, getEndpointType } from "./mode_endpoint_mapping";
import ReasoningContent from "./ReasoningContent";
import ResponseMetrics, { TokenUsage } from "./ResponseMetrics";
import ResponsesImageRenderer from "./ResponsesImageRenderer";
@@ -106,9 +106,7 @@ const ChatUI: React.FC = ({ accessToken, token, userRole, userID, d
return [];
}
});
- const [selectedModel, setSelectedModel] = useState(
- () => sessionStorage.getItem("selectedModel") || undefined,
- );
+ const [selectedModel, setSelectedModel] = useState(undefined);
const [showCustomModelInput, setShowCustomModelInput] = useState(false);
const [modelInfo, setModelInfo] = useState([]);
const customModelTimeout = useRef(null);
@@ -311,7 +309,7 @@ const ChatUI: React.FC = ({ accessToken, token, userRole, userID, d
if (!uniqueModels.length) {
setSelectedModel(undefined);
} else if (!hasSelection) {
- setSelectedModel(uniqueModels[0].model_group);
+ setSelectedModel(undefined);
}
} catch (error) {
console.error("Error fetching model info:", error);
@@ -978,44 +976,6 @@ const ChatUI: React.FC = ({ accessToken, token, userRole, userID, d
)}
-
-
- Select Model
-
-
-
Endpoint Type
@@ -1024,6 +984,12 @@ const ChatUI: React.FC = ({ accessToken, token, userRole, userID, d
endpointType={endpointType}
onEndpointChange={(value) => {
setEndpointType(value);
+ // Clear model selection when switching endpoint type
+ setSelectedModel(undefined);
+ setShowCustomModelInput(false);
+ try {
+ sessionStorage.removeItem("selectedModel");
+ } catch {}
}}
className="mb-4"
/>
@@ -1057,6 +1023,68 @@ const ChatUI: React.FC = ({ accessToken, token, userRole, userID, d
/>
+
+
+ Select Model
+
+
+
Tags
diff --git a/ui/litellm-dashboard/src/components/chat_ui/mode_endpoint_mapping.tsx b/ui/litellm-dashboard/src/components/chat_ui/mode_endpoint_mapping.tsx
index 2aad64fb619..479d3cb8940 100644
--- a/ui/litellm-dashboard/src/components/chat_ui/mode_endpoint_mapping.tsx
+++ b/ui/litellm-dashboard/src/components/chat_ui/mode_endpoint_mapping.tsx
@@ -10,6 +10,7 @@ export enum ModelMode {
RESPONSES = "responses",
IMAGE_EDITS = "image_edits",
ANTHROPIC_MESSAGES = "anthropic_messages",
+ EMBEDDING = "embedding",
// add additional modes as needed
}
@@ -37,6 +38,7 @@ export const litellmModeMapping: Record = {
[ModelMode.ANTHROPIC_MESSAGES]: EndpointType.ANTHROPIC_MESSAGES,
[ModelMode.AUDIO_SPEECH]: EndpointType.SPEECH,
[ModelMode.AUDIO_TRANSCRIPTION]: EndpointType.TRANSCRIPTION,
+ [ModelMode.EMBEDDING]: EndpointType.EMBEDDINGS,
};
export const getEndpointType = (mode: string): EndpointType => {
diff --git a/ui/litellm-dashboard/src/components/create_user_button.test.tsx b/ui/litellm-dashboard/src/components/create_user_button.test.tsx
new file mode 100644
index 00000000000..e40a1e0ac3c
--- /dev/null
+++ b/ui/litellm-dashboard/src/components/create_user_button.test.tsx
@@ -0,0 +1,35 @@
+import React from "react";
+import { render } from "@testing-library/react";
+import { describe, it, expect, vi } from "vitest";
+import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
+import Createuser from "./create_user_button";
+
+vi.mock("./networking", () => ({
+ userCreateCall: vi.fn(),
+ modelAvailableCall: vi.fn().mockResolvedValue({ data: [] }),
+ invitationCreateCall: vi.fn(),
+ getProxyUISettings: vi.fn().mockResolvedValue({
+ PROXY_BASE_URL: null,
+ PROXY_LOGOUT_URL: null,
+ DEFAULT_TEAM_DISABLED: false,
+ SSO_ENABLED: false,
+ }),
+ getProxyBaseUrl: vi.fn().mockReturnValue("http://localhost"),
+}));
+
+const createQueryClient = () =>
+ new QueryClient({
+ defaultOptions: { queries: { retry: false, gcTime: 0 } },
+ });
+
+describe("Create User Button", () => {
+ it("should render the create user button", () => {
+ const qc = createQueryClient();
+ const { getByText } = render(
+
+
+ ,
+ );
+ expect(getByText("Create User")).toBeInTheDocument();
+ });
+});
diff --git a/ui/litellm-dashboard/src/components/create_user_button.tsx b/ui/litellm-dashboard/src/components/create_user_button.tsx
index 9973c2e777c..6fb6f80c4b4 100644
--- a/ui/litellm-dashboard/src/components/create_user_button.tsx
+++ b/ui/litellm-dashboard/src/components/create_user_button.tsx
@@ -26,6 +26,7 @@ import { InfoCircleOutlined } from "@ant-design/icons";
import { getModelDisplayName } from "./key_team_helpers/fetch_available_models_team_key";
import { useQueryClient } from "@tanstack/react-query";
import NotificationsManager from "./molecules/notifications_manager";
+import TeamDropdown from "./common_components/team_dropdown";
// Helper function to generate UUID compatible across all environments
const generateUUID = (): string => {
@@ -201,19 +202,9 @@ const Createuser: React.FC = ({
))}
-
-
-
- {teams ? (
- teams.map((team: any) => (
-
- ))
- ) : (
-
- )}
-
+
diff --git a/ui/litellm-dashboard/src/components/guardrails.tsx b/ui/litellm-dashboard/src/components/guardrails.tsx
index 23aec34c404..3861545f8cd 100644
--- a/ui/litellm-dashboard/src/components/guardrails.tsx
+++ b/ui/litellm-dashboard/src/components/guardrails.tsx
@@ -8,6 +8,7 @@ import { isAdminRole } from "@/utils/roles";
import GuardrailInfoView from "./guardrails/guardrail_info";
import GuardrailTestPlayground from "./guardrails/GuardrailTestPlayground";
import NotificationsManager from "./molecules/notifications_manager";
+import { Guardrail, GuardrailDefinitionLocation } from "./guardrails/types";
interface GuardrailsPanelProps {
accessToken: string | null;
@@ -25,14 +26,15 @@ interface GuardrailItem {
guardrail_info: Record | null;
created_at?: string;
updated_at?: string;
+ guardrail_definition_location: GuardrailDefinitionLocation;
}
interface GuardrailsResponse {
- guardrails: GuardrailItem[];
+ guardrails: Guardrail[];
}
const GuardrailsPanel: React.FC = ({ accessToken, userRole }) => {
- const [guardrailsList, setGuardrailsList] = useState([]);
+ const [guardrailsList, setGuardrailsList] = useState([]);
const [isAddModalVisible, setIsAddModalVisible] = useState(false);
const [isLoading, setIsLoading] = useState(false);
const [isDeleting, setIsDeleting] = useState(false);
diff --git a/ui/litellm-dashboard/src/components/guardrails/guardrail_table.test.tsx b/ui/litellm-dashboard/src/components/guardrails/guardrail_table.test.tsx
new file mode 100644
index 00000000000..dcc67677357
--- /dev/null
+++ b/ui/litellm-dashboard/src/components/guardrails/guardrail_table.test.tsx
@@ -0,0 +1,56 @@
+import GuardrailTable from "./guardrail_table";
+import { render } from "@testing-library/react";
+import { describe, it, expect } from "vitest";
+import { GuardrailDefinitionLocation } from "./types";
+describe("GuardrailTable", () => {
+ it("should render", () => {
+ const { getByText } = render(
+ {}}
+ accessToken={null}
+ onGuardrailUpdated={() => {}}
+ onGuardrailClick={() => {}}
+ />,
+ );
+ expect(getByText("Guardrail ID")).toBeInTheDocument();
+ expect(getByText("Name")).toBeInTheDocument();
+ expect(getByText("Provider")).toBeInTheDocument();
+ expect(getByText("Mode")).toBeInTheDocument();
+ expect(getByText("Default On")).toBeInTheDocument();
+ expect(getByText("Created At")).toBeInTheDocument();
+ expect(getByText("Updated At")).toBeInTheDocument();
+ });
+
+ it("should not allow deletion of config guardrails", () => {
+ const { getByTestId } = render(
+ {}}
+ accessToken={null}
+ onGuardrailUpdated={() => {}}
+ onGuardrailClick={() => {}}
+ />,
+ );
+
+ const deleteGuardrailButton = getByTestId("config-delete-icon");
+ expect(deleteGuardrailButton).toBeInTheDocument();
+ expect(deleteGuardrailButton).toHaveClass("cursor-not-allowed text-gray-400");
+ expect(deleteGuardrailButton).toHaveAttribute(
+ "title",
+ "Config guardrail cannot be deleted on the dashboard. Please delete it from the config file.",
+ );
+ });
+});
diff --git a/ui/litellm-dashboard/src/components/guardrails/guardrail_table.tsx b/ui/litellm-dashboard/src/components/guardrails/guardrail_table.tsx
index 4857cdd31ee..352f3148e7b 100644
--- a/ui/litellm-dashboard/src/components/guardrails/guardrail_table.tsx
+++ b/ui/litellm-dashboard/src/components/guardrails/guardrail_table.tsx
@@ -13,24 +13,10 @@ import {
} from "@tanstack/react-table";
import { getGuardrailLogoAndName, guardrail_provider_map } from "./guardrail_info_helpers";
import EditGuardrailForm from "./edit_guardrail_form";
-
-interface GuardrailItem {
- guardrail_id?: string;
- guardrail_name: string | null;
- litellm_params: {
- guardrail: string;
- mode: string;
- default_on: boolean;
- pii_entities_config?: { [key: string]: string };
- [key: string]: any;
- };
- guardrail_info: Record | null;
- created_at?: string;
- updated_at?: string;
-}
+import { Guardrail, GuardrailDefinitionLocation } from "./types";
interface GuardrailTableProps {
- guardrailsList: GuardrailItem[];
+ guardrailsList: Guardrail[];
isLoading: boolean;
onDeleteClick: (guardrailId: string, guardrailName: string) => void;
accessToken: string | null;
@@ -50,7 +36,7 @@ const GuardrailTable: React.FC = ({
}) => {
const [sorting, setSorting] = useState([{ id: "created_at", desc: true }]);
const [editModalVisible, setEditModalVisible] = useState(false);
- const [selectedGuardrail, setSelectedGuardrail] = useState(null);
+ const [selectedGuardrail, setSelectedGuardrail] = useState(null);
// Format date helper function
const formatDate = (dateString?: string) => {
@@ -59,7 +45,7 @@ const GuardrailTable: React.FC = ({
return date.toLocaleString();
};
- const handleEditClick = (guardrail: GuardrailItem) => {
+ const handleEditClick = (guardrail: Guardrail) => {
setSelectedGuardrail(guardrail);
setEditModalVisible(true);
};
@@ -70,7 +56,7 @@ const GuardrailTable: React.FC = ({
onGuardrailUpdated();
};
- const columns: ColumnDef[] = [
+ const columns: ColumnDef[] = [
{
header: "Guardrail ID",
accessorKey: "guardrail_id",
@@ -173,21 +159,36 @@ const GuardrailTable: React.FC = ({
},
{
id: "actions",
- header: "",
+ header: "Actions",
cell: ({ row }) => {
const guardrail = row.original;
+ const isConfigGuardrail = guardrail.guardrail_definition_location === GuardrailDefinitionLocation.CONFIG;
return (
-
- guardrail.guardrail_id &&
- onDeleteClick(guardrail.guardrail_id, guardrail.guardrail_name || "Unnamed Guardrail")
- }
- className="cursor-pointer hover:text-red-500"
- tooltip="Delete guardrail"
- />
+ {isConfigGuardrail ? (
+
+
+
+ ) : (
+
+
+ guardrail.guardrail_id &&
+ onDeleteClick(guardrail.guardrail_id, guardrail.guardrail_name || "Unnamed Guardrail")
+ }
+ className="cursor-pointer hover:text-red-500"
+ />
+
+ )}
);
},
diff --git a/ui/litellm-dashboard/src/components/guardrails/pii_components.test.tsx b/ui/litellm-dashboard/src/components/guardrails/pii_components.test.tsx
new file mode 100644
index 00000000000..3accbf13bd1
--- /dev/null
+++ b/ui/litellm-dashboard/src/components/guardrails/pii_components.test.tsx
@@ -0,0 +1,40 @@
+import { render } from "@testing-library/react";
+import { describe, it, expect } from "vitest";
+import { CategoryFilter, QuickActions, PiiEntityList } from "./pii_components";
+import type { PiiEntityCategory } from "./types";
+
+describe("CategoryFilter", () => {
+ it("should render", () => {
+ const emptyCategories: PiiEntityCategory[] = [];
+ const { getByText } = render(
+ {}} />,
+ );
+ expect(getByText("Filter by category")).toBeInTheDocument();
+ });
+});
+
+describe("QuickActions", () => {
+ it("should render", () => {
+ const { getByText } = render(
+ {}} onUnselectAll={() => {}} hasSelectedEntities={false} />,
+ );
+ expect(getByText("Quick Actions")).toBeInTheDocument();
+ });
+});
+
+describe("PiiEntityList", () => {
+ it("should render", () => {
+ const { getByText } = render(
+ {}}
+ onActionSelect={() => {}}
+ entityToCategoryMap={new Map()}
+ />,
+ );
+ expect(getByText("No PII types match your filter criteria")).toBeInTheDocument();
+ });
+});
diff --git a/ui/litellm-dashboard/src/components/guardrails/pii_components.tsx b/ui/litellm-dashboard/src/components/guardrails/pii_components.tsx
index 1b744cad31a..e3b3926b994 100644
--- a/ui/litellm-dashboard/src/components/guardrails/pii_components.tsx
+++ b/ui/litellm-dashboard/src/components/guardrails/pii_components.tsx
@@ -83,6 +83,7 @@ export const QuickActions: React.FC = ({ onSelectAll, onUnsel
}
@@ -103,6 +104,7 @@ export const QuickActions: React.FC = ({ onSelectAll, onUnsel