diff --git a/ui/litellm-dashboard/src/components/all_keys_table.tsx b/ui/litellm-dashboard/src/components/all_keys_table.tsx index 4ed2886745a..66644da6d3c 100644 --- a/ui/litellm-dashboard/src/components/all_keys_table.tsx +++ b/ui/litellm-dashboard/src/components/all_keys_table.tsx @@ -186,6 +186,18 @@ export function AllKeysTable({ accessorKey: "organization_id", cell: (info) => info.getValue() ? info.renderValue() : "Not Set", }, + { + header: "User ID", + accessorKey: "user_id", + cell: (info) => { + const userId = info.getValue() as string; + return userId ? ( + + {userId.slice(0, 5)}... + + ) : "Not Set"; + }, + }, { header: "Created", accessorKey: "created_at", diff --git a/ui/litellm-dashboard/src/components/common_components/user_form.tsx b/ui/litellm-dashboard/src/components/common_components/user_form.tsx new file mode 100644 index 00000000000..2b0a3564575 --- /dev/null +++ b/ui/litellm-dashboard/src/components/common_components/user_form.tsx @@ -0,0 +1,74 @@ +import React from "react"; +import { Form, Select, TextInput } from "@tremor/react"; +import { Form as AntForm, Radio } from "antd"; +import TeamDropdown from "./team_dropdown"; +import { getPossibleUserRoles } from "../networking"; + +interface UserFormProps { + form: any; + teams: any[] | null; + possibleUIRoles: null | Record>; + setPossibleUIRoles?: (roles: any) => void; + accessToken?: string; +} + +const UserForm: React.FC = ({ + form, + teams, + possibleUIRoles, + setPossibleUIRoles, + accessToken +}) => { + React.useEffect(() => { + // Fetch roles if they're not available and we have a setter + if (!possibleUIRoles && setPossibleUIRoles && accessToken) { + getPossibleUserRoles(accessToken).then(roles => { + setPossibleUIRoles(roles); + }); + } + }, [possibleUIRoles, setPossibleUIRoles, accessToken]); + + return ( + <> + + + + + + + + + + + + + + + + + ); +}; + +export default UserForm; \ No newline at end of file diff --git a/ui/litellm-dashboard/src/components/create_key_button.tsx b/ui/litellm-dashboard/src/components/create_key_button.tsx index 47ceebbf941..46cb3ef01eb 100644 --- a/ui/litellm-dashboard/src/components/create_key_button.tsx +++ b/ui/litellm-dashboard/src/components/create_key_button.tsx @@ -1,5 +1,5 @@ "use client"; -import React, { useState, useEffect, useRef } from "react"; +import React, { useState, useEffect, useRef, useCallback } from "react"; import { Button, TextInput, Grid, Col } from "@tremor/react"; import { Card, @@ -30,11 +30,15 @@ import { modelAvailableCall, getGuardrailsList, proxyBaseUrl, + getPossibleUserRoles, + userFilterUICall, } from "./networking"; import { Team } from "./key_team_helpers/key_list"; import TeamDropdown from "./common_components/team_dropdown"; import { InfoCircleOutlined } from '@ant-design/icons'; import { Tooltip } from 'antd'; +import Createuser from "./create_user_button"; +import debounce from 'lodash/debounce'; const { Option } = Select; @@ -48,6 +52,18 @@ interface CreateKeyProps { teams: Team[] | null; } +interface User { + user_id: string; + user_email: string; + role?: string; +} + +interface UserOption { + label: string; + value: string; + user: User; +} + const getPredefinedTags = (data: any[] | null) => { let allTags = []; @@ -138,6 +154,13 @@ const CreateKey: React.FC = ({ const [predefinedTags, setPredefinedTags] = useState(getPredefinedTags(data)); const [guardrailsList, setGuardrailsList] = useState([]); const [selectedCreateKeyTeam, setSelectedCreateKeyTeam] = useState(team); + const [isCreateUserModalVisible, setIsCreateUserModalVisible] = useState(false); + const [newlyCreatedUserId, setNewlyCreatedUserId] = useState(null); + const [possibleUIRoles, setPossibleUIRoles] = useState< + Record> + >({}); + const [userOptions, setUserOptions] = useState([]); + const [userSearchLoading, setUserSearchLoading] = useState(false); const handleOk = () => { setIsModalVisible(false); @@ -172,6 +195,29 @@ const CreateKey: React.FC = ({ fetchGuardrails(); }, [accessToken]); + // Fetch possible user roles when component mounts + useEffect(() => { + const fetchPossibleRoles = async () => { + try { + if (accessToken) { + // Check if roles are cached in session storage + const cachedRoles = sessionStorage.getItem('possibleUserRoles'); + if (cachedRoles) { + setPossibleUIRoles(JSON.parse(cachedRoles)); + } else { + const availableUserRoles = await getPossibleUserRoles(accessToken); + sessionStorage.setItem('possibleUserRoles', JSON.stringify(availableUserRoles)); + setPossibleUIRoles(availableUserRoles); + } + } + } catch (error) { + console.error("Error fetching possible user roles:", error); + } + }; + + fetchPossibleRoles(); + }, [accessToken]); + const handleCreate = async (formValues: Record) => { try { const newKeyAlias = formValues?.key_alias ?? ""; @@ -233,6 +279,60 @@ const CreateKey: React.FC = ({ form.setFieldValue('models', []); }, [selectedCreateKeyTeam, userModels]); + // Add a callback function to handle user creation + const handleUserCreated = (userId: string) => { + setNewlyCreatedUserId(userId); + form.setFieldsValue({ user_id: userId }); + setIsCreateUserModalVisible(false); + }; + + const fetchUsers = async (searchText: string): Promise => { + if (!searchText) { + setUserOptions([]); + return; + } + + setUserSearchLoading(true); + try { + const params = new URLSearchParams(); + params.append('user_email', searchText); // Always search by email + if (accessToken == null) { + return; + } + const response = await userFilterUICall(accessToken, params); + + const data: User[] = response; + const options: UserOption[] = data.map(user => ({ + label: `${user.user_email} (${user.user_id})`, + value: user.user_id, + user + })); + + setUserOptions(options); + } catch (error) { + console.error('Error fetching users:', error); + message.error('Failed to search for users'); + } finally { + setUserSearchLoading(false); + } + }; + + const debouncedSearch = useCallback( + debounce((text: string) => fetchUsers(text), 300), + [accessToken] + ); + + const handleUserSearch = (value: string): void => { + debouncedSearch(value); + }; + + const handleUserSelect = (_value: string, option: UserOption): void => { + const selectedUser = option.user; + form.setFieldsValue({ + user_id: selectedUser.user_id + }); + }; + return (
+
+ + ); + } + + // Original return for standalone mode return (
setIsModalVisible(true)}>