mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-10 03:28:53 +00:00
[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:
parent
ee066481f8
commit
b443817a56
11 changed files with 538 additions and 68 deletions
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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]: {
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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}>
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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" />
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
/>
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue