diff --git a/ui/litellm-dashboard/src/app/(dashboard)/components/SidebarProvider.tsx b/ui/litellm-dashboard/src/app/(dashboard)/components/SidebarProvider.tsx
index 8b934e10779..8ab54ca6bd7 100644
--- a/ui/litellm-dashboard/src/app/(dashboard)/components/SidebarProvider.tsx
+++ b/ui/litellm-dashboard/src/app/(dashboard)/components/SidebarProvider.tsx
@@ -1,4 +1,9 @@
+"use client";
+
import Sidebar from "@/components/leftnav";
+import { getUISettings } from "@/components/networking";
+import useAuthorized from "@/app/(dashboard)/hooks/useAuthorized";
+import { useEffect, useState } from "react";
interface SidebarProviderProps {
setPage: (page: string) => void;
@@ -7,7 +12,34 @@ interface SidebarProviderProps {
}
const SidebarProvider = ({ setPage, defaultSelectedKey, sidebarCollapsed }: SidebarProviderProps) => {
- return ;
+ const { accessToken } = useAuthorized();
+ const [enabledPagesInternalUsers, setEnabledPagesInternalUsers] = useState(null);
+
+ useEffect(() => {
+ const fetchUISettings = async () => {
+ if (!accessToken) return;
+
+ try {
+ const settings = await getUISettings(accessToken);
+ if (settings?.settings?.enabled_ui_pages_internal_users !== undefined) {
+ setEnabledPagesInternalUsers(settings.settings.enabled_ui_pages_internal_users);
+ }
+ } catch (error) {
+ console.error("Failed to fetch UI settings:", error);
+ }
+ };
+
+ fetchUISettings();
+ }, [accessToken]);
+
+ return (
+
+ );
};
export default SidebarProvider;
diff --git a/ui/litellm-dashboard/src/components/leftnav.tsx b/ui/litellm-dashboard/src/components/leftnav.tsx
index f24f4d60219..442ec24a2a0 100644
--- a/ui/litellm-dashboard/src/components/leftnav.tsx
+++ b/ui/litellm-dashboard/src/components/leftnav.tsx
@@ -39,6 +39,7 @@ interface SidebarProps {
setPage: (page: string) => void;
defaultSelectedKey: string;
collapsed?: boolean;
+ enabledPagesInternalUsers?: string[] | null;
}
// Menu item configuration
@@ -59,28 +60,8 @@ interface MenuGroup {
roles?: string[];
}
-const Sidebar: React.FC = ({ setPage, defaultSelectedKey, collapsed = false }) => {
- const { userId, accessToken, userRole } = useAuthorized();
- const { data: organizations } = useOrganizations();
-
- // Check if user is an org_admin
- const isOrgAdmin = useMemo(() => {
- if (!userId || !organizations) return false;
- return organizations.some((org: Organization) =>
- org.members?.some((member) => member.user_id === userId && member.user_role === "org_admin"),
- );
- }, [userId, organizations]);
-
- // Navigate to page helper
- const navigateToPage = (page: string) => {
- const newSearchParams = new URLSearchParams(window.location.search);
- newSearchParams.set("page", page);
- window.history.pushState(null, "", `?${newSearchParams.toString()}`);
- setPage(page);
- };
-
- // Menu groups organized by category
- const menuGroups: MenuGroup[] = [
+// Menu groups organized by category - defined outside component for export
+const menuGroups: MenuGroup[] = [
{
groupLabel: "AI GATEWAY",
items: [
@@ -337,15 +318,53 @@ const Sidebar: React.FC = ({ setPage, defaultSelectedKey, collapse
},
];
- // Filter items based on user role
+const Sidebar: React.FC = ({ setPage, defaultSelectedKey, collapsed = false, enabledPagesInternalUsers }) => {
+ const { userId, accessToken, userRole } = useAuthorized();
+ const { data: organizations } = useOrganizations();
+
+ // Check if user is an org_admin
+ const isOrgAdmin = useMemo(() => {
+ if (!userId || !organizations) return false;
+ return organizations.some((org: Organization) =>
+ org.members?.some((member) => member.user_id === userId && member.user_role === "org_admin"),
+ );
+ }, [userId, organizations]);
+
+ // Navigate to page helper
+ const navigateToPage = (page: string) => {
+ const newSearchParams = new URLSearchParams(window.location.search);
+ newSearchParams.set("page", page);
+ window.history.pushState(null, "", `?${newSearchParams.toString()}`);
+ setPage(page);
+ };
+
+ // Filter items based on user role and enabled pages for internal users
const filterItemsByRole = (items: MenuItem[]): MenuItem[] => {
+ const isAdmin = isAdminRole(userRole);
+
return items
.filter((item) => {
// Special handling for organizations menu item - allow org_admins
if (item.key === "organizations") {
- return !item.roles || item.roles.includes(userRole) || isOrgAdmin;
+ const hasRoleAccess = !item.roles || item.roles.includes(userRole) || isOrgAdmin;
+ if (!hasRoleAccess) return false;
+
+ // Check enabled pages for internal users (non-admins)
+ if (!isAdmin && enabledPagesInternalUsers !== null && enabledPagesInternalUsers !== undefined) {
+ return enabledPagesInternalUsers.includes(item.page);
+ }
+ return true;
}
- return !item.roles || item.roles.includes(userRole);
+
+ // Existing role check
+ if (item.roles && !item.roles.includes(userRole)) return false;
+
+ // Check enabled pages for internal users (non-admins)
+ if (!isAdmin && enabledPagesInternalUsers !== null && enabledPagesInternalUsers !== undefined) {
+ return enabledPagesInternalUsers.includes(item.page);
+ }
+
+ return true;
})
.map((item) => ({
...item,
@@ -485,3 +504,6 @@ const Sidebar: React.FC = ({ setPage, defaultSelectedKey, collapse
};
export default Sidebar;
+
+// Also export menuGroups for advanced use cases
+export { menuGroups };
diff --git a/ui/litellm-dashboard/src/components/networking.tsx b/ui/litellm-dashboard/src/components/networking.tsx
index a1625b6ffbe..758358b8b0d 100644
--- a/ui/litellm-dashboard/src/components/networking.tsx
+++ b/ui/litellm-dashboard/src/components/networking.tsx
@@ -5330,6 +5330,66 @@ export const getProxyUISettings = async (accessToken: string) => {
}
};
+export const getUISettings = async (accessToken: string) => {
+ /**
+ * Get UI-specific configuration flags from the database
+ */
+ try {
+ const url = proxyBaseUrl ? `${proxyBaseUrl}/get/ui_settings` : `/get/ui_settings`;
+ const response = await fetch(url, {
+ method: "GET",
+ headers: {
+ [globalLitellmHeaderName]: `Bearer ${accessToken}`,
+ "Content-Type": "application/json",
+ },
+ });
+
+ if (!response.ok) {
+ const errorData = await response.json();
+ const errorMessage = deriveErrorMessage(errorData);
+ console.error("Failed to get UI settings:", errorMessage);
+ return null;
+ }
+
+ const data = await response.json();
+ return data;
+ } catch (error) {
+ console.error("Failed to get UI settings:", error);
+ return null;
+ }
+};
+
+export const updateUISettings = async (accessToken: string, settings: any) => {
+ /**
+ * Update UI-specific configuration flags in the database
+ * Only proxy admins can update these settings
+ */
+ try {
+ const url = proxyBaseUrl ? `${proxyBaseUrl}/update/ui_settings` : `/update/ui_settings`;
+ const response = await fetch(url, {
+ method: "PATCH",
+ headers: {
+ [globalLitellmHeaderName]: `Bearer ${accessToken}`,
+ "Content-Type": "application/json",
+ },
+ body: JSON.stringify(settings),
+ });
+
+ if (!response.ok) {
+ const errorData = await response.json();
+ const errorMessage = deriveErrorMessage(errorData);
+ handleError(errorMessage);
+ throw new Error(errorMessage);
+ }
+
+ const data = await response.json();
+ return data;
+ } catch (error) {
+ console.error("Failed to update UI settings:", error);
+ throw error;
+ }
+};
+
export const getGuardrailsList = async (accessToken: string) => {
try {
const url = proxyBaseUrl ? `${proxyBaseUrl}/v2/guardrails/list` : `/v2/guardrails/list`;
diff --git a/ui/litellm-dashboard/src/components/page_metadata.ts b/ui/litellm-dashboard/src/components/page_metadata.ts
new file mode 100644
index 00000000000..2e67f5baa22
--- /dev/null
+++ b/ui/litellm-dashboard/src/components/page_metadata.ts
@@ -0,0 +1,41 @@
+/**
+ * Page metadata for UI Settings configuration
+ * This file contains descriptions and metadata for all navigation pages
+ */
+
+// Page descriptions for UI Settings configuration
+export const pageDescriptions: Record = {
+ "api-keys": "Manage virtual keys for API access and authentication",
+ playground: "Interactive playground for testing LLM requests",
+ "models-and-endpoints": "Configure and manage LLM models and endpoints",
+ agents: "Create and manage AI agents",
+ "mcp-servers": "Configure Model Context Protocol servers",
+ guardrails: "Set up content moderation and safety guardrails",
+ policies: "Define access control and usage policies",
+ "search-tools": "Configure RAG search and retrieval tools",
+ "vector-stores": "Manage vector databases for embeddings",
+ usage: "View usage analytics and metrics",
+ logs: "Access request and response logs",
+ "internal-users": "Manage internal user accounts and permissions",
+ teams: "Create and manage teams for access control",
+ organizations: "Manage organizations and their members",
+ budgets: "Set and monitor spending budgets",
+ "api-reference": "Browse API documentation and endpoints",
+ "ai-hub": "Explore available AI models and providers",
+ "learning-resources": "Access tutorials and documentation",
+ caching: "Configure response caching settings",
+ "transform-request": "Set up request transformation rules",
+ "pass-through-endpoints": "Configure pass-through API endpoints",
+ "cost-tracking": "Track and analyze API costs",
+ "ui-themes": "Customize dashboard appearance",
+ "tag-management": "Organize resources with tags",
+ prompts: "Manage and version prompt templates",
+ "claude-code-plugins": "Configure Claude Code plugins",
+};
+
+export interface PageMetadata {
+ page: string;
+ label: string;
+ group: string;
+ description: string;
+}
diff --git a/ui/litellm-dashboard/src/components/page_utils.ts b/ui/litellm-dashboard/src/components/page_utils.ts
new file mode 100644
index 00000000000..ca15c6257b6
--- /dev/null
+++ b/ui/litellm-dashboard/src/components/page_utils.ts
@@ -0,0 +1,45 @@
+/**
+ * Utility functions for working with navigation pages
+ */
+
+import { menuGroups } from "./leftnav";
+import { pageDescriptions, PageMetadata } from "./page_metadata";
+
+/**
+ * Get all available pages from the navigation menu configuration
+ * Used by UI Settings to display available pages for visibility control
+ */
+export const getAvailablePages = (): PageMetadata[] => {
+ const pages: PageMetadata[] = [];
+
+ menuGroups.forEach((group) => {
+ group.items.forEach((item) => {
+ // Add top-level items (skip parent containers like 'tools', 'experimental', 'settings')
+ if (item.page && item.page !== "tools" && item.page !== "experimental" && item.page !== "settings") {
+ const label = typeof item.label === "string" ? item.label : item.key;
+ pages.push({
+ page: item.page,
+ label: label,
+ group: group.groupLabel,
+ description: pageDescriptions[item.page] || "No description available",
+ });
+ }
+
+ // Add children items
+ if (item.children) {
+ const parentLabel = typeof item.label === "string" ? item.label : item.key;
+ item.children.forEach((child) => {
+ const childLabel = typeof child.label === "string" ? child.label : child.key;
+ pages.push({
+ page: child.page,
+ label: childLabel,
+ group: `${group.groupLabel} > ${parentLabel}`,
+ description: pageDescriptions[child.page] || "No description available",
+ });
+ });
+ }
+ });
+ });
+
+ return pages;
+};