mirror of
https://github.com/tiennm99/litellm.git
synced 2026-08-11 14:22:48 +00:00
Config Guardrails should not be deletable from table (#16540)
This commit is contained in:
@@ -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<string, any> | null;
|
||||
created_at?: string;
|
||||
updated_at?: string;
|
||||
guardrail_definition_location: GuardrailDefinitionLocation;
|
||||
}
|
||||
|
||||
interface GuardrailsResponse {
|
||||
guardrails: GuardrailItem[];
|
||||
guardrails: Guardrail[];
|
||||
}
|
||||
|
||||
const GuardrailsPanel: React.FC<GuardrailsPanelProps> = ({ accessToken, userRole }) => {
|
||||
const [guardrailsList, setGuardrailsList] = useState<GuardrailItem[]>([]);
|
||||
const [guardrailsList, setGuardrailsList] = useState<Guardrail[]>([]);
|
||||
const [isAddModalVisible, setIsAddModalVisible] = useState(false);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [isDeleting, setIsDeleting] = useState(false);
|
||||
|
||||
@@ -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(
|
||||
<GuardrailTable
|
||||
guardrailsList={[]}
|
||||
isLoading={false}
|
||||
onDeleteClick={() => {}}
|
||||
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(
|
||||
<GuardrailTable
|
||||
guardrailsList={[
|
||||
{
|
||||
guardrail_id: "1",
|
||||
guardrail_name: "Guardrail 1",
|
||||
litellm_params: { guardrail: "presidio", mode: "pre_call", default_on: true },
|
||||
guardrail_info: null,
|
||||
created_at: "2021-01-01",
|
||||
updated_at: "2021-01-01",
|
||||
guardrail_definition_location: GuardrailDefinitionLocation.CONFIG,
|
||||
},
|
||||
]}
|
||||
isLoading={false}
|
||||
onDeleteClick={() => {}}
|
||||
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.",
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -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<string, any> | 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<GuardrailTableProps> = ({
|
||||
}) => {
|
||||
const [sorting, setSorting] = useState<SortingState>([{ id: "created_at", desc: true }]);
|
||||
const [editModalVisible, setEditModalVisible] = useState(false);
|
||||
const [selectedGuardrail, setSelectedGuardrail] = useState<GuardrailItem | null>(null);
|
||||
const [selectedGuardrail, setSelectedGuardrail] = useState<Guardrail | null>(null);
|
||||
|
||||
// Format date helper function
|
||||
const formatDate = (dateString?: string) => {
|
||||
@@ -59,7 +45,7 @@ const GuardrailTable: React.FC<GuardrailTableProps> = ({
|
||||
return date.toLocaleString();
|
||||
};
|
||||
|
||||
const handleEditClick = (guardrail: GuardrailItem) => {
|
||||
const handleEditClick = (guardrail: Guardrail) => {
|
||||
setSelectedGuardrail(guardrail);
|
||||
setEditModalVisible(true);
|
||||
};
|
||||
@@ -70,7 +56,7 @@ const GuardrailTable: React.FC<GuardrailTableProps> = ({
|
||||
onGuardrailUpdated();
|
||||
};
|
||||
|
||||
const columns: ColumnDef<GuardrailItem>[] = [
|
||||
const columns: ColumnDef<Guardrail>[] = [
|
||||
{
|
||||
header: "Guardrail ID",
|
||||
accessorKey: "guardrail_id",
|
||||
@@ -176,18 +162,32 @@ const GuardrailTable: React.FC<GuardrailTableProps> = ({
|
||||
header: "",
|
||||
cell: ({ row }) => {
|
||||
const guardrail = row.original;
|
||||
const isConfigGuardrail = guardrail.guardrail_definition_location === GuardrailDefinitionLocation.CONFIG;
|
||||
return (
|
||||
<div className="flex space-x-2">
|
||||
<Icon
|
||||
icon={TrashIcon}
|
||||
size="sm"
|
||||
onClick={() =>
|
||||
guardrail.guardrail_id &&
|
||||
onDeleteClick(guardrail.guardrail_id, guardrail.guardrail_name || "Unnamed Guardrail")
|
||||
}
|
||||
className="cursor-pointer hover:text-red-500"
|
||||
tooltip="Delete guardrail"
|
||||
/>
|
||||
{isConfigGuardrail ? (
|
||||
<Tooltip title="Config guardrail cannot be deleted on the dashboard. Please delete it from the config file.">
|
||||
<Icon
|
||||
data-testid="config-delete-icon"
|
||||
icon={TrashIcon}
|
||||
size="sm"
|
||||
className="cursor-not-allowed text-gray-400"
|
||||
title="Config guardrail cannot be deleted on the dashboard. Please delete it from the config file."
|
||||
aria-label="Delete guardrail (config)"
|
||||
/>
|
||||
</Tooltip>
|
||||
) : (
|
||||
<Icon
|
||||
icon={TrashIcon}
|
||||
size="sm"
|
||||
onClick={() =>
|
||||
guardrail.guardrail_id &&
|
||||
onDeleteClick(guardrail.guardrail_id, guardrail.guardrail_name || "Unnamed Guardrail")
|
||||
}
|
||||
className="cursor-pointer hover:text-red-500"
|
||||
tooltip="Delete guardrail"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
|
||||
@@ -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(
|
||||
<CategoryFilter categories={emptyCategories} selectedCategories={[]} onChange={() => {}} />,
|
||||
);
|
||||
expect(getByText("Filter by category")).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe("QuickActions", () => {
|
||||
it("should render", () => {
|
||||
const { getByText } = render(
|
||||
<QuickActions onSelectAll={() => {}} onUnselectAll={() => {}} hasSelectedEntities={false} />,
|
||||
);
|
||||
expect(getByText("Quick Actions")).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe("PiiEntityList", () => {
|
||||
it("should render", () => {
|
||||
const { getByText } = render(
|
||||
<PiiEntityList
|
||||
entities={[]}
|
||||
selectedEntities={[]}
|
||||
selectedActions={{}}
|
||||
actions={[]}
|
||||
onEntitySelect={() => {}}
|
||||
onActionSelect={() => {}}
|
||||
entityToCategoryMap={new Map()}
|
||||
/>,
|
||||
);
|
||||
expect(getByText("No PII types match your filter criteria")).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
@@ -83,6 +83,7 @@ export const QuickActions: React.FC<QuickActionsProps> = ({ onSelectAll, onUnsel
|
||||
</div>
|
||||
<Button
|
||||
type="default"
|
||||
danger
|
||||
onClick={onUnselectAll}
|
||||
disabled={!hasSelectedEntities}
|
||||
icon={<CloseOutlined />}
|
||||
@@ -103,6 +104,7 @@ export const QuickActions: React.FC<QuickActionsProps> = ({ onSelectAll, onUnsel
|
||||
</Button>
|
||||
<Button
|
||||
type="default"
|
||||
danger
|
||||
onClick={() => onSelectAll("BLOCK")}
|
||||
className="flex items-center justify-center h-10 border-red-200 hover:border-red-300 hover:text-red-700 bg-red-50 hover:bg-red-100 text-red-600"
|
||||
block
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
import { render } from "@testing-library/react";
|
||||
import { describe, it, expect } from "vitest";
|
||||
import PiiConfiguration from "./pii_configuration";
|
||||
|
||||
describe("PiiConfiguration", () => {
|
||||
it("should render", () => {
|
||||
const { getByText } = render(
|
||||
<PiiConfiguration
|
||||
entities={[]}
|
||||
actions={[]}
|
||||
selectedEntities={[]}
|
||||
selectedActions={{}}
|
||||
onEntitySelect={() => {}}
|
||||
onActionSelect={() => {}}
|
||||
entityCategories={[]}
|
||||
/>,
|
||||
);
|
||||
expect(getByText("Configure PII Protection")).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Typography } from "antd";
|
||||
import React, { useState } from "react";
|
||||
import { Typography, Badge } from "antd";
|
||||
import { CategoryFilter, PiiEntityList, QuickActions } from "./pii_components";
|
||||
import { PiiConfigurationProps } from "./types";
|
||||
import { CategoryFilter, QuickActions, PiiEntityList } from "./pii_components";
|
||||
|
||||
const { Title, Text } = Typography;
|
||||
|
||||
@@ -57,18 +57,11 @@ const PiiConfiguration: React.FC<PiiConfigurationProps> = ({
|
||||
<div className="pii-configuration">
|
||||
<div className="flex justify-between items-center mb-5">
|
||||
<div className="flex items-center">
|
||||
<Title level={4} className="mb-0 font-semibold text-gray-800">
|
||||
<Title level={4} className="!m-0 font-semibold text-gray-800">
|
||||
Configure PII Protection
|
||||
</Title>
|
||||
</div>
|
||||
<Badge
|
||||
count={selectedEntities.length}
|
||||
showZero
|
||||
style={{ backgroundColor: selectedEntities.length > 0 ? "#4f46e5" : "#d9d9d9" }}
|
||||
overflowCount={999}
|
||||
>
|
||||
<Text className="text-gray-500">{selectedEntities.length} items selected</Text>
|
||||
</Badge>
|
||||
<Text className="text-gray-500">{selectedEntities.length} items selected</Text>
|
||||
</div>
|
||||
|
||||
<div className="mb-6">
|
||||
|
||||
@@ -31,4 +31,10 @@ export interface Guardrail {
|
||||
guardrail_info: Record<string, any> | null;
|
||||
created_at?: string;
|
||||
updated_at?: string;
|
||||
guardrail_definition_location: GuardrailDefinitionLocation;
|
||||
}
|
||||
|
||||
export enum GuardrailDefinitionLocation {
|
||||
DB = "db",
|
||||
CONFIG = "config",
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user