mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-10 03:28:53 +00:00
refactoredUI feature flag
This commit is contained in:
parent
3f53d6b7ad
commit
53ee3e2634
6 changed files with 78 additions and 30 deletions
|
|
@ -44,7 +44,7 @@ interface MenuItem {
|
|||
icon?: React.ReactNode;
|
||||
}
|
||||
|
||||
const Sidebar: React.FC<SidebarProps> = ({ accessToken, userRole, defaultSelectedKey, collapsed = false }) => {
|
||||
const Sidebar2: React.FC<SidebarProps> = ({ accessToken, userRole, defaultSelectedKey, collapsed = false }) => {
|
||||
const menuItems: MenuItem[] = [
|
||||
{ key: "1", page: "api-keys", label: "Virtual Keys", icon: <KeyOutlined style={{ fontSize: 18 }} /> },
|
||||
{
|
||||
|
|
@ -288,4 +288,4 @@ const Sidebar: React.FC<SidebarProps> = ({ accessToken, userRole, defaultSelecte
|
|||
);
|
||||
};
|
||||
|
||||
export default Sidebar;
|
||||
export default Sidebar2;
|
||||
|
|
@ -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 }) {
|
|||
/>
|
||||
<div className="flex flex-1 overflow-auto">
|
||||
<div className="mt-2">
|
||||
<Sidebar
|
||||
<Sidebar2
|
||||
collapsed={sidebarCollapsed}
|
||||
accessToken={accessToken}
|
||||
userRole={userRole}
|
||||
|
|
|
|||
|
|
@ -39,6 +39,8 @@ import VectorStoreManagement from "@/components/vector_store_management";
|
|||
import UIThemeSettings from "@/components/ui_theme_settings";
|
||||
import { UiLoadingSpinner } from "@/components/ui/ui-loading-spinner";
|
||||
import { cx } from "@/lib/cva.config";
|
||||
import useFeatureFlags from "@/hooks/useFeatureFlags";
|
||||
import Sidebar2 from "@/app/(console)/components/Sidebar2";
|
||||
|
||||
function getCookie(name: string) {
|
||||
const cookieValue = document.cookie.split("; ").find((row) => row.startsWith(name + "="));
|
||||
|
|
@ -116,6 +118,8 @@ export default function CreateKeyPage() {
|
|||
const [authLoading, setAuthLoading] = useState(true);
|
||||
const [userID, setUserID] = useState<string | null>(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}
|
||||
/>
|
||||
<div className="flex flex-1 overflow-auto">
|
||||
<div className="mt-2">
|
||||
<Sidebar
|
||||
accessToken={accessToken}
|
||||
setPage={updatePage}
|
||||
userRole={userRole}
|
||||
defaultSelectedKey={page}
|
||||
collapsed={sidebarCollapsed}
|
||||
/>
|
||||
{refactoredUIFlag ? (
|
||||
<Sidebar2 accessToken={accessToken} userRole={userRole} />
|
||||
) : (
|
||||
<Sidebar
|
||||
accessToken={accessToken}
|
||||
setPage={updatePage}
|
||||
userRole={userRole}
|
||||
defaultSelectedKey={page}
|
||||
collapsed={sidebarCollapsed}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{page == "api-keys" ? (
|
||||
|
|
|
|||
|
|
@ -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<SidebarProps> = ({ 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<SidebarProps> = ({ 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 (
|
||||
<Layout style={{ minHeight: "100vh" }}>
|
||||
<Sider
|
||||
|
|
@ -293,9 +291,21 @@ const Sidebar: React.FC<SidebarProps> = ({ 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,
|
||||
}))}
|
||||
/>
|
||||
</ConfigProvider>
|
||||
|
|
|
|||
|
|
@ -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<React.SetStateAction<boolean>>;
|
||||
}
|
||||
|
||||
const Navbar: React.FC<NavbarProps> = ({
|
||||
|
|
@ -44,6 +47,8 @@ const Navbar: React.FC<NavbarProps> = ({
|
|||
isPublicPage = false,
|
||||
sidebarCollapsed = false,
|
||||
onToggleSidebar,
|
||||
refactoredUIFlag,
|
||||
setRefactoredUIFlag,
|
||||
}) => {
|
||||
const baseUrl = getProxyBaseUrl();
|
||||
const [logoutUrl, setLogoutUrl] = useState("");
|
||||
|
|
@ -79,6 +84,8 @@ const Navbar: React.FC<NavbarProps> = ({
|
|||
const userItems: MenuProps["items"] = [
|
||||
{
|
||||
key: "user-info",
|
||||
// Prevent dropdown from closing when interacting with the toggle
|
||||
onClick: (info) => info.domEvent?.stopPropagation(),
|
||||
label: (
|
||||
<div className="px-3 py-3 border-b border-gray-100">
|
||||
<div className="flex items-center justify-between mb-3">
|
||||
|
|
@ -115,6 +122,18 @@ const Navbar: React.FC<NavbarProps> = ({
|
|||
{userEmail || "Unknown"}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{/* NEW: Feature flag label + toggle below the email field */}
|
||||
<div className="flex items-center text-sm pt-2 mt-2 border-t border-gray-100">
|
||||
<span className="text-gray-500 text-xs">Refactored UI</span>
|
||||
<Switch
|
||||
className="ml-auto"
|
||||
size="small"
|
||||
checked={refactoredUIFlag}
|
||||
onChange={(checked) => setRefactoredUIFlag(checked)}
|
||||
aria-label="Toggle refactored UI feature flag"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
),
|
||||
|
|
|
|||
9
ui/litellm-dashboard/src/hooks/useFeatureFlags.ts
Normal file
9
ui/litellm-dashboard/src/hooks/useFeatureFlags.ts
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
import { useState } from "react";
|
||||
|
||||
const useFeatureFlags = () => {
|
||||
const [refactoredUIFlag, setRefactoredUIFlag] = useState<boolean>(false);
|
||||
|
||||
return { refactoredUIFlag, setRefactoredUIFlag };
|
||||
};
|
||||
|
||||
export default useFeatureFlags;
|
||||
Loading…
Add table
Reference in a new issue