From d97b17b161dec706e5ce0fd007db3324d692d44e Mon Sep 17 00:00:00 2001 From: ryan-crabbe-berri Date: Wed, 17 Jun 2026 18:10:31 -0700 Subject: [PATCH] feat(ui): migrate models page to App Router path route (#30677) * feat(ui): migrate models page to App Router path route Cut the Models + Endpoints page over from the legacy ?page=models switch in (dashboard)/page.tsx to a path route at (dashboard)/models-and-endpoints. Adding the MIGRATED_PAGES entry repoints the sidebar link and redirects old ?page=models bookmarks to /ui/models-and-endpoints. ModelsAndEndpointsView already sourced identity from useAuthorized() and its own data via useModelsInfo(), so the token/keys/modelData/setModelData props were dead; drop them from ModelDashboardProps (and the parent's now-unused setModelData state) to sever the last of the shared-state coupling. * test(ui): scope migration smoke's shell probe to the exact sidebar link The migration smoke used a loose `locator("a", { hasText: "Virtual Keys" })` to assert the dashboard shell rendered. The Models + Endpoints page content itself links to the "Virtual Keys page", so on that route the substring filter matched two anchors and tripped Playwright strict mode. Match the sidebar link by its exact accessible name instead, which resolves to just the nav item. --- .../e2e_tests/fixtures/migratedPages.ts | 1 + .../tests/migration/migratedPages.spec.ts | 2 +- .../ModelsAndEndpointsView.test.tsx | 45 +++---------------- .../ModelsAndEndpointsView.tsx | 4 -- .../(dashboard)/models-and-endpoints/page.tsx | 11 +++++ .../src/app/(dashboard)/page.tsx | 12 +---- .../src/utils/migratedPages.test.ts | 8 ++++ .../src/utils/migratedPages.ts | 1 + 8 files changed, 28 insertions(+), 56 deletions(-) create mode 100644 ui/litellm-dashboard/src/app/(dashboard)/models-and-endpoints/page.tsx diff --git a/ui/litellm-dashboard/e2e_tests/fixtures/migratedPages.ts b/ui/litellm-dashboard/e2e_tests/fixtures/migratedPages.ts index 154badac021..af1991d2cf1 100644 --- a/ui/litellm-dashboard/e2e_tests/fixtures/migratedPages.ts +++ b/ui/litellm-dashboard/e2e_tests/fixtures/migratedPages.ts @@ -11,6 +11,7 @@ * Keep this in lockstep with MIGRATED_PAGES in src/utils/migratedPages.ts. */ export const MIGRATED_E2E_PAGES: Record = { + models: "models-and-endpoints", api_ref: "api-reference", "llm-playground": "playground", projects: "projects", diff --git a/ui/litellm-dashboard/e2e_tests/tests/migration/migratedPages.spec.ts b/ui/litellm-dashboard/e2e_tests/tests/migration/migratedPages.spec.ts index 98f4fee1450..c512ab2ddfb 100644 --- a/ui/litellm-dashboard/e2e_tests/tests/migration/migratedPages.spec.ts +++ b/ui/litellm-dashboard/e2e_tests/tests/migration/migratedPages.spec.ts @@ -17,7 +17,7 @@ const ROOT = process.env.SERVER_ROOT_PATH ?? ""; const esc = (s: string) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); const pathRe = (segment: string) => new RegExp(`${esc(ROOT)}/ui/${esc(segment)}/?($|\\?)`); -const legacyAnchor = (page: Page) => page.locator("a", { hasText: "Virtual Keys" }); +const legacyAnchor = (page: Page) => page.getByRole("link", { name: "Virtual Keys", exact: true }); /** The dashboard shell is present (sidebar rendered); page didn't 404 / crash. */ async function expectRendered(page: Page) { diff --git a/ui/litellm-dashboard/src/app/(dashboard)/models-and-endpoints/ModelsAndEndpointsView.test.tsx b/ui/litellm-dashboard/src/app/(dashboard)/models-and-endpoints/ModelsAndEndpointsView.test.tsx index 3c5101fc2dc..b4f95efade7 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/models-and-endpoints/ModelsAndEndpointsView.test.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/models-and-endpoints/ModelsAndEndpointsView.test.tsx @@ -120,14 +120,7 @@ describe("ModelsAndEndpointsView", () => { const queryClient = createQueryClient(); const { findByText } = render( - {}} - premiumUser={false} - teams={[]} - /> + , ); expect(await findByText("Model Management", {}, { timeout: 10000 })).toBeInTheDocument(); @@ -138,14 +131,7 @@ describe("ModelsAndEndpointsView", () => { const queryClient = createQueryClient(); const { findByText } = render( - {}} - premiumUser={false} - teams={[]} - /> + , ); expect(await findByText("Missing a provider?", {}, { timeout: 10000 })).toBeInTheDocument(); @@ -156,14 +142,7 @@ describe("ModelsAndEndpointsView", () => { const queryClient = createQueryClient(); const { findByText, queryByText, container } = render( - {}} - premiumUser={false} - teams={[]} - /> + , ); @@ -188,14 +167,7 @@ describe("ModelsAndEndpointsView", () => { const queryClient = createQueryClient(); const { findByText, queryByText } = render( - {}} - premiumUser={false} - teams={[]} - /> + , ); @@ -228,14 +200,7 @@ describe("ModelsAndEndpointsView", () => { const queryClient = createQueryClient(); const { getByRole } = render( - {}} - premiumUser={false} - teams={[]} - /> + , ); diff --git a/ui/litellm-dashboard/src/app/(dashboard)/models-and-endpoints/ModelsAndEndpointsView.tsx b/ui/litellm-dashboard/src/app/(dashboard)/models-and-endpoints/ModelsAndEndpointsView.tsx index 88f4382d7dd..2f8f7350db9 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/models-and-endpoints/ModelsAndEndpointsView.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/models-and-endpoints/ModelsAndEndpointsView.tsx @@ -30,10 +30,6 @@ import TeamInfoView from "../../../components/team/TeamInfo"; import useAuthorized from "../hooks/useAuthorized"; interface ModelDashboardProps { - token: string | null; - modelData: any; - keys: any[] | null; - setModelData: any; premiumUser: boolean; teams: Team[] | null; } diff --git a/ui/litellm-dashboard/src/app/(dashboard)/models-and-endpoints/page.tsx b/ui/litellm-dashboard/src/app/(dashboard)/models-and-endpoints/page.tsx new file mode 100644 index 00000000000..7594ee2f492 --- /dev/null +++ b/ui/litellm-dashboard/src/app/(dashboard)/models-and-endpoints/page.tsx @@ -0,0 +1,11 @@ +"use client"; + +import ModelsAndEndpointsView from "@/app/(dashboard)/models-and-endpoints/ModelsAndEndpointsView"; +import useAuthorized from "@/app/(dashboard)/hooks/useAuthorized"; +import { useTeams } from "@/app/(dashboard)/hooks/teams/useTeams"; + +export default function ModelsAndEndpointsPage() { + const { premiumUser } = useAuthorized(); + const { data: teams } = useTeams(); + return ; +} diff --git a/ui/litellm-dashboard/src/app/(dashboard)/page.tsx b/ui/litellm-dashboard/src/app/(dashboard)/page.tsx index c99b6eb9b40..fe842be3ba6 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/page.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/page.tsx @@ -1,6 +1,5 @@ "use client"; -import ModelsAndEndpointsView from "@/app/(dashboard)/models-and-endpoints/ModelsAndEndpointsView"; import { teamListCall as v2TeamListCall } from "@/app/(dashboard)/hooks/teams/useTeams"; import { useUISettings } from "@/app/(dashboard)/hooks/uiSettings/useUISettings"; import LoadingScreen from "@/components/common_components/LoadingScreen"; @@ -34,7 +33,7 @@ function CreateKeyPageContent() { const router = useRouter(); const searchParams = useSearchParams()!; - const [modelData, setModelData] = useState({ data: [] }); + const [modelData] = useState({ data: [] }); const [createClicked, setCreateClicked] = useState(false); const { data: uiSettingsData, isLoading: uiSettingsLoading } = useUISettings(); @@ -308,15 +307,6 @@ function CreateKeyPageContent() { autoOpenCreate={autoOpenCreate} prefillData={prefillData} /> - ) : page == "models" ? ( - ) : page == "pass-through-settings" ? ( { expect(MIGRATED_PAGES["llm-playground"]).toBe("playground"); }); + it("maps the models sidebar id to the models-and-endpoints route and builds its redirect href", async () => { + vi.doMock("@/components/networking", () => ({ serverRootPath: "/" })); + const { MIGRATED_PAGES, migratedHref } = await import("./migratedPages"); + + expect(MIGRATED_PAGES.models).toBe("models-and-endpoints"); + expect(migratedHref(MIGRATED_PAGES.models)).toBe("/ui/models-and-endpoints"); + }); + it("maps the projects and access-groups sidebar ids to their routes", async () => { vi.doMock("@/components/networking", () => ({ serverRootPath: "/" })); const { MIGRATED_PAGES } = await import("./migratedPages"); diff --git a/ui/litellm-dashboard/src/utils/migratedPages.ts b/ui/litellm-dashboard/src/utils/migratedPages.ts index 9c08aa1960b..f4b324cfe91 100644 --- a/ui/litellm-dashboard/src/utils/migratedPages.ts +++ b/ui/litellm-dashboard/src/utils/migratedPages.ts @@ -9,6 +9,7 @@ import { serverRootPath } from "@/components/networking"; * legacy `?page=` URL; remove it to roll back. */ export const MIGRATED_PAGES: Record = { + models: "models-and-endpoints", api_ref: "api-reference", // Legacy alias: older bookmarks used the hyphenated ?page=api-reference form. "api-reference": "api-reference",