diff --git a/ui/litellm-dashboard/eslint-suppressions.json b/ui/litellm-dashboard/eslint-suppressions.json index 5d9d6075335..6bfbf397628 100644 --- a/ui/litellm-dashboard/eslint-suppressions.json +++ b/ui/litellm-dashboard/eslint-suppressions.json @@ -1878,9 +1878,6 @@ "local/filename-pascal-case": { "count": 1 }, - "no-restricted-imports": { - "count": 3 - }, "prefer-const": { "count": 1 }, diff --git a/ui/litellm-dashboard/src/app/(dashboard)/users/_components/view_users.test.tsx b/ui/litellm-dashboard/src/app/(dashboard)/users/_components/view_users.test.tsx index 42f21cd7b69..2632f40adbe 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/users/_components/view_users.test.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/users/_components/view_users.test.tsx @@ -37,6 +37,20 @@ vi.mock("./view_users/user_info_view", () => ({ }, })); +vi.mock("./default-user-settings/DefaultUserSettingsForm", () => ({ + DefaultUserSettingsForm: function DefaultUserSettingsFormMock() { + const [value, setValue] = React.useState(""); + return ( +
+ +
+ ); + }, +})); + // Mock NotificationsManager vi.mock("@/components/molecules/notifications_manager", () => ({ default: { @@ -78,10 +92,10 @@ const defaultProps = { teams: [], }; -const renderDashboard = () => +const renderDashboard = (overrides: Partial = {}) => renderWithProviders( - + , ); @@ -107,6 +121,51 @@ describe("ViewUserDashboard", () => { expect(screen.getAllByText("Default User Settings").length).toBeGreaterThan(0); }); + it("switches between the users table and default settings tabs for proxy admins", async () => { + const user = userEvent.setup(); + renderDashboard(); + + expect(await screen.findByText("test@example.com")).toBeInTheDocument(); + + const usersTab = screen.getByRole("tab", { name: "Users" }); + const settingsTab = screen.getByRole("tab", { name: "Default User Settings" }); + expect(usersTab).toHaveAttribute("aria-selected", "true"); + + await user.click(settingsTab); + + expect(settingsTab).toHaveAttribute("aria-selected", "true"); + expect(usersTab).toHaveAttribute("aria-selected", "false"); + expect(screen.getByRole("region", { name: "Default user settings panel" })).toBeInTheDocument(); + await user.type(screen.getByRole("textbox", { name: "Default setting" }), "unsaved change"); + + await user.click(usersTab); + + expect(usersTab).toHaveAttribute("aria-selected", "true"); + expect(settingsTab).toHaveAttribute("aria-selected", "false"); + + await user.click(settingsTab); + + expect(screen.getByRole("textbox", { name: "Default setting" })).toHaveValue("unsaved change"); + }); + + it("shows the users table without admin controls for non-proxy admins", async () => { + renderDashboard({ userRole: "Internal User" }); + + expect(await screen.findByText("test@example.com")).toBeInTheDocument(); + expect(screen.queryByRole("tab")).not.toBeInTheDocument(); + expect(screen.queryByTestId("toggle-user-selection")).not.toBeInTheDocument(); + }); + + it("keeps actions unavailable while the user list is loading", () => { + userListCall.mockReturnValue(new Promise(() => undefined)); + + renderDashboard(); + + expect(screen.getByText("Loading users…")).toBeInTheDocument(); + expect(screen.queryByTestId("toggle-user-selection")).not.toBeInTheDocument(); + expect(screen.queryByTestId("bulk-edit-users")).not.toBeInTheDocument(); + }); + it("should show delete modal after choosing delete from the row actions menu", async () => { const user = userEvent.setup(); renderDashboard(); diff --git a/ui/litellm-dashboard/src/app/(dashboard)/users/_components/view_users.tsx b/ui/litellm-dashboard/src/app/(dashboard)/users/_components/view_users.tsx index 9eb7645fb2e..1de01e88866 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/users/_components/view_users.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/users/_components/view_users.tsx @@ -1,10 +1,11 @@ -import { Tab, TabGroup, TabList, TabPanel, TabPanels } from "@tremor/react"; import { parseAsString, useQueryState } from "nuqs"; import React, { useCallback, useEffect, useMemo, useState } from "react"; -import { Button } from "antd"; import BulkEditUserModal from "./BulkEditUsers"; import { CreateUserButton } from "@/components/CreateUserButton"; +import { Button } from "@/components/ui/button"; +import { Skeleton } from "@/components/ui/skeleton"; +import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import EditUserModal from "./edit_user"; import { getPossibleUserRoles, @@ -35,7 +36,6 @@ import { DefaultUserSettingsForm } from "./default-user-settings/DefaultUserSett import { UsersTable } from "./view_users/UsersTable"; import UserInfoView from "./view_users/user_info_view"; import { UserInfo } from "@/components/networking"; -import { Skeleton } from "antd"; interface ViewUserDashboardProps { accessToken: string | null; @@ -352,14 +352,14 @@ const ViewUserDashboard: React.FC = ({ ); return ( -
-
+
+
{userListQuery.isLoading && ( <> - - - + + + )} {!userListQuery.isLoading && userID && accessToken && ( @@ -375,9 +375,9 @@ const ViewUserDashboard: React.FC = ({ {isProxyAdmin && (
{isProxyAdmin ? ( - - - Users - Default User Settings - + + + + Users + + + Default User Settings + + - - {usersTable} + + {usersTable} + - - {!userID || !userRole || !accessToken ? ( -
- + + {!userID || !userRole || !accessToken ? ( +
+
+ + + +
- ) : ( - - )} - - - +
+ ) : ( + + )} +
+ ) : ( usersTable )}