diff --git a/litellm/proxy/ui_crud_endpoints/proxy_setting_endpoints.py b/litellm/proxy/ui_crud_endpoints/proxy_setting_endpoints.py index 30ec0766db..f626ad7eb1 100644 --- a/litellm/proxy/ui_crud_endpoints/proxy_setting_endpoints.py +++ b/litellm/proxy/ui_crud_endpoints/proxy_setting_endpoints.py @@ -83,6 +83,11 @@ class UISettings(BaseModel): 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.", ) + require_auth_for_public_ai_hub: bool = Field( + default=False, + description="If true, requires authentication for accessing the public AI Hub." + ) + class UISettingsResponse(SettingsResponse): """Response model for UI settings""" @@ -95,6 +100,7 @@ ALLOWED_UI_SETTINGS_FIELDS = { "disable_model_add_for_internal_users", "disable_team_admin_delete_team_user", "enabled_ui_pages_internal_users", + "require_auth_for_public_ai_hub", } diff --git a/ui/litellm-dashboard/src/components/Settings/AdminSettings/UISettings/UISettings.test.tsx b/ui/litellm-dashboard/src/components/Settings/AdminSettings/UISettings/UISettings.test.tsx index 31dcfc102e..639564bbd3 100644 --- a/ui/litellm-dashboard/src/components/Settings/AdminSettings/UISettings/UISettings.test.tsx +++ b/ui/litellm-dashboard/src/components/Settings/AdminSettings/UISettings/UISettings.test.tsx @@ -20,6 +20,13 @@ vi.mock("@/app/(dashboard)/hooks/uiSettings/useUpdateUISettings", () => ({ useUpdateUISettings: mockUseUpdateUISettings, })); +vi.mock("@/components/molecules/notifications_manager", () => ({ + default: { + success: vi.fn(), + fromBackend: vi.fn(), + }, +})); + const buildSettingsResponse = (overrides?: Partial>) => ({ data: { field_schema: { @@ -28,10 +35,18 @@ const buildSettingsResponse = (overrides?: Partial>) => disable_model_add_for_internal_users: { description: "Disable model add for internal users", }, + disable_team_admin_delete_team_user: { + description: "Disable team admin delete team user", + }, + require_auth_for_public_ai_hub: { + description: "Require authentication for public AI Hub", + }, }, }, values: { disable_model_add_for_internal_users: false, + disable_team_admin_delete_team_user: false, + require_auth_for_public_ai_hub: false, }, }, isLoading: false, @@ -57,6 +72,8 @@ describe("UISettings", () => { expect(screen.getByText("UI Settings")).toBeInTheDocument(); expect(screen.getByRole("switch", { name: "Disable model add for internal users" })).toBeInTheDocument(); + expect(screen.getByRole("switch", { name: "Disable team admin delete team user" })).toBeInTheDocument(); + expect(screen.getByRole("switch", { name: "Require authentication for public AI Hub" })).toBeInTheDocument(); }); it("should toggle setting and call update", () => { @@ -87,4 +104,62 @@ describe("UISettings", () => { ); expect(NotificationManager.success).toHaveBeenCalledWith("UI settings updated successfully"); }); + + it("should toggle disable team admin delete team user setting and call update", () => { + const mutateMock = vi.fn((_settings, options) => { + options?.onSuccess?.(); + }); + + mockUseUpdateUISettings.mockReturnValue({ + mutate: mutateMock, + isPending: false, + error: null, + }); + + render(); + + const toggle = screen.getByRole("switch", { name: "Disable team admin delete team user" }); + + act(() => { + fireEvent.click(toggle); + }); + + expect(mutateMock).toHaveBeenCalledWith( + { disable_team_admin_delete_team_user: true }, + expect.objectContaining({ + onSuccess: expect.any(Function), + onError: expect.any(Function), + }), + ); + expect(NotificationManager.success).toHaveBeenCalledWith("UI settings updated successfully"); + }); + + it("should toggle require auth for public AI Hub setting and call update", () => { + const mutateMock = vi.fn((_settings, options) => { + options?.onSuccess?.(); + }); + + mockUseUpdateUISettings.mockReturnValue({ + mutate: mutateMock, + isPending: false, + error: null, + }); + + render(); + + const toggle = screen.getByRole("switch", { name: "Require authentication for public AI Hub" }); + + act(() => { + fireEvent.click(toggle); + }); + + expect(mutateMock).toHaveBeenCalledWith( + { require_auth_for_public_ai_hub: true }, + expect.objectContaining({ + onSuccess: expect.any(Function), + onError: expect.any(Function), + }), + ); + expect(NotificationManager.success).toHaveBeenCalledWith("UI settings updated successfully"); + }); }); 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 6cc9cbf430..a43f0e9d42 100644 --- a/ui/litellm-dashboard/src/components/Settings/AdminSettings/UISettings/UISettings.tsx +++ b/ui/litellm-dashboard/src/components/Settings/AdminSettings/UISettings/UISettings.tsx @@ -15,6 +15,7 @@ export default function UISettings() { const schema = data?.field_schema; const property = schema?.properties?.disable_model_add_for_internal_users; const disableTeamAdminDeleteProperty = schema?.properties?.disable_team_admin_delete_team_user; + const requireAuthForPublicAIHubProperty = schema?.properties?.require_auth_for_public_ai_hub; const enabledPagesProperty = schema?.properties?.enabled_ui_pages_internal_users; const values = data?.values ?? {}; const isDisabledForInternalUsers = Boolean(values.disable_model_add_for_internal_users); @@ -59,6 +60,20 @@ export default function UISettings() { }); }; + const handleToggleRequireAuthForPublicAIHub = (checked: boolean) => { + updateSettings( + { require_auth_for_public_ai_hub: checked }, + { + onSuccess: () => { + NotificationManager.success("UI settings updated successfully"); + }, + onError: (error) => { + NotificationManager.fromBackend(error); + }, + }, + ); + }; + return ( {isLoading ? ( @@ -113,6 +128,22 @@ export default function UISettings() { + + + + Require authentication for public AI Hub + {requireAuthForPublicAIHubProperty?.description && ( + {requireAuthForPublicAIHubProperty.description} + )} + + + {/* Page Visibility for Internal Users */}