From cc94fb87b5179936bf02cede86617eb043fbc2b4 Mon Sep 17 00:00:00 2001 From: Achintya Rajan Date: Mon, 6 Oct 2025 20:32:47 -0700 Subject: [PATCH] added base URL helpers --- .../app/(dashboard)/components/Sidebar2.tsx | 28 +++++++++++++++---- .../src/app/(dashboard)/layout.tsx | 17 ++++++++++- ui/litellm-dashboard/src/app/page.tsx | 20 +++++++++++-- 3 files changed, 57 insertions(+), 8 deletions(-) diff --git a/ui/litellm-dashboard/src/app/(dashboard)/components/Sidebar2.tsx b/ui/litellm-dashboard/src/app/(dashboard)/components/Sidebar2.tsx index 843a176f6e..95907f3def 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/components/Sidebar2.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/components/Sidebar2.tsx @@ -44,6 +44,24 @@ interface MenuItem { icon?: React.ReactNode; } +/** ---- BASE URL HELPERS (shared pattern across files) ---- */ +function normalizeBasePrefix(raw: string | undefined | null): string { + const trimmed = (raw ?? "").trim(); + if (!trimmed) return ""; // no base + // strip leading/trailing slashes then rebuild as "/segment/" + const core = trimmed.replace(/^\/+/, "").replace(/\/+$/, ""); + return core ? `/${core}/` : "/"; +} +const BASE_PREFIX = normalizeBasePrefix(process.env.NEXT_PUBLIC_BASE_URL); +/** Builds an app-relative path under the configured base prefix. */ +function withBase(path: string): string { + // Accepts paths like "/virtual-keys" or "/?page=..." and prefixes with BASE_PREFIX when present. + const body = path.startsWith("/") ? path.slice(1) : path; + const combined = `${BASE_PREFIX}${body}`; + return combined.startsWith("/") ? combined : `/${combined}`; +} +/** -------------------------------------------------------- */ + const Sidebar2: React.FC = ({ accessToken, setPage, @@ -229,7 +247,7 @@ const Sidebar2: React.FC = ({ const pushToRootWithPage = (page: string, useReplace = false) => { const params = new URLSearchParams(); params.set("page", page); - const url = `/?${params.toString()}`; + const url = withBase(`/?${params.toString()}`); if (useReplace) { router.replace(url); } else { @@ -242,8 +260,8 @@ const Sidebar2: React.FC = ({ // Special-case: Virtual Keys target if (page === "api-keys") { if (refactoredUIFlag) { - // Go to the dedicated /virtual-keys page - router.push("/virtual-keys"); + // Go to the dedicated /virtual-keys page (always under base) + router.push(withBase("/virtual-keys")); return; // do not call setPage here (parity with previous behavior) } // Legacy behavior @@ -255,8 +273,8 @@ const Sidebar2: React.FC = ({ // All other pages if (refactoredUIFlag) { // If currently on /virtual-keys, REPLACE it with /?page=... - const onVirtualKeys = pathname?.startsWith("/virtual-keys"); - pushToRootWithPage(page, onVirtualKeys); + const onVirtualKeys = pathname?.startsWith(withBase("/virtual-keys")); + pushToRootWithPage(page, !!onVirtualKeys); } else { pushToRootWithPage(page); } diff --git a/ui/litellm-dashboard/src/app/(dashboard)/layout.tsx b/ui/litellm-dashboard/src/app/(dashboard)/layout.tsx index 7e60443d24..3d38a7362b 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/layout.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/layout.tsx @@ -7,6 +7,21 @@ import Sidebar2 from "@/app/(dashboard)/components/Sidebar2"; import useAuthorized from "@/app/(dashboard)/hooks/useAuthorized"; import { useRouter, useSearchParams } from "next/navigation"; +/** ---- BASE URL HELPERS ---- */ +function normalizeBasePrefix(raw: string | undefined | null): string { + const trimmed = (raw ?? "").trim(); + if (!trimmed) return ""; + const core = trimmed.replace(/^\/+/, "").replace(/\/+$/, ""); + return core ? `/${core}/` : "/"; +} +const BASE_PREFIX = normalizeBasePrefix(process.env.NEXT_PUBLIC_BASE_URL); +function withBase(path: string): string { + const body = path.startsWith("/") ? path.slice(1) : path; + const combined = `${BASE_PREFIX}${body}`; + return combined.startsWith("/") ? combined : `/${combined}`; +} +/** -------------------------------- */ + export default function Layout({ children }: { children: React.ReactNode }) { const router = useRouter(); const searchParams = useSearchParams(); @@ -19,7 +34,7 @@ export default function Layout({ children }: { children: React.ReactNode }) { const updatePage = (newPage: string) => { const newSearchParams = new URLSearchParams(searchParams); newSearchParams.set("page", newPage); - router.push(`/?${newSearchParams.toString()}`); // absolute, not relative + router.push(withBase(`/?${newSearchParams.toString()}`)); // always under BASE setPage(newPage); }; diff --git a/ui/litellm-dashboard/src/app/page.tsx b/ui/litellm-dashboard/src/app/page.tsx index d386df4b77..b110858349 100644 --- a/ui/litellm-dashboard/src/app/page.tsx +++ b/ui/litellm-dashboard/src/app/page.tsx @@ -40,6 +40,21 @@ import { UiLoadingSpinner } from "@/components/ui/ui-loading-spinner"; import { cx } from "@/lib/cva.config"; import Sidebar2 from "@/app/(dashboard)/components/Sidebar2"; +/** ---- BASE URL HELPERS ---- */ +function normalizeBasePrefix(raw: string | undefined | null): string { + const trimmed = (raw ?? "").trim(); + if (!trimmed) return ""; + const core = trimmed.replace(/^\/+/, "").replace(/\/+$/, ""); + return core ? `/${core}/` : "/"; +} +const BASE_PREFIX = normalizeBasePrefix(process.env.NEXT_PUBLIC_BASE_URL); +function withBase(path: string): string { + const body = path.startsWith("/") ? path.slice(1) : path; + const combined = `${BASE_PREFIX}${body}`; + return combined.startsWith("/") ? combined : `/${combined}`; +} +/** -------------------------------- */ + function getCookie(name: string) { const cookieValue = document.cookie.split("; ").find((row) => row.startsWith(name + "=")); return cookieValue ? cookieValue.split("=")[1] : null; @@ -128,11 +143,11 @@ export default function CreateKeyPage() { setPage(searchParams.get("page") || "api-keys"); }, [searchParams]); - // Custom setPage function that updates URL + // Custom setPage function that updates URL (always under BASE) const updatePage = (newPage: string) => { const newSearchParams = new URLSearchParams(searchParams); newSearchParams.set("page", newPage); - router.push(`/?${newSearchParams.toString()}`); // absolute, not relative + router.push(withBase(`/?${newSearchParams.toString()}`)); setPage(newPage); }; @@ -160,6 +175,7 @@ export default function CreateKeyPage() { useEffect(() => { if (redirectToLogin) { + // This is an external auth route (kept as-is, not app navigation). window.location.href = (proxyBaseUrl || "") + "/sso/key/generate"; } }, [redirectToLogin]);