@@ -110,6 +163,15 @@ export function UserDataTable({
? 'sticky right-0 bg-white shadow-[-4px_0_8px_-6px_rgba(0,0,0,0.1)]'
: ''
}`}
+ onClick={() => {
+ if (cell.column.id === 'user_id') {
+ handleUserClick(cell.getValue() as string);
+ }
+ }}
+ style={{
+ cursor: cell.column.id === 'user_id' ? 'pointer' : 'default',
+ color: cell.column.id === 'user_id' ? '#3b82f6' : 'inherit',
+ }}
>
{flexRender(cell.column.columnDef.cell, cell.getContext())}
diff --git a/ui/litellm-dashboard/src/components/view_users/user_info_view.tsx b/ui/litellm-dashboard/src/components/view_users/user_info_view.tsx
new file mode 100644
index 0000000000..4e82f20821
--- /dev/null
+++ b/ui/litellm-dashboard/src/components/view_users/user_info_view.tsx
@@ -0,0 +1,297 @@
+import React, { useState } from "react";
+import {
+ Card,
+ Text,
+ Button,
+ Grid,
+ Col,
+ Tab,
+ TabList,
+ TabGroup,
+ TabPanel,
+ TabPanels,
+ Title,
+ Badge,
+} from "@tremor/react";
+import { ArrowLeftIcon, TrashIcon } from "@heroicons/react/outline";
+import { userInfoCall, userDeleteCall } from "../networking";
+import { message } from "antd";
+import { rolesWithWriteAccess } from '../../utils/roles';
+
+interface UserInfoViewProps {
+ userId: string;
+ onClose: () => void;
+ accessToken: string | null;
+ userRole: string | null;
+ onDelete?: () => void;
+}
+
+interface UserInfo {
+ user_id: string;
+ user_info: {
+ user_email: string | null;
+ user_role: string | null;
+ teams: any[] | null;
+ models: string[] | null;
+ max_budget: number | null;
+ spend: number | null;
+ metadata: Record
| null;
+ created_at: string | null;
+ updated_at: string | null;
+ };
+ keys: any[] | null;
+ teams: any[] | null;
+}
+
+export default function UserInfoView({ userId, onClose, accessToken, userRole, onDelete }: UserInfoViewProps) {
+ const [userData, setUserData] = useState(null);
+ const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
+ const [isLoading, setIsLoading] = useState(true);
+
+ React.useEffect(() => {
+ console.log(`userId: ${userId}, userRole: ${userRole}, accessToken: ${accessToken}`)
+ const fetchUserData = async () => {
+ try {
+ if (!accessToken) return;
+ const data = await userInfoCall(accessToken, userId, userRole || "", false, null, null, true);
+ setUserData(data);
+ } catch (error) {
+ console.error("Error fetching user data:", error);
+ message.error("Failed to fetch user data");
+ } finally {
+ setIsLoading(false);
+ }
+ };
+
+ fetchUserData();
+ }, [accessToken, userId, userRole]);
+
+ const handleDelete = async () => {
+ try {
+ if (!accessToken) return;
+ await userDeleteCall(accessToken, [userId]);
+ message.success("User deleted successfully");
+ if (onDelete) {
+ onDelete();
+ }
+ onClose();
+ } catch (error) {
+ console.error("Error deleting user:", error);
+ message.error("Failed to delete user");
+ }
+ };
+
+ if (isLoading) {
+ return (
+
+
+ Loading user data...
+
+ );
+ }
+
+ if (!userData) {
+ return (
+
+
+ User not found
+
+ );
+ }
+
+ return (
+
+
+
+
+
{userData.user_info?.user_email || "User"}
+ {userData.user_id}
+
+ {userRole && rolesWithWriteAccess.includes(userRole) && (
+
+ )}
+
+
+ {/* Delete Confirmation Modal */}
+ {isDeleteModalOpen && (
+
+
+
+
+
+
+
+
+
+
+
+ Delete User
+
+
+
+ Are you sure you want to delete this user?
+
+
+
+
+
+
+
+
+
+
+
+
+ )}
+
+
+
+ Overview
+ Details
+
+
+
+ {/* Overview Panel */}
+
+
+
+ Spend
+
+
${Number(userData.user_info?.spend || 0).toFixed(4)}
+ of {userData.user_info?.max_budget !== null ? `$${userData.user_info.max_budget}` : "Unlimited"}
+
+
+
+
+ Teams
+
+ {userData.teams?.length || 0} teams
+
+
+
+
+ API Keys
+
+ {userData.keys?.length || 0} keys
+
+
+
+
+
+ {/* Details Panel */}
+
+
+
+
+ User ID
+ {userData.user_id}
+
+
+
+ Email
+ {userData.user_info?.user_email || "Not Set"}
+
+
+
+ Role
+ {userData.user_info?.user_role || "Not Set"}
+
+
+
+ Created
+ {userData.user_info?.created_at ? new Date(userData.user_info.created_at).toLocaleString() : "Unknown"}
+
+
+
+ Last Updated
+ {userData.user_info?.updated_at ? new Date(userData.user_info.updated_at).toLocaleString() : "Unknown"}
+
+
+
+
Teams
+
+ {userData.teams?.length && userData.teams?.length > 0 ? (
+ userData.teams?.map((team, index) => (
+
+ {team.team_alias || team.team_id}
+
+ ))
+ ) : (
+ No teams
+ )}
+
+
+
+
+
API Keys
+
+ {userData.keys?.length && userData.keys?.length > 0 ? (
+ userData.keys.map((key, index) => (
+
+ {key.key_alias || key.token}
+
+ ))
+ ) : (
+ No API keys
+ )}
+
+
+
+
+
Metadata
+
+ {JSON.stringify(userData.user_info?.metadata || {}, null, 2)}
+
+
+
+
+
+
+
+
+ );
+}
\ No newline at end of file