[Key Access] Litellm disabled callbacks for UI (#12769)

* add disabled callbacks to ui

* added body

* update edit settings

* add tests
This commit is contained in:
Jugal D. Bhatt 2025-07-20 03:02:05 +05:30 • committed by GitHub
parent ee066481f8
commit b443817a56
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 538 additions and 68 deletions

View file

@ -782,6 +782,12 @@ async def add_litellm_data_to_request( # noqa: PLR0915
for k, v in callback_settings_obj.callback_vars.items():
data[k] = v
# Add disabled callbacks from key metadata
if user_api_key_dict.metadata and "litellm_disabled_callbacks" in user_api_key_dict.metadata:
disabled_callbacks = user_api_key_dict.metadata["litellm_disabled_callbacks"]
if disabled_callbacks and isinstance(disabled_callbacks, list):
data["litellm_disabled_callbacks"] = disabled_callbacks
# Guardrails
move_guardrails_to_metadata(
data=data,

View file

@ -220,6 +220,279 @@ async def test_add_litellm_data_to_request_audio_transcription_multipart():
]
@pytest.mark.asyncio
async def test_add_litellm_data_to_request_disabled_callbacks():
"""
Test that litellm_disabled_callbacks from key metadata is properly added to the request data.
"""
from litellm.proxy.litellm_pre_call_utils import add_litellm_data_to_request
# Setup mock request
request_mock = MagicMock(spec=Request)
request_mock.url.path = "/chat/completions"
request_mock.url = MagicMock()
request_mock.url.__str__.return_value = "http://localhost/chat/completions"
request_mock.method = "POST"
request_mock.query_params = {}
request_mock.headers = {"Content-Type": "application/json"}
request_mock.client = MagicMock()
request_mock.client.host = "127.0.0.1"
# Setup user API key with disabled callbacks in metadata
user_api_key_dict = UserAPIKeyAuth(
api_key="test_api_key",
user_id="test_user_id",
org_id="test_org_id",
metadata={
"litellm_disabled_callbacks": ["langfuse", "langsmith", "datadog"]
}
)
# Setup request data
data = {
"model": "gpt-3.5-turbo",
"messages": [{"role": "user", "content": "Hello"}]
}
# Setup proxy config
proxy_config = MagicMock()
# Call add_litellm_data_to_request
result = await add_litellm_data_to_request(
data=data,
request=request_mock,
user_api_key_dict=user_api_key_dict,
proxy_config=proxy_config
)
# Verify that litellm_disabled_callbacks was added to the request data
assert "litellm_disabled_callbacks" in result
assert result["litellm_disabled_callbacks"] == ["langfuse", "langsmith", "datadog"]
# Verify that other data is still present
assert "model" in result
assert result["model"] == "gpt-3.5-turbo"
assert "messages" in result
@pytest.mark.asyncio
async def test_add_litellm_data_to_request_disabled_callbacks_empty():
"""
Test that litellm_disabled_callbacks is not added when it's empty.
"""
from litellm.proxy.litellm_pre_call_utils import add_litellm_data_to_request
# Setup mock request
request_mock = MagicMock(spec=Request)
request_mock.url.path = "/chat/completions"
request_mock.url = MagicMock()
request_mock.url.__str__.return_value = "http://localhost/chat/completions"
request_mock.method = "POST"
request_mock.query_params = {}
request_mock.headers = {"Content-Type": "application/json"}
request_mock.client = MagicMock()
request_mock.client.host = "127.0.0.1"
# Setup user API key with empty disabled callbacks
user_api_key_dict = UserAPIKeyAuth(
api_key="test_api_key",
user_id="test_user_id",
org_id="test_org_id",
metadata={
"litellm_disabled_callbacks": []
}
)
# Setup request data
data = {
"model": "gpt-3.5-turbo",
"messages": [{"role": "user", "content": "Hello"}]
}
# Setup proxy config
proxy_config = MagicMock()
# Call add_litellm_data_to_request
result = await add_litellm_data_to_request(
data=data,
request=request_mock,
user_api_key_dict=user_api_key_dict,
proxy_config=proxy_config
)
# Verify that litellm_disabled_callbacks is not added when empty
assert "litellm_disabled_callbacks" not in result
# Verify that other data is still present
assert "model" in result
assert result["model"] == "gpt-3.5-turbo"
assert "messages" in result
@pytest.mark.asyncio
async def test_add_litellm_data_to_request_disabled_callbacks_not_present():
"""
Test that litellm_disabled_callbacks is not added when it's not present in metadata.
"""
from litellm.proxy.litellm_pre_call_utils import add_litellm_data_to_request
# Setup mock request
request_mock = MagicMock(spec=Request)
request_mock.url.path = "/chat/completions"
request_mock.url = MagicMock()
request_mock.url.__str__.return_value = "http://localhost/chat/completions"
request_mock.method = "POST"
request_mock.query_params = {}
request_mock.headers = {"Content-Type": "application/json"}
request_mock.client = MagicMock()
request_mock.client.host = "127.0.0.1"
# Setup user API key without disabled callbacks in metadata
user_api_key_dict = UserAPIKeyAuth(
api_key="test_api_key",
user_id="test_user_id",
org_id="test_org_id",
metadata={} # No litellm_disabled_callbacks
)
# Setup request data
data = {
"model": "gpt-3.5-turbo",
"messages": [{"role": "user", "content": "Hello"}]
}
# Setup proxy config
proxy_config = MagicMock()
# Call add_litellm_data_to_request
result = await add_litellm_data_to_request(
data=data,
request=request_mock,
user_api_key_dict=user_api_key_dict,
proxy_config=proxy_config
)
# Verify that litellm_disabled_callbacks is not added when not present
assert "litellm_disabled_callbacks" not in result
# Verify that other data is still present
assert "model" in result
assert result["model"] == "gpt-3.5-turbo"
assert "messages" in result
@pytest.mark.asyncio
async def test_add_litellm_data_to_request_disabled_callbacks_invalid_type():
"""
Test that litellm_disabled_callbacks is not added when it's not a list.
"""
from litellm.proxy.litellm_pre_call_utils import add_litellm_data_to_request
# Setup mock request
request_mock = MagicMock(spec=Request)
request_mock.url.path = "/chat/completions"
request_mock.url = MagicMock()
request_mock.url.__str__.return_value = "http://localhost/chat/completions"
request_mock.method = "POST"
request_mock.query_params = {}
request_mock.headers = {"Content-Type": "application/json"}
request_mock.client = MagicMock()
request_mock.client.host = "127.0.0.1"
# Setup user API key with invalid disabled callbacks type
user_api_key_dict = UserAPIKeyAuth(
api_key="test_api_key",
user_id="test_user_id",
org_id="test_org_id",
metadata={
"litellm_disabled_callbacks": "not_a_list" # Should be a list
}
)
# Setup request data
data = {
"model": "gpt-3.5-turbo",
"messages": [{"role": "user", "content": "Hello"}]
}
# Setup proxy config
proxy_config = MagicMock()
# Call add_litellm_data_to_request
result = await add_litellm_data_to_request(
data=data,
request=request_mock,
user_api_key_dict=user_api_key_dict,
proxy_config=proxy_config
)
# Verify that litellm_disabled_callbacks is not added when invalid type
assert "litellm_disabled_callbacks" not in result
# Verify that other data is still present
assert "model" in result
assert result["model"] == "gpt-3.5-turbo"
assert "messages" in result
@pytest.mark.asyncio
async def test_add_litellm_data_to_request_disabled_callbacks_with_logging_settings():
"""
Test that litellm_disabled_callbacks works correctly alongside logging settings.
"""
from litellm.proxy.litellm_pre_call_utils import add_litellm_data_to_request
# Setup mock request
request_mock = MagicMock(spec=Request)
request_mock.url.path = "/chat/completions"
request_mock.url = MagicMock()
request_mock.url.__str__.return_value = "http://localhost/chat/completions"
request_mock.method = "POST"
request_mock.query_params = {}
request_mock.headers = {"Content-Type": "application/json"}
request_mock.client = MagicMock()
request_mock.client.host = "127.0.0.1"
# Setup user API key with both logging settings and disabled callbacks
user_api_key_dict = UserAPIKeyAuth(
api_key="test_api_key",
user_id="test_user_id",
org_id="test_org_id",
metadata={
"logging": [
{"callback_name": "langfuse", "callback_type": "success", "callback_vars": {}}
],
"litellm_disabled_callbacks": ["langsmith", "datadog"]
}
)
# Setup request data
data = {
"model": "gpt-3.5-turbo",
"messages": [{"role": "user", "content": "Hello"}]
}
# Setup proxy config
proxy_config = MagicMock()
# Call add_litellm_data_to_request
result = await add_litellm_data_to_request(
data=data,
request=request_mock,
user_api_key_dict=user_api_key_dict,
proxy_config=proxy_config
)
# Verify that both logging settings and disabled callbacks are handled correctly
assert "litellm_disabled_callbacks" in result
assert result["litellm_disabled_callbacks"] == ["langsmith", "datadog"]
# Verify that other data is still present
assert "model" in result
assert result["model"] == "gpt-3.5-turbo"
assert "messages" in result
def test_key_dynamic_logging_settings():
"""
Test KeyAndTeamLoggingSettings.get_key_dynamic_logging_settings method with arize and langfuse callbacks

View file

@ -1,7 +1,7 @@
export enum Callbacks {
Braintrust = "Braintrust",
CustomCallbackAPI = "Custom Callback API",
Datadog = "Datagog",
Datadog = "Datadog",
Langfuse = "Langfuse",
LangSmith = "LangSmith",
Lago = "Lago",
@ -24,7 +24,7 @@ export const callback_map: Record<string, string> = {
Arize: "arize",
}
const asset_logos_folder = '/ui/assets/logos/';
const asset_logos_folder = '/assets/logos/';
interface CallbackInfo {
logo: string;
@ -66,7 +66,7 @@ export const callbackInfo: Record<string, CallbackInfo> = {
},
[Callbacks.CustomCallbackAPI]: {
logo: `${asset_logos_folder}custom.svg`,
supports_key_team_logging: false,
supports_key_team_logging: true,
dynamic_params: {}
},
[Callbacks.Datadog]: {

View file

@ -6,12 +6,16 @@ interface PremiumLoggingSettingsProps {
value: any[];
onChange: (settings: any[]) => void;
premiumUser?: boolean;
disabledCallbacks?: string[];
onDisabledCallbacksChange?: (disabledCallbacks: string[]) => void;
}
export function PremiumLoggingSettings({
value,
onChange,
premiumUser = false
premiumUser = false,
disabledCallbacks = [],
onDisabledCallbacksChange
}: PremiumLoggingSettingsProps) {
if (!premiumUser) {
return (
@ -37,6 +41,8 @@ export function PremiumLoggingSettings({
<LoggingSettings
value={value}
onChange={onChange}
disabledCallbacks={disabledCallbacks}
onDisabledCallbacksChange={onDisabledCallbacksChange}
/>
);
}

View file

@ -181,11 +181,13 @@ const CreateKey: React.FC<CreateKeyProps> = ({
const [userSearchLoading, setUserSearchLoading] = useState<boolean>(false);
const [mcpAccessGroups, setMcpAccessGroups] = useState<string[]>([]);
const [mcpAccessGroupsLoaded, setMcpAccessGroupsLoaded] = useState(false);
const [disabledCallbacks, setDisabledCallbacks] = useState<string[]>([]);
const handleOk = () => {
setIsModalVisible(false);
form.resetFields();
setLoggingSettings([]);
setDisabledCallbacks([]);
};
const handleCancel = () => {
@ -194,6 +196,7 @@ const CreateKey: React.FC<CreateKeyProps> = ({
setSelectedCreateKeyTeam(null);
form.resetFields();
setLoggingSettings([]);
setDisabledCallbacks([]);
};
useEffect(() => {
@ -306,6 +309,14 @@ const CreateKey: React.FC<CreateKeyProps> = ({
};
}
// Add disabled callbacks to the metadata
if (disabledCallbacks.length > 0) {
metadata = {
...metadata,
litellm_disabled_callbacks: disabledCallbacks
};
}
// Update the formValues with the final metadata
formValues.metadata = JSON.stringify(metadata);
@ -866,6 +877,8 @@ const CreateKey: React.FC<CreateKeyProps> = ({
value={loggingSettings}
onChange={setLoggingSettings}
premiumUser={premiumUser}
disabledCallbacks={disabledCallbacks}
onDisabledCallbacksChange={setDisabledCallbacks}
/>
</div>
</AccordionBody>

View file

@ -57,6 +57,7 @@ export function KeyEditView({
const [availableModels, setAvailableModels] = useState<string[]>([]);
const [mcpAccessGroups, setMcpAccessGroups] = useState<string[]>([]);
const [mcpAccessGroupsLoaded, setMcpAccessGroupsLoaded] = useState(false);
const [disabledCallbacks, setDisabledCallbacks] = useState<string[]>(Array.isArray(keyData.metadata?.litellm_disabled_callbacks) ? keyData.metadata.litellm_disabled_callbacks : []);
const fetchMcpAccessGroups = async () => {
if (!accessToken) return;
@ -99,6 +100,11 @@ export function KeyEditView({
fetchModels();
}, [userID, userRole, accessToken, team, keyData.team_id]);
// Sync disabled callbacks with form when component mounts
useEffect(() => {
form.setFieldValue('disabled_callbacks', disabledCallbacks);
}, [form, disabledCallbacks]);
// Convert API budget duration to form format
const getBudgetDuration = (duration: string | null) => {
if (!duration) return null;
@ -121,7 +127,8 @@ export function KeyEditView({
servers: keyData.object_permission?.mcp_servers || [],
accessGroups: keyData.object_permission?.mcp_access_groups || []
},
logging_settings: extractLoggingSettings(keyData.metadata)
logging_settings: extractLoggingSettings(keyData.metadata),
disabled_callbacks: Array.isArray(keyData.metadata?.litellm_disabled_callbacks) ? keyData.metadata.litellm_disabled_callbacks : []
};
return (
@ -229,6 +236,11 @@ export function KeyEditView({
<EditLoggingSettings
value={form.getFieldValue('logging_settings')}
onChange={(values) => form.setFieldValue('logging_settings', values)}
disabledCallbacks={disabledCallbacks}
onDisabledCallbacksChange={(values) => {
setDisabledCallbacks(values);
form.setFieldValue('disabled_callbacks', values);
}}
/>
</Form.Item>
@ -243,6 +255,11 @@ export function KeyEditView({
<Input />
</Form.Item>
{/* Hidden form field for disabled callbacks */}
<Form.Item name="disabled_callbacks" hidden>
<Input />
</Form.Item>
<div className="sticky z-10 bg-white p-4 border-t border-gray-200 bottom-[-1.5rem] inset-x-[-1.5rem]">
<div className="flex justify-end items-center gap-2">
<AntdButton onClick={onCancel}>

View file

@ -98,8 +98,15 @@ export default function KeyInfoView({ keyId, onClose, keyData, accessToken, user
const parsedMetadata = JSON.parse(formValues.metadata);
formValues.metadata = {
...parsedMetadata,
...(formValues.guardrails?.length > 0 ? { guardrails: formValues.guardrails } : {}),
...(formValues.logging_settings ? { logging: formValues.logging_settings } : {})
...(formValues.guardrails?.length > 0
? { guardrails: formValues.guardrails }
: {}),
...(formValues.logging_settings
? { logging: formValues.logging_settings }
: {}),
...(formValues.disabled_callbacks?.length > 0
? { litellm_disabled_callbacks: formValues.disabled_callbacks }
: {}),
};
} catch (error) {
console.error("Error parsing metadata JSON:", error);
@ -109,8 +116,15 @@ export default function KeyInfoView({ keyId, onClose, keyData, accessToken, user
} else {
formValues.metadata = {
...(formValues.metadata || {}),
...(formValues.guardrails?.length > 0 ? { guardrails: formValues.guardrails } : {}),
...(formValues.logging_settings ? { logging: formValues.logging_settings } : {})
...(formValues.guardrails?.length > 0
? { guardrails: formValues.guardrails }
: {}),
...(formValues.logging_settings
? { logging: formValues.logging_settings }
: {}),
...(formValues.disabled_callbacks?.length > 0
? { litellm_disabled_callbacks: formValues.disabled_callbacks }
: {}),
};
}
@ -321,7 +335,8 @@ export default function KeyInfoView({ keyId, onClose, keyData, accessToken, user
</Card>
<LoggingSettingsView
loggingConfigs={extractLoggingSettings(keyData.metadata)}
loggingConfigs={extractLoggingSettings(currentKeyData.metadata)}
disabledCallbacks={Array.isArray(currentKeyData.metadata?.litellm_disabled_callbacks) ? currentKeyData.metadata.litellm_disabled_callbacks : []}
variant="card"
/>
</Grid>
@ -442,7 +457,10 @@ export default function KeyInfoView({ keyId, onClose, keyData, accessToken, user
/>
<LoggingSettingsView
loggingConfigs={extractLoggingSettings(keyData.metadata)}
loggingConfigs={extractLoggingSettings(
currentKeyData.metadata,
)}
disabledCallbacks={Array.isArray(currentKeyData.metadata?.litellm_disabled_callbacks) ? currentKeyData.metadata.litellm_disabled_callbacks : []}
variant="inline"
className="pt-4 border-t border-gray-200"
/>

View file

@ -1,6 +1,6 @@
import React from "react";
import { Text, Badge } from "@tremor/react";
import { CogIcon } from "@heroicons/react/outline";
import { CogIcon, BanIcon } from "@heroicons/react/outline";
import { callbackInfo, callback_map } from "./callback_info_helpers";
interface LoggingConfig {
@ -11,12 +11,14 @@ interface LoggingConfig {
interface LoggingSettingsViewProps {
loggingConfigs?: LoggingConfig[];
disabledCallbacks?: string[];
variant?: "card" | "inline";
className?: string;
}
export function LoggingSettingsView({
loggingConfigs = [],
disabledCallbacks = [],
variant = "card",
className = ""
}: LoggingSettingsViewProps) {
@ -55,59 +57,117 @@ export function LoggingSettingsView({
};
const content = (
<div className="space-y-3">
<div className="flex items-center gap-2">
<CogIcon className="h-4 w-4 text-blue-600" />
<Text className="font-semibold text-gray-900">Logging Integrations</Text>
<Badge color="blue" size="xs">
{loggingConfigs.length}
</Badge>
</div>
{loggingConfigs.length > 0 ? (
<div className="space-y-3">
{loggingConfigs.map((config, index) => {
const displayName = getLoggingDisplayName(config.callback_name);
const logoUrl = callbackInfo[displayName]?.logo;
return (
<div
key={index}
className="flex items-center justify-between p-3 rounded-lg bg-blue-50 border border-blue-200"
>
<div className="flex items-center gap-3">
{logoUrl ? (
<img
src={logoUrl}
alt={displayName}
className="w-5 h-5 object-contain"
/>
) : (
<CogIcon className="h-5 w-5 text-gray-400" />
)}
<div>
<Text className="font-medium text-blue-800">{displayName}</Text>
<Text className="text-xs text-blue-600">
{Object.keys(config.callback_vars).length} parameters configured
</Text>
</div>
</div>
<Badge
color={getEventTypeColor(config.callback_type)}
size="sm"
<div className="space-y-6">
{/* Logging Integrations Section */}
<div className="space-y-3">
<div className="flex items-center gap-2">
<CogIcon className="h-4 w-4 text-blue-600" />
<Text className="font-semibold text-gray-900">Logging Integrations</Text>
<Badge color="blue" size="xs">
{loggingConfigs.length}
</Badge>
</div>
{loggingConfigs.length > 0 ? (
<div className="space-y-3">
{loggingConfigs.map((config, index) => {
const displayName = getLoggingDisplayName(config.callback_name);
const logoUrl = callbackInfo[displayName]?.logo;
return (
<div
key={index}
className="flex items-center justify-between p-3 rounded-lg bg-blue-50 border border-blue-200"
>
{getEventTypeLabel(config.callback_type)}
</Badge>
</div>
);
})}
<div className="flex items-center gap-3">
{logoUrl ? (
<img
src={logoUrl}
alt={displayName}
className="w-5 h-5 object-contain"
/>
) : (
<CogIcon className="h-5 w-5 text-gray-400" />
)}
<div>
<Text className="font-medium text-blue-800">{displayName}</Text>
<Text className="text-xs text-blue-600">
{Object.keys(config.callback_vars).length} parameters configured
</Text>
</div>
</div>
<Badge
color={getEventTypeColor(config.callback_type)}
size="sm"
>
{getEventTypeLabel(config.callback_type)}
</Badge>
</div>
);
})}
</div>
) : (
<div className="flex items-center gap-2 px-3 py-2 rounded-lg bg-gray-50 border border-gray-200">
<CogIcon className="h-4 w-4 text-gray-400" />
<Text className="text-gray-500 text-sm">No logging integrations configured</Text>
</div>
)}
</div>
{/* Disabled Callbacks Section */}
<div className="space-y-3">
<div className="flex items-center gap-2">
<BanIcon className="h-4 w-4 text-red-600" />
<Text className="font-semibold text-gray-900">Disabled Callbacks</Text>
<Badge color="red" size="xs">
{disabledCallbacks.length}
</Badge>
</div>
) : (
<div className="flex items-center gap-2 px-3 py-2 rounded-lg bg-gray-50 border border-gray-200">
<CogIcon className="h-4 w-4 text-gray-400" />
<Text className="text-gray-500 text-sm">No logging integrations configured</Text>
</div>
)}
{disabledCallbacks.length > 0 ? (
<div className="space-y-3">
{disabledCallbacks.map((callbackName, index) => {
const logoUrl = callbackInfo[callbackName]?.logo;
return (
<div
key={index}
className="flex items-center justify-between p-3 rounded-lg bg-red-50 border border-red-200"
>
<div className="flex items-center gap-3">
{logoUrl ? (
<img
src={logoUrl}
alt={callbackName}
className="w-5 h-5 object-contain"
/>
) : (
<BanIcon className="h-5 w-5 text-gray-400" />
)}
<div>
<Text className="font-medium text-red-800">{callbackName}</Text>
<Text className="text-xs text-red-600">
Disabled for this key
</Text>
</div>
</div>
<Badge
color="red"
size="sm"
>
Disabled
</Badge>
</div>
);
})}
</div>
) : (
<div className="flex items-center gap-2 px-3 py-2 rounded-lg bg-gray-50 border border-gray-200">
<BanIcon className="h-4 w-4 text-gray-400" />
<Text className="text-gray-500 text-sm">No callbacks disabled</Text>
</div>
)}
</div>
</div>
);
@ -118,7 +178,7 @@ export function LoggingSettingsView({
<div>
<Text className="font-semibold text-gray-900">Logging Settings</Text>
<Text className="text-xs text-gray-500">
Active logging integrations for this team
Active logging integrations and disabled callbacks for this key
</Text>
</div>
</div>

View file

@ -4,14 +4,28 @@ import LoggingSettings from './LoggingSettings';
interface EditLoggingSettingsProps {
value: any[];
onChange: (value: any[]) => void;
disabledCallbacks?: string[];
onDisabledCallbacksChange?: (disabledCallbacks: string[]) => void;
}
/**
* Wrapper component around LoggingSettings used for editing
* a team's logging integrations.
*/
const EditLoggingSettings: React.FC<EditLoggingSettingsProps> = ({ value, onChange }) => {
return <LoggingSettings value={value} onChange={onChange} />;
const EditLoggingSettings: React.FC<EditLoggingSettingsProps> = ({
value,
onChange,
disabledCallbacks = [],
onDisabledCallbacksChange
}) => {
return (
<LoggingSettings
value={value}
onChange={onChange}
disabledCallbacks={disabledCallbacks}
onDisabledCallbacksChange={onDisabledCallbacksChange}
/>
);
};
export default EditLoggingSettings;

View file

@ -4,7 +4,7 @@ import React, { useState } from 'react';
import { Form, Select, Space, Tooltip, Divider } from 'antd';
import { InfoCircleOutlined } from '@ant-design/icons';
import { Button, Card, TextInput } from '@tremor/react';
import { PlusIcon, TrashIcon, CogIcon } from '@heroicons/react/outline';
import { PlusIcon, TrashIcon, CogIcon, BanIcon } from '@heroicons/react/outline';
import { callbackInfo, Callbacks, callback_map } from '../callback_info_helpers';
const { Option } = Select;
@ -18,18 +18,32 @@ interface LoggingConfig {
interface LoggingSettingsProps {
value?: LoggingConfig[];
onChange?: (value: LoggingConfig[]) => void;
disabledCallbacks?: string[];
onDisabledCallbacksChange?: (disabledCallbacks: string[]) => void;
}
const LoggingSettings: React.FC<LoggingSettingsProps> = ({ value = [], onChange }) => {
const LoggingSettings: React.FC<LoggingSettingsProps> = ({
value = [],
onChange,
disabledCallbacks = [],
onDisabledCallbacksChange
}) => {
// Get callbacks that support team and key logging
const supportedCallbacks = Object.entries(callbackInfo)
.filter(([_, info]) => info.supports_key_team_logging)
.map(([name, _]) => name);
// Get all available callbacks for disabled selection
const allCallbacks = Object.keys(callbackInfo);
const handleChange = (newValue: LoggingConfig[]) => {
onChange?.(newValue);
};
const handleDisabledCallbacksChange = (newDisabledCallbacks: string[]) => {
onDisabledCallbacksChange?.(newDisabledCallbacks);
};
const addLoggingConfig = () => {
const newConfig: LoggingConfig = {
callback_name: '',
@ -126,6 +140,53 @@ const LoggingSettings: React.FC<LoggingSettingsProps> = ({ value = [], onChange
return (
<div className="space-y-6">
{/* Disabled Callbacks Section */}
<div className="space-y-4">
<div className="flex items-center space-x-2">
<BanIcon className="w-5 h-5 text-red-500" />
<span className="text-base font-semibold text-gray-800">Disabled Callbacks</span>
<Tooltip title="Select callbacks to disable for this key. Disabled callbacks will not receive any logging data.">
<InfoCircleOutlined className="text-gray-400 cursor-help" />
</Tooltip>
</div>
<div className="space-y-2">
<label className="text-sm font-medium text-gray-700">Disabled Callbacks</label>
<Select
mode="multiple"
placeholder="Select callbacks to disable"
value={disabledCallbacks}
onChange={handleDisabledCallbacksChange}
style={{ width: '100%' }}
optionLabelProp="label"
>
{allCallbacks.map((callbackName) => {
const logo = callbackInfo[callbackName]?.logo;
return (
<Option key={callbackName} value={callbackName} label={callbackName}>
<div className="flex items-center space-x-2">
{logo && (
<img
src={logo}
alt={callbackName}
className="w-4 h-4 object-contain"
/>
)}
<span>{callbackName}</span>
</div>
</Option>
);
})}
</Select>
<div className="text-xs text-gray-500">
Select callbacks that should be disabled for this key. These callbacks will not receive any logging data.
</div>
</div>
</div>
<Divider />
{/* Logging Integrations Section */}
<div className="flex justify-between items-center">
<div className="flex items-center space-x-2">
<CogIcon className="w-5 h-5 text-blue-500" />

View file

@ -502,6 +502,7 @@ const TeamInfoView: React.FC<TeamInfoProps> = ({
<LoggingSettingsView
loggingConfigs={info.metadata?.logging || []}
disabledCallbacks={[]}
variant="card"
/>
</Grid>
@ -803,6 +804,7 @@ const TeamInfoView: React.FC<TeamInfoProps> = ({
<LoggingSettingsView
loggingConfigs={info.metadata?.logging || []}
disabledCallbacks={[]}
variant="inline"
className="pt-4 border-t border-gray-200"
/>