diff --git a/ui/litellm-dashboard/src/app/(console)/components/Sidebar.tsx b/ui/litellm-dashboard/src/app/(console)/components/Sidebar2.tsx similarity index 98% rename from ui/litellm-dashboard/src/app/(console)/components/Sidebar.tsx rename to ui/litellm-dashboard/src/app/(console)/components/Sidebar2.tsx index ad60ba626aa..b2477243a0e 100644 --- a/ui/litellm-dashboard/src/app/(console)/components/Sidebar.tsx +++ b/ui/litellm-dashboard/src/app/(console)/components/Sidebar2.tsx @@ -44,7 +44,7 @@ interface MenuItem { icon?: React.ReactNode; } -const Sidebar: React.FC = ({ accessToken, userRole, defaultSelectedKey, collapsed = false }) => { +const Sidebar2: React.FC = ({ accessToken, userRole, defaultSelectedKey, collapsed = false }) => { const menuItems: MenuItem[] = [ { key: "1", page: "api-keys", label: "Virtual Keys", icon: }, { @@ -288,4 +288,4 @@ const Sidebar: React.FC = ({ accessToken, userRole, defaultSelecte ); }; -export default Sidebar; +export default Sidebar2; diff --git a/ui/litellm-dashboard/src/app/(console)/layout.tsx b/ui/litellm-dashboard/src/app/(console)/layout.tsx index cbfb5bdcb53..6ad84208f56 100644 --- a/ui/litellm-dashboard/src/app/(console)/layout.tsx +++ b/ui/litellm-dashboard/src/app/(console)/layout.tsx @@ -3,7 +3,7 @@ import React from "react"; import Navbar from "@/components/navbar"; import { ThemeProvider } from "@/contexts/ThemeContext"; -import Sidebar from "@/app/(console)/components/Sidebar"; +import Sidebar2 from "@/app/(console)/components/Sidebar2"; import useAuthorized from "@/app/(console)/hooks/useAuthorized"; export default function Layout({ children }: { children: React.ReactNode }) { @@ -31,7 +31,7 @@ export default function Layout({ children }: { children: React.ReactNode }) { />
- row.startsWith(name + "=")); @@ -116,6 +118,8 @@ export default function CreateKeyPage() { const [authLoading, setAuthLoading] = useState(true); const [userID, setUserID] = useState(null); + const { refactoredUIFlag, setRefactoredUIFlag } = useFeatureFlags(); + const invitation_id = searchParams.get("invitation_id"); // Get page from URL, default to 'api-keys' if not present @@ -254,16 +258,22 @@ export default function CreateKeyPage() { isPublicPage={false} sidebarCollapsed={sidebarCollapsed} onToggleSidebar={toggleSidebar} + refactoredUIFlag={refactoredUIFlag} + setRefactoredUIFlag={setRefactoredUIFlag} />
- + {refactoredUIFlag ? ( + + ) : ( + + )}
{page == "api-keys" ? ( diff --git a/ui/litellm-dashboard/src/components/leftnav.tsx b/ui/litellm-dashboard/src/components/leftnav.tsx index d9326cb8878..34916cddb9f 100644 --- a/ui/litellm-dashboard/src/components/leftnav.tsx +++ b/ui/litellm-dashboard/src/components/leftnav.tsx @@ -12,17 +12,29 @@ import { AppstoreOutlined, DatabaseOutlined, FileTextOutlined, + LineOutlined, LineChartOutlined, SafetyOutlined, ExperimentOutlined, + ThunderboltOutlined, + LockOutlined, ToolOutlined, TagsOutlined, BgColorsOutlined, + MenuFoldOutlined, + MenuUnfoldOutlined, } from "@ant-design/icons"; -import { all_admin_roles, rolesWithWriteAccess, internalUserRoles, isAdminRole } from "../utils/roles"; +import { + old_admin_roles, + v2_admin_role_names, + all_admin_roles, + rolesAllowedToSeeUsage, + rolesWithWriteAccess, + internalUserRoles, + isAdminRole, +} from "../utils/roles"; import UsageIndicator from "./usage_indicator"; import { ConfigProvider } from "antd"; -import { useRouter } from "next/navigation"; const { Sider } = Layout; // Define the props type @@ -45,7 +57,6 @@ interface MenuItem { } const Sidebar: React.FC = ({ accessToken, setPage, userRole, defaultSelectedKey, collapsed = false }) => { - const router = useRouter(); // Note: If a menu item does not have a role, it is visible to all roles. const menuItems: MenuItem[] = [ { @@ -237,19 +248,6 @@ const Sidebar: React.FC = ({ accessToken, setPage, userRole, defau return true; }); - const navigateToPage = (page: string) => { - if (page === "api-keys") { - // Go to clean route and DO NOT call setPage to avoid any parent sync that re-adds ?page=api-keys - router.replace("/virtual-keys"); - return; - } - const newSearchParams = new URLSearchParams(window.location.search); - newSearchParams.set("page", page); - // Use absolute root to replace everything after the domain - router.replace(`/?${newSearchParams.toString()}`); - setPage(page); - }; - return ( = ({ accessToken, setPage, userRole, defau key: child.key, icon: child.icon, label: child.label, - onClick: () => navigateToPage(child.page), + onClick: () => { + const newSearchParams = new URLSearchParams(window.location.search); + newSearchParams.set("page", child.page); + window.history.pushState(null, "", `?${newSearchParams.toString()}`); + setPage(child.page); + }, })), - onClick: !item.children ? () => navigateToPage(item.page) : undefined, + onClick: !item.children + ? () => { + const newSearchParams = new URLSearchParams(window.location.search); + newSearchParams.set("page", item.page); + window.history.pushState(null, "", `?${newSearchParams.toString()}`); + setPage(item.page); + } + : undefined, }))} /> diff --git a/ui/litellm-dashboard/src/components/navbar.tsx b/ui/litellm-dashboard/src/components/navbar.tsx index 07642880570..e4a9c21c443 100644 --- a/ui/litellm-dashboard/src/components/navbar.tsx +++ b/ui/litellm-dashboard/src/components/navbar.tsx @@ -1,7 +1,7 @@ import Link from "next/link"; import React, { useState, useEffect } from "react"; import type { MenuProps } from "antd"; -import { Dropdown, Tooltip } from "antd"; +import { Dropdown, Tooltip, Switch } from "antd"; import { getProxyBaseUrl, Organization } from "@/components/networking"; import { defaultOrg } from "@/components/common_components/default_org"; import { @@ -19,6 +19,7 @@ import { clearTokenCookies } from "@/utils/cookieUtils"; import { fetchProxySettings } from "@/utils/proxyUtils"; import { useTheme } from "@/contexts/ThemeContext"; import { clearMCPAuthTokens } from "./mcp_tools/mcp_auth_storage"; +import useFeatureFlags from "@/hooks/useFeatureFlags"; interface NavbarProps { userID: string | null; @@ -31,6 +32,8 @@ interface NavbarProps { isPublicPage: boolean; sidebarCollapsed?: boolean; onToggleSidebar?: () => void; + refactoredUIFlag: boolean; + setRefactoredUIFlag: React.Dispatch>; } const Navbar: React.FC = ({ @@ -44,6 +47,8 @@ const Navbar: React.FC = ({ isPublicPage = false, sidebarCollapsed = false, onToggleSidebar, + refactoredUIFlag, + setRefactoredUIFlag, }) => { const baseUrl = getProxyBaseUrl(); const [logoutUrl, setLogoutUrl] = useState(""); @@ -79,6 +84,8 @@ const Navbar: React.FC = ({ const userItems: MenuProps["items"] = [ { key: "user-info", + // Prevent dropdown from closing when interacting with the toggle + onClick: (info) => info.domEvent?.stopPropagation(), label: (
@@ -115,6 +122,18 @@ const Navbar: React.FC = ({ {userEmail || "Unknown"}
+ + {/* NEW: Feature flag label + toggle below the email field */} +
+ Refactored UI + setRefactoredUIFlag(checked)} + aria-label="Toggle refactored UI feature flag" + /> +
), diff --git a/ui/litellm-dashboard/src/hooks/useFeatureFlags.ts b/ui/litellm-dashboard/src/hooks/useFeatureFlags.ts new file mode 100644 index 00000000000..b82f89e8749 --- /dev/null +++ b/ui/litellm-dashboard/src/hooks/useFeatureFlags.ts @@ -0,0 +1,9 @@ +import { useState } from "react"; + +const useFeatureFlags = () => { + const [refactoredUIFlag, setRefactoredUIFlag] = useState(false); + + return { refactoredUIFlag, setRefactoredUIFlag }; +}; + +export default useFeatureFlags;