mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-09 03:18:44 +00:00
init UI
This commit is contained in:
parent
ca8c2c3938
commit
bfa5d59dd2
11 changed files with 1504 additions and 0 deletions
|
|
@ -19,6 +19,7 @@ import {
|
|||
ExperimentOutlined,
|
||||
ToolOutlined,
|
||||
TagsOutlined,
|
||||
AuditOutlined,
|
||||
} from "@ant-design/icons";
|
||||
// import {
|
||||
// all_admin_roles,
|
||||
|
|
@ -102,6 +103,8 @@ const routeFor = (slug: string): string => {
|
|||
return "logs";
|
||||
case "guardrails":
|
||||
return "guardrails";
|
||||
case "policies":
|
||||
return "policies";
|
||||
|
||||
// tools
|
||||
case "mcp-servers":
|
||||
|
|
@ -202,6 +205,13 @@ const menuItems: MenuItemCfg[] = [
|
|||
icon: <SafetyOutlined style={{ fontSize: 18 }} />,
|
||||
roles: all_admin_roles,
|
||||
},
|
||||
{
|
||||
key: "28",
|
||||
page: "policies",
|
||||
label: "Policies",
|
||||
icon: <AuditOutlined style={{ fontSize: 18 }} />,
|
||||
roles: all_admin_roles,
|
||||
},
|
||||
{
|
||||
key: "26",
|
||||
page: "tools",
|
||||
|
|
|
|||
37
ui/litellm-dashboard/src/app/(dashboard)/policies/page.tsx
Normal file
37
ui/litellm-dashboard/src/app/(dashboard)/policies/page.tsx
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
"use client";
|
||||
|
||||
import PoliciesPanel from "@/components/policies";
|
||||
import useAuthorized from "@/app/(dashboard)/hooks/useAuthorized";
|
||||
import {
|
||||
getPoliciesList,
|
||||
createPolicyCall,
|
||||
updatePolicyCall,
|
||||
deletePolicyCall,
|
||||
getPolicyInfo,
|
||||
getPolicyAttachmentsList,
|
||||
createPolicyAttachmentCall,
|
||||
deletePolicyAttachmentCall,
|
||||
getGuardrailsList,
|
||||
} from "@/components/networking";
|
||||
|
||||
const PoliciesPage = () => {
|
||||
const { accessToken, userRole } = useAuthorized();
|
||||
|
||||
return (
|
||||
<PoliciesPanel
|
||||
accessToken={accessToken}
|
||||
userRole={userRole}
|
||||
getPoliciesList={getPoliciesList}
|
||||
createPolicy={createPolicyCall}
|
||||
updatePolicy={updatePolicyCall}
|
||||
deletePolicy={deletePolicyCall}
|
||||
getPolicy={getPolicyInfo}
|
||||
getAttachmentsList={getPolicyAttachmentsList}
|
||||
createAttachment={createPolicyAttachmentCall}
|
||||
deleteAttachment={deletePolicyAttachmentCall}
|
||||
getGuardrailsList={getGuardrailsList}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default PoliciesPage;
|
||||
|
|
@ -3,6 +3,7 @@ import useAuthorized from "@/app/(dashboard)/hooks/useAuthorized";
|
|||
import {
|
||||
ApiOutlined,
|
||||
AppstoreOutlined,
|
||||
AuditOutlined,
|
||||
BankOutlined,
|
||||
BarChartOutlined,
|
||||
BgColorsOutlined,
|
||||
|
|
@ -123,6 +124,13 @@ const Sidebar: React.FC<SidebarProps> = ({ setPage, defaultSelectedKey, collapse
|
|||
icon: <SafetyOutlined />,
|
||||
roles: all_admin_roles,
|
||||
},
|
||||
{
|
||||
key: "policies",
|
||||
page: "policies",
|
||||
label: "Policies",
|
||||
icon: <AuditOutlined />,
|
||||
roles: all_admin_roles,
|
||||
},
|
||||
{
|
||||
key: "tools",
|
||||
page: "tools",
|
||||
|
|
|
|||
|
|
@ -5347,6 +5347,223 @@ export const getGuardrailsList = async (accessToken: string) => {
|
|||
}
|
||||
};
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Policy CRUD API Calls
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
export const getPoliciesList = async (accessToken: string) => {
|
||||
try {
|
||||
const url = proxyBaseUrl ? `${proxyBaseUrl}/policies/list` : `/policies/list`;
|
||||
const response = await fetch(url, {
|
||||
method: "GET",
|
||||
headers: {
|
||||
[globalLitellmHeaderName]: `Bearer ${accessToken}`,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const errorData = await response.json();
|
||||
const errorMessage = deriveErrorMessage(errorData);
|
||||
handleError(errorMessage);
|
||||
throw new Error(errorMessage);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
return data;
|
||||
} catch (error) {
|
||||
console.error("Failed to get policies list:", error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
export const createPolicyCall = async (accessToken: string, policyData: any) => {
|
||||
try {
|
||||
const url = proxyBaseUrl ? `${proxyBaseUrl}/policies` : `/policies`;
|
||||
const response = await fetch(url, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
[globalLitellmHeaderName]: `Bearer ${accessToken}`,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify(policyData),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const errorData = await response.json();
|
||||
const errorMessage = deriveErrorMessage(errorData);
|
||||
handleError(errorMessage);
|
||||
throw new Error(errorMessage);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
return data;
|
||||
} catch (error) {
|
||||
console.error("Failed to create policy:", error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
export const updatePolicyCall = async (accessToken: string, policyId: string, policyData: any) => {
|
||||
try {
|
||||
const url = proxyBaseUrl ? `${proxyBaseUrl}/policies/${policyId}` : `/policies/${policyId}`;
|
||||
const response = await fetch(url, {
|
||||
method: "PUT",
|
||||
headers: {
|
||||
[globalLitellmHeaderName]: `Bearer ${accessToken}`,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify(policyData),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const errorData = await response.json();
|
||||
const errorMessage = deriveErrorMessage(errorData);
|
||||
handleError(errorMessage);
|
||||
throw new Error(errorMessage);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
return data;
|
||||
} catch (error) {
|
||||
console.error("Failed to update policy:", error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
export const deletePolicyCall = async (accessToken: string, policyId: string) => {
|
||||
try {
|
||||
const url = proxyBaseUrl ? `${proxyBaseUrl}/policies/${policyId}` : `/policies/${policyId}`;
|
||||
const response = await fetch(url, {
|
||||
method: "DELETE",
|
||||
headers: {
|
||||
[globalLitellmHeaderName]: `Bearer ${accessToken}`,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const errorData = await response.json();
|
||||
const errorMessage = deriveErrorMessage(errorData);
|
||||
handleError(errorMessage);
|
||||
throw new Error(errorMessage);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
return data;
|
||||
} catch (error) {
|
||||
console.error("Failed to delete policy:", error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
export const getPolicyInfo = async (accessToken: string, policyId: string) => {
|
||||
try {
|
||||
const url = proxyBaseUrl ? `${proxyBaseUrl}/policies/${policyId}` : `/policies/${policyId}`;
|
||||
const response = await fetch(url, {
|
||||
method: "GET",
|
||||
headers: {
|
||||
[globalLitellmHeaderName]: `Bearer ${accessToken}`,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const errorData = await response.json();
|
||||
const errorMessage = deriveErrorMessage(errorData);
|
||||
handleError(errorMessage);
|
||||
throw new Error(errorMessage);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
return data;
|
||||
} catch (error) {
|
||||
console.error("Failed to get policy info:", error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
// Policy Attachments API Calls
|
||||
|
||||
export const getPolicyAttachmentsList = async (accessToken: string) => {
|
||||
try {
|
||||
const url = proxyBaseUrl ? `${proxyBaseUrl}/policies/attachments/list` : `/policies/attachments/list`;
|
||||
const response = await fetch(url, {
|
||||
method: "GET",
|
||||
headers: {
|
||||
[globalLitellmHeaderName]: `Bearer ${accessToken}`,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const errorData = await response.json();
|
||||
const errorMessage = deriveErrorMessage(errorData);
|
||||
handleError(errorMessage);
|
||||
throw new Error(errorMessage);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
return data;
|
||||
} catch (error) {
|
||||
console.error("Failed to get policy attachments list:", error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
export const createPolicyAttachmentCall = async (accessToken: string, attachmentData: any) => {
|
||||
try {
|
||||
const url = proxyBaseUrl ? `${proxyBaseUrl}/policies/attachments` : `/policies/attachments`;
|
||||
const response = await fetch(url, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
[globalLitellmHeaderName]: `Bearer ${accessToken}`,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify(attachmentData),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const errorData = await response.json();
|
||||
const errorMessage = deriveErrorMessage(errorData);
|
||||
handleError(errorMessage);
|
||||
throw new Error(errorMessage);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
return data;
|
||||
} catch (error) {
|
||||
console.error("Failed to create policy attachment:", error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
export const deletePolicyAttachmentCall = async (accessToken: string, attachmentId: string) => {
|
||||
try {
|
||||
const url = proxyBaseUrl ? `${proxyBaseUrl}/policies/attachments/${attachmentId}` : `/policies/attachments/${attachmentId}`;
|
||||
const response = await fetch(url, {
|
||||
method: "DELETE",
|
||||
headers: {
|
||||
[globalLitellmHeaderName]: `Bearer ${accessToken}`,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const errorData = await response.json();
|
||||
const errorMessage = deriveErrorMessage(errorData);
|
||||
handleError(errorMessage);
|
||||
throw new Error(errorMessage);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
return data;
|
||||
} catch (error) {
|
||||
console.error("Failed to delete policy attachment:", error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
export const getPromptsList = async (accessToken: string): Promise<ListPromptsResponse> => {
|
||||
try {
|
||||
const url = proxyBaseUrl ? `${proxyBaseUrl}/prompts/list` : `/prompts/list`;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,171 @@
|
|||
import React, { useState } from "react";
|
||||
import {
|
||||
Modal,
|
||||
Form,
|
||||
Input,
|
||||
Select,
|
||||
Button,
|
||||
Space,
|
||||
Radio,
|
||||
Divider,
|
||||
} from "antd";
|
||||
import { Policy, PolicyAttachmentCreateRequest } from "./types";
|
||||
|
||||
interface AddAttachmentFormProps {
|
||||
visible: boolean;
|
||||
onClose: () => void;
|
||||
onSuccess: () => void;
|
||||
accessToken: string | null;
|
||||
policies: Policy[];
|
||||
onCreateAttachment: (data: PolicyAttachmentCreateRequest) => Promise<void>;
|
||||
}
|
||||
|
||||
const AddAttachmentForm: React.FC<AddAttachmentFormProps> = ({
|
||||
visible,
|
||||
onClose,
|
||||
onSuccess,
|
||||
accessToken,
|
||||
policies,
|
||||
onCreateAttachment,
|
||||
}) => {
|
||||
const [form] = Form.useForm();
|
||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||
const [scopeType, setScopeType] = useState<"global" | "specific">("global");
|
||||
|
||||
const handleSubmit = async (values: any) => {
|
||||
if (!accessToken) return;
|
||||
|
||||
setIsSubmitting(true);
|
||||
try {
|
||||
const data: PolicyAttachmentCreateRequest = {
|
||||
policy_name: values.policy_name,
|
||||
};
|
||||
|
||||
if (scopeType === "global") {
|
||||
data.scope = "*";
|
||||
} else {
|
||||
if (values.teams && values.teams.length > 0) {
|
||||
data.teams = values.teams;
|
||||
}
|
||||
if (values.keys && values.keys.length > 0) {
|
||||
data.keys = values.keys;
|
||||
}
|
||||
if (values.models && values.models.length > 0) {
|
||||
data.models = values.models;
|
||||
}
|
||||
}
|
||||
|
||||
await onCreateAttachment(data);
|
||||
onSuccess();
|
||||
onClose();
|
||||
form.resetFields();
|
||||
setScopeType("global");
|
||||
} catch (error) {
|
||||
console.error("Error creating attachment:", error);
|
||||
} finally {
|
||||
setIsSubmitting(false);
|
||||
}
|
||||
};
|
||||
|
||||
const policyOptions = policies.map((p) => ({
|
||||
label: p.policy_name,
|
||||
value: p.policy_name,
|
||||
}));
|
||||
|
||||
return (
|
||||
<Modal
|
||||
title="Create Policy Attachment"
|
||||
open={visible}
|
||||
onCancel={onClose}
|
||||
footer={null}
|
||||
width={600}
|
||||
>
|
||||
<Form
|
||||
form={form}
|
||||
layout="vertical"
|
||||
onFinish={handleSubmit}
|
||||
initialValues={{
|
||||
scope_type: "global",
|
||||
}}
|
||||
>
|
||||
<Form.Item
|
||||
name="policy_name"
|
||||
label="Policy"
|
||||
rules={[{ required: true, message: "Please select a policy" }]}
|
||||
>
|
||||
<Select
|
||||
placeholder="Select a policy to attach"
|
||||
options={policyOptions}
|
||||
showSearch
|
||||
filterOption={(input, option) =>
|
||||
(option?.label ?? "").toLowerCase().includes(input.toLowerCase())
|
||||
}
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
<Divider orientation="left">Scope</Divider>
|
||||
|
||||
<Form.Item label="Scope Type">
|
||||
<Radio.Group
|
||||
value={scopeType}
|
||||
onChange={(e) => setScopeType(e.target.value)}
|
||||
>
|
||||
<Radio value="global">Global (applies to all requests)</Radio>
|
||||
<Radio value="specific">Specific (teams, keys, or models)</Radio>
|
||||
</Radio.Group>
|
||||
</Form.Item>
|
||||
|
||||
{scopeType === "specific" && (
|
||||
<>
|
||||
<Form.Item
|
||||
name="teams"
|
||||
label="Teams"
|
||||
tooltip="Team aliases this attachment applies to. Supports wildcards (e.g., healthcare-*)"
|
||||
>
|
||||
<Select
|
||||
mode="tags"
|
||||
placeholder="Enter team aliases (e.g., healthcare-team)"
|
||||
tokenSeparators={[","]}
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
name="keys"
|
||||
label="Keys"
|
||||
tooltip="Key aliases this attachment applies to. Supports wildcards (e.g., dev-*)"
|
||||
>
|
||||
<Select
|
||||
mode="tags"
|
||||
placeholder="Enter key aliases (e.g., dev-key-*)"
|
||||
tokenSeparators={[","]}
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
name="models"
|
||||
label="Models"
|
||||
tooltip="Model names this attachment applies to. Supports wildcards (e.g., gpt-4*)"
|
||||
>
|
||||
<Select
|
||||
mode="tags"
|
||||
placeholder="Enter model names (e.g., gpt-4, bedrock/*)"
|
||||
tokenSeparators={[","]}
|
||||
/>
|
||||
</Form.Item>
|
||||
</>
|
||||
)}
|
||||
|
||||
<Form.Item>
|
||||
<Space>
|
||||
<Button onClick={onClose}>Cancel</Button>
|
||||
<Button type="primary" htmlType="submit" loading={isSubmitting}>
|
||||
Create Attachment
|
||||
</Button>
|
||||
</Space>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
export default AddAttachmentForm;
|
||||
213
ui/litellm-dashboard/src/components/policies/add_policy_form.tsx
Normal file
213
ui/litellm-dashboard/src/components/policies/add_policy_form.tsx
Normal file
|
|
@ -0,0 +1,213 @@
|
|||
import React, { useState, useEffect } from "react";
|
||||
import {
|
||||
Modal,
|
||||
Form,
|
||||
Input,
|
||||
Select,
|
||||
Button,
|
||||
Space,
|
||||
Typography,
|
||||
Divider,
|
||||
} from "antd";
|
||||
import { PlusOutlined, MinusCircleOutlined } from "@ant-design/icons";
|
||||
import { Policy, PolicyCreateRequest, PolicyUpdateRequest } from "./types";
|
||||
import { Guardrail } from "../guardrails/types";
|
||||
|
||||
const { TextArea } = Input;
|
||||
const { Text } = Typography;
|
||||
|
||||
interface AddPolicyFormProps {
|
||||
visible: boolean;
|
||||
onClose: () => void;
|
||||
onSuccess: () => void;
|
||||
accessToken: string | null;
|
||||
editingPolicy?: Policy | null;
|
||||
existingPolicies: Policy[];
|
||||
availableGuardrails: Guardrail[];
|
||||
onCreatePolicy: (data: PolicyCreateRequest) => Promise<void>;
|
||||
onUpdatePolicy: (policyId: string, data: PolicyUpdateRequest) => Promise<void>;
|
||||
}
|
||||
|
||||
const AddPolicyForm: React.FC<AddPolicyFormProps> = ({
|
||||
visible,
|
||||
onClose,
|
||||
onSuccess,
|
||||
accessToken,
|
||||
editingPolicy,
|
||||
existingPolicies,
|
||||
availableGuardrails,
|
||||
onCreatePolicy,
|
||||
onUpdatePolicy,
|
||||
}) => {
|
||||
const [form] = Form.useForm();
|
||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||
|
||||
const isEditing = !!editingPolicy;
|
||||
|
||||
useEffect(() => {
|
||||
if (visible && editingPolicy) {
|
||||
form.setFieldsValue({
|
||||
policy_name: editingPolicy.policy_name,
|
||||
description: editingPolicy.description,
|
||||
inherit: editingPolicy.inherit,
|
||||
guardrails_add: editingPolicy.guardrails_add || [],
|
||||
guardrails_remove: editingPolicy.guardrails_remove || [],
|
||||
model_condition: editingPolicy.condition?.model,
|
||||
});
|
||||
} else if (visible) {
|
||||
form.resetFields();
|
||||
}
|
||||
}, [visible, editingPolicy, form]);
|
||||
|
||||
const handleSubmit = async (values: any) => {
|
||||
if (!accessToken) return;
|
||||
|
||||
setIsSubmitting(true);
|
||||
try {
|
||||
const data: PolicyCreateRequest | PolicyUpdateRequest = {
|
||||
policy_name: values.policy_name,
|
||||
description: values.description || undefined,
|
||||
inherit: values.inherit || undefined,
|
||||
guardrails_add: values.guardrails_add || [],
|
||||
guardrails_remove: values.guardrails_remove || [],
|
||||
condition: values.model_condition
|
||||
? { model: values.model_condition }
|
||||
: undefined,
|
||||
};
|
||||
|
||||
if (isEditing && editingPolicy) {
|
||||
await onUpdatePolicy(editingPolicy.policy_id, data as PolicyUpdateRequest);
|
||||
} else {
|
||||
await onCreatePolicy(data as PolicyCreateRequest);
|
||||
}
|
||||
|
||||
onSuccess();
|
||||
onClose();
|
||||
form.resetFields();
|
||||
} catch (error) {
|
||||
console.error("Error saving policy:", error);
|
||||
} finally {
|
||||
setIsSubmitting(false);
|
||||
}
|
||||
};
|
||||
|
||||
const guardrailOptions = availableGuardrails.map((g) => ({
|
||||
label: g.guardrail_name || g.guardrail_id,
|
||||
value: g.guardrail_name || g.guardrail_id,
|
||||
}));
|
||||
|
||||
const policyOptions = existingPolicies
|
||||
.filter((p) => !editingPolicy || p.policy_id !== editingPolicy.policy_id)
|
||||
.map((p) => ({
|
||||
label: p.policy_name,
|
||||
value: p.policy_name,
|
||||
}));
|
||||
|
||||
return (
|
||||
<Modal
|
||||
title={isEditing ? "Edit Policy" : "Create New Policy"}
|
||||
open={visible}
|
||||
onCancel={onClose}
|
||||
footer={null}
|
||||
width={700}
|
||||
>
|
||||
<Form
|
||||
form={form}
|
||||
layout="vertical"
|
||||
onFinish={handleSubmit}
|
||||
initialValues={{
|
||||
guardrails_add: [],
|
||||
guardrails_remove: [],
|
||||
}}
|
||||
>
|
||||
<Form.Item
|
||||
name="policy_name"
|
||||
label="Policy Name"
|
||||
rules={[
|
||||
{ required: true, message: "Please enter a policy name" },
|
||||
{
|
||||
pattern: /^[a-zA-Z0-9_-]+$/,
|
||||
message:
|
||||
"Policy name can only contain letters, numbers, hyphens, and underscores",
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Input
|
||||
placeholder="e.g., global-baseline, healthcare-compliance"
|
||||
disabled={isEditing}
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item name="description" label="Description">
|
||||
<TextArea
|
||||
rows={2}
|
||||
placeholder="Describe what this policy does..."
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
<Divider orientation="left">Inheritance</Divider>
|
||||
|
||||
<Form.Item
|
||||
name="inherit"
|
||||
label="Inherit From"
|
||||
tooltip="Inherit guardrails from another policy. The child policy will include all guardrails from the parent."
|
||||
>
|
||||
<Select
|
||||
allowClear
|
||||
placeholder="Select a parent policy (optional)"
|
||||
options={policyOptions}
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
<Divider orientation="left">Guardrails</Divider>
|
||||
|
||||
<Form.Item
|
||||
name="guardrails_add"
|
||||
label="Guardrails to Add"
|
||||
tooltip="These guardrails will be added to requests matching this policy"
|
||||
>
|
||||
<Select
|
||||
mode="multiple"
|
||||
allowClear
|
||||
placeholder="Select guardrails to add"
|
||||
options={guardrailOptions}
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
name="guardrails_remove"
|
||||
label="Guardrails to Remove"
|
||||
tooltip="These guardrails will be removed from inherited guardrails"
|
||||
>
|
||||
<Select
|
||||
mode="multiple"
|
||||
allowClear
|
||||
placeholder="Select guardrails to remove (from inherited)"
|
||||
options={guardrailOptions}
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
<Divider orientation="left">Conditions (Optional)</Divider>
|
||||
|
||||
<Form.Item
|
||||
name="model_condition"
|
||||
label="Model Condition"
|
||||
tooltip="Only apply this policy when the model matches this pattern (supports regex)"
|
||||
>
|
||||
<Input placeholder="e.g., gpt-4.* or bedrock/claude-3" />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item>
|
||||
<Space>
|
||||
<Button onClick={onClose}>Cancel</Button>
|
||||
<Button type="primary" htmlType="submit" loading={isSubmitting}>
|
||||
{isEditing ? "Update Policy" : "Create Policy"}
|
||||
</Button>
|
||||
</Space>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
export default AddPolicyForm;
|
||||
|
|
@ -0,0 +1,153 @@
|
|||
import React from "react";
|
||||
import { Table, Tag, Button, Space, Tooltip, Typography } from "antd";
|
||||
import { DeleteOutlined } from "@ant-design/icons";
|
||||
import { PolicyAttachment } from "./types";
|
||||
|
||||
const { Text } = Typography;
|
||||
|
||||
interface AttachmentTableProps {
|
||||
attachments: PolicyAttachment[];
|
||||
isLoading: boolean;
|
||||
onDeleteClick: (attachmentId: string) => void;
|
||||
isAdmin: boolean;
|
||||
}
|
||||
|
||||
const AttachmentTable: React.FC<AttachmentTableProps> = ({
|
||||
attachments,
|
||||
isLoading,
|
||||
onDeleteClick,
|
||||
isAdmin,
|
||||
}) => {
|
||||
const columns = [
|
||||
{
|
||||
title: "Policy",
|
||||
dataIndex: "policy_name",
|
||||
key: "policy_name",
|
||||
render: (text: string) => <Tag color="blue">{text}</Tag>,
|
||||
},
|
||||
{
|
||||
title: "Scope",
|
||||
dataIndex: "scope",
|
||||
key: "scope",
|
||||
render: (scope: string | null) =>
|
||||
scope === "*" ? (
|
||||
<Tag color="gold">Global (*)</Tag>
|
||||
) : scope ? (
|
||||
<Tag>{scope}</Tag>
|
||||
) : (
|
||||
<Text type="secondary">-</Text>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "Teams",
|
||||
dataIndex: "teams",
|
||||
key: "teams",
|
||||
render: (teams: string[]) => (
|
||||
<Space wrap>
|
||||
{teams && teams.length > 0 ? (
|
||||
teams.slice(0, 3).map((t) => (
|
||||
<Tag key={t} color="cyan">
|
||||
{t}
|
||||
</Tag>
|
||||
))
|
||||
) : (
|
||||
<Text type="secondary">-</Text>
|
||||
)}
|
||||
{teams && teams.length > 3 && (
|
||||
<Tooltip title={teams.slice(3).join(", ")}>
|
||||
<Tag>+{teams.length - 3} more</Tag>
|
||||
</Tooltip>
|
||||
)}
|
||||
</Space>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "Keys",
|
||||
dataIndex: "keys",
|
||||
key: "keys",
|
||||
render: (keys: string[]) => (
|
||||
<Space wrap>
|
||||
{keys && keys.length > 0 ? (
|
||||
keys.slice(0, 3).map((k) => (
|
||||
<Tag key={k} color="purple">
|
||||
{k}
|
||||
</Tag>
|
||||
))
|
||||
) : (
|
||||
<Text type="secondary">-</Text>
|
||||
)}
|
||||
{keys && keys.length > 3 && (
|
||||
<Tooltip title={keys.slice(3).join(", ")}>
|
||||
<Tag>+{keys.length - 3} more</Tag>
|
||||
</Tooltip>
|
||||
)}
|
||||
</Space>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "Models",
|
||||
dataIndex: "models",
|
||||
key: "models",
|
||||
render: (models: string[]) => (
|
||||
<Space wrap>
|
||||
{models && models.length > 0 ? (
|
||||
models.slice(0, 3).map((m) => (
|
||||
<Tag key={m} color="green">
|
||||
{m}
|
||||
</Tag>
|
||||
))
|
||||
) : (
|
||||
<Text type="secondary">-</Text>
|
||||
)}
|
||||
{models && models.length > 3 && (
|
||||
<Tooltip title={models.slice(3).join(", ")}>
|
||||
<Tag>+{models.length - 3} more</Tag>
|
||||
</Tooltip>
|
||||
)}
|
||||
</Space>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "Created At",
|
||||
dataIndex: "created_at",
|
||||
key: "created_at",
|
||||
render: (date: string) =>
|
||||
date ? new Date(date).toLocaleDateString() : "-",
|
||||
},
|
||||
{
|
||||
title: "Actions",
|
||||
key: "actions",
|
||||
render: (_: any, record: PolicyAttachment) => (
|
||||
<Space>
|
||||
{isAdmin && (
|
||||
<Tooltip title="Delete">
|
||||
<Button
|
||||
type="text"
|
||||
danger
|
||||
icon={<DeleteOutlined />}
|
||||
onClick={() => onDeleteClick(record.attachment_id)}
|
||||
/>
|
||||
</Tooltip>
|
||||
)}
|
||||
</Space>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<Table
|
||||
columns={columns}
|
||||
dataSource={attachments}
|
||||
rowKey="attachment_id"
|
||||
loading={isLoading}
|
||||
pagination={{
|
||||
pageSize: 10,
|
||||
showSizeChanger: true,
|
||||
showTotal: (total, range) =>
|
||||
`${range[0]}-${range[1]} of ${total} attachments`,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default AttachmentTable;
|
||||
301
ui/litellm-dashboard/src/components/policies/index.tsx
Normal file
301
ui/litellm-dashboard/src/components/policies/index.tsx
Normal file
|
|
@ -0,0 +1,301 @@
|
|||
import React, { useState, useEffect, useCallback } from "react";
|
||||
import { Button, Tabs, Modal, message } from "antd";
|
||||
import { PlusOutlined, ExclamationCircleOutlined } from "@ant-design/icons";
|
||||
import { isAdminRole } from "@/utils/roles";
|
||||
import PolicyTable from "./policy_table";
|
||||
import PolicyInfoView from "./policy_info";
|
||||
import AddPolicyForm from "./add_policy_form";
|
||||
import AttachmentTable from "./attachment_table";
|
||||
import AddAttachmentForm from "./add_attachment_form";
|
||||
import {
|
||||
Policy,
|
||||
PolicyAttachment,
|
||||
PolicyCreateRequest,
|
||||
PolicyUpdateRequest,
|
||||
PolicyAttachmentCreateRequest,
|
||||
} from "./types";
|
||||
import { Guardrail } from "../guardrails/types";
|
||||
|
||||
interface PoliciesPanelProps {
|
||||
accessToken: string | null;
|
||||
userRole?: string;
|
||||
// API functions passed from parent
|
||||
getPoliciesList: (accessToken: string) => Promise<{ policies: Policy[]; total_count: number }>;
|
||||
createPolicy: (accessToken: string, data: PolicyCreateRequest) => Promise<Policy>;
|
||||
updatePolicy: (accessToken: string, policyId: string, data: PolicyUpdateRequest) => Promise<Policy>;
|
||||
deletePolicy: (accessToken: string, policyId: string) => Promise<void>;
|
||||
getPolicy: (accessToken: string, policyId: string) => Promise<Policy>;
|
||||
getAttachmentsList: (accessToken: string) => Promise<{ attachments: PolicyAttachment[]; total_count: number }>;
|
||||
createAttachment: (accessToken: string, data: PolicyAttachmentCreateRequest) => Promise<PolicyAttachment>;
|
||||
deleteAttachment: (accessToken: string, attachmentId: string) => Promise<void>;
|
||||
getGuardrailsList: (accessToken: string) => Promise<{ guardrails: Guardrail[] }>;
|
||||
}
|
||||
|
||||
const PoliciesPanel: React.FC<PoliciesPanelProps> = ({
|
||||
accessToken,
|
||||
userRole,
|
||||
getPoliciesList,
|
||||
createPolicy,
|
||||
updatePolicy,
|
||||
deletePolicy,
|
||||
getPolicy,
|
||||
getAttachmentsList,
|
||||
createAttachment,
|
||||
deleteAttachment,
|
||||
getGuardrailsList,
|
||||
}) => {
|
||||
const [policiesList, setPoliciesList] = useState<Policy[]>([]);
|
||||
const [attachmentsList, setAttachmentsList] = useState<PolicyAttachment[]>([]);
|
||||
const [guardrailsList, setGuardrailsList] = useState<Guardrail[]>([]);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [isAttachmentsLoading, setIsAttachmentsLoading] = useState(false);
|
||||
const [isAddPolicyModalVisible, setIsAddPolicyModalVisible] = useState(false);
|
||||
const [isAddAttachmentModalVisible, setIsAddAttachmentModalVisible] = useState(false);
|
||||
const [editingPolicy, setEditingPolicy] = useState<Policy | null>(null);
|
||||
const [selectedPolicyId, setSelectedPolicyId] = useState<string | null>(null);
|
||||
const [activeTab, setActiveTab] = useState<string>("policies");
|
||||
|
||||
const isAdmin = userRole ? isAdminRole(userRole) : false;
|
||||
|
||||
const fetchPolicies = useCallback(async () => {
|
||||
if (!accessToken) return;
|
||||
|
||||
setIsLoading(true);
|
||||
try {
|
||||
const response = await getPoliciesList(accessToken);
|
||||
setPoliciesList(response.policies);
|
||||
} catch (error) {
|
||||
console.error("Error fetching policies:", error);
|
||||
message.error("Failed to fetch policies");
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
}, [accessToken, getPoliciesList]);
|
||||
|
||||
const fetchAttachments = useCallback(async () => {
|
||||
if (!accessToken) return;
|
||||
|
||||
setIsAttachmentsLoading(true);
|
||||
try {
|
||||
const response = await getAttachmentsList(accessToken);
|
||||
setAttachmentsList(response.attachments);
|
||||
} catch (error) {
|
||||
console.error("Error fetching attachments:", error);
|
||||
message.error("Failed to fetch attachments");
|
||||
} finally {
|
||||
setIsAttachmentsLoading(false);
|
||||
}
|
||||
}, [accessToken, getAttachmentsList]);
|
||||
|
||||
const fetchGuardrails = useCallback(async () => {
|
||||
if (!accessToken) return;
|
||||
|
||||
try {
|
||||
const response = await getGuardrailsList(accessToken);
|
||||
setGuardrailsList(response.guardrails);
|
||||
} catch (error) {
|
||||
console.error("Error fetching guardrails:", error);
|
||||
}
|
||||
}, [accessToken, getGuardrailsList]);
|
||||
|
||||
useEffect(() => {
|
||||
fetchPolicies();
|
||||
fetchAttachments();
|
||||
fetchGuardrails();
|
||||
}, [fetchPolicies, fetchAttachments, fetchGuardrails]);
|
||||
|
||||
const handleCreatePolicy = async (data: PolicyCreateRequest) => {
|
||||
if (!accessToken) return;
|
||||
await createPolicy(accessToken, data);
|
||||
message.success("Policy created successfully");
|
||||
};
|
||||
|
||||
const handleUpdatePolicy = async (policyId: string, data: PolicyUpdateRequest) => {
|
||||
if (!accessToken) return;
|
||||
await updatePolicy(accessToken, policyId, data);
|
||||
message.success("Policy updated successfully");
|
||||
};
|
||||
|
||||
const handleDeletePolicy = (policyId: string, policyName: string) => {
|
||||
Modal.confirm({
|
||||
title: "Delete Policy",
|
||||
icon: <ExclamationCircleOutlined />,
|
||||
content: `Are you sure you want to delete the policy "${policyName}"? This action cannot be undone.`,
|
||||
okText: "Delete",
|
||||
okType: "danger",
|
||||
cancelText: "Cancel",
|
||||
onOk: async () => {
|
||||
if (!accessToken) return;
|
||||
try {
|
||||
await deletePolicy(accessToken, policyId);
|
||||
message.success("Policy deleted successfully");
|
||||
fetchPolicies();
|
||||
} catch (error) {
|
||||
console.error("Error deleting policy:", error);
|
||||
message.error("Failed to delete policy");
|
||||
}
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const handleCreateAttachment = async (data: PolicyAttachmentCreateRequest) => {
|
||||
if (!accessToken) return;
|
||||
await createAttachment(accessToken, data);
|
||||
message.success("Attachment created successfully");
|
||||
};
|
||||
|
||||
const handleDeleteAttachment = (attachmentId: string) => {
|
||||
Modal.confirm({
|
||||
title: "Delete Attachment",
|
||||
icon: <ExclamationCircleOutlined />,
|
||||
content: "Are you sure you want to delete this attachment? This action cannot be undone.",
|
||||
okText: "Delete",
|
||||
okType: "danger",
|
||||
cancelText: "Cancel",
|
||||
onOk: async () => {
|
||||
if (!accessToken) return;
|
||||
try {
|
||||
await deleteAttachment(accessToken, attachmentId);
|
||||
message.success("Attachment deleted successfully");
|
||||
fetchAttachments();
|
||||
} catch (error) {
|
||||
console.error("Error deleting attachment:", error);
|
||||
message.error("Failed to delete attachment");
|
||||
}
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const fetchPolicyById = useCallback(
|
||||
async (policyId: string): Promise<Policy | null> => {
|
||||
if (!accessToken) return null;
|
||||
try {
|
||||
return await getPolicy(accessToken, policyId);
|
||||
} catch (error) {
|
||||
console.error("Error fetching policy:", error);
|
||||
return null;
|
||||
}
|
||||
},
|
||||
[accessToken, getPolicy]
|
||||
);
|
||||
|
||||
const handlePolicySuccess = () => {
|
||||
fetchPolicies();
|
||||
setEditingPolicy(null);
|
||||
};
|
||||
|
||||
const handleAttachmentSuccess = () => {
|
||||
fetchAttachments();
|
||||
};
|
||||
|
||||
const tabItems = [
|
||||
{
|
||||
key: "policies",
|
||||
label: "Policies",
|
||||
children: selectedPolicyId ? (
|
||||
<PolicyInfoView
|
||||
policyId={selectedPolicyId}
|
||||
onClose={() => setSelectedPolicyId(null)}
|
||||
onEdit={(policy) => {
|
||||
setEditingPolicy(policy);
|
||||
setIsAddPolicyModalVisible(true);
|
||||
setSelectedPolicyId(null);
|
||||
}}
|
||||
accessToken={accessToken}
|
||||
isAdmin={isAdmin}
|
||||
fetchPolicy={fetchPolicyById}
|
||||
/>
|
||||
) : (
|
||||
<>
|
||||
<div style={{ marginBottom: 16 }}>
|
||||
{isAdmin && (
|
||||
<Button
|
||||
type="primary"
|
||||
icon={<PlusOutlined />}
|
||||
onClick={() => {
|
||||
setEditingPolicy(null);
|
||||
setIsAddPolicyModalVisible(true);
|
||||
}}
|
||||
>
|
||||
Add New Policy
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
<PolicyTable
|
||||
policies={policiesList}
|
||||
isLoading={isLoading}
|
||||
onDeleteClick={handleDeletePolicy}
|
||||
onEditClick={(policy) => {
|
||||
setEditingPolicy(policy);
|
||||
setIsAddPolicyModalVisible(true);
|
||||
}}
|
||||
onViewClick={(policyId) => setSelectedPolicyId(policyId)}
|
||||
isAdmin={isAdmin}
|
||||
/>
|
||||
</>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: "attachments",
|
||||
label: "Attachments",
|
||||
children: (
|
||||
<>
|
||||
<div style={{ marginBottom: 16 }}>
|
||||
{isAdmin && (
|
||||
<Button
|
||||
type="primary"
|
||||
icon={<PlusOutlined />}
|
||||
onClick={() => setIsAddAttachmentModalVisible(true)}
|
||||
disabled={policiesList.length === 0}
|
||||
>
|
||||
Add New Attachment
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
<AttachmentTable
|
||||
attachments={attachmentsList}
|
||||
isLoading={isAttachmentsLoading}
|
||||
onDeleteClick={handleDeleteAttachment}
|
||||
isAdmin={isAdmin}
|
||||
/>
|
||||
</>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="w-full mx-auto flex-auto overflow-y-auto m-8 p-2">
|
||||
<Tabs
|
||||
activeKey={activeTab}
|
||||
onChange={setActiveTab}
|
||||
items={tabItems}
|
||||
/>
|
||||
|
||||
<AddPolicyForm
|
||||
visible={isAddPolicyModalVisible}
|
||||
onClose={() => {
|
||||
setIsAddPolicyModalVisible(false);
|
||||
setEditingPolicy(null);
|
||||
}}
|
||||
onSuccess={handlePolicySuccess}
|
||||
accessToken={accessToken}
|
||||
editingPolicy={editingPolicy}
|
||||
existingPolicies={policiesList}
|
||||
availableGuardrails={guardrailsList}
|
||||
onCreatePolicy={handleCreatePolicy}
|
||||
onUpdatePolicy={handleUpdatePolicy}
|
||||
/>
|
||||
|
||||
<AddAttachmentForm
|
||||
visible={isAddAttachmentModalVisible}
|
||||
onClose={() => setIsAddAttachmentModalVisible(false)}
|
||||
onSuccess={handleAttachmentSuccess}
|
||||
accessToken={accessToken}
|
||||
policies={policiesList}
|
||||
onCreateAttachment={handleCreateAttachment}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default PoliciesPanel;
|
||||
168
ui/litellm-dashboard/src/components/policies/policy_info.tsx
Normal file
168
ui/litellm-dashboard/src/components/policies/policy_info.tsx
Normal file
|
|
@ -0,0 +1,168 @@
|
|||
import React, { useState, useEffect } from "react";
|
||||
import {
|
||||
Card,
|
||||
Descriptions,
|
||||
Tag,
|
||||
Button,
|
||||
Space,
|
||||
Typography,
|
||||
Spin,
|
||||
Divider,
|
||||
} from "antd";
|
||||
import { ArrowLeftOutlined, EditOutlined } from "@ant-design/icons";
|
||||
import { Policy } from "./types";
|
||||
|
||||
const { Title, Text } = Typography;
|
||||
|
||||
interface PolicyInfoViewProps {
|
||||
policyId: string;
|
||||
onClose: () => void;
|
||||
onEdit: (policy: Policy) => void;
|
||||
accessToken: string | null;
|
||||
isAdmin: boolean;
|
||||
fetchPolicy: (policyId: string) => Promise<Policy | null>;
|
||||
}
|
||||
|
||||
const PolicyInfoView: React.FC<PolicyInfoViewProps> = ({
|
||||
policyId,
|
||||
onClose,
|
||||
onEdit,
|
||||
accessToken,
|
||||
isAdmin,
|
||||
fetchPolicy,
|
||||
}) => {
|
||||
const [policy, setPolicy] = useState<Policy | null>(null);
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
const loadPolicy = async () => {
|
||||
if (!accessToken || !policyId) return;
|
||||
|
||||
setIsLoading(true);
|
||||
try {
|
||||
const data = await fetchPolicy(policyId);
|
||||
setPolicy(data);
|
||||
} catch (error) {
|
||||
console.error("Error fetching policy:", error);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
loadPolicy();
|
||||
}, [policyId, accessToken, fetchPolicy]);
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div style={{ textAlign: "center", padding: "50px" }}>
|
||||
<Spin size="large" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (!policy) {
|
||||
return (
|
||||
<Card>
|
||||
<Text type="danger">Policy not found</Text>
|
||||
<br />
|
||||
<Button onClick={onClose} style={{ marginTop: 16 }}>
|
||||
Go Back
|
||||
</Button>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<Space direction="vertical" style={{ width: "100%" }} size="large">
|
||||
<Space style={{ justifyContent: "space-between", width: "100%" }}>
|
||||
<Button icon={<ArrowLeftOutlined />} onClick={onClose}>
|
||||
Back to Policies
|
||||
</Button>
|
||||
{isAdmin && (
|
||||
<Button
|
||||
type="primary"
|
||||
icon={<EditOutlined />}
|
||||
onClick={() => onEdit(policy)}
|
||||
>
|
||||
Edit Policy
|
||||
</Button>
|
||||
)}
|
||||
</Space>
|
||||
|
||||
<Title level={4}>{policy.policy_name}</Title>
|
||||
|
||||
<Descriptions bordered column={1}>
|
||||
<Descriptions.Item label="Policy ID">
|
||||
<Text code>{policy.policy_id}</Text>
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="Description">
|
||||
{policy.description || <Text type="secondary">No description</Text>}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="Inherits From">
|
||||
{policy.inherit ? (
|
||||
<Tag color="blue">{policy.inherit}</Tag>
|
||||
) : (
|
||||
<Text type="secondary">None</Text>
|
||||
)}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="Created At">
|
||||
{policy.created_at
|
||||
? new Date(policy.created_at).toLocaleString()
|
||||
: "-"}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="Updated At">
|
||||
{policy.updated_at
|
||||
? new Date(policy.updated_at).toLocaleString()
|
||||
: "-"}
|
||||
</Descriptions.Item>
|
||||
</Descriptions>
|
||||
|
||||
<Divider orientation="left">Guardrails Configuration</Divider>
|
||||
|
||||
<Descriptions bordered column={1}>
|
||||
<Descriptions.Item label="Guardrails to Add">
|
||||
<Space wrap>
|
||||
{policy.guardrails_add && policy.guardrails_add.length > 0 ? (
|
||||
policy.guardrails_add.map((g) => (
|
||||
<Tag key={g} color="green">
|
||||
{g}
|
||||
</Tag>
|
||||
))
|
||||
) : (
|
||||
<Text type="secondary">None</Text>
|
||||
)}
|
||||
</Space>
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="Guardrails to Remove">
|
||||
<Space wrap>
|
||||
{policy.guardrails_remove && policy.guardrails_remove.length > 0 ? (
|
||||
policy.guardrails_remove.map((g) => (
|
||||
<Tag key={g} color="red">
|
||||
{g}
|
||||
</Tag>
|
||||
))
|
||||
) : (
|
||||
<Text type="secondary">None</Text>
|
||||
)}
|
||||
</Space>
|
||||
</Descriptions.Item>
|
||||
</Descriptions>
|
||||
|
||||
<Divider orientation="left">Conditions</Divider>
|
||||
|
||||
<Descriptions bordered column={1}>
|
||||
<Descriptions.Item label="Model Condition">
|
||||
{policy.condition?.model ? (
|
||||
<Tag color="purple">{policy.condition.model}</Tag>
|
||||
) : (
|
||||
<Text type="secondary">No model condition (applies to all models)</Text>
|
||||
)}
|
||||
</Descriptions.Item>
|
||||
</Descriptions>
|
||||
</Space>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
export default PolicyInfoView;
|
||||
160
ui/litellm-dashboard/src/components/policies/policy_table.tsx
Normal file
160
ui/litellm-dashboard/src/components/policies/policy_table.tsx
Normal file
|
|
@ -0,0 +1,160 @@
|
|||
import React from "react";
|
||||
import { Table, Tag, Button, Space, Tooltip, Typography } from "antd";
|
||||
import { DeleteOutlined, EditOutlined, EyeOutlined } from "@ant-design/icons";
|
||||
import { Policy } from "./types";
|
||||
|
||||
const { Text } = Typography;
|
||||
|
||||
interface PolicyTableProps {
|
||||
policies: Policy[];
|
||||
isLoading: boolean;
|
||||
onDeleteClick: (policyId: string, policyName: string) => void;
|
||||
onEditClick: (policy: Policy) => void;
|
||||
onViewClick: (policyId: string) => void;
|
||||
isAdmin: boolean;
|
||||
}
|
||||
|
||||
const PolicyTable: React.FC<PolicyTableProps> = ({
|
||||
policies,
|
||||
isLoading,
|
||||
onDeleteClick,
|
||||
onEditClick,
|
||||
onViewClick,
|
||||
isAdmin,
|
||||
}) => {
|
||||
const columns = [
|
||||
{
|
||||
title: "Policy Name",
|
||||
dataIndex: "policy_name",
|
||||
key: "policy_name",
|
||||
render: (text: string, record: Policy) => (
|
||||
<Button type="link" onClick={() => onViewClick(record.policy_id)}>
|
||||
{text}
|
||||
</Button>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "Description",
|
||||
dataIndex: "description",
|
||||
key: "description",
|
||||
render: (text: string | null) => text || <Text type="secondary">-</Text>,
|
||||
},
|
||||
{
|
||||
title: "Inherits From",
|
||||
dataIndex: "inherit",
|
||||
key: "inherit",
|
||||
render: (text: string | null) =>
|
||||
text ? <Tag color="blue">{text}</Tag> : <Text type="secondary">-</Text>,
|
||||
},
|
||||
{
|
||||
title: "Guardrails (Add)",
|
||||
dataIndex: "guardrails_add",
|
||||
key: "guardrails_add",
|
||||
render: (guardrails: string[]) => (
|
||||
<Space wrap>
|
||||
{guardrails && guardrails.length > 0 ? (
|
||||
guardrails.slice(0, 3).map((g) => (
|
||||
<Tag key={g} color="green">
|
||||
{g}
|
||||
</Tag>
|
||||
))
|
||||
) : (
|
||||
<Text type="secondary">-</Text>
|
||||
)}
|
||||
{guardrails && guardrails.length > 3 && (
|
||||
<Tooltip title={guardrails.slice(3).join(", ")}>
|
||||
<Tag>+{guardrails.length - 3} more</Tag>
|
||||
</Tooltip>
|
||||
)}
|
||||
</Space>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "Guardrails (Remove)",
|
||||
dataIndex: "guardrails_remove",
|
||||
key: "guardrails_remove",
|
||||
render: (guardrails: string[]) => (
|
||||
<Space wrap>
|
||||
{guardrails && guardrails.length > 0 ? (
|
||||
guardrails.slice(0, 3).map((g) => (
|
||||
<Tag key={g} color="red">
|
||||
{g}
|
||||
</Tag>
|
||||
))
|
||||
) : (
|
||||
<Text type="secondary">-</Text>
|
||||
)}
|
||||
{guardrails && guardrails.length > 3 && (
|
||||
<Tooltip title={guardrails.slice(3).join(", ")}>
|
||||
<Tag>+{guardrails.length - 3} more</Tag>
|
||||
</Tooltip>
|
||||
)}
|
||||
</Space>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "Model Condition",
|
||||
dataIndex: "condition",
|
||||
key: "condition",
|
||||
render: (condition: { model?: string } | null) =>
|
||||
condition?.model ? (
|
||||
<Tag color="purple">{condition.model}</Tag>
|
||||
) : (
|
||||
<Text type="secondary">-</Text>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "Actions",
|
||||
key: "actions",
|
||||
render: (_: any, record: Policy) => (
|
||||
<Space>
|
||||
<Tooltip title="View">
|
||||
<Button
|
||||
type="text"
|
||||
icon={<EyeOutlined />}
|
||||
onClick={() => onViewClick(record.policy_id)}
|
||||
/>
|
||||
</Tooltip>
|
||||
{isAdmin && (
|
||||
<>
|
||||
<Tooltip title="Edit">
|
||||
<Button
|
||||
type="text"
|
||||
icon={<EditOutlined />}
|
||||
onClick={() => onEditClick(record)}
|
||||
/>
|
||||
</Tooltip>
|
||||
<Tooltip title="Delete">
|
||||
<Button
|
||||
type="text"
|
||||
danger
|
||||
icon={<DeleteOutlined />}
|
||||
onClick={() =>
|
||||
onDeleteClick(record.policy_id, record.policy_name)
|
||||
}
|
||||
/>
|
||||
</Tooltip>
|
||||
</>
|
||||
)}
|
||||
</Space>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<Table
|
||||
columns={columns}
|
||||
dataSource={policies}
|
||||
rowKey="policy_id"
|
||||
loading={isLoading}
|
||||
pagination={{
|
||||
pageSize: 10,
|
||||
showSizeChanger: true,
|
||||
showTotal: (total, range) =>
|
||||
`${range[0]}-${range[1]} of ${total} policies`,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default PolicyTable;
|
||||
66
ui/litellm-dashboard/src/components/policies/types.ts
Normal file
66
ui/litellm-dashboard/src/components/policies/types.ts
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
export interface Policy {
|
||||
policy_id: string;
|
||||
policy_name: string;
|
||||
inherit: string | null;
|
||||
description: string | null;
|
||||
guardrails_add: string[];
|
||||
guardrails_remove: string[];
|
||||
condition: PolicyCondition | null;
|
||||
created_at?: string;
|
||||
updated_at?: string;
|
||||
created_by?: string;
|
||||
updated_by?: string;
|
||||
}
|
||||
|
||||
export interface PolicyCondition {
|
||||
model?: string;
|
||||
}
|
||||
|
||||
export interface PolicyAttachment {
|
||||
attachment_id: string;
|
||||
policy_name: string;
|
||||
scope: string | null;
|
||||
teams: string[];
|
||||
keys: string[];
|
||||
models: string[];
|
||||
created_at?: string;
|
||||
updated_at?: string;
|
||||
created_by?: string;
|
||||
updated_by?: string;
|
||||
}
|
||||
|
||||
export interface PolicyCreateRequest {
|
||||
policy_name: string;
|
||||
inherit?: string;
|
||||
description?: string;
|
||||
guardrails_add?: string[];
|
||||
guardrails_remove?: string[];
|
||||
condition?: PolicyCondition;
|
||||
}
|
||||
|
||||
export interface PolicyUpdateRequest {
|
||||
policy_name?: string;
|
||||
inherit?: string;
|
||||
description?: string;
|
||||
guardrails_add?: string[];
|
||||
guardrails_remove?: string[];
|
||||
condition?: PolicyCondition;
|
||||
}
|
||||
|
||||
export interface PolicyAttachmentCreateRequest {
|
||||
policy_name: string;
|
||||
scope?: string;
|
||||
teams?: string[];
|
||||
keys?: string[];
|
||||
models?: string[];
|
||||
}
|
||||
|
||||
export interface PolicyListResponse {
|
||||
policies: Policy[];
|
||||
total_count: number;
|
||||
}
|
||||
|
||||
export interface PolicyAttachmentListResponse {
|
||||
attachments: PolicyAttachment[];
|
||||
total_count: number;
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue