diff --git a/ui/litellm-dashboard/src/app/(dashboard)/hooks/useHideAgentPlatformBanner.ts b/ui/litellm-dashboard/src/app/(dashboard)/hooks/useHideAgentPlatformBanner.ts deleted file mode 100644 index 15b44a5abc64..000000000000 --- a/ui/litellm-dashboard/src/app/(dashboard)/hooks/useHideAgentPlatformBanner.ts +++ /dev/null @@ -1,36 +0,0 @@ -// hooks/useHideAgentPlatformBanner.ts -import { useSyncExternalStore } from "react"; -import { getLocalStorageItem, LOCAL_STORAGE_EVENT } from "@/utils/localStorageUtils"; - -export const HIDE_AGENT_PLATFORM_BANNER_KEY = "litellmHideAgentPlatformBanner"; - -function subscribe(callback: () => void) { - const onStorage = (e: StorageEvent) => { - if (e.key === HIDE_AGENT_PLATFORM_BANNER_KEY) { - callback(); - } - }; - - const onCustom = (e: Event) => { - const { key } = (e as CustomEvent).detail; - if (key === HIDE_AGENT_PLATFORM_BANNER_KEY) { - callback(); - } - }; - - window.addEventListener("storage", onStorage); - window.addEventListener(LOCAL_STORAGE_EVENT, onCustom); - - return () => { - window.removeEventListener("storage", onStorage); - window.removeEventListener(LOCAL_STORAGE_EVENT, onCustom); - }; -} - -function getSnapshot() { - return getLocalStorageItem(HIDE_AGENT_PLATFORM_BANNER_KEY) === "true"; -} - -export function useHideAgentPlatformBanner() { - return useSyncExternalStore(subscribe, getSnapshot); -} diff --git a/ui/litellm-dashboard/src/app/(dashboard)/layout.tsx b/ui/litellm-dashboard/src/app/(dashboard)/layout.tsx index 5bb55ee8d106..619d7d73506b 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/layout.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/layout.tsx @@ -35,7 +35,7 @@ const MIGRATED_PAGES: Record = {}; function LayoutContent({ children }: { children: React.ReactNode }) { const router = useRouter(); const searchParams = useSearchParams(); - const { accessToken } = useAuthorized(); + const { accessToken, userRole, userId, userEmail, premiumUser } = useAuthorized(); const [sidebarCollapsed, setSidebarCollapsed] = React.useState(false); const [page, setPage] = useState(() => { return searchParams.get("page") || "api-keys"; @@ -68,9 +68,15 @@ function LayoutContent({ children }: { children: React.ReactNode }) { isPublicPage={false} sidebarCollapsed={sidebarCollapsed} onToggleSidebar={toggleSidebar} + userID={userId} + userEmail={userEmail} + userRole={userRole} + premiumUser={premiumUser} proxySettings={undefined} setProxySettings={() => { }} accessToken={accessToken} + isDarkMode={false} + toggleDarkMode={() => { }} />
diff --git a/ui/litellm-dashboard/src/app/page.tsx b/ui/litellm-dashboard/src/app/page.tsx index ce12967c9112..054e1c798f8b 100644 --- a/ui/litellm-dashboard/src/app/page.tsx +++ b/ui/litellm-dashboard/src/app/page.tsx @@ -376,12 +376,18 @@ function CreateKeyPageContent() { ) : (
diff --git a/ui/litellm-dashboard/src/components/Navbar/BlogDropdown/BlogDropdown.tsx b/ui/litellm-dashboard/src/components/Navbar/BlogDropdown/BlogDropdown.tsx index 657e5a39bab6..ddb2a33cdaa2 100644 --- a/ui/litellm-dashboard/src/components/Navbar/BlogDropdown/BlogDropdown.tsx +++ b/ui/litellm-dashboard/src/components/Navbar/BlogDropdown/BlogDropdown.tsx @@ -1,7 +1,6 @@ import { useDisableBlogPosts } from "@/app/(dashboard)/hooks/useDisableBlogPosts"; import { useBlogPosts, type BlogPost } from "@/app/(dashboard)/hooks/blogPosts/useBlogPosts"; -import { NAV_PRODUCT_LINK_CLASS } from "@/components/Navbar/navProductLinkClass"; -import { DownOutlined, LoadingOutlined } from "@ant-design/icons"; +import { LoadingOutlined } from "@ant-design/icons"; import { Button, Dropdown, Space, Typography } from "antd"; import type { MenuProps } from "antd"; import React from "react"; @@ -75,13 +74,9 @@ export const BlogDropdown: React.FC = () => { ]; } - // Blog opens a post list; Docs is a single outbound link — navbar adds a layout-only chevron there for alignment. return ( - + ); }; diff --git a/ui/litellm-dashboard/src/components/Navbar/CommunityEngagementButtons/CommunityEngagementButtons.test.tsx b/ui/litellm-dashboard/src/components/Navbar/CommunityEngagementButtons/CommunityEngagementButtons.test.tsx index 4f07f0e2daa2..6994def858b1 100644 --- a/ui/litellm-dashboard/src/components/Navbar/CommunityEngagementButtons/CommunityEngagementButtons.test.tsx +++ b/ui/litellm-dashboard/src/components/Navbar/CommunityEngagementButtons/CommunityEngagementButtons.test.tsx @@ -29,14 +29,14 @@ describe("CommunityEngagementButtons", () => { expect(joinSlackLink).toHaveAttribute("rel", "noopener noreferrer"); }); - it("should render GitHub link with correct href", () => { + it("should render Star us on GitHub button with correct link", () => { renderWithProviders(); - const githubLink = screen.getByRole("link", { name: /litellm on github/i }); - expect(githubLink).toBeInTheDocument(); - expect(githubLink).toHaveAttribute("href", "https://github.com/BerriAI/litellm"); - expect(githubLink).toHaveAttribute("target", "_blank"); - expect(githubLink).toHaveAttribute("rel", "noopener noreferrer"); + const starOnGithubLink = screen.getByRole("link", { name: /star us on github/i }); + expect(starOnGithubLink).toBeInTheDocument(); + expect(starOnGithubLink).toHaveAttribute("href", "https://github.com/BerriAI/litellm"); + expect(starOnGithubLink).toHaveAttribute("target", "_blank"); + expect(starOnGithubLink).toHaveAttribute("rel", "noopener noreferrer"); }); it("should not render buttons when prompts are disabled", () => { @@ -45,6 +45,6 @@ describe("CommunityEngagementButtons", () => { renderWithProviders(); expect(screen.queryByRole("link", { name: /join slack/i })).not.toBeInTheDocument(); - expect(screen.queryByRole("link", { name: /litellm on github/i })).not.toBeInTheDocument(); + expect(screen.queryByRole("link", { name: /star us on github/i })).not.toBeInTheDocument(); }); }); diff --git a/ui/litellm-dashboard/src/components/Navbar/CommunityEngagementButtons/CommunityEngagementButtons.tsx b/ui/litellm-dashboard/src/components/Navbar/CommunityEngagementButtons/CommunityEngagementButtons.tsx index f6a43196a326..649bcc0b589c 100644 --- a/ui/litellm-dashboard/src/components/Navbar/CommunityEngagementButtons/CommunityEngagementButtons.tsx +++ b/ui/litellm-dashboard/src/components/Navbar/CommunityEngagementButtons/CommunityEngagementButtons.tsx @@ -1,45 +1,36 @@ import { useDisableShowPrompts } from "@/app/(dashboard)/hooks/useDisableShowPrompts"; import { GithubOutlined, SlackOutlined } from "@ant-design/icons"; -import { Tooltip } from "antd"; +import { Button } from "antd"; import React from "react"; -const iconBtnClass = - "inline-flex h-9 w-9 shrink-0 items-center justify-center rounded-md border-0 bg-transparent text-gray-500 transition-colors hover:bg-gray-100 hover:text-gray-700 cursor-pointer"; - export const CommunityEngagementButtons: React.FC = () => { const disableShowPrompts = useDisableShowPrompts(); + // Hide buttons if prompts are disabled if (disableShowPrompts) { return null; } return ( -
- - - - - - - - - - -
+ <> + + + ); }; diff --git a/ui/litellm-dashboard/src/components/Navbar/NotificationsBell/NotificationsBell.test.tsx b/ui/litellm-dashboard/src/components/Navbar/NotificationsBell/NotificationsBell.test.tsx deleted file mode 100644 index 4ead085d9774..000000000000 --- a/ui/litellm-dashboard/src/components/Navbar/NotificationsBell/NotificationsBell.test.tsx +++ /dev/null @@ -1,69 +0,0 @@ -import { renderWithProviders, screen } from "../../../../tests/test-utils"; -import { NotificationsBell, AGENT_PLATFORM_URL } from "./NotificationsBell"; -import React from "react"; -import userEvent from "@testing-library/user-event"; - -describe("NotificationsBell", () => { - beforeEach(() => { - localStorage.clear(); - }); - - it("should open notifications with Agent Platform details and GitHub link", async () => { - const user = userEvent.setup(); - renderWithProviders(); - await user.click(screen.getByRole("button", { name: /^notifications$/i })); - expect(screen.getByText(/LiteLLM Agent Platform/i)).toBeInTheDocument(); - const githubBtn = screen.getByRole("link", { name: /^GitHub$/i }); - expect(githubBtn).toHaveAttribute("href", AGENT_PLATFORM_URL); - expect(githubBtn).toHaveAttribute("target", "_blank"); - expect(githubBtn).toHaveAttribute("rel", "noopener noreferrer"); - }); - - it("should offer mark as read when announcement is unread", async () => { - const user = userEvent.setup(); - renderWithProviders(); - await user.click(screen.getByRole("button", { name: /^notifications$/i })); - expect(screen.getByRole("button", { name: /^mark as read$/i })).toBeInTheDocument(); - }); - - it("should hide mark as read and persist after marking read", async () => { - const user = userEvent.setup(); - renderWithProviders(); - await user.click(screen.getByRole("button", { name: /^notifications$/i })); - await user.click(screen.getByRole("button", { name: /^mark as read$/i })); - expect(localStorage.getItem("litellmHideAgentPlatformBanner")).toBe("true"); - await user.click(screen.getByRole("button", { name: /^notifications$/i })); - expect(screen.queryByRole("button", { name: /^mark as read$/i })).not.toBeInTheDocument(); - }); - - it("should not show mark as read when previously dismissed", async () => { - localStorage.setItem("litellmHideAgentPlatformBanner", "true"); - const user = userEvent.setup(); - renderWithProviders(); - await user.click(screen.getByRole("button", { name: /^notifications$/i })); - expect(screen.queryByRole("button", { name: /^mark as read$/i })).not.toBeInTheDocument(); - }); - - it("should sync sibling instances when one is dismissed", async () => { - const user = userEvent.setup(); - renderWithProviders( - <> -
- -
-
- -
- , - ); - - // Both bells start unread → both render the "Mark as read" affordance once opened. - const [bellA, bellB] = screen.getAllByRole("button", { name: /^notifications$/i }); - await user.click(bellA); - await user.click(screen.getByRole("button", { name: /^mark as read$/i })); - - // Dismissing in bell A must also clear bell B without a remount. - await user.click(bellB); - expect(screen.queryByRole("button", { name: /^mark as read$/i })).not.toBeInTheDocument(); - }); -}); diff --git a/ui/litellm-dashboard/src/components/Navbar/NotificationsBell/NotificationsBell.tsx b/ui/litellm-dashboard/src/components/Navbar/NotificationsBell/NotificationsBell.tsx deleted file mode 100644 index a3b7678e1e7e..000000000000 --- a/ui/litellm-dashboard/src/components/Navbar/NotificationsBell/NotificationsBell.tsx +++ /dev/null @@ -1,59 +0,0 @@ -"use client"; - -import { - HIDE_AGENT_PLATFORM_BANNER_KEY, - useHideAgentPlatformBanner, -} from "@/app/(dashboard)/hooks/useHideAgentPlatformBanner"; -import { emitLocalStorageChange, setLocalStorageItem } from "@/utils/localStorageUtils"; -import { BellOutlined } from "@ant-design/icons"; -import { Badge, Button, Popover, Typography } from "antd"; -import React, { useState } from "react"; - -export const AGENT_PLATFORM_URL = "https://github.com/BerriAI/litellm-agent-platform"; - -export const NotificationsBell: React.FC = () => { - const hidden = useHideAgentPlatformBanner(); - const hasUnread = !hidden; - const [open, setOpen] = useState(false); - - const markDismissed = () => { - setLocalStorageItem(HIDE_AGENT_PLATFORM_BANNER_KEY, "true"); - emitLocalStorageChange(HIDE_AGENT_PLATFORM_BANNER_KEY); - setOpen(false); - }; - - const content = ( -
- - LiteLLM Agent Platform - - - Open-source agent infra — sandboxes, durable sessions, and workers on AWS Fargate. - -
- - {hasUnread ? ( - - ) : null} -
-
- ); - - return ( - - - - ); -}; diff --git a/ui/litellm-dashboard/src/components/Navbar/UserDropdown/UserDropdown.test.tsx b/ui/litellm-dashboard/src/components/Navbar/UserDropdown/UserDropdown.test.tsx index 31ddae317985..de853303c15c 100644 --- a/ui/litellm-dashboard/src/components/Navbar/UserDropdown/UserDropdown.test.tsx +++ b/ui/litellm-dashboard/src/components/Navbar/UserDropdown/UserDropdown.test.tsx @@ -37,8 +37,6 @@ vi.mock("@/utils/localStorageUtils", () => ({ describe("UserDropdown", () => { const mockOnLogout = vi.fn(); - const getAccountTrigger = () => screen.getByRole("button", { name: /account menu/i }); - beforeEach(() => { vi.clearAllMocks(); mockUseAuthorizedImpl = () => ({ @@ -57,23 +55,22 @@ describe("UserDropdown", () => { it("should render", () => { renderWithProviders(); - expect(getAccountTrigger()).toBeInTheDocument(); + expect(screen.getByRole("button")).toBeInTheDocument(); }); - it("should surface initials and account menu affordance", () => { + it("should display user button with User text", () => { renderWithProviders(); - expect(getAccountTrigger()).toBeInTheDocument(); - expect(screen.getByText("TE")).toBeInTheDocument(); + expect(screen.getByText("User")).toBeInTheDocument(); }); it("should show user email when dropdown is opened", async () => { const user = userEvent.setup(); renderWithProviders(); - await user.click(getAccountTrigger()); + await user.click(screen.getByText("User")); await waitFor(() => { - expect(screen.getAllByText("test@example.com").length).toBeGreaterThan(0); + expect(screen.getByText("test@example.com")).toBeInTheDocument(); }); }); @@ -81,7 +78,7 @@ describe("UserDropdown", () => { const user = userEvent.setup(); renderWithProviders(); - await user.click(getAccountTrigger()); + await user.click(screen.getByText("User")); await waitFor(() => { expect(screen.getByText("test-user-id")).toBeInTheDocument(); @@ -92,10 +89,10 @@ describe("UserDropdown", () => { const user = userEvent.setup(); renderWithProviders(); - await user.click(getAccountTrigger()); + await user.click(screen.getByText("User")); await waitFor(() => { - expect(screen.getAllByText("Admin").length).toBeGreaterThan(0); + expect(screen.getByText("Admin")).toBeInTheDocument(); }); }); @@ -103,7 +100,7 @@ describe("UserDropdown", () => { const user = userEvent.setup(); renderWithProviders(); - await user.click(getAccountTrigger()); + await user.click(screen.getByText("User")); await waitFor(() => { expect(screen.getByText("Standard")).toBeInTheDocument(); @@ -121,7 +118,7 @@ describe("UserDropdown", () => { renderWithProviders(); - await user.click(getAccountTrigger()); + await user.click(screen.getByText("User")); await waitFor(() => { expect(screen.getByText("Premium")).toBeInTheDocument(); @@ -132,10 +129,10 @@ describe("UserDropdown", () => { const user = userEvent.setup(); renderWithProviders(); - await user.click(getAccountTrigger()); + await user.click(screen.getByText("User")); await waitFor(() => { - expect(screen.getAllByText("test@example.com").length).toBeGreaterThan(0); + expect(screen.getByText("test@example.com")).toBeInTheDocument(); }); await user.click(screen.getByText("Logout")); @@ -147,10 +144,10 @@ describe("UserDropdown", () => { const user = userEvent.setup(); renderWithProviders(); - await user.click(getAccountTrigger()); + await user.click(screen.getByText("User")); await waitFor(() => { - expect(screen.getAllByText("test@example.com").length).toBeGreaterThan(0); + expect(screen.getByText("test@example.com")).toBeInTheDocument(); }); const toggle = screen.getByLabelText("Toggle hide new feature indicators"); @@ -172,10 +169,10 @@ describe("UserDropdown", () => { renderWithProviders(); - await user.click(getAccountTrigger()); + await user.click(screen.getByText("User")); await waitFor(() => { - expect(screen.getAllByText("test@example.com").length).toBeGreaterThan(0); + expect(screen.getByText("test@example.com")).toBeInTheDocument(); }); const toggle = screen.getByLabelText("Toggle hide new feature indicators"); @@ -192,10 +189,10 @@ describe("UserDropdown", () => { const user = userEvent.setup(); renderWithProviders(); - await user.click(getAccountTrigger()); + await user.click(screen.getByText("User")); await waitFor(() => { - expect(screen.getAllByText("test@example.com").length).toBeGreaterThan(0); + expect(screen.getByText("test@example.com")).toBeInTheDocument(); }); const toggle = screen.getByLabelText("Toggle hide all prompts"); @@ -218,10 +215,10 @@ describe("UserDropdown", () => { renderWithProviders(); - await user.click(getAccountTrigger()); + await user.click(screen.getByText("User")); await waitFor(() => { - expect(screen.getAllByText("test@example.com").length).toBeGreaterThan(0); + expect(screen.getByText("test@example.com")).toBeInTheDocument(); }); const toggle = screen.getByLabelText("Toggle hide all prompts"); @@ -234,17 +231,6 @@ describe("UserDropdown", () => { expect(localStorageUtils.emitLocalStorageChange).toHaveBeenCalledWith("disableShowPrompts"); }); - it("should show Account in the trigger when user id is the default placeholder", () => { - mockUseAuthorizedImpl = () => ({ - userId: "default_user_id", - userEmail: null as any, - userRole: "Admin", - premiumUser: false, - }); - renderWithProviders(); - expect(screen.getByText("Account")).toBeInTheDocument(); - }); - it("should display dash when user email is not available", async () => { const user = userEvent.setup(); mockUseAuthorizedImpl = () => ({ @@ -256,7 +242,7 @@ describe("UserDropdown", () => { renderWithProviders(); - await user.click(getAccountTrigger()); + await user.click(screen.getByText("User")); await waitFor(() => { expect(screen.getByText("-")).toBeInTheDocument(); @@ -274,7 +260,7 @@ describe("UserDropdown", () => { renderWithProviders(); - await user.click(getAccountTrigger()); + await user.click(screen.getByText("User")); await waitFor(() => { const dashElements = screen.getAllByText("-"); @@ -291,10 +277,10 @@ describe("UserDropdown", () => { renderWithProviders(); - await user.click(getAccountTrigger()); + await user.click(screen.getByText("User")); await waitFor(() => { - expect(screen.getAllByText("test@example.com").length).toBeGreaterThan(0); + expect(screen.getByText("test@example.com")).toBeInTheDocument(); }); const toggle = screen.getByLabelText("Toggle hide new feature indicators"); diff --git a/ui/litellm-dashboard/src/components/Navbar/UserDropdown/UserDropdown.tsx b/ui/litellm-dashboard/src/components/Navbar/UserDropdown/UserDropdown.tsx index 64a2f1260ba4..6490cd32fa7c 100644 --- a/ui/litellm-dashboard/src/components/Navbar/UserDropdown/UserDropdown.tsx +++ b/ui/litellm-dashboard/src/components/Navbar/UserDropdown/UserDropdown.tsx @@ -9,7 +9,6 @@ import { removeLocalStorageItem, setLocalStorageItem, } from "@/utils/localStorageUtils"; -import { navAccountDisplayName } from "@/components/Navbar/navDisplayName"; import { CrownOutlined, DownOutlined, @@ -24,39 +23,6 @@ import React, { useEffect, useState } from "react"; const { Text } = Typography; -function hueFromString(seed: string): number { - let h = 0; - for (let i = 0; i < seed.length; i += 1) { - h = seed.charCodeAt(i) + ((h << 5) - h); - } - return Math.abs(h) % 360; -} - -function initialsFromIdentity(email: string | null, userId: string | null): string { - const local = email?.split("@")[0]?.trim(); - if (local) { - const parts = local - .replace(/[^a-zA-Z0-9]+/g, " ") - .trim() - .split(/\s+/) - .filter(Boolean); - if (parts.length >= 2) { - return `${parts[0]!.charAt(0)}${parts[1]!.charAt(0)}`.toUpperCase(); - } - if (parts.length === 1) { - const p = parts[0]!; - return p.length >= 2 ? p.slice(0, 2).toUpperCase() : `${p.charAt(0)}`.toUpperCase(); - } - } - if (userId && userId.length >= 2) { - return userId.slice(0, 2).toUpperCase(); - } - if (userId && userId.length === 1) { - return `${userId.toUpperCase()}•`; - } - return "?"; -} - interface UserDropdownProps { onLogout: () => void; } @@ -95,12 +61,19 @@ const UserDropdown: React.FC = ({ onLogout }) => { {userEmail || "-"} {premiumUser ? ( - } color="gold"> + } + color="gold" + > Premium ) : ( - }>Standard + } + > + Standard + )} @@ -110,7 +83,12 @@ const UserDropdown: React.FC = ({ onLogout }) => { User ID - + {userId || "-"} @@ -211,17 +189,13 @@ const UserDropdown: React.FC = ({ onLogout }) => { ); - const seed = userEmail || userId || "user"; - const initials = initialsFromIdentity(userEmail, userId); - const hue = hueFromString(seed); - const displayName = navAccountDisplayName(userEmail, userId); - return ( ( -
+
{renderUserInfoSection()} {React.cloneElement(menu as React.ReactElement, { @@ -230,23 +204,12 @@ const UserDropdown: React.FC = ({ onLogout }) => {
)} > - ); diff --git a/ui/litellm-dashboard/src/components/Navbar/navDisplayName.test.ts b/ui/litellm-dashboard/src/components/Navbar/navDisplayName.test.ts deleted file mode 100644 index 96e768c5d315..000000000000 --- a/ui/litellm-dashboard/src/components/Navbar/navDisplayName.test.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { describe, expect, it } from "vitest"; -import { navAccountDisplayName } from "./navDisplayName"; - -describe("navAccountDisplayName", () => { - it("should prefer email when present", () => { - expect(navAccountDisplayName("x@y.com", "ignored")).toBe("x@y.com"); - }); - - it("should map default_user_id placeholder to Account", () => { - expect(navAccountDisplayName(null, "default_user_id")).toBe("Account"); - expect(navAccountDisplayName(null, "DEFAULT_USER_ID")).toBe("Account"); - }); - - it("should show a sensible token when user id is non-placeholder", () => { - expect(navAccountDisplayName(null, "user-uuid-123")).toBe("user-uuid-123"); - }); -}); diff --git a/ui/litellm-dashboard/src/components/Navbar/navDisplayName.ts b/ui/litellm-dashboard/src/components/Navbar/navDisplayName.ts deleted file mode 100644 index d6f51dc37a83..000000000000 --- a/ui/litellm-dashboard/src/components/Navbar/navDisplayName.ts +++ /dev/null @@ -1,15 +0,0 @@ -/** Primary label for the navbar account control — avoids raw placeholder JWT/user IDs in the UI. */ -export function navAccountDisplayName(userEmail: string | null, userId: string | null): string { - const email = userEmail?.trim(); - if (email) { - return email; - } - const id = userId?.trim(); - if (!id) { - return "Account"; - } - if (/^default[_\s-]?user[_\s-]?id$/i.test(id)) { - return "Account"; - } - return id; -} diff --git a/ui/litellm-dashboard/src/components/Navbar/navProductLinkClass.ts b/ui/litellm-dashboard/src/components/Navbar/navProductLinkClass.ts deleted file mode 100644 index ca4b2e5d1f39..000000000000 --- a/ui/litellm-dashboard/src/components/Navbar/navProductLinkClass.ts +++ /dev/null @@ -1,3 +0,0 @@ -/** Shared styling for Docs / Blog in the top nav (product navigation zone). */ -export const NAV_PRODUCT_LINK_CLASS = - "inline-flex h-9 shrink-0 items-center justify-center gap-1 rounded-md px-2 text-sm font-medium leading-none text-gray-800 transition-colors hover:bg-gray-100 hover:text-gray-950"; diff --git a/ui/litellm-dashboard/src/components/navbar.test.tsx b/ui/litellm-dashboard/src/components/navbar.test.tsx index 274e81db527f..2e122164969b 100644 --- a/ui/litellm-dashboard/src/components/navbar.test.tsx +++ b/ui/litellm-dashboard/src/components/navbar.test.tsx @@ -30,7 +30,6 @@ const mockUserDropdownData = vi.hoisted(() => ({ vi.mock("./Navbar/UserDropdown/UserDropdown", async (importOriginal) => { const React = await import("react"); const { useState } = React; - const { Button } = await import("antd"); const localStorageUtils = await import("@/utils/localStorageUtils"); return { default: function MockUserDropdown({ onLogout }: { onLogout: () => void }) { @@ -38,9 +37,9 @@ vi.mock("./Navbar/UserDropdown/UserDropdown", async (importOriginal) => { const [open, setOpen] = useState(false); return (
- + {open && (
{userId} @@ -137,25 +136,30 @@ Object.defineProperty(window, "location", { describe("Navbar", () => { const defaultProps = { + userID: "test-user", + userEmail: "test@example.com", + userRole: "Admin", + premiumUser: false, proxySettings: {}, setProxySettings: vi.fn(), accessToken: "test-token", isPublicPage: false, + isDarkMode: false, + toggleDarkMode: vi.fn(), }; it("should render without crashing", () => { renderWithProviders(); - expect(screen.getByRole("button", { name: /^notifications$/i })).toBeInTheDocument(); expect(screen.getByText("Docs")).toBeInTheDocument(); - expect(screen.getByRole("button", { name: /open account menu/i })).toBeInTheDocument(); + expect(screen.getByText("User")).toBeInTheDocument(); }); it("should display user information in dropdown", async () => { const user = userEvent.setup(); renderWithProviders(); - await user.click(screen.getByRole("button", { name: /open account menu/i })); + await user.click(screen.getByText("User")); await waitFor(() => { expect(screen.getByText("test-user")).toBeInTheDocument(); @@ -194,7 +198,7 @@ describe("Navbar", () => { }); renderWithProviders(); - await user.click(screen.getByRole("button", { name: /open account menu/i })); + await user.click(screen.getByText("User")); await waitFor(() => { expect(screen.getByText("Premium")).toBeInTheDocument(); @@ -243,12 +247,11 @@ describe("Navbar", () => { mockUseThemeImpl = () => ({ logoUrl: null }); }); - it("should hide user dropdown and notifications on public pages", () => { + it("should hide user dropdown on public pages", () => { const publicPageProps = { ...defaultProps, isPublicPage: true }; renderWithProviders(); - expect(screen.queryByRole("button", { name: /open account menu/i })).not.toBeInTheDocument(); - expect(screen.queryByRole("button", { name: /^notifications$/i })).not.toBeInTheDocument(); + expect(screen.queryByText("User")).not.toBeInTheDocument(); }); it("should handle hide new features toggle", async () => { @@ -262,7 +265,7 @@ describe("Navbar", () => { renderWithProviders(); - await user.click(screen.getByRole("button", { name: /open account menu/i })); + await user.click(screen.getByText("User")); await waitFor(() => { expect(screen.getByText("test-user")).toBeInTheDocument(); @@ -287,7 +290,7 @@ describe("Navbar", () => { renderWithProviders(); - await user.click(screen.getByRole("button", { name: /open account menu/i })); + await user.click(screen.getByText("User")); await waitFor(() => { expect(screen.getByText("test-user")).toBeInTheDocument(); diff --git a/ui/litellm-dashboard/src/components/navbar.tsx b/ui/litellm-dashboard/src/components/navbar.tsx index e5a1490788c0..055038b6d881 100644 --- a/ui/litellm-dashboard/src/components/navbar.tsx +++ b/ui/litellm-dashboard/src/components/navbar.tsx @@ -1,39 +1,47 @@ import { useHealthReadinessDetails } from "@/app/(dashboard)/hooks/healthReadiness/useHealthReadinessDetails"; import { useDisableBouncingIcon } from "@/app/(dashboard)/hooks/useDisableBouncingIcon"; -import { useDisableShowPrompts } from "@/app/(dashboard)/hooks/useDisableShowPrompts"; -import { useWorker } from "@/hooks/useWorker"; import { getProxyBaseUrl } from "@/components/networking"; import { useTheme } from "@/contexts/ThemeContext"; import { clearTokenCookies } from "@/utils/cookieUtils"; import { clearStoredReturnUrl } from "@/utils/returnUrlUtils"; import { fetchProxySettings } from "@/utils/proxyUtils"; -import { DownOutlined, MenuFoldOutlined, MenuUnfoldOutlined } from "@ant-design/icons"; -import { Tag } from "antd"; +import { MenuFoldOutlined, MenuUnfoldOutlined, MoonOutlined, SunOutlined } from "@ant-design/icons"; +import { Button, Switch, Tag } from "antd"; import Link from "next/link"; import React, { useEffect, useState } from "react"; import { BlogDropdown } from "./Navbar/BlogDropdown/BlogDropdown"; import { CommunityEngagementButtons } from "./Navbar/CommunityEngagementButtons/CommunityEngagementButtons"; -import { NAV_PRODUCT_LINK_CLASS } from "./Navbar/navProductLinkClass"; -import { NotificationsBell } from "./Navbar/NotificationsBell/NotificationsBell"; import UserDropdown from "./Navbar/UserDropdown/UserDropdown"; import WorkerDropdown from "./Navbar/WorkerDropdown/WorkerDropdown"; interface NavbarProps { + userID: string | null; + userEmail: string | null; + userRole: string | null; + premiumUser: boolean; proxySettings: any; setProxySettings: React.Dispatch>; accessToken: string | null; isPublicPage: boolean; sidebarCollapsed?: boolean; onToggleSidebar?: () => void; + isDarkMode: boolean; + toggleDarkMode: () => void; } const Navbar: React.FC = ({ + userID, + userEmail, + userRole, + premiumUser, proxySettings, setProxySettings, accessToken, isPublicPage = false, sidebarCollapsed = false, onToggleSidebar, + isDarkMode, + toggleDarkMode, }) => { const baseUrl = getProxyBaseUrl(); const [logoutUrl, setLogoutUrl] = useState(""); @@ -41,10 +49,8 @@ const Navbar: React.FC = ({ const { data: healthData } = useHealthReadinessDetails(accessToken); const version = healthData?.litellm_version; const disableBouncingIcon = useDisableBouncingIcon(); - const hideCommunityLinks = useDisableShowPrompts(); - const { isControlPlane, selectedWorker } = useWorker(); - const showWorkerSwitch = isControlPlane && selectedWorker !== null; + // Simple logo URL: use custom logo if available, otherwise default const imageUrl = logoUrl || `${baseUrl}/get_image`; useEffect(() => { @@ -81,14 +87,14 @@ const Navbar: React.FC = ({ }; return ( -