ui for policy mgmt

This commit is contained in:
Ishaan Jaffer 2026-01-23 18:37:44 -08:00
parent 46ef001150
commit 28ef76ba16
10 changed files with 473 additions and 17 deletions

View file

@ -14,7 +14,7 @@ import PremiumLoggingSettings from "@/components/common_components/PremiumLoggin
import ModelAliasManager from "@/components/common_components/ModelAliasManager";
import React, { useEffect, useState } from "react";
import NotificationsManager from "@/components/molecules/notifications_manager";
import { fetchMCPAccessGroups, getGuardrailsList, Organization, Team, teamCreateCall } from "@/components/networking";
import { fetchMCPAccessGroups, getGuardrailsList, getPoliciesList, Organization, Team, teamCreateCall } from "@/components/networking";
import useAuthorized from "@/app/(dashboard)/hooks/useAuthorized";
import MCPToolPermissions from "@/components/mcp_server_management/MCPToolPermissions";
@ -76,6 +76,7 @@ const CreateTeamModal = ({
const [currentOrgForCreateTeam, setCurrentOrgForCreateTeam] = useState<Organization | null>(null);
const [modelsToPick, setModelsToPick] = useState<string[]>([]);
const [guardrailsList, setGuardrailsList] = useState<string[]>([]);
const [policiesList, setPoliciesList] = useState<string[]>([]);
const [mcpAccessGroups, setMcpAccessGroups] = useState<string[]>([]);
const [mcpAccessGroupsLoaded, setMcpAccessGroupsLoaded] = useState(false);
@ -136,7 +137,22 @@ const CreateTeamModal = ({
}
};
const fetchPolicies = async () => {
try {
if (accessToken == null) {
return;
}
const response = await getPoliciesList(accessToken);
const policyNames = response.policies.map((p: { policy_name: string }) => p.policy_name);
setPoliciesList(policyNames);
} catch (error) {
console.error("Failed to fetch policies:", error);
}
};
fetchGuardrails();
fetchPolicies();
}, [accessToken]);
const handleCreate = async (formValues: Record<string, any>) => {
@ -531,6 +547,36 @@ const CreateTeamModal = ({
unCheckedChildren="No"
/>
</Form.Item>
<Form.Item
label={
<span>
Policies{" "}
<Tooltip title="Apply policies to this team to control guardrails and other settings">
<a
href="https://docs.litellm.ai/docs/proxy/guardrails/guardrail_policies"
target="_blank"
rel="noopener noreferrer"
onClick={(e) => e.stopPropagation()}
>
<InfoCircleOutlined style={{ marginLeft: "4px" }} />
</a>
</Tooltip>
</span>
}
name="policies"
className="mt-8"
help="Select existing policies or enter new ones"
>
<Select2
mode="tags"
style={{ width: "100%" }}
placeholder="Select or enter policies"
options={policiesList.map((name) => ({
value: name,
label: name,
}))}
/>
</Form.Item>
<Form.Item
label={
<span>

View file

@ -107,7 +107,7 @@ const Sidebar: React.FC<SidebarProps> = ({ setPage, defaultSelectedKey, collapse
{
key: "agents",
page: "agents",
label: <span className="flex items-center gap-4">Agents</span>,
label: "Agents",
icon: <RobotOutlined />,
roles: rolesWithWriteAccess,
},
@ -127,7 +127,11 @@ const Sidebar: React.FC<SidebarProps> = ({ setPage, defaultSelectedKey, collapse
{
key: "policies",
page: "policies",
label: "Policies",
label: (
<span className="flex items-center gap-4">
Policies <NewBadge />
</span>
),
icon: <AuditOutlined />,
roles: all_admin_roles,
},

View file

@ -5377,6 +5377,32 @@ export const getPoliciesList = async (accessToken: string) => {
}
};
export const getPolicyInfoWithGuardrails = async (accessToken: string, policyName: string) => {
try {
const url = proxyBaseUrl ? `${proxyBaseUrl}/policy/info/${policyName}` : `/policy/info/${policyName}`;
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 for ${policyName}:`, error);
throw error;
}
};
export const createPolicyCall = async (accessToken: string, policyData: any) => {
try {
const url = proxyBaseUrl ? `${proxyBaseUrl}/policies` : `/policies`;

View file

@ -29,6 +29,7 @@ import MCPToolPermissions from "../mcp_server_management/MCPToolPermissions";
import NotificationsManager from "../molecules/notifications_manager";
import {
getGuardrailsList,
getPoliciesList,
getPossibleUserRoles,
getPromptsList,
keyCreateCall,
@ -150,6 +151,7 @@ const CreateKey: React.FC<CreateKeyProps> = ({ team, teams, data, addKey }) => {
const [keyOwner, setKeyOwner] = useState("you");
const [predefinedTags, setPredefinedTags] = useState(getPredefinedTags(data));
const [guardrailsList, setGuardrailsList] = useState<string[]>([]);
const [policiesList, setPoliciesList] = useState<string[]>([]);
const [promptsList, setPromptsList] = useState<string[]>([]);
const [loggingSettings, setLoggingSettings] = useState<any[]>([]);
const [selectedCreateKeyTeam, setSelectedCreateKeyTeam] = useState<Team | null>(team);
@ -211,6 +213,16 @@ const CreateKey: React.FC<CreateKeyProps> = ({ team, teams, data, addKey }) => {
}
};
const fetchPolicies = async () => {
try {
const response = await getPoliciesList(accessToken);
const policyNames = response.policies.map((p: { policy_name: string }) => p.policy_name);
setPoliciesList(policyNames);
} catch (error) {
console.error("Failed to fetch policies:", error);
}
};
const fetchPrompts = async () => {
try {
const response = await getPromptsList(accessToken);
@ -221,6 +233,7 @@ const CreateKey: React.FC<CreateKeyProps> = ({ team, teams, data, addKey }) => {
};
fetchGuardrails();
fetchPolicies();
fetchPrompts();
}, [accessToken]);
@ -915,6 +928,42 @@ const CreateKey: React.FC<CreateKeyProps> = ({ team, teams, data, addKey }) => {
>
<Switch disabled={!premiumUser} checkedChildren="Yes" unCheckedChildren="No" />
</Form.Item>
<Form.Item
label={
<span>
Policies{" "}
<Tooltip title="Apply policies to this key to control guardrails and other settings">
<a
href="https://docs.litellm.ai/docs/proxy/guardrails/guardrail_policies"
target="_blank"
rel="noopener noreferrer"
onClick={(e) => e.stopPropagation()} // Prevent accordion from collapsing when clicking link
>
<InfoCircleOutlined style={{ marginLeft: "4px" }} />
</a>
</Tooltip>
</span>
}
name="policies"
className="mt-4"
help={
premiumUser
? "Select existing policies or enter new ones"
: "Premium feature - Upgrade to set policies by key"
}
>
<Select
mode="tags"
style={{ width: "100%" }}
disabled={!premiumUser}
placeholder={
!premiumUser
? "Premium feature - Upgrade to set policies by key"
: "Select or enter policies"
}
options={policiesList.map((name) => ({ value: name, label: name }))}
/>
</Form.Item>
<Form.Item
label={
<span>

View file

@ -2,7 +2,7 @@ import React, { useState, useEffect } from "react";
import { Modal, Form, Select, Radio, Divider, Typography } from "antd";
import { Button } from "@tremor/react";
import { Policy, PolicyAttachmentCreateRequest } from "./types";
import { teamListCall, keyInfoCall } from "../networking";
import { teamListCall, keyInfoCall, modelAvailableCall } from "../networking";
import NotificationsManager from "../molecules/notifications_manager";
import useAuthorized from "@/app/(dashboard)/hooks/useAuthorized";
@ -30,24 +30,27 @@ const AddAttachmentForm: React.FC<AddAttachmentFormProps> = ({
const [scopeType, setScopeType] = useState<"global" | "specific">("global");
const [availableTeams, setAvailableTeams] = useState<string[]>([]);
const [availableKeys, setAvailableKeys] = useState<string[]>([]);
const [availableModels, setAvailableModels] = useState<string[]>([]);
const [isLoadingTeams, setIsLoadingTeams] = useState(false);
const [isLoadingKeys, setIsLoadingKeys] = useState(false);
const [isLoadingModels, setIsLoadingModels] = useState(false);
const { userId, userRole } = useAuthorized();
useEffect(() => {
if (visible && accessToken) {
loadTeamsAndKeys();
loadTeamsKeysAndModels();
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [visible, accessToken]);
const loadTeamsAndKeys = async () => {
const loadTeamsKeysAndModels = async () => {
if (!accessToken) return;
// Load teams
setIsLoadingTeams(true);
try {
const teamsResponse = await teamListCall(accessToken, userId, userRole);
// Pass null for organizationID since we're loading all teams the user has access to
const teamsResponse = await teamListCall(accessToken, null, userId);
if (teamsResponse?.data) {
const teamAliases = teamsResponse.data
.map((t: any) => t.team_alias)
@ -75,6 +78,22 @@ const AddAttachmentForm: React.FC<AddAttachmentFormProps> = ({
} finally {
setIsLoadingKeys(false);
}
// Load models
setIsLoadingModels(true);
try {
const modelsResponse = await modelAvailableCall(accessToken, userId || "", userRole || "");
if (modelsResponse?.data) {
const modelIds = modelsResponse.data
.map((m: any) => m.id || m.model_name)
.filter(Boolean);
setAvailableModels(modelIds);
}
} catch (error) {
console.error("Failed to load models:", error);
} finally {
setIsLoadingModels(false);
}
};
const resetForm = () => {
@ -230,12 +249,21 @@ const AddAttachmentForm: React.FC<AddAttachmentFormProps> = ({
<Form.Item
name="models"
label="Models"
tooltip="Model names this attachment applies to. Supports wildcards (e.g., gpt-4*)"
tooltip="Model names this attachment applies to. Supports wildcards (e.g., gpt-4*). Leave empty to apply to all models."
>
<Select
mode="tags"
placeholder="Enter model names (e.g., gpt-4, bedrock/*)"
placeholder={isLoadingModels ? "Loading models..." : "Select or enter model names (e.g., gpt-4, bedrock/*)"}
loading={isLoadingModels}
options={availableModels.map((model) => ({
label: model,
value: model,
}))}
tokenSeparators={[","]}
showSearch
filterOption={(input, option) =>
(option?.label ?? "").toLowerCase().includes(input.toLowerCase())
}
style={{ width: "100%" }}
/>
</Form.Item>

View file

@ -344,6 +344,14 @@ const AddPolicyForm: React.FC<AddPolicyFormProps> = ({
<Text strong>Conditions (Optional)</Text>
</Divider>
<Alert
message="Model Scope"
description="By default, this policy will run on all models. You can optionally restrict it to specific models below."
type="info"
showIcon
style={{ marginBottom: 16 }}
/>
<Form.Item label="Model Condition Type">
<Radio.Group
value={modelConditionType}
@ -359,18 +367,18 @@ const AddPolicyForm: React.FC<AddPolicyFormProps> = ({
<Form.Item
name="model_condition"
label={modelConditionType === "model" ? "Model" : "Regex Pattern"}
label={modelConditionType === "model" ? "Model (Optional)" : "Regex Pattern (Optional)"}
tooltip={
modelConditionType === "model"
? "Select a specific model to apply this policy to"
: "Enter a regex pattern to match models (e.g., gpt-4.* or bedrock/.*)"
? "Select a specific model to apply this policy to. Leave empty to apply to all models."
: "Enter a regex pattern to match models (e.g., gpt-4.* or bedrock/.*). Leave empty to apply to all models."
}
>
{modelConditionType === "model" ? (
<Select
showSearch
allowClear
placeholder="Select a model"
placeholder="Leave empty to apply to all models"
options={availableModels.map((model) => ({
label: model,
value: model,
@ -381,7 +389,7 @@ const AddPolicyForm: React.FC<AddPolicyFormProps> = ({
style={{ width: "100%" }}
/>
) : (
<TextInput placeholder="e.g., gpt-4.* or bedrock/claude-.*" />
<TextInput placeholder="Leave empty to apply to all models (e.g., gpt-4.* or bedrock/claude-.*)" />
)}
</Form.Item>

View file

@ -1,7 +1,7 @@
import React, { useState, useEffect, useCallback } from "react";
import { Button, TabGroup, TabList, Tab, TabPanels, TabPanel } from "@tremor/react";
import { Modal, message } from "antd";
import { ExclamationCircleOutlined } from "@ant-design/icons";
import { Modal, message, Alert } from "antd";
import { ExclamationCircleOutlined, InfoCircleOutlined } from "@ant-design/icons";
import { isAdminRole } from "@/utils/roles";
import PolicyTable from "./policy_table";
import PolicyInfoView from "./policy_info";
@ -181,6 +181,36 @@ const PoliciesPanel: React.FC<PoliciesPanelProps> = ({
<TabPanels>
<TabPanel>
<Alert
message="About Policies"
description={
<div>
<p className="mb-3">
Use policies to group guardrails and control which ones run for specific teams, keys, or models.
</p>
<p className="mb-2 font-semibold">Why use policies?</p>
<ul className="list-disc list-inside mb-3 space-y-1 ml-2">
<li>Enable/disable specific guardrails for teams, keys, or models</li>
<li>Group guardrails into a single policy</li>
<li>Inherit from existing policies and override what you need</li>
</ul>
<a
href="https://docs.litellm.ai/docs/proxy/guardrails/guardrail_policies"
target="_blank"
rel="noopener noreferrer"
className="text-blue-600 hover:text-blue-800 underline inline-block mt-1"
>
Learn more in the documentation
</a>
</div>
}
type="info"
icon={<InfoCircleOutlined />}
showIcon
closable
className="mb-6"
/>
<div className="flex justify-between items-center mb-4">
<Button onClick={handleAddPolicy} disabled={!accessToken}>
+ Add New Policy
@ -244,6 +274,37 @@ const PoliciesPanel: React.FC<PoliciesPanelProps> = ({
</TabPanel>
<TabPanel>
<Alert
message="About Policy Attachments"
description={
<div>
<p className="mb-3">
Policy attachments control where your policies apply. Policies don't do anything until you attach them to specific teams, keys, models, or globally.
</p>
<p className="mb-2 font-semibold">Attachment Scopes:</p>
<ul className="list-disc list-inside mb-3 space-y-1 ml-2">
<li><strong>Global (*)</strong> - Applies to all requests</li>
<li><strong>Teams</strong> - Applies only to specific teams</li>
<li><strong>Keys</strong> - Applies only to specific API keys (supports wildcards like dev-*)</li>
<li><strong>Models</strong> - Applies only when specific models are used</li>
</ul>
<a
href="https://docs.litellm.ai/docs/proxy/guardrails/guardrail_policies#attachments"
target="_blank"
rel="noopener noreferrer"
className="text-blue-600 hover:text-blue-800 underline inline-block mt-1"
>
Learn more about attachments
</a>
</div>
}
type="info"
icon={<InfoCircleOutlined />}
showIcon
closable
className="mb-6"
/>
<div className="flex justify-between items-center mb-4">
<Button
onClick={() => setIsAddAttachmentModalVisible(true)}

View file

@ -2,6 +2,8 @@ import useAuthorized from "@/app/(dashboard)/hooks/useAuthorized";
import UserSearchModal from "@/components/common_components/user_search_modal";
import {
getGuardrailsList,
getPoliciesList,
getPolicyInfoWithGuardrails,
Member,
Organization,
organizationInfoCall,
@ -171,6 +173,9 @@ const TeamInfoView: React.FC<TeamInfoProps> = ({
const [mcpAccessGroupsLoaded, setMcpAccessGroupsLoaded] = useState(false);
const [copiedStates, setCopiedStates] = useState<Record<string, boolean>>({});
const [guardrailsList, setGuardrailsList] = useState<string[]>([]);
const [policiesList, setPoliciesList] = useState<string[]>([]);
const [policyGuardrails, setPolicyGuardrails] = useState<Record<string, string[]>>({});
const [loadingPolicies, setLoadingPolicies] = useState(false);
const [memberToDelete, setMemberToDelete] = useState<Member | null>(null);
const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
const [isDeleting, setIsDeleting] = useState(false);
@ -247,9 +252,54 @@ const TeamInfoView: React.FC<TeamInfoProps> = ({
}
};
const fetchPolicies = async () => {
try {
if (!accessToken) return;
const response = await getPoliciesList(accessToken);
const policyNames = response.policies.map((p: { policy_name: string }) => p.policy_name);
setPoliciesList(policyNames);
} catch (error) {
console.error("Failed to fetch policies:", error);
}
};
fetchGuardrails();
fetchPolicies();
}, [accessToken]);
// Fetch resolved guardrails for all policies
useEffect(() => {
const fetchPolicyGuardrails = async () => {
if (!accessToken || !info?.policies || info.policies.length === 0) {
return;
}
setLoadingPolicies(true);
const guardrailsMap: Record<string, string[]> = {};
try {
await Promise.all(
info.policies.map(async (policyName: string) => {
try {
const policyInfo = await getPolicyInfoWithGuardrails(accessToken, policyName);
guardrailsMap[policyName] = policyInfo.resolved_guardrails || [];
} catch (error) {
console.error(`Failed to fetch guardrails for policy ${policyName}:`, error);
guardrailsMap[policyName] = [];
}
})
);
setPolicyGuardrails(guardrailsMap);
} catch (error) {
console.error("Failed to fetch policy guardrails:", error);
} finally {
setLoadingPolicies(false);
}
};
fetchPolicyGuardrails();
}, [accessToken, info?.policies]);
const handleMemberCreate = async (values: any) => {
try {
if (accessToken == null) return;
@ -603,6 +653,56 @@ const TeamInfoView: React.FC<TeamInfoProps> = ({
accessToken={accessToken}
/>
<Card>
<Text className="font-semibold text-gray-900 mb-3">Guardrails</Text>
{info.guardrails && info.guardrails.length > 0 ? (
<div className="flex flex-wrap gap-2">
{info.guardrails.map((guardrail: string, index: number) => (
<Badge key={index} color="blue">
{guardrail}
</Badge>
))}
</div>
) : (
<Text className="text-gray-500">No guardrails configured</Text>
)}
{info.metadata?.disable_global_guardrails && (
<div className="mt-3 pt-3 border-t border-gray-200">
<Badge color="yellow">Global Guardrails Disabled</Badge>
</div>
)}
</Card>
<Card>
<Text className="font-semibold text-gray-900 mb-3">Policies</Text>
{info.policies && info.policies.length > 0 ? (
<div className="space-y-4">
{info.policies.map((policy: string, index: number) => (
<div key={index} className="space-y-2">
<div className="flex items-center gap-2">
<Badge color="purple">{policy}</Badge>
{loadingPolicies && <Text className="text-xs text-gray-400">Loading guardrails...</Text>}
</div>
{!loadingPolicies && policyGuardrails[policy] && policyGuardrails[policy].length > 0 && (
<div className="ml-4 pl-3 border-l-2 border-gray-200">
<Text className="text-xs text-gray-500 mb-1">Resolved Guardrails:</Text>
<div className="flex flex-wrap gap-1">
{policyGuardrails[policy].map((guardrail: string, gIndex: number) => (
<Badge key={gIndex} color="blue" size="xs">
{guardrail}
</Badge>
))}
</div>
</div>
)}
</div>
))}
</div>
) : (
<Text className="text-gray-500">No policies configured</Text>
)}
</Card>
<LoggingSettingsView
loggingConfigs={info.metadata?.logging || []}
disabledCallbacks={[]}
@ -814,6 +914,32 @@ const TeamInfoView: React.FC<TeamInfoProps> = ({
<Switch checkedChildren="Yes" unCheckedChildren="No" />
</Form.Item>
<Form.Item
label={
<span>
Policies{" "}
<Tooltip title="Apply policies to this team to control guardrails and other settings">
<a
href="https://docs.litellm.ai/docs/proxy/guardrails/guardrail_policies"
target="_blank"
rel="noopener noreferrer"
onClick={(e) => e.stopPropagation()}
>
<InfoCircleOutlined style={{ marginLeft: "4px" }} />
</a>
</Tooltip>
</span>
}
name="policies"
help="Select existing policies or enter new ones"
>
<Select
mode="tags"
placeholder="Select or enter policies"
options={policiesList.map((name) => ({ value: name, label: name }))}
/>
</Form.Item>
<Form.Item label="Vector Stores" name="vector_stores" aria-label="Vector Stores">
<VectorStoreSelector
onChange={(values: string[]) => form.setFieldValue("vector_stores", values)}

View file

@ -1,4 +1,5 @@
import GuardrailSelector from "@/components/guardrails/GuardrailSelector";
import PolicySelector from "@/components/policies/PolicySelector";
import { InfoCircleOutlined } from "@ant-design/icons";
import { TextInput, Button as TremorButton } from "@tremor/react";
import { Form, Input, Select, Switch, Tooltip } from "antd";
@ -406,6 +407,28 @@ export function KeyEditView({
<Switch disabled={!premiumUser} checkedChildren="Yes" unCheckedChildren="No" />
</Form.Item>
<Form.Item
label={
<span>
Policies{" "}
<Tooltip title="Apply policies to this key to control guardrails and other settings">
<InfoCircleOutlined style={{ marginLeft: "4px" }} />
</Tooltip>
</span>
}
name="policies"
>
{accessToken && (
<PolicySelector
onChange={(v) => {
form.setFieldValue("policies", v);
}}
accessToken={accessToken}
disabled={!premiumUser}
/>
)}
</Form.Item>
<Form.Item label="Tags" name="tags">
<Select
mode="tags"

View file

@ -14,7 +14,7 @@ import { extractLoggingSettings, formatMetadataForDisplay, stripTagsFromMetadata
import { KeyResponse } from "../key_team_helpers/key_list";
import LoggingSettingsView from "../logging_settings_view";
import NotificationManager from "../molecules/notifications_manager";
import { keyDeleteCall, keyUpdateCall } from "../networking";
import { keyDeleteCall, keyUpdateCall, getPolicyInfoWithGuardrails } from "../networking";
import ObjectPermissionsView from "../object_permissions_view";
import { RegenerateKeyModal } from "../organisms/regenerate_key_modal";
import { parseErrorMessage } from "../shared/errorUtils";
@ -60,6 +60,8 @@ export default function KeyInfoView({
const [currentKeyData, setCurrentKeyData] = useState<KeyResponse | undefined>(keyData);
const [lastRegeneratedAt, setLastRegeneratedAt] = useState<Date | null>(null);
const [isRecentlyRegenerated, setIsRecentlyRegenerated] = useState(false);
const [policyGuardrails, setPolicyGuardrails] = useState<Record<string, string[]>>({});
const [loadingPolicies, setLoadingPolicies] = useState(false);
// Update local state when keyData prop changes (but don't reset to undefined)
useEffect(() => {
@ -68,6 +70,39 @@ export default function KeyInfoView({
}
}, [keyData]);
// Fetch resolved guardrails for all policies
useEffect(() => {
const fetchPolicyGuardrails = async () => {
if (!accessToken || !currentKeyData?.metadata?.policies || currentKeyData.metadata.policies.length === 0) {
return;
}
setLoadingPolicies(true);
const guardrailsMap: Record<string, string[]> = {};
try {
await Promise.all(
currentKeyData.metadata.policies.map(async (policyName: string) => {
try {
const policyInfo = await getPolicyInfoWithGuardrails(accessToken, policyName);
guardrailsMap[policyName] = policyInfo.resolved_guardrails || [];
} catch (error) {
console.error(`Failed to fetch guardrails for policy ${policyName}:`, error);
guardrailsMap[policyName] = [];
}
})
);
setPolicyGuardrails(guardrailsMap);
} catch (error) {
console.error("Failed to fetch policy guardrails:", error);
} finally {
setLoadingPolicies(false);
}
};
fetchPolicyGuardrails();
}, [accessToken, currentKeyData?.metadata?.policies]);
// Reset recent regeneration indicator after 5 seconds
useEffect(() => {
if (isRecentlyRegenerated) {
@ -484,6 +519,56 @@ export default function KeyInfoView({
/>
</Card>
<Card>
<Text className="font-medium mb-3">Guardrails</Text>
{currentKeyData.guardrails && currentKeyData.guardrails.length > 0 ? (
<div className="flex flex-wrap gap-2">
{currentKeyData.guardrails.map((guardrail, index) => (
<Badge key={index} color="blue">
{guardrail}
</Badge>
))}
</div>
) : (
<Text className="text-gray-500">No guardrails configured</Text>
)}
{currentKeyData.metadata?.disable_global_guardrails && (
<div className="mt-3 pt-3 border-t border-gray-200">
<Badge color="yellow">Global Guardrails Disabled</Badge>
</div>
)}
</Card>
<Card>
<Text className="font-medium mb-3">Policies</Text>
{currentKeyData.metadata?.policies && currentKeyData.metadata.policies.length > 0 ? (
<div className="space-y-4">
{currentKeyData.metadata.policies.map((policy: string, index: number) => (
<div key={index} className="space-y-2">
<div className="flex items-center gap-2">
<Badge color="purple">{policy}</Badge>
{loadingPolicies && <Text className="text-xs text-gray-400">Loading guardrails...</Text>}
</div>
{!loadingPolicies && policyGuardrails[policy] && policyGuardrails[policy].length > 0 && (
<div className="ml-4 pl-3 border-l-2 border-gray-200">
<Text className="text-xs text-gray-500 mb-1">Resolved Guardrails:</Text>
<div className="flex flex-wrap gap-1">
{policyGuardrails[policy].map((guardrail: string, gIndex: number) => (
<Badge key={gIndex} color="blue" size="xs">
{guardrail}
</Badge>
))}
</div>
</div>
)}
</div>
))}
</div>
) : (
<Text className="text-gray-500">No policies configured</Text>
)}
</Card>
<LoggingSettingsView
loggingConfigs={extractLoggingSettings(currentKeyData.metadata)}
disabledCallbacks={