Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
121 changes: 121 additions & 0 deletions docs/my-website/docs/proxy/ui/page_visibility.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
import Image from '@theme/IdealImage';

# Control Page Visibility for Internal Users

Configure which navigation tabs and pages are visible to internal users (non-admin developers) in the LiteLLM UI.

Use this feature to simplify the UI and control which pages your internal users/developers can see when signing in.

## Overview

By default, all pages accessible to internal users are visible in the navigation sidebar. The page visibility control allows admins to restrict which pages internal users can see, creating a more focused and streamlined experience.


## Configure Page Visibility

### 1. Navigate to Settings

Click the **Settings** icon in the sidebar.

![Navigate to Settings](https://colony-recorder.s3.amazonaws.com/files/2026-01-28/cbb6f272-ab18-4996-b57d-7ed4aad721ea/ascreenshot_ab80f3175b1a41b0bdabdd2cd3980573_text_export.jpeg)

### 2. Go to Admin Settings

Click **Admin Settings** from the settings menu.

![Go to Admin Settings](https://colony-recorder.s3.amazonaws.com/files/2026-01-28/e2b327bf-1cfd-4519-a9ce-8a6ecb2de53a/ascreenshot_23bb1577b3f84d22be78e0faa58dee3d_text_export.jpeg)

### 3. Select UI Settings

Click **UI Settings** to access the page visibility controls.

![Select UI Settings](https://colony-recorder.s3.amazonaws.com/files/2026-01-28/fff0366a-4944-457a-8f6a-e22018dde108/ascreenshot_0e268e8651654e75bb9fb40d2ed366a9_text_export.jpeg)

### 4. Open Page Visibility Configuration

Click **Configure Page Visibility** to expand the configuration panel.

![Open Configuration](https://colony-recorder.s3.amazonaws.com/files/2026-01-28/3a4761d6-145a-4afd-8abf-d92744b9ac9f/ascreenshot_23c16eb79c32481887b879d961f1f00a_text_export.jpeg)

### 5. Select Pages to Make Visible

Check the boxes for the pages you want internal users to see. Pages are organized by category for easy navigation.

![Select Pages](https://colony-recorder.s3.amazonaws.com/files/2026-01-28/b9c96b54-6c20-484f-8b0b-3a86decb5717/ascreenshot_3347ade01ebe4ea390bc7b57e53db43f_text_export.jpeg)

**Available pages include:**
- Virtual Keys
- Playground
- Models + Endpoints
- Agents
- MCP Servers
- Search Tools
- Vector Stores
- Logs
- Teams
- Organizations
- Usage
- Budgets
- And more...

### 6. Save Your Configuration

Click **Save Page Visibility Settings** to apply the changes.

![Save Settings](https://colony-recorder.s3.amazonaws.com/files/2026-01-28/8a215378-44f5-4bb8-b984-06fa2aa03903/ascreenshot_44e7aeebe25a477ba92f73a3ed3df644_text_export.jpeg)

### 7. Verify Changes

Internal users will now only see the selected pages in their navigation sidebar.

![Verify Changes](https://colony-recorder.s3.amazonaws.com/files/2026-01-28/493a7718-b276-40b9-970f-5814054932d9/ascreenshot_ad23b8691f824095ba60256f91ad24f8_text_export.jpeg)

## Reset to Default

To restore all pages to internal users:

1. Open the Page Visibility configuration
2. Click **Reset to Default (All Pages)**
3. Click **Save Page Visibility Settings**

This will clear the restriction and show all accessible pages to internal users.

## API Configuration

You can also configure page visibility programmatically using the API:

### Get Current Settings

```bash
curl -X GET 'http://localhost:4000/ui_settings/get' \
-H 'Authorization: Bearer <your-admin-key>'
```

### Update Page Visibility

```bash
curl -X PATCH 'http://localhost:4000/ui_settings/update' \
-H 'Authorization: Bearer <your-admin-key>' \
-H 'Content-Type: application/json' \
-d '{
"enabled_ui_pages_internal_users": [
"api-keys",
"agents",
"mcp-servers",
"logs",
"teams"
]
}'
```

### Clear Page Visibility Restrictions

```bash
curl -X PATCH 'http://localhost:4000/ui_settings/update' \
-H 'Authorization: Bearer <your-admin-key>' \
-H 'Content-Type: application/json' \
-d '{
"enabled_ui_pages_internal_users": null
}'
```

1 change: 1 addition & 0 deletions docs/my-website/docs/routing.md
Original file line number Diff line number Diff line change
Expand Up @@ -828,6 +828,7 @@ asyncio.run(router_acompletion())
```

</TabItem>
</Tabs>

## Traffic Mirroring / Silent Experiments

Expand Down
14 changes: 11 additions & 3 deletions docs/my-website/sidebars.js
Original file line number Diff line number Diff line change
Expand Up @@ -274,11 +274,19 @@ const sidebars = {
"proxy/custom_sso",
"proxy/ai_hub",
"proxy/model_compare_ui",
"proxy/public_teams",
"proxy/self_serve",
"proxy/ui/bulk_edit_users",
"proxy/ui_credentials",
"tutorials/scim_litellm",
{
type: "category",
label: "UI User/Team Management",
items: [
"proxy/access_control",
"proxy/public_teams",
"proxy/self_serve",
"proxy/ui/bulk_edit_users",
"proxy/ui/page_visibility",
]
},
{
type: "category",
label: "UI Usage Tracking",
Expand Down
6 changes: 6 additions & 0 deletions litellm/proxy/ui_crud_endpoints/proxy_setting_endpoints.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,11 @@ class UISettings(BaseModel):
description="Prevents Team Admins from deleting users from the teams they manage. Useful for SCIM provisioning where team membership is defined externally.",
)

enabled_ui_pages_internal_users: Optional[List[str]] = Field(
default=None,
description="List of page keys that internal users (non-admins) can see in the UI sidebar. If not set, all pages are visible based on role permissions.",
)


class UISettingsResponse(SettingsResponse):
"""Response model for UI settings"""
Expand All @@ -89,6 +94,7 @@ class UISettingsResponse(SettingsResponse):
ALLOWED_UI_SETTINGS_FIELDS = {
"disable_model_add_for_internal_users",
"disable_team_admin_delete_team_user",
"enabled_ui_pages_internal_users",
}


Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -7,7 +12,44 @@ interface SidebarProviderProps {
}

const SidebarProvider = ({ setPage, defaultSelectedKey, sidebarCollapsed }: SidebarProviderProps) => {
return <Sidebar setPage={setPage} defaultSelectedKey={defaultSelectedKey} collapsed={sidebarCollapsed} />;
const { accessToken } = useAuthorized();
const [enabledPagesInternalUsers, setEnabledPagesInternalUsers] = useState<string[] | null>(null);

useEffect(() => {
const fetchUISettings = async () => {
if (!accessToken) {
console.log("[SidebarProvider] No access token, skipping UI settings fetch");
return;
}

try {
console.log("[SidebarProvider] Fetching UI settings from /get/ui_settings");
const settings = await getUISettings(accessToken);
console.log("[SidebarProvider] UI settings response:", settings);

// API returns 'values' not 'settings'
if (settings?.values?.enabled_ui_pages_internal_users !== undefined) {
console.log("[SidebarProvider] Setting enabled pages:", settings.values.enabled_ui_pages_internal_users);
setEnabledPagesInternalUsers(settings.values.enabled_ui_pages_internal_users);
} else {
console.log("[SidebarProvider] No enabled_ui_pages_internal_users in response (all pages visible by default)");
}
} catch (error) {
console.error("[SidebarProvider] Failed to fetch UI settings:", error);
}
};

fetchUISettings();
}, [accessToken]);

return (
<Sidebar
setPage={setPage}
defaultSelectedKey={defaultSelectedKey}
collapsed={sidebarCollapsed}
enabledPagesInternalUsers={enabledPagesInternalUsers}
/>
);
};

export default SidebarProvider;
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
"use client";

import { getAvailablePages } from "@/components/page_utils";
import { Button, Checkbox, Collapse, Space, Tag, Typography } from "antd";
import { useMemo, useState } from "react";

interface PageVisibilitySettingsProps {
enabledPagesInternalUsers: string[] | null | undefined;
enabledPagesPropertyDescription?: string;
isUpdating: boolean;
onUpdate: (settings: { enabled_ui_pages_internal_users: string[] | null }) => void;
}

export default function PageVisibilitySettings({
enabledPagesInternalUsers,
enabledPagesPropertyDescription,
isUpdating,
onUpdate,
}: PageVisibilitySettingsProps) {
// Check if page visibility is set (null/undefined means "not set" = all pages visible)
const isPageVisibilitySet = enabledPagesInternalUsers !== null && enabledPagesInternalUsers !== undefined;

// Get available pages from leftnav configuration
const availablePages = useMemo(() => getAvailablePages(), []);

// Group pages by their group for better UI
const pagesByGroup = useMemo(() => {
const grouped: Record<string, typeof availablePages> = {};
availablePages.forEach((page) => {
if (!grouped[page.group]) {
grouped[page.group] = [];
}
grouped[page.group].push(page);
});
return grouped;
}, [availablePages]);

// Local state for page selection
const [selectedPages, setSelectedPages] = useState<string[]>(enabledPagesInternalUsers || []);

// Update local state when data changes
useMemo(() => {
if (enabledPagesInternalUsers) {
setSelectedPages(enabledPagesInternalUsers);
} else {
setSelectedPages([]);
}
}, [enabledPagesInternalUsers]);

const handleSavePageVisibility = () => {
onUpdate({ enabled_ui_pages_internal_users: selectedPages.length > 0 ? selectedPages : null });
};

const handleResetToDefault = () => {
setSelectedPages([]);
onUpdate({ enabled_ui_pages_internal_users: null });
};

return (
<Space direction="vertical" size="middle" style={{ width: "100%" }}>
<Space direction="vertical" size={4}>
<Space align="center">
<Typography.Text strong>Internal User Page Visibility</Typography.Text>
{!isPageVisibilitySet && (
<Tag color="default" style={{ marginLeft: "8px" }}>
Not set (all pages visible)
</Tag>
)}
{isPageVisibilitySet && (
<Tag color="blue" style={{ marginLeft: "8px" }}>
{selectedPages.length} page{selectedPages.length !== 1 ? "s" : ""} selected
</Tag>
)}
</Space>
{enabledPagesPropertyDescription && (
<Typography.Text type="secondary">{enabledPagesPropertyDescription}</Typography.Text>
)}
<Typography.Text type="secondary" style={{ fontSize: "12px", fontStyle: "italic" }}>
By default, all pages are visible to internal users. Select specific pages to restrict visibility.
</Typography.Text>
<Typography.Text type="secondary" style={{ fontSize: "12px", color: "#8b5cf6" }}>
Note: Only pages accessible to internal user roles are shown here. Admin-only pages are excluded as they
cannot be made visible to internal users regardless of this setting.
</Typography.Text>
</Space>

<Collapse
items={[
{
key: "page-visibility",
label: "Configure Page Visibility",
children: (
<Space direction="vertical" size="middle" style={{ width: "100%" }}>
<Checkbox.Group value={selectedPages} onChange={setSelectedPages} style={{ width: "100%" }}>
<Space direction="vertical" size="middle" style={{ width: "100%" }}>
{Object.entries(pagesByGroup).map(([groupName, pages]) => (
<div key={groupName}>
<Typography.Text
strong
style={{
fontSize: "11px",
color: "#6b7280",
letterSpacing: "0.05em",
display: "block",
marginBottom: "8px",
}}
>
{groupName}
</Typography.Text>
<Space direction="vertical" size="small" style={{ marginLeft: "16px", width: "100%" }}>
{pages.map((page) => (
<div key={page.page} style={{ marginBottom: "4px" }}>
<Checkbox value={page.page}>
<Space direction="vertical" size={0}>
<Typography.Text>{page.label}</Typography.Text>
<Typography.Text type="secondary" style={{ fontSize: "12px" }}>
{page.description}
</Typography.Text>
</Space>
</Checkbox>
</div>
))}
</Space>
</div>
))}
</Space>
</Checkbox.Group>

<Space>
<Button type="primary" onClick={handleSavePageVisibility} loading={isUpdating} disabled={isUpdating}>
Save Page Visibility Settings
</Button>
{isPageVisibilitySet && (
<Button onClick={handleResetToDefault} loading={isUpdating} disabled={isUpdating}>
Reset to Default (All Pages)
</Button>
)}
</Space>
</Space>
),
},
]}
/>
</Space>
);
}
Loading
Loading