-
+
+
+
+
+
+
+
+ {CONTENT_WIDTHS.map((width) => (
+
-
-
-
}>
-
-
-
-
}>
-
-
-
-
}>
-
-
-
-
}>
-
-
-
-
+ ))}
+
+
);
}
diff --git a/ui/litellm-dashboard/src/components/Settings/AdminSettings/UISettings/PageVisibilitySettings.test.tsx b/ui/litellm-dashboard/src/components/Settings/AdminSettings/UISettings/PageVisibilitySettings.test.tsx
index a3245b7e76b..3c64938d06a 100644
--- a/ui/litellm-dashboard/src/components/Settings/AdminSettings/UISettings/PageVisibilitySettings.test.tsx
+++ b/ui/litellm-dashboard/src/components/Settings/AdminSettings/UISettings/PageVisibilitySettings.test.tsx
@@ -1,6 +1,8 @@
import { describe, it, expect, vi } from "vitest";
-import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
+
+import { renderWithProviders, screen } from "@/../tests/test-utils";
+
import PageVisibilitySettings from "./PageVisibilitySettings";
vi.mock("@/components/page_utils", () => ({
@@ -13,26 +15,32 @@ vi.mock("@/components/page_utils", () => ({
describe("PageVisibilitySettings", () => {
it("should render the not-set tag when enabledPagesInternalUsers is null", () => {
- render(
);
+ renderWithProviders(
+
,
+ );
expect(screen.getByText("Not set (all pages visible)")).toBeInTheDocument();
});
it("should show the selected page count tag when pages are configured", () => {
- render(
+ renderWithProviders(
,
);
expect(screen.getByText("2 pages selected")).toBeInTheDocument();
});
it("should show singular 'page' when exactly one page is selected", () => {
- render(
);
+ renderWithProviders(
+
,
+ );
expect(screen.getByText("1 page selected")).toBeInTheDocument();
});
it("should call onUpdate with null when reset button is clicked", async () => {
const onUpdate = vi.fn();
const user = userEvent.setup();
- render(
);
+ renderWithProviders(
+
,
+ );
// Expand the collapse panel first to reveal the reset button
await user.click(screen.getByRole("button", { name: /configure page visibility/i }));
@@ -41,8 +49,34 @@ describe("PageVisibilitySettings", () => {
expect(onUpdate).toHaveBeenCalledWith({ enabled_ui_pages_internal_users: null });
});
+ it("should render every page under its original group when Object.groupBy is unavailable", async () => {
+ const groupByDescriptor = Object.getOwnPropertyDescriptor(Object, "groupBy");
+ Object.defineProperty(Object, "groupBy", { configurable: true, value: undefined });
+
+ try {
+ const user = userEvent.setup();
+ renderWithProviders(
+
,
+ );
+
+ await user.click(screen.getByRole("button", { name: /configure page visibility/i }));
+
+ expect(screen.getByRole("group", { name: "Analytics" })).toBeInTheDocument();
+ expect(screen.getByRole("group", { name: "Access" })).toBeInTheDocument();
+ expect(screen.getByRole("checkbox", { name: /usage/i })).toBeInTheDocument();
+ expect(screen.getByRole("checkbox", { name: /models/i })).toBeInTheDocument();
+ expect(screen.getByRole("checkbox", { name: /api keys/i })).toBeInTheDocument();
+ } finally {
+ if (groupByDescriptor) {
+ Object.defineProperty(Object, "groupBy", groupByDescriptor);
+ } else {
+ Reflect.deleteProperty(Object, "groupBy");
+ }
+ }
+ });
+
it("should display the property description when provided", () => {
- render(
+ renderWithProviders(
getAvailablePages(), []);
-
- // Group pages by their group for better UI
const pagesByGroup = useMemo(() => {
const grouped: Record = {};
availablePages.forEach((page) => {
@@ -34,19 +34,16 @@ export default function PageVisibilitySettings({
});
return grouped;
}, [availablePages]);
-
- // Local state for page selection
const [selectedPages, setSelectedPages] = useState(enabledPagesInternalUsers || []);
- // Update local state when data changes
useMemo(() => {
- if (enabledPagesInternalUsers) {
- setSelectedPages(enabledPagesInternalUsers);
- } else {
- setSelectedPages([]);
- }
+ setSelectedPages(enabledPagesInternalUsers || []);
}, [enabledPagesInternalUsers]);
+ const togglePage = (page: string, checked: boolean) => {
+ setSelectedPages((current) => (checked ? [...current, page] : current.filter((item) => item !== page)));
+ };
+
const handleSavePageVisibility = () => {
onUpdate({ enabled_ui_pages_internal_users: selectedPages.length > 0 ? selectedPages : null });
};
@@ -57,90 +54,74 @@ export default function PageVisibilitySettings({
};
return (
-
-
-
- Internal User Page Visibility
- {!isPageVisibilitySet && (
-
- Not set (all pages visible)
-
- )}
- {isPageVisibilitySet && (
-
- {selectedPages.length} page{selectedPages.length !== 1 ? "s" : ""} selected
-
- )}
-
+
+
+
+
Internal User Page Visibility
+
+ {isPageVisibilitySet
+ ? `${selectedPages.length} page${selectedPages.length !== 1 ? "s" : ""} selected`
+ : "Not set (all pages visible)"}
+
+
{enabledPagesPropertyDescription && (
-
{enabledPagesPropertyDescription}
+
{enabledPagesPropertyDescription}
)}
-
+
By default, all pages are visible to internal users. Select specific pages to restrict visibility.
-
-
+
+
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.
-
-
+
+
-
-
-
- {Object.entries(pagesByGroup).map(([groupName, pages]) => (
-
-
- {groupName}
-
-
- {pages.map((page) => (
-
-
-
- {page.label}
-
- {page.description}
-
-
-
-
- ))}
-
-
- ))}
-
-
+
+
+ Configure Page Visibility
+
+
+
+
+ {Object.entries(pagesByGroup).map(([groupName, pages]) => (
+
+
+ {groupName}
+
+
+ {pages.map((page) => {
+ const checkboxId = `page-visibility-${page.page}`;
+ return (
+
+ togglePage(page.page, checked === true)}
+ />
+
+ {page.label}
+ {page.description}
+
+
+ );
+ })}
+
+
+ ))}
-
-
- Save Page Visibility Settings
-
- {isPageVisibilitySet && (
-
- Reset to Default (All Pages)
-
- )}
-
-
- ),
- },
- ]}
- />
-
+
+
+ Save Page Visibility Settings
+
+ {isPageVisibilitySet && (
+
+ Reset to Default (All Pages)
+
+ )}
+
+
+
+
+
);
}
diff --git a/ui/litellm-dashboard/src/components/Settings/AdminSettings/UISettings/UISettings.tsx b/ui/litellm-dashboard/src/components/Settings/AdminSettings/UISettings/UISettings.tsx
index ec970c34873..3959fd9f012 100644
--- a/ui/litellm-dashboard/src/components/Settings/AdminSettings/UISettings/UISettings.tsx
+++ b/ui/litellm-dashboard/src/components/Settings/AdminSettings/UISettings/UISettings.tsx
@@ -4,8 +4,46 @@ import { useUISettings } from "@/app/(dashboard)/hooks/uiSettings/useUISettings"
import { useUpdateUISettings } from "@/app/(dashboard)/hooks/uiSettings/useUpdateUISettings";
import useAuthorized from "@/app/(dashboard)/hooks/useAuthorized";
import NotificationManager from "@/components/molecules/notifications_manager";
+import { Alert, AlertDescription, AlertTitle } from "@/components/shared/Alert";
+import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
+import { Separator } from "@/components/ui/separator";
+import { Skeleton } from "@/components/ui/skeleton";
+import { Switch } from "@/components/ui/switch";
import PageVisibilitySettings from "./PageVisibilitySettings";
-import { Alert, Card, Divider, Skeleton, Space, Switch, Typography } from "antd";
+
+interface SettingRowProps {
+ ariaLabel: string;
+ checked: boolean;
+ description?: string;
+ disabled: boolean;
+ indented?: boolean;
+ label: string;
+ muted?: boolean;
+ onCheckedChange: (checked: boolean) => void;
+}
+
+function SettingRow({
+ ariaLabel,
+ checked,
+ description,
+ disabled,
+ indented = false,
+ label,
+ muted = false,
+ onCheckedChange,
+}: SettingRowProps) {
+ return (
+
+
+
+
+ {label}
+
+ {description &&
{description}
}
+
+
+ );
+}
export default function UISettings() {
const { accessToken } = useAuthorized();
@@ -229,270 +267,181 @@ export default function UISettings() {
};
return (
-
- {isLoading ? (
-
- ) : isError ? (
-
- ) : (
-
- {schema?.description && (
- {schema.description}
- )}
+
+
+
+ UI Settings
+
+
+
+ {isLoading ? (
+
+
+
+
+
+ ) : isError ? (
+
+ Could not load UI settings
+ {error instanceof Error && {error.message} }
+
+ ) : (
+
+ {schema?.description &&
{schema.description}
}
+ {updateError && (
+
+ Could not update UI settings
+ {updateError instanceof Error && {updateError.message} }
+
+ )}
- {updateError && (
-
- )}
-
-
-
-
- Disable model add for internal users
- {property?.description && {property.description} }
-
-
-
-
-
-
- Disable team admin delete team user
- {disableTeamAdminDeleteProperty?.description && (
- {disableTeamAdminDeleteProperty.description}
- )}
-
-
-
-
-
-
- Require authentication for public AI Hub
- {requireAuthForPublicAIHubProperty?.description && (
- {requireAuthForPublicAIHubProperty.description}
- )}
-
-
-
-
-
-
- Forward client headers to LLM API
-
- {forwardClientHeadersProperty?.description ??
- "Forwards client headers (Authorization, anthropic-beta, and x-* custom headers) to the upstream LLM. Enable for Claude Code with a Max subscription (forwards the OAuth token) or to pass custom/tracing headers through to the provider. Independent of the BYOK toggle — enable only the one(s) you need."}
-
-
-
-
-
-
-
- Forward LLM provider auth headers
-
- {forwardLLMProviderAuthHeadersProperty?.description ??
- "Forwards provider auth headers (x-api-key, x-goog-api-key, api-key, ocp-apim-subscription-key) to the upstream LLM, overriding any deployment-configured key for that request. Enable for Claude Code BYOK (clients bring their own API key). Independent of the client-headers toggle — enable only the one(s) you need."}
-
-
-
-
- {enableProjectsUIProperty && (
-
-
-
- [BETA] Enable Projects (page will refresh)
-
- {enableProjectsUIProperty.description ??
- "If enabled, shows the Projects feature in the UI sidebar and the project field in key management."}
-
-
-
- )}
-
-
-
-
- [BETA] Enable Chat page (page will refresh)
-
- {enableChatUIProperty?.description ??
- "If enabled, shows the Chat page in the UI sidebar, letting users chat with an LLM and connect their own MCP server credentials via OAuth."}
-
-
-
-
-
- {/* Agents access control */}
-
-
+
-
- Disable agents for internal users
- {disableAgentsProperty?.description && (
- {disableAgentsProperty.description}
- )}
-
-
-
-
-
-
-
- Allow agents for team admins
-
- {allowAgentsTeamAdminsProperty?.description && (
- {allowAgentsTeamAdminsProperty.description}
- )}
-
-
-
-
- {/* Vector Stores access control */}
-
-
+
-
- Disable vector stores for internal users
- {disableVectorStoresProperty?.description && (
- {disableVectorStoresProperty.description}
- )}
-
-
-
-
-
-
-
- Allow vector stores for team admins
-
- {allowVectorStoresTeamAdminsProperty?.description && (
- {allowVectorStoresTeamAdminsProperty.description}
- )}
-
-
-
-
- {/* Scope user search to organization */}
-
-
+
-
- Scope user search to organization
-
- {scopeUserSearchProperty?.description ??
- "If enabled, the user search endpoint restricts results by organization. When off, any authenticated user can search all users."}
-
-
-
-
-
- {/* Disable custom Virtual key values */}
-
-
+
-
- Disable custom Virtual key values
-
- {disableCustomApiKeysProperty?.description ??
- "If true, users cannot specify custom key values. All keys must be auto-generated."}
-
-
-
-
-
- {/* Page Visibility for Internal Users */}
-
-
- )}
+
+
+
+ )}
+
);
}