From ef8fa821362e91ec19c7d6f2811d130110772c11 Mon Sep 17 00:00:00 2001 From: ryan-crabbe-berri Date: Mon, 8 Jun 2026 14:47:15 -0700 Subject: [PATCH] feat(ui): migrate the self-contained leaf pages to App Router path routing Bulk Wave B cutover for the 20 leaf pages whose only dependency is the auth context (budgets, caching, cost-tracking, guardrails, guardrails-monitor, logs, mcp-servers, memory, policies, projects, prompts, search-tools, skills, tag-management, tool-policies, api-playground/transform-request, ui-theme, vector-stores, workflows, access-groups). Each gets a thin (dashboard)//page.tsx that sources its data from useAuthorized and renders the existing component; the legacy switch arm and import are removed from page.tsx, and a map entry is added to MIGRATED_PAGES (skills and claude-code-plugins both map to /ui/skills). A data-driven test pins every forward mapping and reverse lookup. --- .../app/(dashboard)/access-groups/page.tsx | 7 ++ .../src/app/(dashboard)/budgets/page.tsx | 9 +++ .../src/app/(dashboard)/caching/page.tsx | 17 +++++ .../app/(dashboard)/cost-tracking/page.tsx | 9 +++ .../(dashboard)/guardrails-monitor/page.tsx | 9 +++ .../src/app/(dashboard)/guardrails/page.tsx | 9 +++ .../src/app/(dashboard)/logs/page.tsx | 17 +++++ .../src/app/(dashboard)/mcp-servers/page.tsx | 9 +++ .../src/app/(dashboard)/memory/page.tsx | 9 +++ .../src/app/(dashboard)/policies/page.tsx | 9 +++ .../src/app/(dashboard)/projects/page.tsx | 7 ++ .../src/app/(dashboard)/prompts/page.tsx | 9 +++ .../src/app/(dashboard)/search-tools/page.tsx | 9 +++ .../src/app/(dashboard)/skills/page.tsx | 9 +++ .../app/(dashboard)/tag-management/page.tsx | 9 +++ .../app/(dashboard)/tool-policies/page.tsx | 9 +++ .../(dashboard)/transform-request/page.tsx | 9 +++ .../src/app/(dashboard)/ui-theme/page.tsx | 9 +++ .../app/(dashboard)/vector-stores/page.tsx | 9 +++ .../src/app/(dashboard)/workflows/page.tsx | 9 +++ ui/litellm-dashboard/src/app/page.tsx | 72 ------------------- .../src/utils/migratedPages.test.ts | 44 ++++++++++++ .../src/utils/migratedPages.ts | 21 ++++++ 23 files changed, 257 insertions(+), 72 deletions(-) create mode 100644 ui/litellm-dashboard/src/app/(dashboard)/access-groups/page.tsx create mode 100644 ui/litellm-dashboard/src/app/(dashboard)/budgets/page.tsx create mode 100644 ui/litellm-dashboard/src/app/(dashboard)/caching/page.tsx create mode 100644 ui/litellm-dashboard/src/app/(dashboard)/cost-tracking/page.tsx create mode 100644 ui/litellm-dashboard/src/app/(dashboard)/guardrails-monitor/page.tsx create mode 100644 ui/litellm-dashboard/src/app/(dashboard)/guardrails/page.tsx create mode 100644 ui/litellm-dashboard/src/app/(dashboard)/logs/page.tsx create mode 100644 ui/litellm-dashboard/src/app/(dashboard)/mcp-servers/page.tsx create mode 100644 ui/litellm-dashboard/src/app/(dashboard)/memory/page.tsx create mode 100644 ui/litellm-dashboard/src/app/(dashboard)/policies/page.tsx create mode 100644 ui/litellm-dashboard/src/app/(dashboard)/projects/page.tsx create mode 100644 ui/litellm-dashboard/src/app/(dashboard)/prompts/page.tsx create mode 100644 ui/litellm-dashboard/src/app/(dashboard)/search-tools/page.tsx create mode 100644 ui/litellm-dashboard/src/app/(dashboard)/skills/page.tsx create mode 100644 ui/litellm-dashboard/src/app/(dashboard)/tag-management/page.tsx create mode 100644 ui/litellm-dashboard/src/app/(dashboard)/tool-policies/page.tsx create mode 100644 ui/litellm-dashboard/src/app/(dashboard)/transform-request/page.tsx create mode 100644 ui/litellm-dashboard/src/app/(dashboard)/ui-theme/page.tsx create mode 100644 ui/litellm-dashboard/src/app/(dashboard)/vector-stores/page.tsx create mode 100644 ui/litellm-dashboard/src/app/(dashboard)/workflows/page.tsx diff --git a/ui/litellm-dashboard/src/app/(dashboard)/access-groups/page.tsx b/ui/litellm-dashboard/src/app/(dashboard)/access-groups/page.tsx new file mode 100644 index 00000000000..b73785dd12c --- /dev/null +++ b/ui/litellm-dashboard/src/app/(dashboard)/access-groups/page.tsx @@ -0,0 +1,7 @@ +"use client"; + +import { AccessGroupsPage } from "@/components/AccessGroups/AccessGroupsPage"; + +export default function AccessGroupsRoute() { + return ; +} diff --git a/ui/litellm-dashboard/src/app/(dashboard)/budgets/page.tsx b/ui/litellm-dashboard/src/app/(dashboard)/budgets/page.tsx new file mode 100644 index 00000000000..ab85af82de3 --- /dev/null +++ b/ui/litellm-dashboard/src/app/(dashboard)/budgets/page.tsx @@ -0,0 +1,9 @@ +"use client"; + +import BudgetPanel from "@/components/budgets/budget_panel"; +import useAuthorized from "@/app/(dashboard)/hooks/useAuthorized"; + +export default function BudgetsRoute() { + const { accessToken } = useAuthorized(); + return ; +} diff --git a/ui/litellm-dashboard/src/app/(dashboard)/caching/page.tsx b/ui/litellm-dashboard/src/app/(dashboard)/caching/page.tsx new file mode 100644 index 00000000000..59749571d1a --- /dev/null +++ b/ui/litellm-dashboard/src/app/(dashboard)/caching/page.tsx @@ -0,0 +1,17 @@ +"use client"; + +import CacheDashboard from "@/components/cache_dashboard"; +import useAuthorized from "@/app/(dashboard)/hooks/useAuthorized"; + +export default function CachingRoute() { + const { accessToken, userId, userRole, token, premiumUser } = useAuthorized(); + return ( + + ); +} diff --git a/ui/litellm-dashboard/src/app/(dashboard)/cost-tracking/page.tsx b/ui/litellm-dashboard/src/app/(dashboard)/cost-tracking/page.tsx new file mode 100644 index 00000000000..0879fafa142 --- /dev/null +++ b/ui/litellm-dashboard/src/app/(dashboard)/cost-tracking/page.tsx @@ -0,0 +1,9 @@ +"use client"; + +import { CostTrackingSettings } from "@/components/CostTrackingSettings"; +import useAuthorized from "@/app/(dashboard)/hooks/useAuthorized"; + +export default function CostTrackingRoute() { + const { accessToken, userId, userRole } = useAuthorized(); + return ; +} diff --git a/ui/litellm-dashboard/src/app/(dashboard)/guardrails-monitor/page.tsx b/ui/litellm-dashboard/src/app/(dashboard)/guardrails-monitor/page.tsx new file mode 100644 index 00000000000..ad8446d13fa --- /dev/null +++ b/ui/litellm-dashboard/src/app/(dashboard)/guardrails-monitor/page.tsx @@ -0,0 +1,9 @@ +"use client"; + +import GuardrailsMonitorView from "@/components/GuardrailsMonitor/GuardrailsMonitorView"; +import useAuthorized from "@/app/(dashboard)/hooks/useAuthorized"; + +export default function GuardrailsMonitorRoute() { + const { accessToken } = useAuthorized(); + return ; +} diff --git a/ui/litellm-dashboard/src/app/(dashboard)/guardrails/page.tsx b/ui/litellm-dashboard/src/app/(dashboard)/guardrails/page.tsx new file mode 100644 index 00000000000..96da8b5f234 --- /dev/null +++ b/ui/litellm-dashboard/src/app/(dashboard)/guardrails/page.tsx @@ -0,0 +1,9 @@ +"use client"; + +import GuardrailsPanel from "@/components/guardrails"; +import useAuthorized from "@/app/(dashboard)/hooks/useAuthorized"; + +export default function GuardrailsRoute() { + const { accessToken, userRole } = useAuthorized(); + return ; +} diff --git a/ui/litellm-dashboard/src/app/(dashboard)/logs/page.tsx b/ui/litellm-dashboard/src/app/(dashboard)/logs/page.tsx new file mode 100644 index 00000000000..6a91df11344 --- /dev/null +++ b/ui/litellm-dashboard/src/app/(dashboard)/logs/page.tsx @@ -0,0 +1,17 @@ +"use client"; + +import SpendLogsTable from "@/components/view_logs"; +import useAuthorized from "@/app/(dashboard)/hooks/useAuthorized"; + +export default function LogsRoute() { + const { accessToken, userId, userRole, token, premiumUser } = useAuthorized(); + return ( + + ); +} diff --git a/ui/litellm-dashboard/src/app/(dashboard)/mcp-servers/page.tsx b/ui/litellm-dashboard/src/app/(dashboard)/mcp-servers/page.tsx new file mode 100644 index 00000000000..f3267ccea7f --- /dev/null +++ b/ui/litellm-dashboard/src/app/(dashboard)/mcp-servers/page.tsx @@ -0,0 +1,9 @@ +"use client"; + +import { MCPServers } from "@/components/mcp_tools"; +import useAuthorized from "@/app/(dashboard)/hooks/useAuthorized"; + +export default function McpServersRoute() { + const { accessToken, userId, userRole } = useAuthorized(); + return ; +} diff --git a/ui/litellm-dashboard/src/app/(dashboard)/memory/page.tsx b/ui/litellm-dashboard/src/app/(dashboard)/memory/page.tsx new file mode 100644 index 00000000000..9830799db58 --- /dev/null +++ b/ui/litellm-dashboard/src/app/(dashboard)/memory/page.tsx @@ -0,0 +1,9 @@ +"use client"; + +import { MemoryView } from "@/components/MemoryView"; +import useAuthorized from "@/app/(dashboard)/hooks/useAuthorized"; + +export default function MemoryRoute() { + const { accessToken, userId, userRole } = useAuthorized(); + return ; +} diff --git a/ui/litellm-dashboard/src/app/(dashboard)/policies/page.tsx b/ui/litellm-dashboard/src/app/(dashboard)/policies/page.tsx new file mode 100644 index 00000000000..c989655542c --- /dev/null +++ b/ui/litellm-dashboard/src/app/(dashboard)/policies/page.tsx @@ -0,0 +1,9 @@ +"use client"; + +import PoliciesPanel from "@/components/policies"; +import useAuthorized from "@/app/(dashboard)/hooks/useAuthorized"; + +export default function PoliciesRoute() { + const { accessToken, userRole } = useAuthorized(); + return ; +} diff --git a/ui/litellm-dashboard/src/app/(dashboard)/projects/page.tsx b/ui/litellm-dashboard/src/app/(dashboard)/projects/page.tsx new file mode 100644 index 00000000000..272e1961522 --- /dev/null +++ b/ui/litellm-dashboard/src/app/(dashboard)/projects/page.tsx @@ -0,0 +1,7 @@ +"use client"; + +import { ProjectsPage } from "@/components/Projects/ProjectsPage"; + +export default function ProjectsRoute() { + return ; +} diff --git a/ui/litellm-dashboard/src/app/(dashboard)/prompts/page.tsx b/ui/litellm-dashboard/src/app/(dashboard)/prompts/page.tsx new file mode 100644 index 00000000000..d59255f11dc --- /dev/null +++ b/ui/litellm-dashboard/src/app/(dashboard)/prompts/page.tsx @@ -0,0 +1,9 @@ +"use client"; + +import PromptsPanel from "@/components/prompts"; +import useAuthorized from "@/app/(dashboard)/hooks/useAuthorized"; + +export default function PromptsRoute() { + const { accessToken, userRole } = useAuthorized(); + return ; +} diff --git a/ui/litellm-dashboard/src/app/(dashboard)/search-tools/page.tsx b/ui/litellm-dashboard/src/app/(dashboard)/search-tools/page.tsx new file mode 100644 index 00000000000..9d0f97cd78a --- /dev/null +++ b/ui/litellm-dashboard/src/app/(dashboard)/search-tools/page.tsx @@ -0,0 +1,9 @@ +"use client"; + +import { SearchTools } from "@/components/SearchTools"; +import useAuthorized from "@/app/(dashboard)/hooks/useAuthorized"; + +export default function SearchToolsRoute() { + const { accessToken, userId, userRole } = useAuthorized(); + return ; +} diff --git a/ui/litellm-dashboard/src/app/(dashboard)/skills/page.tsx b/ui/litellm-dashboard/src/app/(dashboard)/skills/page.tsx new file mode 100644 index 00000000000..4d8a6247a2c --- /dev/null +++ b/ui/litellm-dashboard/src/app/(dashboard)/skills/page.tsx @@ -0,0 +1,9 @@ +"use client"; + +import ClaudeCodePluginsPanel from "@/components/claude_code_plugins"; +import useAuthorized from "@/app/(dashboard)/hooks/useAuthorized"; + +export default function SkillsRoute() { + const { accessToken, userRole } = useAuthorized(); + return ; +} diff --git a/ui/litellm-dashboard/src/app/(dashboard)/tag-management/page.tsx b/ui/litellm-dashboard/src/app/(dashboard)/tag-management/page.tsx new file mode 100644 index 00000000000..63234d18a03 --- /dev/null +++ b/ui/litellm-dashboard/src/app/(dashboard)/tag-management/page.tsx @@ -0,0 +1,9 @@ +"use client"; + +import TagManagement from "@/components/tag_management"; +import useAuthorized from "@/app/(dashboard)/hooks/useAuthorized"; + +export default function TagManagementRoute() { + const { accessToken, userId, userRole } = useAuthorized(); + return ; +} diff --git a/ui/litellm-dashboard/src/app/(dashboard)/tool-policies/page.tsx b/ui/litellm-dashboard/src/app/(dashboard)/tool-policies/page.tsx new file mode 100644 index 00000000000..2b4f99d507d --- /dev/null +++ b/ui/litellm-dashboard/src/app/(dashboard)/tool-policies/page.tsx @@ -0,0 +1,9 @@ +"use client"; + +import ToolPoliciesView from "@/components/ToolPoliciesView"; +import useAuthorized from "@/app/(dashboard)/hooks/useAuthorized"; + +export default function ToolPoliciesRoute() { + const { accessToken, userRole } = useAuthorized(); + return ; +} diff --git a/ui/litellm-dashboard/src/app/(dashboard)/transform-request/page.tsx b/ui/litellm-dashboard/src/app/(dashboard)/transform-request/page.tsx new file mode 100644 index 00000000000..3d380491788 --- /dev/null +++ b/ui/litellm-dashboard/src/app/(dashboard)/transform-request/page.tsx @@ -0,0 +1,9 @@ +"use client"; + +import TransformRequestPanel from "@/components/transform_request"; +import useAuthorized from "@/app/(dashboard)/hooks/useAuthorized"; + +export default function TransformRequestRoute() { + const { accessToken } = useAuthorized(); + return ; +} diff --git a/ui/litellm-dashboard/src/app/(dashboard)/ui-theme/page.tsx b/ui/litellm-dashboard/src/app/(dashboard)/ui-theme/page.tsx new file mode 100644 index 00000000000..c848c3b7225 --- /dev/null +++ b/ui/litellm-dashboard/src/app/(dashboard)/ui-theme/page.tsx @@ -0,0 +1,9 @@ +"use client"; + +import UIThemeSettings from "@/components/ui_theme_settings"; +import useAuthorized from "@/app/(dashboard)/hooks/useAuthorized"; + +export default function UiThemeRoute() { + const { accessToken, userId, userRole } = useAuthorized(); + return ; +} diff --git a/ui/litellm-dashboard/src/app/(dashboard)/vector-stores/page.tsx b/ui/litellm-dashboard/src/app/(dashboard)/vector-stores/page.tsx new file mode 100644 index 00000000000..5a273dc8154 --- /dev/null +++ b/ui/litellm-dashboard/src/app/(dashboard)/vector-stores/page.tsx @@ -0,0 +1,9 @@ +"use client"; + +import VectorStoreManagement from "@/components/vector_store_management"; +import useAuthorized from "@/app/(dashboard)/hooks/useAuthorized"; + +export default function VectorStoresRoute() { + const { accessToken, userId, userRole } = useAuthorized(); + return ; +} diff --git a/ui/litellm-dashboard/src/app/(dashboard)/workflows/page.tsx b/ui/litellm-dashboard/src/app/(dashboard)/workflows/page.tsx new file mode 100644 index 00000000000..c7a89d608ee --- /dev/null +++ b/ui/litellm-dashboard/src/app/(dashboard)/workflows/page.tsx @@ -0,0 +1,9 @@ +"use client"; + +import WorkflowRuns from "@/components/workflow_runs"; +import useAuthorized from "@/app/(dashboard)/hooks/useAuthorized"; + +export default function WorkflowsRoute() { + const { accessToken } = useAuthorized(); + return ; +} diff --git a/ui/litellm-dashboard/src/app/page.tsx b/ui/litellm-dashboard/src/app/page.tsx index 12dd39a1c21..40792453c5d 100644 --- a/ui/litellm-dashboard/src/app/page.tsx +++ b/ui/litellm-dashboard/src/app/page.tsx @@ -5,18 +5,10 @@ import OldModelDashboard from "@/app/(dashboard)/models-and-endpoints/ModelsAndE import PlaygroundPage from "@/app/(dashboard)/playground/page"; import AdminPanel from "@/components/AdminPanel"; import AgentsPanel from "@/components/agents"; -import BudgetPanel from "@/components/budgets/budget_panel"; -import CacheDashboard from "@/components/cache_dashboard"; -import ClaudeCodePluginsPanel from "@/components/claude_code_plugins"; import { teamListCall as v2TeamListCall } from "@/app/(dashboard)/hooks/teams/useTeams"; import LoadingScreen from "@/components/common_components/LoadingScreen"; -import { CostTrackingSettings } from "@/components/CostTrackingSettings"; import GeneralSettings from "@/components/general_settings"; -import GuardrailsMonitorView from "@/components/GuardrailsMonitor/GuardrailsMonitorView"; -import GuardrailsPanel from "@/components/guardrails"; -import PoliciesPanel from "@/components/policies"; import { Team } from "@/components/key_team_helpers/key_list"; -import { MCPServers } from "@/components/mcp_tools"; import ModelHubTable from "@/components/AIHub/ModelHubTable"; import Navbar from "@/components/navbar"; import { Organization, proxyBaseUrl, getInProductNudgesCall } from "@/components/networking"; @@ -25,23 +17,11 @@ import OldTeams from "@/components/OldTeams"; import { fetchUserModels, CreateKeyPrefillData } from "@/components/organisms/create_key_button"; import Organizations, { fetchOrganizations } from "@/components/organizations"; import PassThroughSettings from "@/components/pass_through_settings"; -import PromptsPanel from "@/components/prompts"; import PublicModelHub from "@/components/public_model_hub"; -import { SearchTools } from "@/components/SearchTools"; import Settings from "@/components/settings"; import { SurveyPrompt, SurveyModal, ClaudeCodePrompt, ClaudeCodeModal } from "@/components/survey"; -import TagManagement from "@/components/tag_management"; -import TransformRequestPanel from "@/components/transform_request"; -import UIThemeSettings from "@/components/ui_theme_settings"; import Usage from "@/components/usage"; import UserDashboard from "@/components/user_dashboard"; -import { AccessGroupsPage } from "@/components/AccessGroups/AccessGroupsPage"; -import { ProjectsPage } from "@/components/Projects/ProjectsPage"; -import VectorStoreManagement from "@/components/vector_store_management"; -import ToolPoliciesView from "@/components/ToolPoliciesView"; -import { MemoryView } from "@/components/MemoryView"; -import WorkflowRuns from "@/components/workflow_runs"; -import SpendLogsTable from "@/components/view_logs"; import ViewUserDashboard from "@/components/view_users"; import { ThemeProvider } from "@/contexts/ThemeContext"; import { useAuth } from "@/contexts/AuthContext"; @@ -442,18 +422,8 @@ function CreateKeyPageContent() { ) : page == "logging-and-alerts" ? ( - ) : page == "budgets" ? ( - - ) : page == "guardrails" ? ( - - ) : page == "policies" ? ( - ) : page == "agents" ? ( - ) : page == "prompts" ? ( - - ) : page == "transform-request" ? ( - ) : page == "router-settings" ? ( - ) : page == "ui-theme" ? ( - - ) : page == "cost-tracking" ? ( - ) : page == "model-hub-table" ? ( isAdminRole(userRole) ? ( ) - ) : page == "caching" ? ( - ) : page == "pass-through-settings" ? ( - ) : page == "logs" ? ( - - ) : page == "mcp-servers" ? ( - - ) : page == "search-tools" ? ( - - ) : page == "tag-management" ? ( - - ) : page == "skills" || page == "claude-code-plugins" ? ( - - ) : page == "access-groups" ? ( - - ) : page == "projects" ? ( - - ) : page == "vector-stores" ? ( - - ) : page == "tool-policies" ? ( - - ) : page == "workflows" ? ( - - ) : page == "memory" ? ( - - ) : page == "guardrails-monitor" ? ( - ) : page == "new_usage" ? ( { expect(legacyKeyForPathname("/ui/api-reference")).toBeNull(); }); }); + +describe("MIGRATED_PAGES leaf cutover", () => { + beforeEach(() => { + vi.resetModules(); + }); + + it("routes every migrated leaf page to its segment and back to a sidebar key", async () => { + vi.doMock("@/components/networking", () => ({ serverRootPath: "/" })); + const { MIGRATED_PAGES, legacyKeyForPathname } = await import("./migratedPages"); + + const cases: [string, string][] = [ + ["budgets", "budgets"], + ["caching", "caching"], + ["cost-tracking", "cost-tracking"], + ["guardrails", "guardrails"], + ["guardrails-monitor", "guardrails-monitor"], + ["logs", "logs"], + ["mcp-servers", "mcp-servers"], + ["memory", "memory"], + ["policies", "policies"], + ["projects", "projects"], + ["prompts", "prompts"], + ["search-tools", "search-tools"], + ["skills", "skills"], + ["claude-code-plugins", "skills"], + ["tag-management", "tag-management"], + ["tool-policies", "tool-policies"], + ["transform-request", "transform-request"], + ["ui-theme", "ui-theme"], + ["vector-stores", "vector-stores"], + ["workflows", "workflows"], + ["access-groups", "access-groups"], + ]; + for (const [key, seg] of cases) { + expect(MIGRATED_PAGES[key]).toBe(seg); + expect(legacyKeyForPathname(`/ui/${seg}`)).not.toBeNull(); + expect(legacyKeyForPathname(`/ui/${seg}/`)).not.toBeNull(); + } + + // The skills alias resolves to the "skills" sidebar key, not "claude-code-plugins". + expect(legacyKeyForPathname("/ui/skills")).toBe("skills"); + expect(legacyKeyForPathname("/ui/budgets")).toBe("budgets"); + }); +}); diff --git a/ui/litellm-dashboard/src/utils/migratedPages.ts b/ui/litellm-dashboard/src/utils/migratedPages.ts index 2c27e4fee64..e1be807037b 100644 --- a/ui/litellm-dashboard/src/utils/migratedPages.ts +++ b/ui/litellm-dashboard/src/utils/migratedPages.ts @@ -12,6 +12,27 @@ export const MIGRATED_PAGES: Record = { api_ref: "api-reference", // Legacy alias: older bookmarks used the hyphenated ?page=api-reference form. "api-reference": "api-reference", + budgets: "budgets", + caching: "caching", + "cost-tracking": "cost-tracking", + guardrails: "guardrails", + "guardrails-monitor": "guardrails-monitor", + logs: "logs", + "mcp-servers": "mcp-servers", + memory: "memory", + policies: "policies", + projects: "projects", + prompts: "prompts", + "search-tools": "search-tools", + skills: "skills", + "claude-code-plugins": "skills", + "tag-management": "tag-management", + "tool-policies": "tool-policies", + "transform-request": "transform-request", + "ui-theme": "ui-theme", + "vector-stores": "vector-stores", + workflows: "workflows", + "access-groups": "access-groups", }; function uiBase(): string {