From 7a32efd02884ae30a558fc76459ebd38d15405d3 Mon Sep 17 00:00:00 2001 From: tahaali-dev Date: Wed, 29 Jan 2025 19:47:30 +0530 Subject: [PATCH 1/6] [Bug] UI: Newly created key does not display on the View Key Page (#8039) - Fixed issue where all keys appeared blank for admin users. - Implemented filtering of data via team settings to ensure all keys are displayed correctly. --- .../src/components/view_key_table.tsx | 1771 +++++++++-------- 1 file changed, 973 insertions(+), 798 deletions(-) diff --git a/ui/litellm-dashboard/src/components/view_key_table.tsx b/ui/litellm-dashboard/src/components/view_key_table.tsx index 9d7ad00a98f9..2aef9116460a 100644 --- a/ui/litellm-dashboard/src/components/view_key_table.tsx +++ b/ui/litellm-dashboard/src/components/view_key_table.tsx @@ -1,9 +1,25 @@ "use client"; import React, { useEffect, useState } from "react"; -import { keyDeleteCall, modelAvailableCall, getGuardrailsList } from "./networking"; -import { add } from 'date-fns'; -import { InformationCircleIcon, StatusOnlineIcon, TrashIcon, PencilAltIcon, RefreshIcon } from "@heroicons/react/outline"; -import { keySpendLogsCall, PredictedSpendLogsCall, keyUpdateCall, modelInfoCall, regenerateKeyCall } from "./networking"; +import { + keyDeleteCall, + modelAvailableCall, + getGuardrailsList, +} from "./networking"; +import { add } from "date-fns"; +import { + InformationCircleIcon, + StatusOnlineIcon, + TrashIcon, + PencilAltIcon, + RefreshIcon, +} from "@heroicons/react/outline"; +import { + keySpendLogsCall, + PredictedSpendLogsCall, + keyUpdateCall, + modelInfoCall, + regenerateKeyCall, +} from "./networking"; import { Badge, Card, @@ -16,7 +32,7 @@ import { TableHead, TableHeaderCell, TableRow, - Dialog, + Dialog, DialogPanel, Text, Title, @@ -26,9 +42,17 @@ import { TextInput, Textarea, } from "@tremor/react"; -import { InfoCircleOutlined } from '@ant-design/icons'; -import { fetchAvailableModelsForTeamOrKey, getModelDisplayName } from "./key_team_helpers/fetch_available_models_team_key"; -import { Select as Select3, SelectItem, MultiSelect, MultiSelectItem } from "@tremor/react"; +import { InfoCircleOutlined } from "@ant-design/icons"; +import { + fetchAvailableModelsForTeamOrKey, + getModelDisplayName, +} from "./key_team_helpers/fetch_available_models_team_key"; +import { + Select as Select3, + SelectItem, + MultiSelect, + MultiSelectItem, +} from "@tremor/react"; import { Button as Button2, Modal, @@ -49,7 +73,7 @@ const { Option } = Select; const isLocal = process.env.NODE_ENV === "development"; const proxyBaseUrl = isLocal ? "http://localhost:4000" : null; if (isLocal != true) { - console.log = function() {}; + console.log = function () {}; } interface EditKeyModalProps { @@ -59,7 +83,6 @@ interface EditKeyModalProps { onSubmit: (data: FormData) => void; // Assuming FormData is the type of data to be submitted } - interface ModelLimitModalProps { visible: boolean; onCancel: () => void; @@ -108,15 +131,15 @@ const ViewKeyTable: React.FC = ({ data, setData, teams, - premiumUser + premiumUser, }) => { const [isButtonClicked, setIsButtonClicked] = useState(false); const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false); const [keyToDelete, setKeyToDelete] = useState(null); const [selectedItem, setSelectedItem] = useState(null); - const [spendData, setSpendData] = useState<{ day: string; spend: number }[] | null>( - null - ); + const [spendData, setSpendData] = useState< + { day: string; spend: number }[] | null + >(null); const [predictedSpendString, setPredictedSpendString] = useState(""); const [editModalVisible, setEditModalVisible] = useState(false); @@ -151,36 +174,44 @@ const ViewKeyTable: React.FC = ({ return data; } - teams.forEach(team => { + teams.forEach((team) => { // For default team or when user is not admin, use personal keys (data) if (team.team_id === "default-team" || !isUserTeamAdmin(team)) { if (selectedTeam && selectedTeam.team_id === team.team_id && data) { - allKeys = [...allKeys, ...data.filter(key => key.team_id === team.team_id)]; + allKeys = [ + ...allKeys, + ...data.filter((key) => key.team_id === team.team_id), + ]; } } // For teams where user is admin, use team keys else if (isUserTeamAdmin(team)) { if (selectedTeam && selectedTeam.team_id === team.team_id) { - allKeys = [...allKeys, ...(team.keys || [])]; + allKeys = [ + ...allKeys, + ...(data?.filter((key) => key?.team_id === team?.team_id) || []), + ]; } } }); // If no team is selected, show all accessible keys if ((!selectedTeam || selectedTeam.team_alias === "Default Team") && data) { - const personalKeys = data.filter(key => !key.team_id || key.team_id === "default-team"); + const personalKeys = data.filter( + (key) => !key.team_id || key.team_id === "default-team" + ); const adminTeamKeys = teams - .filter(team => isUserTeamAdmin(team)) - .flatMap(team => team.keys || []); + .filter((team) => isUserTeamAdmin(team)) + .flatMap((team) => team.keys || []); allKeys = [...personalKeys, ...adminTeamKeys]; } // Filter out litellm-dashboard keys - allKeys = allKeys.filter(key => key.team_id !== "litellm-dashboard"); + allKeys = allKeys.filter((key) => key.team_id !== "litellm-dashboard"); // Remove duplicates based on token const uniqueKeys = Array.from( - new Map(allKeys.map(key => [key.token, key])).values() + new Map(allKeys.map((key) => [key.token, key])).values() ); return uniqueKeys; @@ -191,29 +222,29 @@ const ViewKeyTable: React.FC = ({ if (!duration) { return null; } - + try { const now = new Date(); let newExpiry: Date; - - if (duration.endsWith('s')) { + + if (duration.endsWith("s")) { newExpiry = add(now, { seconds: parseInt(duration) }); - } else if (duration.endsWith('h')) { + } else if (duration.endsWith("h")) { newExpiry = add(now, { hours: parseInt(duration) }); - } else if (duration.endsWith('d')) { + } else if (duration.endsWith("d")) { newExpiry = add(now, { days: parseInt(duration) }); } else { - throw new Error('Invalid duration format'); + throw new Error("Invalid duration format"); } - - return newExpiry.toLocaleString('en-US', { - year: 'numeric', - month: 'numeric', - day: 'numeric', - hour: 'numeric', - minute: 'numeric', - second: 'numeric', - hour12: true + + return newExpiry.toLocaleString("en-US", { + year: "numeric", + month: "numeric", + day: "numeric", + hour: "numeric", + minute: "numeric", + second: "numeric", + hour12: true, }); } catch (error) { return null; @@ -221,8 +252,7 @@ const ViewKeyTable: React.FC = ({ }; console.log("in calculateNewExpiryTime for selectedToken", selectedToken); - - + // When a new duration is entered if (regenerateFormData?.duration) { setNewExpiryTime(calculateNewExpiryTime(regenerateFormData.duration)); @@ -232,9 +262,6 @@ const ViewKeyTable: React.FC = ({ console.log("calculateNewExpiryTime:", newExpiryTime); }, [selectedToken, regenerateFormData?.duration]); - - - useEffect(() => { const fetchUserModels = async () => { @@ -243,7 +270,11 @@ const ViewKeyTable: React.FC = ({ return; } - const models = await fetchAvailableModelsForTeamOrKey(userID, userRole, accessToken); + const models = await fetchAvailableModelsForTeamOrKey( + userID, + userRole, + accessToken + ); if (models) { setUserModels(models); } @@ -251,11 +282,10 @@ const ViewKeyTable: React.FC = ({ console.error("Error fetching user models:", error); } }; - + fetchUserModels(); }, [accessToken, userID, userRole]); - const handleModelLimitClick = (token: ItemData) => { setSelectedToken(token); setModelLimitModalVisible(true); @@ -293,19 +323,22 @@ const ViewKeyTable: React.FC = ({ setSelectedToken(null); }; - - useEffect(() => { if (teams) { const teamIDSet: Set = new Set(); teams.forEach((team: any, index: number) => { - const team_obj: string = team.team_id + const team_obj: string = team.team_id; teamIDSet.add(team_obj); }); - setKnownTeamIDs(teamIDSet) + setKnownTeamIDs(teamIDSet); } - }, [teams]) - const EditKeyModal: React.FC = ({ visible, onCancel, token, onSubmit }) => { + }, [teams]); + const EditKeyModal: React.FC = ({ + visible, + onCancel, + token, + onSubmit, + }) => { const [form] = Form.useForm(); const [keyTeam, setKeyTeam] = useState(selectedTeam); const [errorModels, setErrorModels] = useState([]); @@ -328,7 +361,7 @@ const ViewKeyTable: React.FC = ({ fetchGuardrails(); }, [accessToken]); - let metadataString = ''; + let metadataString = ""; try { // Create a copy of metadata without guardrails for display const displayMetadata = { ...token.metadata }; @@ -336,7 +369,7 @@ const ViewKeyTable: React.FC = ({ metadataString = JSON.stringify(displayMetadata, null, 2); } catch (error) { console.error("Error stringifying metadata:", error); - metadataString = ''; + metadataString = ""; } // Extract existing guardrails from metadata @@ -347,13 +380,15 @@ const ViewKeyTable: React.FC = ({ console.error("Error extracting guardrails:", error); } - const initialValues = token ? { - ...token, - budget_duration: token.budget_duration, - metadata: metadataString, - guardrails: existingGuardrails - } : { metadata: metadataString, guardrails: [] }; - + const initialValues = + token ? + { + ...token, + budget_duration: token.budget_duration, + metadata: metadataString, + guardrails: existingGuardrails, + } + : { metadata: metadataString, guardrails: [] }; const handleOk = () => { form @@ -366,17 +401,17 @@ const ViewKeyTable: React.FC = ({ .catch((error) => { console.error("Validation failed:", error); }); - }; + }; return ( - +
= ({ wrapperCol={{ span: 16 }} labelAlign="left" > - <> - - - - - - + + + + + { - const errorModels = value.filter((model: string) => ( - !keyTeam.models.includes(model) && - model !== "all-team-models" && - model !== "all-proxy-models" && - !keyTeam.models.includes("all-proxy-models") - )); - console.log(`errorModels: ${errorModels}`) + const errorModels = value.filter( + (model: string) => + !keyTeam.models.includes(model) && + model !== "all-team-models" && + model !== "all-proxy-models" && + !keyTeam.models.includes("all-proxy-models") + ); + console.log(`errorModels: ${errorModels}`); if (errorModels.length > 0) { - return Promise.reject(`Some models are not part of the new team\'s models - ${errorModels}Team models: ${keyTeam.models}`); + return Promise.reject( + `Some models are not part of the new team\'s models - ${errorModels}Team models: ${keyTeam.models}` + ); } else { return Promise.resolve(); } - } - } - ]}> - + + {keyTeam && keyTeam.models ? + keyTeam.models.includes("all-proxy-models") ? + userModels + .filter((model) => model !== "all-proxy-models") + .map((model: string) => ( )) - ) - ) : ( - userModels.map((model: string) => ( + : keyTeam.models.map((model: string) => ( )) - )} - - - { - if (value && keyTeam && keyTeam.max_budget !== null && value > keyTeam.max_budget) { - console.log(`keyTeam.max_budget: ${keyTeam.max_budget}`) - throw new Error(`Budget cannot exceed team max budget: $${keyTeam.max_budget}`); - } - }, + + : userModels.map((model: string) => ( + + )) + } + + + { + if ( + value && + keyTeam && + keyTeam.max_budget !== null && + value > keyTeam.max_budget + ) { + console.log(`keyTeam.max_budget: ${keyTeam.max_budget}`); + throw new Error( + `Budget cannot exceed team max budget: $${keyTeam.max_budget}` + ); + } }, + }, ]} - > - - - - - - - - - - + > + + + + + + + + + + {teams?.map((team_obj, index) => ( - setKeyTeam(team_obj)} - > - {team_obj.team_alias} - - ))} + setKeyTeam(team_obj)} + > + {team_obj.team_alias} + + ))} - - - { - if (value && keyTeam && keyTeam.tpm_limit !== null && value > keyTeam.tpm_limit) { - console.log(`keyTeam.tpm_limit: ${keyTeam.tpm_limit}`) - throw new Error(`tpm_limit cannot exceed team max tpm_limit: $${keyTeam.tpm_limit}`); - } - }, + + + { + if ( + value && + keyTeam && + keyTeam.tpm_limit !== null && + value > keyTeam.tpm_limit + ) { + console.log(`keyTeam.tpm_limit: ${keyTeam.tpm_limit}`); + throw new Error( + `tpm_limit cannot exceed team max tpm_limit: $${keyTeam.tpm_limit}` + ); + } }, + }, ]} - > - - - { - if (value && keyTeam && keyTeam.rpm_limit !== null && value > keyTeam.rpm_limit) { - console.log(`keyTeam.rpm_limit: ${keyTeam.rpm_limit}`) - throw new Error(`rpm_limit cannot exceed team max rpm_limit: $${keyTeam.rpm_limit}`); - } - }, + > + + + { + if ( + value && + keyTeam && + keyTeam.rpm_limit !== null && + value > keyTeam.rpm_limit + ) { + console.log(`keyTeam.rpm_limit: ${keyTeam.rpm_limit}`); + throw new Error( + `rpm_limit cannot exceed team max rpm_limit: $${keyTeam.rpm_limit}` + ); + } }, + }, ]} - > - - - - Guardrails{' '} - - e.stopPropagation()} - > - - - - - } - name="guardrails" - className="mt-8" - help="Select existing guardrails or enter new ones" - > -