From 8366d24e229e7c3765d4ecee248aff3555d74cbb Mon Sep 17 00:00:00 2001 From: yuneng-jiang Date: Sat, 21 Mar 2026 13:14:56 -0700 Subject: [PATCH] fix(ui): use old leftnav (SidebarProvider) in dashboard layout for path-routed pages MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The dashboard layout was using Sidebar2, which routed all entries to path-based URLs like /ui/keys — but only api-reference has been migrated. Switch back to the old leftnav so unmigrated pages navigate to the legacy root page (?page=X) and migrated pages use path routing. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../src/app/(dashboard)/layout.tsx | 34 +++++++++++++--- .../src/components/leftnav.tsx | 39 +++++++++++++++++-- 2 files changed, 64 insertions(+), 9 deletions(-) diff --git a/ui/litellm-dashboard/src/app/(dashboard)/layout.tsx b/ui/litellm-dashboard/src/app/(dashboard)/layout.tsx index 1cf7adf1ea9..94dd6eb3cf1 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/layout.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/layout.tsx @@ -3,7 +3,7 @@ import React, { Suspense, useEffect, useState } from "react"; import Navbar from "@/components/navbar"; import { ThemeProvider } from "@/contexts/ThemeContext"; -import Sidebar2 from "@/app/(dashboard)/components/Sidebar2"; +import SidebarProvider from "@/app/(dashboard)/components/SidebarProvider"; import useAuthorized from "@/app/(dashboard)/hooks/useAuthorized"; import { useRouter, useSearchParams } from "next/navigation"; import { DebugWarningBanner } from "@/components/DebugWarningBanner"; @@ -23,6 +23,17 @@ function withBase(path: string): string { } /** -------------------------------- */ +/** + * Pages that have been migrated to path-based routing under (dashboard)/. + * When the leftnav triggers one of these, navigate to the path route instead + * of the legacy query-param root page. + * + * Key = legacy page id used in leftnav, Value = route segment under (dashboard)/ + */ +const MIGRATED_PAGES: Record = { + "api-reference": "api-reference", +}; + function LayoutContent({ children }: { children: React.ReactNode }) { const router = useRouter(); const searchParams = useSearchParams(); @@ -32,10 +43,17 @@ function LayoutContent({ children }: { children: React.ReactNode }) { return searchParams.get("page") || "api-keys"; }); - const updatePage = (newPage: string) => { - const newSearchParams = new URLSearchParams(searchParams); - newSearchParams.set("page", newPage); - router.push(withBase(`/?${newSearchParams.toString()}`)); // always under BASE + const handleSetPage = (newPage: string) => { + // If the page has been migrated to path routing, navigate there + const migratedRoute = MIGRATED_PAGES[newPage]; + if (migratedRoute) { + router.push(withBase(migratedRoute)); + setPage(newPage); + return; + } + + // Otherwise, navigate back to the legacy root page with query params + router.push(withBase(`?page=${newPage}`)); setPage(newPage); }; @@ -65,7 +83,11 @@ function LayoutContent({ children }: { children: React.ReactNode }) {
- +
{children}
diff --git a/ui/litellm-dashboard/src/components/leftnav.tsx b/ui/litellm-dashboard/src/components/leftnav.tsx index 09ab3809427..13b6f660520 100644 --- a/ui/litellm-dashboard/src/components/leftnav.tsx +++ b/ui/litellm-dashboard/src/components/leftnav.tsx @@ -36,8 +36,34 @@ import { all_admin_roles, internalUserRoles, isAdminRole, isUserTeamAdminForAnyT import NewBadge from "./common_components/NewBadge"; import type { Organization } from "./networking"; import UsageIndicator from "./UsageIndicator"; +import { serverRootPath } from "./networking"; const { Sider } = Layout; +/** + * Pages migrated to path-based routing under (dashboard)/. + * Key = legacy page id, Value = route segment. + * Keep in sync with MIGRATED_PAGES in (dashboard)/layout.tsx and + * LEGACY_REDIRECTS in app/page.tsx. + */ +const MIGRATED_PAGES: Record = { + "api-reference": "api-reference", +}; + +/** Build an absolute href for a migrated page, respecting base URL + serverRootPath. */ +function migratedHref(routeSegment: string): string { + const raw = process.env.NEXT_PUBLIC_BASE_URL ?? ""; + const trimmed = raw.replace(/^\/+|\/+$/g, ""); + let base = trimmed ? `/${trimmed}/` : "/"; + + if (serverRootPath && serverRootPath !== "/") { + const cleanRoot = serverRootPath.replace(/\/+$/, ""); + const cleanBase = base.replace(/^\/+/, ""); + base = `${cleanRoot}/${cleanBase}`; + } + + return `${base}${routeSegment}`; +} + // Define the props type interface SidebarProps { setPage: (page: string) => void; @@ -379,6 +405,11 @@ const Sidebar: React.FC = ({ setPage, defaultSelectedKey, collapse // Navigate to page helper const navigateToPage = (page: string) => { + // For migrated pages, just call setPage — the parent layout handles routing + if (MIGRATED_PAGES[page]) { + setPage(page); + return; + } const newSearchParams = new URLSearchParams(window.location.search); newSearchParams.set("page", page); window.history.pushState(null, "", `?${newSearchParams.toString()}`); @@ -405,9 +436,11 @@ const Sidebar: React.FC = ({ setPage, defaultSelectedKey, collapse ); } - const params = new URLSearchParams(window.location.search); - params.set("page", page); - const href = `?${params.toString()}`; + // For migrated pages, generate a path-based href for right-click "Open in new tab" + const migratedRoute = MIGRATED_PAGES[page]; + const href = migratedRoute + ? migratedHref(migratedRoute) + : (() => { const params = new URLSearchParams(window.location.search); params.set("page", page); return `?${params.toString()}`; })(); return (