From 7edf3a9cb55548b143df1692f4ed7c4681d7fcf7 Mon Sep 17 00:00:00 2001 From: ryan-crabbe-berri Date: Thu, 4 Jun 2026 11:37:54 -0700 Subject: [PATCH] style(ui): run prettier --write across the dashboard (#29622) Formatting-only pass; no logic changes. Brings the UI into compliance with .prettierrc so the new format-check CI job passes --- ui/litellm-dashboard/e2e_tests/globalSetup.ts | 7 +- .../e2e_tests/helpers/navigation.ts | 4 +- .../e2e_tests/playwright.config.ts | 2 +- .../e2e_tests/tests/auth/logout.spec.ts | 9 +- .../tests/auth/proxyLogoutUrl.spec.ts | 16 +- .../tests/internal-user/internalUser.spec.ts | 12 +- .../internalUserWithTeams.spec.ts | 6 +- .../internal-viewer/internalViewer.spec.ts | 6 +- .../tests/login/internalUserIdentity.spec.ts | 4 +- .../e2e_tests/tests/login/login.spec.ts | 9 +- .../e2e_tests/tests/mcp/mcpServers.spec.ts | 6 +- .../e2e_tests/tests/modelHub/modelHub.spec.ts | 5 +- .../tests/modelsPage/addModel.spec.ts | 33 +- .../modelsPage/clearCustomPricing.spec.ts | 56 +- .../tests/navigation/sidebar.spec.ts | 10 +- .../e2e_tests/tests/proxy-admin/keys.spec.ts | 8 +- .../tests/proxy-admin/license.spec.ts | 9 +- .../e2e_tests/tests/proxy-admin/teams.spec.ts | 10 +- .../tests/settings/routerSettings.spec.ts | 9 +- .../tests/team-admin/teamAdmin.spec.ts | 12 +- ui/litellm-dashboard/knip.json | 13 +- .../src/app/(dashboard)/README.md | 8 +- .../app/(dashboard)/components/Sidebar2.tsx | 1 - .../components/SidebarProvider.tsx | 6 +- .../accessGroups/useAccessGroupDetails.ts | 20 +- .../hooks/accessGroups/useAccessGroups.ts | 14 +- .../accessGroups/useCreateAccessGroup.ts | 7 +- .../accessGroups/useDeleteAccessGroup.ts | 12 +- .../hooks/accessGroups/useEditAccessGroup.ts | 7 +- .../cloudzero/useCloudZeroCreate.test.ts | 35 +- .../cloudzero/useCloudZeroDryRun.test.ts | 35 +- .../cloudzero/useCloudZeroExport.test.ts | 35 +- .../hooks/common/queryKeysFactory.test.ts | 6 +- .../configOverrides/hashicorpVaultApi.ts | 17 +- .../hooks/guardrails/useGuardrails.test.ts | 8 +- .../hooks/guardrails/useRegisterGuardrail.ts | 7 +- .../useHealthReadinessDetails.ts | 13 +- .../hooks/keys/useKeyAliases.test.ts | 12 +- .../(dashboard)/hooks/keys/useKeyAliases.ts | 14 +- .../(dashboard)/hooks/keys/useKeys.test.ts | 15 +- .../src/app/(dashboard)/hooks/keys/useKeys.ts | 18 +- .../hooks/keys/useResetKeySpend.ts | 12 +- .../hooks/logDetails/useLogDetails.ts | 6 +- .../useMCPSemanticFilterSettings.ts | 4 +- .../useUpdateMCPSemanticFilterSettings.ts | 4 +- .../mcpServers/useMCPAccessGroups.test.ts | 2 +- .../hooks/mcpServers/useMCPServerHealth.ts | 36 +- .../hooks/mcpServers/useMCPServers.test.ts | 2 +- .../app/(dashboard)/hooks/models/useModels.ts | 27 +- .../hooks/projects/useCreateProject.test.ts | 4 +- .../hooks/projects/useCreateProject.ts | 12 +- .../hooks/projects/useDeleteProject.test.ts | 4 +- .../hooks/projects/useDeleteProject.ts | 12 +- .../hooks/projects/useProjectDetails.ts | 20 +- .../(dashboard)/hooks/projects/useProjects.ts | 11 +- .../hooks/projects/useUpdateProject.test.ts | 6 +- .../hooks/projects/useUpdateProject.ts | 13 +- .../storeModelInDB/useStoreModelInDB.test.ts | 9 +- .../hooks/storeModelInDB/useStoreModelInDB.ts | 8 +- .../useStoreRequestInSpendLogs.ts | 2 +- .../(dashboard)/hooks/teams/useTeams.test.ts | 25 +- .../app/(dashboard)/hooks/teams/useTeams.ts | 24 +- .../(dashboard)/hooks/useAuthorized.test.ts | 14 +- .../(dashboard)/hooks/users/useUsers.test.ts | 70 +- .../app/(dashboard)/hooks/users/useUsers.ts | 13 +- .../src/app/(dashboard)/layout.tsx | 8 +- .../ModelsAndEndpointsView.tsx | 6 +- .../components/AllModelsTab.test.tsx | 300 ++- .../components/AllModelsTab.tsx | 78 +- .../src/app/(dashboard)/playground/page.tsx | 74 +- .../src/app/login/LoginPage.test.tsx | 3 +- .../src/app/login/LoginPage.tsx | 19 +- .../src/app/mcp/oauth/callback/page.tsx | 12 +- .../src/app/model_hub_table/page.tsx | 4 +- .../onboarding/OnboardingErrorView.test.tsx | 4 +- .../src/app/onboarding/OnboardingForm.tsx | 10 +- .../onboarding/OnboardingFormBody.test.tsx | 8 +- .../src/app/onboarding/OnboardingFormBody.tsx | 36 +- .../src/app/onboarding/page.tsx | 6 +- ui/litellm-dashboard/src/app/page.tsx | 475 ++-- .../AIHub/AgentHubTableColumns.test.tsx | 10 +- .../AIHub/ClaudeCodeMarketplaceTab.tsx | 39 +- .../components/AIHub/ModelHubTable.test.tsx | 34 +- .../src/components/AIHub/ModelHubTable.tsx | 4 +- .../components/AIHub/SkillHubDashboard.tsx | 12 +- .../AIHub/marketplace_table_columns.tsx | 10 +- .../AccessGroupsDetailsPage.test.tsx | 108 +- .../AccessGroups/AccessGroupsDetailsPage.tsx | 79 +- .../AccessGroupsModal/AccessGroupBaseForm.tsx | 20 +- .../AccessGroupCreateModal.tsx | 11 +- .../AccessGroupEditModal.tsx | 17 +- .../AccessGroups/AccessGroupsPage.test.tsx | 68 +- .../AccessGroups/AccessGroupsPage.tsx | 104 +- .../src/components/AccessGroups/types.ts | 58 +- .../src/components/BulkEditUsers.test.tsx | 12 +- .../src/components/BulkEditUsers.tsx | 12 +- .../add_margin_form.test.tsx | 37 +- .../CostTrackingSettings/add_margin_form.tsx | 17 +- .../add_provider_form.test.tsx | 19 +- .../add_provider_form.tsx | 11 +- .../cost_tracking_settings.test.tsx | 34 +- .../cost_tracking_settings.tsx | 90 +- .../CostTrackingSettings/how_it_works.tsx | 202 +- .../components/CostTrackingSettings/index.ts | 9 +- .../pricing_calculator/index.test.tsx | 28 +- .../pricing_calculator/index.tsx | 31 +- .../multi_cost_results.test.tsx | 59 +- .../pricing_calculator/multi_cost_results.tsx | 73 +- .../multi_export_dropdown.test.tsx | 6 +- .../multi_export_dropdown.tsx | 8 +- .../multi_export_utils.test.ts | 66 +- .../pricing_calculator/multi_export_utils.ts | 19 +- .../pricing_calculator/types.ts | 1 - .../use_multi_cost_estimate.test.ts | 32 +- .../use_multi_cost_estimate.ts | 10 +- .../provider_discount_table.test.tsx | 40 +- .../provider_discount_table.tsx | 5 +- .../provider_display_helpers.ts | 7 +- .../provider_margin_table.test.tsx | 42 +- .../provider_margin_table.tsx | 10 +- .../components/CostTrackingSettings/types.ts | 1 - .../use_discount_config.test.ts | 12 +- .../use_discount_config.ts | 164 +- .../use_margin_config.test.ts | 12 +- .../CostTrackingSettings/use_margin_config.ts | 199 +- .../src/components/CreateUserButton.test.tsx | 107 +- .../src/components/CreateUserButton.tsx | 26 +- .../src/components/DebugWarningBanner.tsx | 7 +- .../src/components/DefaultUserSettings.tsx | 11 +- .../DeletedKeysPage/DeletedKeysPage.tsx | 6 +- .../DeletedKeysTable/DeletedKeysTable.tsx | 53 +- .../DeletedTeamsPage/DeletedTeamsPage.tsx | 12 +- .../DeletedTeamsTable.test.tsx | 8 +- .../DeletedTeamsTable/DeletedTeamsTable.tsx | 51 +- .../ExportFormatSelector.tsx | 1 - .../EntityUsageExport/ExportSummary.test.tsx | 20 +- .../EntityUsageExport/ExportSummary.tsx | 1 - .../ExportTypeSelector.test.tsx | 20 +- .../EntityUsageExport/UsageExportHeader.tsx | 2 +- .../src/components/EntityUsageExport/index.ts | 1 - .../EntityUsageExport/utils.test.ts | 1 - .../src/components/EntityUsageExport/utils.ts | 17 +- .../src/components/GuardrailSettingsView.tsx | 11 +- .../EvaluationSettingsModal.tsx | 10 +- .../GuardrailConfig.test.tsx | 4 +- .../GuardrailsMonitor/GuardrailConfig.tsx | 26 +- .../GuardrailsMonitor/GuardrailDetail.tsx | 38 +- .../GuardrailsMonitorView.test.tsx | 11 +- .../GuardrailsMonitorView.tsx | 11 +- .../GuardrailsMonitor/GuardrailsOverview.tsx | 71 +- .../GuardrailsMonitor/LogViewer.tsx | 37 +- .../GuardrailsMonitor/MetricCard.test.tsx | 12 +- .../GuardrailsMonitor/MetricCard.tsx | 12 +- .../GuardrailsMonitor/ScoreChart.tsx | 4 +- .../src/components/HelpLink.test.tsx | 14 +- .../src/components/HelpLink.tsx | 37 +- .../PaginatedKeyAliasSelect.test.tsx | 12 +- .../PaginatedKeyAliasSelect.tsx | 15 +- .../components/MemoryView/MemoryEditModal.tsx | 37 +- .../src/components/MemoryView/MemoryView.tsx | 156 +- .../ModelSelect/ModelSelect.test.tsx | 14 +- .../components/ModelSelect/ModelSelect.tsx | 98 +- .../PaginatedModelSelect.test.tsx | 16 +- .../PaginatedModelSelect.tsx | 23 +- .../Navbar/BlogDropdown/BlogDropdown.test.tsx | 4 +- .../WorkerDropdown/WorkerDropdown.test.tsx | 7 +- .../Navbar/WorkerDropdown/WorkerDropdown.tsx | 4 +- .../src/components/OldTeams.test.tsx | 22 +- .../src/components/OldTeams.tsx | 1531 ++++++------ .../Projects/ProjectDetailsPage.test.tsx | 3 +- .../Projects/ProjectKeysSection.test.tsx | 4 +- .../Projects/ProjectKeysTable.test.tsx | 13 +- .../ProjectModals/CreateProjectModal.tsx | 15 +- .../ProjectModals/EditProjectModal.test.tsx | 24 +- .../ProjectModals/EditProjectModal.tsx | 21 +- .../ProjectModals/ProjectBaseForm.tsx | 131 +- .../ProjectModals/projectFormUtils.ts | 7 +- .../components/Projects/ProjectsPage.test.tsx | 12 +- .../src/components/Projects/ProjectsPage.tsx | 44 +- .../SearchTools/CreateSearchTools.tsx | 10 +- .../SearchTools/SearchConnectionTest.tsx | 20 +- .../SearchTools/SearchToolColumn.tsx | 180 +- .../SearchTools/SearchToolTester.test.tsx | 5 +- .../SearchTools/SearchToolTester.tsx | 106 +- .../SearchTools/SearchToolView.test.tsx | 38 +- .../components/SearchTools/SearchToolView.tsx | 33 +- .../components/SearchTools/SearchTools.tsx | 38 +- .../src/components/SearchTools/index.tsx | 11 +- .../src/components/SearchTools/types.tsx | 1 - .../EditHashicorpVaultModal.tsx | 23 +- .../HashicorpVault/HashicorpVault.tsx | 52 +- .../HashicorpVaultEmptyPlaceholder.test.tsx | 4 +- .../HashicorpVaultEmptyPlaceholder.tsx | 3 +- .../AdminSettings/HashicorpVault/constants.ts | 6 +- .../LoggingSettings/LoggingSettings.tsx | 16 +- .../MCPSemanticFilterSettings.test.tsx | 22 +- .../MCPSemanticFilterSettings.tsx | 23 +- .../MCPSemanticFilterTestPanel.test.tsx | 31 +- .../MCPSemanticFilterTestPanel.tsx | 188 +- .../semanticFilterTestUtils.test.ts | 6 +- .../semanticFilterTestUtils.ts | 14 +- .../AdminSettings/SSOSettings/SSOSettings.tsx | 37 +- .../PageVisibilitySettings.test.tsx | 30 +- .../AdminSettings/UISettings/UISettings.tsx | 8 +- .../RouterSettings/Fallbacks/AddFallbacks.tsx | 17 +- .../Fallbacks/AddFallbacksModal.test.tsx | 4 +- .../Fallbacks/AddFallbacksModal.tsx | 6 +- .../Fallbacks/FallbackGroupConfig.tsx | 31 +- .../Fallbacks/FallbackSelectionForm.test.tsx | 78 +- .../Fallbacks/FallbackSelectionForm.tsx | 10 +- .../Fallbacks/Fallbacks.test.tsx | 17 +- .../RouterSettings/Fallbacks/Fallbacks.tsx | 29 +- .../src/components/TeamSSOSettings.test.tsx | 13 +- .../src/components/TeamSSOSettings.tsx | 8 +- .../src/components/ToolDetail.tsx | 59 +- .../src/components/ToolPolicies.tsx | 19 +- .../ToolPolicies/PolicySelect.test.tsx | 43 +- .../components/ToolPolicies/PolicySelect.tsx | 3 +- .../src/components/ToolPoliciesView.tsx | 16 +- .../src/components/UsageIndicator.tsx | 50 +- .../components/EndpointUsageBarChart.test.tsx | 4 +- .../components/EntityUsage/EntityUsage.tsx | 5 +- .../EntityUsage/SpendByProvider.test.tsx | 8 +- .../EntityUsage/SpendByProvider.tsx | 8 +- .../components/KeyModelUsageView.tsx | 6 +- .../components/UsageAIChatPanel.test.tsx | 9 +- .../UsagePage/components/UsageAIChatPanel.tsx | 66 +- .../components/UsagePageView.test.tsx | 42 +- .../UsageViewSelect/UsageViewSelect.tsx | 11 +- .../hooks/usePaginatedDailyActivity.ts | 13 +- .../VirtualKeysPage/VirtualKeysTable.test.tsx | 40 +- .../VirtualKeysPage/VirtualKeysTable.tsx | 904 ++++--- .../src/components/WebRTCTester.jsx | 295 ++- .../src/components/activity_metrics.test.tsx | 12 +- .../src/components/activity_metrics.tsx | 18 +- .../add_model/AddModelForm.test.tsx | 13 +- .../src/components/add_model/AddModelForm.tsx | 4 +- .../add_model/ComplexityRouterConfig.test.tsx | 40 +- .../add_model/RouterConfigBuilder.test.tsx | 11 +- .../add_model/RouterConfigBuilder.tsx | 22 +- .../add_model/add_auto_router_tab.tsx | 52 +- .../handle_add_auto_router_submit.tsx | 5 +- .../add_model/handle_add_model_submit.tsx | 18 +- .../add_model/model_connection_test.tsx | 6 +- .../src/components/add_pass_through.tsx | 41 +- .../agent_management/AgentSelector.test.tsx | 13 +- .../agent_management/AgentSelector.tsx | 21 +- .../src/components/agents.test.tsx | 4 +- .../src/components/agents.tsx | 26 +- .../src/components/agents/add_agent_form.tsx | 152 +- .../src/components/agents/agent_card.test.tsx | 10 +- .../src/components/agents/agent_card.tsx | 25 +- .../agents/agent_card_discovery.test.tsx | 95 +- .../agents/agent_card_discovery.tsx | 106 +- .../agents/agent_card_grid.test.tsx | 20 +- .../src/components/agents/agent_cost_view.tsx | 13 +- .../agents/agent_discovery_utils.test.ts | 22 +- .../agents/agent_discovery_utils.ts | 24 +- .../components/agents/agent_form_fields.tsx | 404 ++-- .../src/components/agents/agent_info.tsx | 157 +- .../src/components/agents/agent_table.tsx | 32 +- .../src/components/agents/agent_type_utils.ts | 8 +- .../components/agents/cost_config_fields.tsx | 8 +- .../agents/dynamic_agent_form_fields.tsx | 30 +- .../components/alerting/alerting_settings.tsx | 1 - .../src/components/atoms/Tooltip.test.tsx | 6 +- .../components/budgets/budget_panel.test.tsx | 7 +- .../src/components/budgets/budget_panel.tsx | 5 +- .../components/budgets/edit_budget_modal.tsx | 21 +- .../components/bulk_create_users_button.tsx | 30 +- .../cache_settings/CacheFieldGroup.tsx | 11 +- .../src/components/chat/ChatMessages.tsx | 155 +- .../src/components/chat/ChatPage.tsx | 723 ++++-- .../src/components/chat/ConversationList.tsx | 51 +- .../src/components/chat/MCPAppsPanel.tsx | 307 ++- .../src/components/chat/MCPConnectPicker.tsx | 21 +- .../src/components/chat/MCPCredentialsTab.tsx | 35 +- .../src/components/chat/useChatHistory.ts | 142 +- .../src/components/claude_code_plugins.tsx | 21 +- .../MakeSkillPublicForm.tsx | 35 +- .../add_plugin_form.test.tsx | 20 +- .../claude_code_plugins/add_plugin_form.tsx | 82 +- .../claude_code_plugins/helpers.test.ts | 6 +- .../components/claude_code_plugins/helpers.ts | 48 +- .../claude_code_plugins/plugin_info.tsx | 66 +- .../claude_code_plugins/plugin_table.tsx | 124 +- .../claude_code_plugins/skill_detail.tsx | 74 +- .../components/claude_code_plugins/types.ts | 14 +- .../common_components/AccessGroupSelector.tsx | 14 +- .../DefaultProxyAdminTag.tsx | 4 +- .../DeleteResourceModal.test.tsx | 12 +- .../common_components/FilterTeamDropdown.tsx | 7 +- .../KeyLifecycleSettings.test.tsx | 72 +- .../KeyLifecycleSettings.tsx | 4 +- .../common_components/LabeledField.test.tsx | 16 +- .../common_components/MemberTable.tsx | 6 +- .../components/common_components/NewBadge.tsx | 8 +- .../PassThroughGuardrailsSection.tsx | 69 +- .../PassThroughRoutesSelector.tsx | 15 +- .../common_components/ProjectDropdown.tsx | 13 +- .../RateLimitTypeFormItem.test.tsx | 14 +- .../RouterSettingsAccordion.tsx | 16 +- .../TableHeaderSortDropdown.tsx | 5 +- .../common_components/simple_table.tsx | 1 - .../common_components/team_dropdown.tsx | 14 +- .../common_components/team_multi_select.tsx | 14 +- .../src/components/general_settings.tsx | 20 +- .../src/components/guardrails.tsx | 7 +- .../guardrails/GuardrailTestPanel.test.tsx | 3 +- .../guardrails/GuardrailTestPanel.tsx | 20 +- .../guardrails/GuardrailTestPlayground.tsx | 34 +- .../guardrails/GuardrailTestResults.test.tsx | 1 - .../guardrails/GuardrailTestResults.tsx | 22 +- .../guardrails/TeamGuardrailsTab.tsx | 290 +-- .../guardrails/add_guardrail_form.tsx | 51 +- .../content_filter/CategoryTable.tsx | 36 +- .../CompetitorIntentConfiguration.tsx | 55 +- .../ContentCategoryConfiguration.tsx | 76 +- .../ContentFilterConfiguration.tsx | 70 +- .../content_filter/ContentFilterDisplay.tsx | 9 +- .../ContentFilterManager.test.tsx | 74 +- .../content_filter/ContentFilterManager.tsx | 29 +- .../CustomPatternModal.test.tsx | 3 +- .../content_filter/CustomPatternModal.tsx | 19 +- .../content_filter/KeywordModal.tsx | 19 +- .../content_filter/KeywordTable.tsx | 31 +- .../content_filter/PatternModal.test.tsx | 9 +- .../content_filter/PatternModal.tsx | 21 +- .../content_filter/PatternTable.tsx | 44 +- .../custom_code/CustomCodeModal.tsx | 216 +- .../guardrails/edit_guardrail_form.tsx | 12 +- .../guardrails/guardrail_garden.tsx | 33 +- .../guardrails/guardrail_garden_card.tsx | 5 +- .../guardrails/guardrail_garden_data.ts | 18 +- .../guardrails/guardrail_garden_detail.tsx | 40 +- .../guardrails/guardrail_info.test.tsx | 21 +- .../components/guardrails/guardrail_info.tsx | 111 +- .../guardrail_info_helpers.test.tsx | 16 +- .../guardrails/guardrail_info_helpers.tsx | 2 +- .../guardrails/guardrail_optional_params.tsx | 7 +- .../guardrails/guardrail_provider_fields.tsx | 10 +- .../guardrails/llm_judge/LLMJudgeFields.tsx | 24 +- .../ToolPermissionRulesEditor.test.tsx | 4 +- .../ToolPermissionRulesEditor.tsx | 38 +- .../key_team_helpers/BudgetWindowsEditor.tsx | 27 +- .../key_team_helpers/filter_helpers.test.ts | 16 +- .../key_team_helpers/filter_logic.test.tsx | 67 +- .../src/components/leftnav.tsx | 67 +- .../src/components/logging_settings_view.tsx | 16 +- .../src/components/mcp_hub_table_columns.tsx | 17 +- .../MCPToolPermissions.tsx | 8 +- .../mcp_tools/ByokCredentialModal.tsx | 33 +- .../components/mcp_tools/MCPLogoSelector.tsx | 11 +- .../mcp_tools/MCPNetworkSettings.tsx | 17 +- .../MCPPermissionManagement.test.tsx | 63 +- .../mcp_tools/MCPPermissionManagement.tsx | 30 +- .../mcp_tools/MCPStandardsSettings.test.tsx | 10 +- .../mcp_tools/MCPSubmissionsTab.tsx | 73 +- .../mcp_tools/MCPToolArgumentsForm.tsx | 42 +- .../components/mcp_tools/MCPToolsetsTab.tsx | 113 +- .../mcp_tools/McpCrudPermissionPanel.tsx | 90 +- .../components/mcp_tools/OAuthFormFields.tsx | 72 +- .../mcp_tools/OpenAPIFormSection.tsx | 6 +- .../mcp_tools/OpenAPIQuickPicker.tsx | 13 +- .../components/mcp_tools/ToolTestPanel.tsx | 89 +- .../mcp_tools/create_mcp_server.tsx | 57 +- .../mcp_tools/mcp_connection_status.test.tsx | 30 +- .../mcp_tools/mcp_connection_status.tsx | 27 +- .../components/mcp_tools/mcp_discovery.tsx | 24 +- .../mcp_tools/mcp_server_columns.tsx | 12 +- .../components/mcp_tools/mcp_server_edit.tsx | 11 +- .../components/mcp_tools/mcp_server_view.tsx | 90 +- .../components/mcp_tools/mcp_servers.test.tsx | 10 +- .../src/components/mcp_tools/mcp_servers.tsx | 117 +- .../src/components/mcp_tools/mcp_tools.tsx | 305 ++- .../src/components/mcp_tools/utils.test.tsx | 8 +- .../components/model_add/credentials.test.tsx | 4 +- .../src/components/model_add/credentials.tsx | 4 +- .../ModelSettingsModal.test.tsx | 18 +- .../ModelSettingsModal/ModelSettingsModal.tsx | 6 +- .../model_dashboard/all_models_table.tsx | 32 +- .../src/components/model_info_view.test.tsx | 4 +- .../src/components/model_info_view.tsx | 51 +- .../molecules/models/columns.test.tsx | 40 +- .../components/molecules/models/columns.tsx | 769 +++--- .../src/components/networking.tsx | 295 +-- .../components/object_permissions_view.tsx | 6 +- .../organisms/RegenerateKeyModal.tsx | 8 +- .../organisms/create_key_button.test.tsx | 87 +- .../organisms/create_key_button.tsx | 14 +- .../organization/organization_view.tsx | 37 +- .../src/components/page_utils.test.ts | 109 +- .../src/components/page_utils.ts | 6 +- .../src/components/pass_through_info.tsx | 20 +- .../src/components/pass_through_settings.tsx | 8 +- .../permissions/AgentPermissions.tsx | 17 +- .../permissions/MCPServerPermissions.test.tsx | 25 +- .../permissions/MCPServerPermissions.tsx | 111 +- .../playground/chat_ui/A2AMetrics.tsx | 12 +- .../chat_ui/AdditionalModelSettings.test.tsx | 12 +- .../chat_ui/AdditionalModelSettings.tsx | 9 +- .../playground/chat_ui/AgentBuilderView.tsx | 77 +- .../chat_ui/ChatMessageBubble.test.tsx | 48 +- .../playground/chat_ui/ChatMessageBubble.tsx | 8 +- .../playground/chat_ui/ChatUI.test.tsx | 8 +- .../components/playground/chat_ui/ChatUI.tsx | 2136 ++++++++--------- .../chat_ui/CodeInterpreterOutput.tsx | 54 +- .../chat_ui/CodeInterpreterTool.tsx | 2 +- .../playground/chat_ui/CodeSnippets.tsx | 2 +- .../chat_ui/FilePreviewCard.test.tsx | 32 +- .../playground/chat_ui/FilePreviewCard.tsx | 4 +- .../playground/chat_ui/RealtimePlayground.tsx | 59 +- .../chat_ui/SearchResultsDisplay.tsx | 5 +- .../playground/chat_ui/useChatHistory.test.ts | 8 +- .../playground/chat_ui/useChatHistory.ts | 8 +- .../playground/chat_ui/useCodeInterpreter.ts | 2 +- .../playground/compareUI/CompareUI.test.tsx | 2 +- .../playground/compareUI/CompareUI.tsx | 15 +- .../compareUI/components/ComparisonPanel.tsx | 4 +- .../components/MessageInput.test.tsx | 8 +- .../compareUI/components/UnifiedSelector.tsx | 13 +- .../playground/compareUI/endpoint_config.ts | 16 +- .../playground/complianceUI/ComplianceUI.tsx | 1780 +++++++------- .../playground/llm_calls/a2a_send_message.tsx | 8 +- .../playground/llm_calls/chat_completion.tsx | 23 +- .../llm_calls/code_interpreter_handler.ts | 17 +- .../playground/llm_calls/fetch_agents.tsx | 8 +- .../playground/llm_calls/interactions_api.tsx | 4 +- .../policies/PolicySelector.test.tsx | 10 +- .../components/policies/PolicySelector.tsx | 4 +- .../policies/add_attachment_form.test.tsx | 5 +- .../policies/add_attachment_form.tsx | 73 +- .../components/policies/add_policy_form.tsx | 65 +- .../policies/ai_suggestion_modal.tsx | 686 +++--- .../policies/attachment_table.test.tsx | 37 +- .../policies/build_attachment_data.test.ts | 2 +- .../policies/build_attachment_data.ts | 2 +- .../policies/guardrail_selection_modal.tsx | 61 +- .../policies/impact_popover.test.tsx | 14 +- .../components/policies/impact_popover.tsx | 21 +- .../policies/impact_preview_alert.tsx | 38 +- .../src/components/policies/index.test.tsx | 13 +- .../src/components/policies/index.tsx | 58 +- .../policies/pipeline_flow_builder.tsx | 262 +- .../src/components/policies/policy_info.tsx | 25 +- .../components/policies/policy_table.test.tsx | 34 +- .../src/components/policies/policy_table.tsx | 11 +- .../policies/policy_templates.test.tsx | 20 +- .../components/policies/policy_templates.tsx | 62 +- .../components/policies/policy_test_panel.tsx | 62 +- .../policies/template_parameter_modal.tsx | 48 +- .../src/components/price_data_reload.tsx | 10 +- .../DeveloperMessageCard.tsx | 17 +- .../prompt_editor_view/DotpromptViewTab.tsx | 13 +- .../prompt_editor_view/ModelConfigCard.tsx | 13 +- .../prompt_editor_view/PromptCodeSnippets.tsx | 67 +- .../prompt_editor_view/PromptEditorHeader.tsx | 20 +- .../prompt_editor_view/PromptMessagesCard.tsx | 13 +- .../prompt_editor_view/PublishModal.tsx | 3 +- .../prompts/prompt_editor_view/ToolsCard.tsx | 23 +- .../VersionHistorySidePanel.tsx | 28 +- .../conversation_panel/EmptyState.tsx | 1 - .../conversation_panel/MessageBubble.tsx | 24 +- .../conversation_panel/MessageInput.tsx | 6 +- .../conversation_panel/MessageList.tsx | 8 +- .../conversation_panel/VariableInput.tsx | 15 +- .../conversation_panel/VariableWarning.tsx | 12 +- .../conversation_panel/index.tsx | 5 +- .../conversation_panel/types.ts | 1 - .../prompts/prompt_editor_view/index.tsx | 25 +- .../prompts/prompt_editor_view/types.ts | 1 - .../prompts/prompt_editor_view/utils.ts | 8 +- .../src/components/prompts/prompt_info.tsx | 107 +- .../src/components/prompts/prompt_table.tsx | 37 +- .../src/components/prompts/prompt_utils.tsx | 19 +- .../src/components/prompts/tool_modal.tsx | 7 +- .../components/prompts/variable_textarea.tsx | 18 +- .../src/components/public_model_hub.tsx | 6 +- .../src/components/query_param_input.tsx | 2 +- .../src/components/route_preview.tsx | 8 +- .../LatencyBasedConfiguration.test.tsx | 10 +- .../LatencyBasedConfiguration.tsx | 13 +- .../ReliabilityRetriesSection.test.tsx | 20 +- .../ReliabilityRetriesSection.tsx | 1 - .../RouterSettingsForm.test.tsx | 20 +- .../RoutingStrategySelector.test.tsx | 12 +- .../RoutingStrategySelector.tsx | 12 +- .../TagFilteringToggle.test.tsx | 44 +- .../router_settings/TagFilteringToggle.tsx | 13 +- .../components/router_settings/index.test.tsx | 32 +- .../src/components/router_settings/index.tsx | 4 +- .../routing_groups/RoutingGroupModal.tsx | 22 +- .../routing_groups/RoutingGroupsTable.tsx | 16 +- .../src/components/routing_groups/index.tsx | 24 +- .../src/components/routing_groups/types.ts | 6 +- .../components/shared/CreatedKeyDisplay.tsx | 8 +- .../components/skill_hub_table_columns.tsx | 18 +- .../survey/ClaudeCodeModal.test.tsx | 26 +- .../src/components/survey/ClaudeCodeModal.tsx | 14 +- .../survey/ClaudeCodePrompt.test.tsx | 20 +- .../components/survey/ClaudeCodePrompt.tsx | 3 +- .../src/components/survey/NudgePrompt.tsx | 19 +- .../components/survey/SurveyModal.test.tsx | 80 +- .../src/components/survey/SurveyModal.tsx | 24 +- .../components/survey/SurveyPrompt.test.tsx | 20 +- .../src/components/survey/SurveyPrompt.tsx | 1 - .../src/components/survey/index.tsx | 1 - .../src/components/team/EditMembership.tsx | 8 +- .../src/components/team/MyUserTab.tsx | 23 +- .../src/components/team/TeamInfo.test.tsx | 39 +- .../src/components/team/TeamInfo.tsx | 183 +- .../src/components/team/TeamMemberTab.tsx | 16 +- .../team/TeamVirtualKeysTable.test.tsx | 48 +- .../components/team/TeamVirtualKeysTable.tsx | 55 +- .../src/components/team/available_teams.tsx | 6 +- .../team/permission_definitions.tsx | 16 +- .../team/tabVisibilityUtils.test.ts | 6 +- .../src/components/team/tabVisibilityUtils.ts | 11 +- .../src/components/team/useMyTeamMember.ts | 15 +- .../components/templates/KeyInfoHeader.tsx | 28 +- .../key_info_view.budget_display.test.tsx | 4 +- .../templates/key_info_view.test.tsx | 102 +- .../components/templates/key_info_view.tsx | 74 +- .../components/ui/AntDLoadingSpinner.test.tsx | 7 +- .../src/components/ui_theme_settings.tsx | 71 +- .../src/components/user_dashboard.test.tsx | 12 +- .../src/components/user_edit_view.tsx | 12 +- .../CreateVectorStore.test.tsx | 4 +- .../CreateVectorStore.tsx | 28 +- .../S3VectorsConfig.test.tsx | 2 +- .../S3VectorsConfig.tsx | 14 +- .../TestVectorStoreTab.tsx | 6 +- .../VectorStoreForm.tsx | 4 +- .../VectorStoreTable.tsx | 19 +- .../vector_store_management/index.tsx | 13 +- .../AuditLogDrawer/AuditLogDrawer.tsx | 36 +- .../view_logs/CostBreakdownViewer.test.tsx | 42 +- .../view_logs/CostBreakdownViewer.tsx | 289 +-- .../components/view_logs/ErrorViewer.test.tsx | 4 +- .../view_logs/EvalViewer/EvalViewer.tsx | 31 +- .../GuardrailViewer/CompliancePanel.tsx | 22 +- .../GuardrailViewer/ContentFilterDetails.tsx | 6 +- .../GuardrailViewer/GuardrailViewer.tsx | 104 +- .../CollapsibleMessage.test.tsx | 20 +- .../LogDetailsDrawer/CollapsibleMessage.tsx | 44 +- .../LogDetailsDrawer/DrawerHeader.tsx | 10 +- .../LogDetailsDrawer/HistoryTree.test.tsx | 12 +- .../LogDetailsDrawer/HistoryTree.tsx | 38 +- .../LogDetailsDrawer/InputCard.test.tsx | 2 +- .../view_logs/LogDetailsDrawer/InputCard.tsx | 32 +- .../LogDetailsDrawer/LogDetailContent.tsx | 76 +- .../LogDetailsDrawer/LogDetailsDrawer.tsx | 123 +- .../LogDetailsDrawer/OutputCard.test.tsx | 2 +- .../view_logs/LogDetailsDrawer/OutputCard.tsx | 49 +- .../LogDetailsDrawer/PrettyMessagesView.tsx | 14 +- .../RealtimePrettyView.test.tsx | 26 +- .../LogDetailsDrawer/RealtimePrettyView.tsx | 266 +- .../LogDetailsDrawer/SectionHeader.tsx | 71 +- .../SimpleMessageBlock.test.tsx | 24 +- .../LogDetailsDrawer/SimpleMessageBlock.tsx | 37 +- .../SimpleToolCallBlock.test.tsx | 20 +- .../LogDetailsDrawer/SimpleToolCallBlock.tsx | 28 +- .../LogDetailsDrawer/prettyMessagesTypes.ts | 2 +- .../LogDetailsDrawer/prettyMessagesUtils.ts | 84 +- .../components/view_logs/LogsTableToolbar.tsx | 13 +- .../view_logs/RequestResponsePanel.test.tsx | 8 +- .../ToolsSection/FormattedToolView.tsx | 28 +- .../ToolsSection/ToolExpandedContent.tsx | 12 +- .../view_logs/ToolsSection/ToolItem.tsx | 4 +- .../view_logs/ToolsSection/ToolsSection.tsx | 2 +- .../view_logs/ToolsSection/utils.ts | 33 +- .../src/components/view_logs/TypeBadges.tsx | 36 +- .../view_logs/VectorStoreViewer.tsx | 174 +- .../src/components/view_logs/audit_logs.tsx | 96 +- .../src/components/view_logs/columns.tsx | 6 +- .../src/components/view_logs/table.tsx | 25 +- .../src/components/view_logs/utils.ts | 9 +- .../src/components/view_users.tsx | 27 +- .../src/components/view_users/columns.tsx | 3 +- .../src/components/view_users/table.test.tsx | 8 +- .../view_users/user_info_view.test.tsx | 9 +- .../components/view_users/user_info_view.tsx | 137 +- .../src/components/workflow_runs/index.tsx | 140 +- .../src/contexts/ThemeContext.tsx | 4 +- .../src/data/canadianPiiCompliancePrompts.ts | 102 +- .../src/data/claimsCompliancePrompts.ts | 104 +- .../data/codeExecutionCompliancePrompts.ts | 220 +- .../src/data/compliancePrompts.ts | 65 +- .../src/data/financialCompliancePrompts.ts | 624 +++-- .../src/data/insultsCompliancePrompts.ts | 900 ++++--- .../useDeletePolicyAttachment.test.tsx | 4 +- .../policies/useDeletePolicyAttachment.ts | 6 +- .../src/hooks/useMcpOAuthFlow.tsx | 18 +- .../src/hooks/useTestMCPConnection.tsx | 57 +- .../src/hooks/useToolsOAuthFlow.tsx | 10 +- .../src/hooks/useUserMcpOAuthFlow.tsx | 4 +- ui/litellm-dashboard/src/hooks/useWorker.ts | 3 +- .../src/utils/cookieUtils.test.ts | 24 +- ui/litellm-dashboard/src/utils/errorUtils.ts | 16 +- .../src/utils/mcpTokenStore.test.ts | 8 +- .../src/utils/mcpTokenStore.ts | 11 +- .../src/utils/proxyUtils.test.ts | 2 +- .../src/utils/returnUrlUtils.test.ts | 8 +- ui/litellm-dashboard/src/utils/roles.test.ts | 4 +- ui/litellm-dashboard/src/utils/roles.ts | 6 +- .../src/utils/secureStorage.ts | 9 +- .../tests/CreateKeyPage.expiredToken.test.tsx | 2 +- ui/litellm-dashboard/tsconfig.json | 24 +- 608 files changed, 13772 insertions(+), 16368 deletions(-) diff --git a/ui/litellm-dashboard/e2e_tests/globalSetup.ts b/ui/litellm-dashboard/e2e_tests/globalSetup.ts index 6ff5522244a..8f80f57bd78 100644 --- a/ui/litellm-dashboard/e2e_tests/globalSetup.ts +++ b/ui/litellm-dashboard/e2e_tests/globalSetup.ts @@ -14,10 +14,9 @@ async function globalSetup() { await page.getByPlaceholder("Enter your username").fill(email); await page.getByPlaceholder("Enter your password").fill(password); await page.getByRole("button", { name: "Login", exact: true }).click(); - await page.waitForURL( - (url) => url.pathname.startsWith("/ui") && !url.pathname.includes("/login"), - { timeout: 30_000 }, - ); + await page.waitForURL((url) => url.pathname.startsWith("/ui") && !url.pathname.includes("/login"), { + timeout: 30_000, + }); await expect(page.locator("a", { hasText: "Virtual Keys" })).toBeVisible({ timeout: 30_000 }); // Dismiss feedback popup if present const dismiss = page.getByText("Don't ask me again"); diff --git a/ui/litellm-dashboard/e2e_tests/helpers/navigation.ts b/ui/litellm-dashboard/e2e_tests/helpers/navigation.ts index 556e964842a..6ca18890f7a 100644 --- a/ui/litellm-dashboard/e2e_tests/helpers/navigation.ts +++ b/ui/litellm-dashboard/e2e_tests/helpers/navigation.ts @@ -20,7 +20,9 @@ export async function dismissFeedbackPopup(page: PlaywrightPage): Promise if (await dismissButton.isVisible({ timeout: 1_500 }).catch(() => false)) { await dismissButton.click(); // Wait for the popup to disappear - await expect(dismissButton).not.toBeVisible({ timeout: 2_000 }).catch(() => {}); + await expect(dismissButton) + .not.toBeVisible({ timeout: 2_000 }) + .catch(() => {}); } } diff --git a/ui/litellm-dashboard/e2e_tests/playwright.config.ts b/ui/litellm-dashboard/e2e_tests/playwright.config.ts index 6964fe52a14..8d586ce9503 100644 --- a/ui/litellm-dashboard/e2e_tests/playwright.config.ts +++ b/ui/litellm-dashboard/e2e_tests/playwright.config.ts @@ -31,7 +31,7 @@ export default defineConfig({ /* Slow down actions when SLOWMO= is set, useful for headed local debugging */ launchOptions: { - slowMo: process.env.SLOWMO ? (parseInt(process.env.SLOWMO, 10) || 0) : 0, + slowMo: process.env.SLOWMO ? parseInt(process.env.SLOWMO, 10) || 0 : 0, }, }, diff --git a/ui/litellm-dashboard/e2e_tests/tests/auth/logout.spec.ts b/ui/litellm-dashboard/e2e_tests/tests/auth/logout.spec.ts index fefadf27548..d8644babfe3 100644 --- a/ui/litellm-dashboard/e2e_tests/tests/auth/logout.spec.ts +++ b/ui/litellm-dashboard/e2e_tests/tests/auth/logout.spec.ts @@ -13,9 +13,12 @@ test.describe("Logout", () => { // is declared with trigger={["click"]}, so a plain click opens the popup. await page.getByRole("button", { name: /Account menu/i }).click(); - const popup = page.locator(".ant-dropdown:visible").filter({ - has: page.locator(".bg-white.rounded-lg.shadow-lg"), - }).first(); + const popup = page + .locator(".ant-dropdown:visible") + .filter({ + has: page.locator(".bg-white.rounded-lg.shadow-lg"), + }) + .first(); await expect(popup).toBeVisible({ timeout: 5_000 }); // Click Logout — the handler clears the auth cookie and navigates via diff --git a/ui/litellm-dashboard/e2e_tests/tests/auth/proxyLogoutUrl.spec.ts b/ui/litellm-dashboard/e2e_tests/tests/auth/proxyLogoutUrl.spec.ts index 4a233ed1bb1..6358fcf438e 100644 --- a/ui/litellm-dashboard/e2e_tests/tests/auth/proxyLogoutUrl.spec.ts +++ b/ui/litellm-dashboard/e2e_tests/tests/auth/proxyLogoutUrl.spec.ts @@ -38,10 +38,9 @@ test.describe("PROXY_LOGOUT_URL redirect", () => { // fetch (/sso/get/ui_settings) resolves. Clicking Logout before that lands // runs `window.location.href = ""` — a same-origin reload, not a redirect — // so gate the click on the settings response, not just on first paint. - const settingsLoaded = page.waitForResponse( - (r) => r.url().includes("/sso/get/ui_settings") && r.ok(), - { timeout: 30_000 }, - ); + const settingsLoaded = page.waitForResponse((r) => r.url().includes("/sso/get/ui_settings") && r.ok(), { + timeout: 30_000, + }); await page.goto("/ui"); await expect(page.getByText("Virtual Keys")).toBeVisible({ timeout: 15_000 }); await settingsLoaded; @@ -59,10 +58,7 @@ test.describe("PROXY_LOGOUT_URL redirect", () => { // handleLogout clears cookies/local storage, then assigns window.location.href. // Arm the navigation wait before the click so we never miss the redirect. - await Promise.all([ - page.waitForURL((url) => url.origin === target.origin, { timeout: 15_000 }), - logout.click(), - ]); + await Promise.all([page.waitForURL((url) => url.origin === target.origin, { timeout: 15_000 }), logout.click()]); // The browser landed on exactly the configured logout URL. Compare normalized // hrefs (both sides through URL()) so trailing-slash / default-port rewrites the @@ -74,9 +70,7 @@ test.describe("PROXY_LOGOUT_URL redirect", () => { // ...and the client-side session cookie is gone (clearTokenCookies ran before // the redirect). HttpOnly cookies set server-side can't be cleared from JS, // so scope the check to the JS-managed token the UI is responsible for. - const clientTokensAfter = (await page.context().cookies()).filter( - (c) => c.name === "token" && !c.httpOnly, - ); + const clientTokensAfter = (await page.context().cookies()).filter((c) => c.name === "token" && !c.httpOnly); expect(clientTokensAfter, "client token cookie should be cleared on logout").toHaveLength(0); }); }); diff --git a/ui/litellm-dashboard/e2e_tests/tests/internal-user/internalUser.spec.ts b/ui/litellm-dashboard/e2e_tests/tests/internal-user/internalUser.spec.ts index c706ae0aefc..07a75dc007d 100644 --- a/ui/litellm-dashboard/e2e_tests/tests/internal-user/internalUser.spec.ts +++ b/ui/litellm-dashboard/e2e_tests/tests/internal-user/internalUser.spec.ts @@ -22,9 +22,9 @@ test.describe("Internal User", () => { const teamSelect = page.locator(".ant-select", { hasText: "Search or select a team" }); await teamSelect.click(); await page.keyboard.type(E2E_TEAM_CRUD_ALIAS); - await expect( - page.locator(".ant-select-dropdown:visible").getByText(E2E_TEAM_CRUD_ALIAS).first(), - ).toBeVisible({ timeout: 5_000 }); + await expect(page.locator(".ant-select-dropdown:visible").getByText(E2E_TEAM_CRUD_ALIAS).first()).toBeVisible({ + timeout: 5_000, + }); }); test("Team info page omits the Settings tab for non-admin members", async ({ page }) => { @@ -44,9 +44,9 @@ test.describe("Internal User", () => { // Anchor on the user's own seeded key so the absence check below cannot // pass vacuously against an empty table. - await expect( - page.locator("table tbody").getByText(E2E_INTERNAL_USER_KEY_ALIAS).first(), - ).toBeVisible({ timeout: 10_000 }); + await expect(page.locator("table tbody").getByText(E2E_INTERNAL_USER_KEY_ALIAS).first()).toBeVisible({ + timeout: 10_000, + }); // The litellm-dashboard team is the proxy's internal bookkeeping team — // its keys must never leak into an internal user's Virtual Keys table. diff --git a/ui/litellm-dashboard/e2e_tests/tests/internal-user/internalUserWithTeams.spec.ts b/ui/litellm-dashboard/e2e_tests/tests/internal-user/internalUserWithTeams.spec.ts index f6b60f411b4..7d5058a8140 100644 --- a/ui/litellm-dashboard/e2e_tests/tests/internal-user/internalUserWithTeams.spec.ts +++ b/ui/litellm-dashboard/e2e_tests/tests/internal-user/internalUserWithTeams.spec.ts @@ -1,9 +1,5 @@ import { test, expect } from "@playwright/test"; -import { - INTERNAL_USER_STORAGE_PATH, - E2E_TEAM_CRUD_ALIAS, - E2E_TEAM_ORG_ALIAS, -} from "../../constants"; +import { INTERNAL_USER_STORAGE_PATH, E2E_TEAM_CRUD_ALIAS, E2E_TEAM_ORG_ALIAS } from "../../constants"; import { Page } from "../../fixtures/pages"; import { navigateToPage } from "../../helpers/navigation"; diff --git a/ui/litellm-dashboard/e2e_tests/tests/internal-viewer/internalViewer.spec.ts b/ui/litellm-dashboard/e2e_tests/tests/internal-viewer/internalViewer.spec.ts index f5ab3c00503..4de86c46398 100644 --- a/ui/litellm-dashboard/e2e_tests/tests/internal-viewer/internalViewer.spec.ts +++ b/ui/litellm-dashboard/e2e_tests/tests/internal-viewer/internalViewer.spec.ts @@ -1,9 +1,5 @@ import { test, expect } from "@playwright/test"; -import { - E2E_TEAM_CRUD_ID, - E2E_VIEWER_KEY_ALIAS, - INTERNAL_VIEWER_STORAGE_PATH, -} from "../../constants"; +import { E2E_TEAM_CRUD_ID, E2E_VIEWER_KEY_ALIAS, INTERNAL_VIEWER_STORAGE_PATH } from "../../constants"; import { Page } from "../../fixtures/pages"; import { navigateToPage } from "../../helpers/navigation"; diff --git a/ui/litellm-dashboard/e2e_tests/tests/login/internalUserIdentity.spec.ts b/ui/litellm-dashboard/e2e_tests/tests/login/internalUserIdentity.spec.ts index cbe95276929..6008049a2aa 100644 --- a/ui/litellm-dashboard/e2e_tests/tests/login/internalUserIdentity.spec.ts +++ b/ui/litellm-dashboard/e2e_tests/tests/login/internalUserIdentity.spec.ts @@ -22,9 +22,7 @@ test.describe("Navbar identity scoping", () => { await expect(accountButton).toHaveAttribute("aria-label", /Internal User/, { timeout: 5_000 }); await expect(accountButton).toHaveAttribute( "aria-label", - new RegExp( - `signed in as (${escapeRegExp(E2E_INTERNAL_USER_EMAIL)}|${escapeRegExp(E2E_INTERNAL_USER_ID)})`, - ), + new RegExp(`signed in as (${escapeRegExp(E2E_INTERNAL_USER_EMAIL)}|${escapeRegExp(E2E_INTERNAL_USER_ID)})`), { timeout: 5_000 }, ); diff --git a/ui/litellm-dashboard/e2e_tests/tests/login/login.spec.ts b/ui/litellm-dashboard/e2e_tests/tests/login/login.spec.ts index 994d211cc18..d1b64f37156 100644 --- a/ui/litellm-dashboard/e2e_tests/tests/login/login.spec.ts +++ b/ui/litellm-dashboard/e2e_tests/tests/login/login.spec.ts @@ -21,9 +21,12 @@ test("user can log in", async ({ page }) => { // Filter by the popupRender wrapper class to disambiguate from other // ant-dropdown popups. - const popup = page.locator(".ant-dropdown:visible").filter({ - has: page.locator(".bg-white.rounded-lg.shadow-lg"), - }).first(); + const popup = page + .locator(".ant-dropdown:visible") + .filter({ + has: page.locator(".bg-white.rounded-lg.shadow-lg"), + }) + .first(); await expect(popup).toBeVisible({ timeout: 5_000 }); await expect(popup.getByText("Admin", { exact: true })).toBeVisible({ timeout: 5_000 }); await expect(popup.getByText("default_user_id", { exact: true })).toBeVisible({ timeout: 5_000 }); diff --git a/ui/litellm-dashboard/e2e_tests/tests/mcp/mcpServers.spec.ts b/ui/litellm-dashboard/e2e_tests/tests/mcp/mcpServers.spec.ts index f953a82daaa..22ba85956da 100644 --- a/ui/litellm-dashboard/e2e_tests/tests/mcp/mcpServers.spec.ts +++ b/ui/litellm-dashboard/e2e_tests/tests/mcp/mcpServers.spec.ts @@ -54,9 +54,7 @@ test.describe("MCP Servers", () => { // the MCP servers table so the form modal's `server_name` input — which // still holds the timestamped value during its close animation — can't // satisfy the assertion before the server actually lands in the list. - await expect(page.getByText("MCP Server created successfully").first()) - .toBeVisible({ timeout: 15_000 }); - await expect(page.locator("table tbody").getByText(uniqueName).first()) - .toBeVisible({ timeout: 10_000 }); + await expect(page.getByText("MCP Server created successfully").first()).toBeVisible({ timeout: 15_000 }); + await expect(page.locator("table tbody").getByText(uniqueName).first()).toBeVisible({ timeout: 10_000 }); }); }); diff --git a/ui/litellm-dashboard/e2e_tests/tests/modelHub/modelHub.spec.ts b/ui/litellm-dashboard/e2e_tests/tests/modelHub/modelHub.spec.ts index ada4dfb735e..ca9c35ce722 100644 --- a/ui/litellm-dashboard/e2e_tests/tests/modelHub/modelHub.spec.ts +++ b/ui/litellm-dashboard/e2e_tests/tests/modelHub/modelHub.spec.ts @@ -31,8 +31,9 @@ test.describe("AI Hub (internal admin view)", () => { // Submit await modal.getByRole("button", { name: "Make Public" }).click(); - await expect(page.getByText(/Successfully made .* model group\(s\) public/i).first()) - .toBeVisible({ timeout: 15_000 }); + await expect(page.getByText(/Successfully made .* model group\(s\) public/i).first()).toBeVisible({ + timeout: 15_000, + }); }); test("AI Hub tab list renders Model Hub, Agent Hub, MCP Hub and Skill Hub", async ({ page }) => { diff --git a/ui/litellm-dashboard/e2e_tests/tests/modelsPage/addModel.spec.ts b/ui/litellm-dashboard/e2e_tests/tests/modelsPage/addModel.spec.ts index bb53fb7a23b..17ff1fc3f83 100644 --- a/ui/litellm-dashboard/e2e_tests/tests/modelsPage/addModel.spec.ts +++ b/ui/litellm-dashboard/e2e_tests/tests/modelsPage/addModel.spec.ts @@ -154,10 +154,7 @@ test.describe("Add Model", () => { // The Team-BYOK switch is gated on `premiumUser` — without a license set // for the proxy under test, the toggle is disabled and this manual-QA // step cannot be exercised. - test.skip( - !process.env.LITELLM_LICENSE, - "LITELLM_LICENSE not set in test env — Team-BYOK switch is disabled", - ); + test.skip(!process.env.LITELLM_LICENSE, "LITELLM_LICENSE not set in test env — Team-BYOK switch is disabled"); // Make the test idempotent across retries and local reruns: delete any // Cohere model already scoped to the e2e team before we start, and again @@ -170,10 +167,11 @@ test.describe("Add Model", () => { const res = await request.get("/v2/model/info", { headers: auth }); if (!res.ok()) return; const body = await res.json(); - const matches: Array<{ id: string }> = (body?.data ?? []).filter((m: any) => - typeof m?.model_name === "string" && - m.model_name.startsWith("cohere") && - m?.model_info?.team_id === E2E_TEAM_CRUD_ID, + const matches: Array<{ id: string }> = (body?.data ?? []).filter( + (m: any) => + typeof m?.model_name === "string" && + m.model_name.startsWith("cohere") && + m?.model_info?.team_id === E2E_TEAM_CRUD_ID, ); for (const m of matches) { await request.post("/model/delete", { headers: auth, data: { id: m.id } }); @@ -208,9 +206,7 @@ test.describe("Add Model", () => { const teamDropdown = page.getByTestId("team-dropdown"); await expect(teamDropdown).toBeVisible({ timeout: 5_000 }); await teamDropdown.click(); - const teamOption = page.locator(".ant-select-dropdown:visible") - .getByText(E2E_TEAM_CRUD_ID) - .first(); + const teamOption = page.locator(".ant-select-dropdown:visible").getByText(E2E_TEAM_CRUD_ID).first(); await expect(teamOption).toBeVisible({ timeout: 5_000 }); await teamOption.click(); @@ -219,8 +215,9 @@ test.describe("Add Model", () => { // Scope the success toast to antd's notification container so a stale // success message from an earlier test in the same context can't satisfy // the assertion. - await expect(page.locator(".ant-notification").getByText("created successfully").last()) - .toBeVisible({ timeout: 15_000 }); + await expect(page.locator(".ant-notification").getByText("created successfully").last()).toBeVisible({ + timeout: 15_000, + }); // Verify the model is now in All Models with the team_id attached. The // Models table renders team-scoped models with the team id in the row. @@ -237,16 +234,16 @@ test.describe("Add Model", () => { // Confirm the search returned at least one result — gives a clear // failure message when the table is empty instead of timing out on a // row assertion. - await expect(page.getByTestId("models-results-count")).toHaveText( - /Showing \d+ - \d+ of \d+ results/, - { timeout: 15_000 }, - ); + await expect(page.getByTestId("models-results-count")).toHaveText(/Showing \d+ - \d+ of \d+ results/, { + timeout: 15_000, + }); // Stronger than "alias appears somewhere in tbody" — pin the assertion // to a single row that has BOTH the cohere model_name AND the seeded // team alias, so a stale cohere row from "Add wildcard route" (no team) // can't satisfy the check. - const teamCohereRow = page.locator("table tbody tr") + const teamCohereRow = page + .locator("table tbody tr") .filter({ hasText: "cohere/" }) .filter({ hasText: E2E_TEAM_CRUD_ALIAS }); await expect(teamCohereRow).toHaveCount(1, { timeout: 15_000 }); diff --git a/ui/litellm-dashboard/e2e_tests/tests/modelsPage/clearCustomPricing.spec.ts b/ui/litellm-dashboard/e2e_tests/tests/modelsPage/clearCustomPricing.spec.ts index d21192d237d..877c7f8c555 100644 --- a/ui/litellm-dashboard/e2e_tests/tests/modelsPage/clearCustomPricing.spec.ts +++ b/ui/litellm-dashboard/e2e_tests/tests/modelsPage/clearCustomPricing.spec.ts @@ -62,9 +62,7 @@ test.describe("Clear custom pricing on a deployment", () => { } }); - test("UI sends null for cleared pricing and backend removes the override", async ({ - page, - }) => { + test("UI sends null for cleared pricing and backend removes the override", async ({ page }) => { // Navigate to the model detail view. await page.goto("/ui"); await page.getByText("Models + Endpoints").click(); @@ -97,34 +95,24 @@ test.describe("Clear custom pricing on a deployment", () => { // Capture the outgoing PATCH so we can assert the UI sends explicit nulls. const patchPromise = page.waitForRequest( - (req) => - req.method() === "PATCH" && - req.url().includes(`/model/${createdModelId}/update`) + (req) => req.method() === "PATCH" && req.url().includes(`/model/${createdModelId}/update`), ); await page.getByRole("button", { name: "Save Changes" }).click(); const patchReq = await patchPromise; const patchBody = JSON.parse(patchReq.postData() ?? "{}"); - expect( - patchBody.litellm_params.input_cost_per_token, - "UI sends explicit null for cleared input cost" - ).toBeNull(); - expect( - patchBody.litellm_params.output_cost_per_token, - "UI sends explicit null for cleared output cost" - ).toBeNull(); + expect(patchBody.litellm_params.input_cost_per_token, "UI sends explicit null for cleared input cost").toBeNull(); + expect(patchBody.litellm_params.output_cost_per_token, "UI sends explicit null for cleared output cost").toBeNull(); expect( patchBody.litellm_params.cache_read_input_token_cost, - "UI sends explicit null for cleared cache_read cost" + "UI sends explicit null for cleared cache_read cost", ).toBeNull(); expect( patchBody.litellm_params.cache_creation_input_token_cost, - "UI sends explicit null for cleared cache_write cost" + "UI sends explicit null for cleared cache_write cost", ).toBeNull(); // Success toast confirms the save was accepted. - await expect( - page.getByText("Model settings updated successfully") - ).toBeVisible({ timeout: 10_000 }); + await expect(page.getByText("Model settings updated successfully")).toBeVisible({ timeout: 10_000 }); // Verify via the management API: the user-set rate is gone from both blobs. // The cost-map may synthesize a default for known providers in the response, @@ -132,46 +120,40 @@ test.describe("Clear custom pricing on a deployment", () => { // undefined. const infoRes = await page.request.get( `/v2/model/info?include_team_models=true&page=1&size=100&modelId=${createdModelId}`, - { headers: { Authorization: `Bearer ${masterKey}` } } + { headers: { Authorization: `Bearer ${masterKey}` } }, ); expect(infoRes.ok()).toBe(true); const infoBody = await infoRes.json(); - const row = (infoBody.data ?? infoBody).find?.( - (m: any) => m?.model_info?.id === createdModelId - ); + const row = (infoBody.data ?? infoBody).find?.((m: any) => m?.model_info?.id === createdModelId); expect(row, "model info row").toBeTruthy(); - expect( - "input_cost_per_token" in row.litellm_params, - "litellm_params.input_cost_per_token key removed" - ).toBe(false); - expect( - "output_cost_per_token" in row.litellm_params, - "litellm_params.output_cost_per_token key removed" - ).toBe(false); + expect("input_cost_per_token" in row.litellm_params, "litellm_params.input_cost_per_token key removed").toBe(false); + expect("output_cost_per_token" in row.litellm_params, "litellm_params.output_cost_per_token key removed").toBe( + false, + ); expect( "cache_read_input_token_cost" in row.litellm_params, - "litellm_params.cache_read_input_token_cost key removed" + "litellm_params.cache_read_input_token_cost key removed", ).toBe(false); expect( "cache_creation_input_token_cost" in row.litellm_params, - "litellm_params.cache_creation_input_token_cost key removed" + "litellm_params.cache_creation_input_token_cost key removed", ).toBe(false); expect( row.model_info.input_cost_per_token, - "model_info.input_cost_per_token no longer the seeded override" + "model_info.input_cost_per_token no longer the seeded override", ).not.toBe(SEED_INPUT_PER_TOKEN); expect( row.model_info.output_cost_per_token, - "model_info.output_cost_per_token no longer the seeded override" + "model_info.output_cost_per_token no longer the seeded override", ).not.toBe(SEED_OUTPUT_PER_TOKEN); expect( row.model_info.cache_read_input_token_cost, - "model_info.cache_read_input_token_cost no longer the seeded override" + "model_info.cache_read_input_token_cost no longer the seeded override", ).not.toBe(SEED_CACHE_READ_PER_TOKEN); expect( row.model_info.cache_creation_input_token_cost, - "model_info.cache_creation_input_token_cost no longer the seeded override" + "model_info.cache_creation_input_token_cost no longer the seeded override", ).not.toBe(SEED_CACHE_WRITE_PER_TOKEN); }); }); diff --git a/ui/litellm-dashboard/e2e_tests/tests/navigation/sidebar.spec.ts b/ui/litellm-dashboard/e2e_tests/tests/navigation/sidebar.spec.ts index f56b5875dc6..b8fb95b764d 100644 --- a/ui/litellm-dashboard/e2e_tests/tests/navigation/sidebar.spec.ts +++ b/ui/litellm-dashboard/e2e_tests/tests/navigation/sidebar.spec.ts @@ -6,15 +6,7 @@ import { menuLabelToPage } from "../../fixtures/menuMappings"; import { navigateToPage } from "../../helpers/navigation"; const sidebarButtons = { - [Role.ProxyAdmin]: [ - "Virtual Keys", - "Playground", - "Models", - "Usage", - "Teams", - "Internal Users", - "AI Hub", - ], + [Role.ProxyAdmin]: ["Virtual Keys", "Playground", "Models", "Usage", "Teams", "Internal Users", "AI Hub"], }; const roles = [{ role: Role.ProxyAdmin, storage: ADMIN_STORAGE_PATH }]; diff --git a/ui/litellm-dashboard/e2e_tests/tests/proxy-admin/keys.spec.ts b/ui/litellm-dashboard/e2e_tests/tests/proxy-admin/keys.spec.ts index 1e44d9a25a0..644228c5ff9 100644 --- a/ui/litellm-dashboard/e2e_tests/tests/proxy-admin/keys.spec.ts +++ b/ui/litellm-dashboard/e2e_tests/tests/proxy-admin/keys.spec.ts @@ -89,12 +89,8 @@ test.describe("Proxy Admin - Keys", () => { await page.getByRole("spinbutton", { name: "RPM Limit" }).fill("456"); await page.getByRole("button", { name: "Save Changes" }).click(); - await expect( - page.getByRole("paragraph").filter({ hasText: "TPM: 123" }) - ).toBeVisible({ timeout: 10_000 }); - await expect( - page.getByRole("paragraph").filter({ hasText: "RPM: 456" }) - ).toBeVisible({ timeout: 10_000 }); + await expect(page.getByRole("paragraph").filter({ hasText: "TPM: 123" })).toBeVisible({ timeout: 10_000 }); + await expect(page.getByRole("paragraph").filter({ hasText: "RPM: 456" })).toBeVisible({ timeout: 10_000 }); }); test("Delete key", async ({ page }) => { diff --git a/ui/litellm-dashboard/e2e_tests/tests/proxy-admin/license.spec.ts b/ui/litellm-dashboard/e2e_tests/tests/proxy-admin/license.spec.ts index 579b3cede7c..37a0e324f27 100644 --- a/ui/litellm-dashboard/e2e_tests/tests/proxy-admin/license.spec.ts +++ b/ui/litellm-dashboard/e2e_tests/tests/proxy-admin/license.spec.ts @@ -14,10 +14,7 @@ import { ADMIN_STORAGE_PATH } from "../../constants"; */ test.describe("Premium license wiring", () => { test("admin session JWT carries premium_user=true when LITELLM_LICENSE is set", () => { - test.skip( - !process.env.LITELLM_LICENSE, - "LITELLM_LICENSE not set in test env — proxy is running unlicensed", - ); + test.skip(!process.env.LITELLM_LICENSE, "LITELLM_LICENSE not set in test env — proxy is running unlicensed"); const storage = JSON.parse(fs.readFileSync(ADMIN_STORAGE_PATH, "utf-8")); const tokenCookie = storage.cookies?.find((c: { name: string }) => c.name === "token"); @@ -28,9 +25,7 @@ test.describe("Premium license wiring", () => { const jwtParts = tokenCookie.value.split("."); expect(jwtParts.length, "token cookie is not a 3-part JWT").toBe(3); const [, payloadB64] = jwtParts; - const payload = JSON.parse( - Buffer.from(payloadB64, "base64url").toString("utf-8"), - ); + const payload = JSON.parse(Buffer.from(payloadB64, "base64url").toString("utf-8")); expect(payload.premium_user).toBe(true); }); diff --git a/ui/litellm-dashboard/e2e_tests/tests/proxy-admin/teams.spec.ts b/ui/litellm-dashboard/e2e_tests/tests/proxy-admin/teams.spec.ts index 4774b50dbc5..b30bb8aca7b 100644 --- a/ui/litellm-dashboard/e2e_tests/tests/proxy-admin/teams.spec.ts +++ b/ui/litellm-dashboard/e2e_tests/tests/proxy-admin/teams.spec.ts @@ -19,7 +19,10 @@ test.describe("Proxy Admin - Teams", () => { const uniqueAlias = `e2e-created-team-${Date.now()}`; // Click the Create Team button — accessible name includes "Create Team" - await page.getByRole("button", { name: /Create Team/i }).first().click(); + await page + .getByRole("button", { name: /Create Team/i }) + .first() + .click(); // Wait for the Create Team modal const dialog = page.locator(".ant-modal:visible"); @@ -157,8 +160,9 @@ test.describe("Proxy Admin - Teams", () => { await page.getByRole("button", { name: "Save Changes" }).click(); - await expect(page.getByText(/Team settings updated|updated successfully/i).first()) - .toBeVisible({ timeout: 10_000 }); + await expect(page.getByText(/Team settings updated|updated successfully/i).first()).toBeVisible({ + timeout: 10_000, + }); } finally { // Leave the team in its seeded state for any subsequent test or rerun. await restore(); diff --git a/ui/litellm-dashboard/e2e_tests/tests/settings/routerSettings.spec.ts b/ui/litellm-dashboard/e2e_tests/tests/settings/routerSettings.spec.ts index 8dd5571f7af..98b86ec9b11 100644 --- a/ui/litellm-dashboard/e2e_tests/tests/settings/routerSettings.spec.ts +++ b/ui/litellm-dashboard/e2e_tests/tests/settings/routerSettings.spec.ts @@ -86,17 +86,16 @@ test.describe("Router Settings - Fallbacks", () => { await modal.getByRole("button", { name: /Save All Configurations/i }).click(); // Success toast - await expect(page.getByText(/fallback configuration\(s\) added successfully/i).first()) - .toBeVisible({ timeout: 10_000 }); + await expect(page.getByText(/fallback configuration\(s\) added successfully/i).first()).toBeVisible({ + timeout: 10_000, + }); // Modal closes, and a single row contains BOTH the primary and the fallback // model — stronger than asserting each name appears somewhere in tbody, // which could be satisfied by leftover rows from prior runs. await expect(modal).not.toBeVisible({ timeout: 5_000 }); - const newRow = page.locator("table tbody tr") - .filter({ hasText: PRIMARY }) - .filter({ hasText: FALLBACK }); + const newRow = page.locator("table tbody tr").filter({ hasText: PRIMARY }).filter({ hasText: FALLBACK }); await expect(newRow).toHaveCount(1, { timeout: 10_000 }); }); }); diff --git a/ui/litellm-dashboard/e2e_tests/tests/team-admin/teamAdmin.spec.ts b/ui/litellm-dashboard/e2e_tests/tests/team-admin/teamAdmin.spec.ts index 1612e6929bd..18b43ec89b2 100644 --- a/ui/litellm-dashboard/e2e_tests/tests/team-admin/teamAdmin.spec.ts +++ b/ui/litellm-dashboard/e2e_tests/tests/team-admin/teamAdmin.spec.ts @@ -28,13 +28,11 @@ test.describe("Team Admin", () => { await clickTeamId(page, E2E_TEAM_CRUD_ID); await page.getByRole("tab", { name: "Virtual Keys" }).click(); - await expect(page.getByText(E2E_INTERNAL_USER_KEY_ALIAS).first()) - .toBeVisible({ timeout: 10_000 }); + await expect(page.getByText(E2E_INTERNAL_USER_KEY_ALIAS).first()).toBeVisible({ timeout: 10_000 }); // And from the global Virtual Keys page, the same key should be visible. await navigateToPage(page, Page.ApiKeys); - await expect(page.getByText(E2E_INTERNAL_USER_KEY_ALIAS).first()) - .toBeVisible({ timeout: 10_000 }); + await expect(page.getByText(E2E_INTERNAL_USER_KEY_ALIAS).first()).toBeVisible({ timeout: 10_000 }); }); test("Team admin can add a member to their team", async ({ page }) => { @@ -60,8 +58,7 @@ test.describe("Team Admin", () => { await modal.getByRole("button", { name: /Add Member/i }).click(); - await expect(page.getByText("Team member added successfully").first()) - .toBeVisible({ timeout: 10_000 }); + await expect(page.getByText("Team member added successfully").first()).toBeVisible({ timeout: 10_000 }); }); test("Team admin can remove a member from their team", async ({ page }) => { @@ -82,8 +79,7 @@ test.describe("Team Admin", () => { await expect(modal).toBeVisible({ timeout: 5_000 }); await modal.getByRole("button", { name: /^Delete$/ }).click(); - await expect(page.getByText("Team member removed successfully").first()) - .toBeVisible({ timeout: 10_000 }); + await expect(page.getByText("Team member removed successfully").first()).toBeVisible({ timeout: 10_000 }); }); test("Team admin can create a team key with All Team Models", async ({ page }) => { diff --git a/ui/litellm-dashboard/knip.json b/ui/litellm-dashboard/knip.json index e93d1997d62..9971ed779ee 100644 --- a/ui/litellm-dashboard/knip.json +++ b/ui/litellm-dashboard/knip.json @@ -1,18 +1,9 @@ { "$schema": "https://unpkg.com/knip@5/schema.json", "entry": ["scripts/**/*.ts"], - "project": [ - "src/**/*.{ts,tsx}", - "tests/**/*.{ts,tsx}", - "scripts/**/*.ts", - "e2e_tests/**/*.ts" - ], + "project": ["src/**/*.{ts,tsx}", "tests/**/*.{ts,tsx}", "scripts/**/*.ts", "e2e_tests/**/*.ts"], "playwright": { "config": "e2e_tests/playwright.config.ts", - "entry": [ - "e2e_tests/**/*.spec.ts", - "e2e_tests/**/*.setup.ts", - "e2e_tests/globalSetup.ts" - ] + "entry": ["e2e_tests/**/*.spec.ts", "e2e_tests/**/*.setup.ts", "e2e_tests/globalSetup.ts"] } } diff --git a/ui/litellm-dashboard/src/app/(dashboard)/README.md b/ui/litellm-dashboard/src/app/(dashboard)/README.md index c913431fc5b..920ea5b4258 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/README.md +++ b/ui/litellm-dashboard/src/app/(dashboard)/README.md @@ -2,7 +2,7 @@ The LiteLLM UI is currently being refactored/rewritten to reduce development friction. Please read this document to understand what's expected for new contributions. -The project follows strict NextJS file structure. All pages on the site (determined by the sidebar) are contained in their own folder, and routing is automatically handled by NextJS based on the file structure. +The project follows strict NextJS file structure. All pages on the site (determined by the sidebar) are contained in their own folder, and routing is automatically handled by NextJS based on the file structure. For example, NextJS will automatically render the admin settings page when the user visits `/settings/admin-settings` @@ -16,7 +16,9 @@ For example, NextJS will automatically render the admin settings page when the u You can use parenthesis around directory names to hide them from the user route, for example `(dashboard)`, while still getting the benefits of `layout` and file structure. ### File Structure + Every page must follow the following file structure pattern. + ``` ├── teams │   ├── TeamsView.tsx @@ -34,11 +36,11 @@ Every page must follow the following file structure pattern. │   └── page.tsx ``` -### Component Files +### Component Files All component files should ideally be as dumb as possible. Their only job should be to take the data they need from hooks or props and render them to the UI. If a component file becomes too large (over `300` lines or so), **please break it down** into smaller components. -A component should only be placed where it will be used. For example, if a component will only be used by the `teams` page, it should belong in the `teams/components` folder. +A component should only be placed where it will be used. For example, if a component will only be used by the `teams` page, it should belong in the `teams/components` folder. **Common components should be moved to the lowest common ancestor components folder.** diff --git a/ui/litellm-dashboard/src/app/(dashboard)/components/Sidebar2.tsx b/ui/litellm-dashboard/src/app/(dashboard)/components/Sidebar2.tsx index 27a6e6c13be..90f498912a8 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/components/Sidebar2.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/components/Sidebar2.tsx @@ -464,7 +464,6 @@ const Sidebar2: React.FC = ({ accessToken, userRole, defaultSelect /> {isAdminRole(userRole) && !collapsed && } - ); diff --git a/ui/litellm-dashboard/src/app/(dashboard)/components/SidebarProvider.tsx b/ui/litellm-dashboard/src/app/(dashboard)/components/SidebarProvider.tsx index 49e6569f1a7..1e091314ecd 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/components/SidebarProvider.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/components/SidebarProvider.tsx @@ -31,13 +31,15 @@ const SidebarProvider = ({ setPage, defaultSelectedKey, sidebarCollapsed }: Side 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)"); + console.log( + "[SidebarProvider] No enabled_ui_pages_internal_users in response (all pages visible by default)", + ); } if (settings?.values?.enable_projects_ui !== undefined) { diff --git a/ui/litellm-dashboard/src/app/(dashboard)/hooks/accessGroups/useAccessGroupDetails.ts b/ui/litellm-dashboard/src/app/(dashboard)/hooks/accessGroups/useAccessGroupDetails.ts index c0379b25321..3dcf73388a5 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/hooks/accessGroups/useAccessGroupDetails.ts +++ b/ui/litellm-dashboard/src/app/(dashboard)/hooks/accessGroups/useAccessGroupDetails.ts @@ -1,20 +1,12 @@ import { useQuery, useQueryClient } from "@tanstack/react-query"; -import { - getProxyBaseUrl, - getGlobalLitellmHeaderName, - deriveErrorMessage, - handleError, -} from "@/components/networking"; +import { getProxyBaseUrl, getGlobalLitellmHeaderName, deriveErrorMessage, handleError } from "@/components/networking"; import { all_admin_roles } from "@/utils/roles"; import useAuthorized from "@/app/(dashboard)/hooks/useAuthorized"; import { AccessGroupResponse, accessGroupKeys } from "./useAccessGroups"; // ── Fetch function ─────────────────────────────────────────────────────────── -const fetchAccessGroupDetails = async ( - accessToken: string, - accessGroupId: string, -): Promise => { +const fetchAccessGroupDetails = async (accessToken: string, accessGroupId: string): Promise => { const baseUrl = getProxyBaseUrl(); const url = `${baseUrl}/v1/access_group/${encodeURIComponent(accessGroupId)}`; @@ -45,17 +37,13 @@ export const useAccessGroupDetails = (accessGroupId?: string) => { return useQuery({ queryKey: accessGroupKeys.detail(accessGroupId!), queryFn: async () => fetchAccessGroupDetails(accessToken!, accessGroupId!), - enabled: - Boolean(accessToken && accessGroupId) && - all_admin_roles.includes(userRole || ""), + enabled: Boolean(accessToken && accessGroupId) && all_admin_roles.includes(userRole || ""), // Seed from the list cache when available initialData: () => { if (!accessGroupId) return undefined; - const groups = queryClient.getQueryData( - accessGroupKeys.list({}), - ); + const groups = queryClient.getQueryData(accessGroupKeys.list({})); return groups?.find((g) => g.access_group_id === accessGroupId); }, diff --git a/ui/litellm-dashboard/src/app/(dashboard)/hooks/accessGroups/useAccessGroups.ts b/ui/litellm-dashboard/src/app/(dashboard)/hooks/accessGroups/useAccessGroups.ts index 215b555fcf9..9f306c21459 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/hooks/accessGroups/useAccessGroups.ts +++ b/ui/litellm-dashboard/src/app/(dashboard)/hooks/accessGroups/useAccessGroups.ts @@ -1,11 +1,6 @@ import { useQuery } from "@tanstack/react-query"; import { createQueryKeys } from "../common/queryKeysFactory"; -import { - getProxyBaseUrl, - getGlobalLitellmHeaderName, - deriveErrorMessage, - handleError, -} from "@/components/networking"; +import { getProxyBaseUrl, getGlobalLitellmHeaderName, deriveErrorMessage, handleError } from "@/components/networking"; import { all_admin_roles } from "@/utils/roles"; import useAuthorized from "@/app/(dashboard)/hooks/useAuthorized"; @@ -32,9 +27,7 @@ export const accessGroupKeys = createQueryKeys("accessGroups"); // ── Fetch function ─────────────────────────────────────────────────────────── -const fetchAccessGroups = async ( - accessToken: string, -): Promise => { +const fetchAccessGroups = async (accessToken: string): Promise => { const baseUrl = getProxyBaseUrl(); const url = `${baseUrl}/v1/access_group`; @@ -64,7 +57,6 @@ export const useAccessGroups = () => { return useQuery({ queryKey: accessGroupKeys.list({}), queryFn: async () => fetchAccessGroups(accessToken!), - enabled: - Boolean(accessToken) && all_admin_roles.includes(userRole || ""), + enabled: Boolean(accessToken) && all_admin_roles.includes(userRole || ""), }); }; diff --git a/ui/litellm-dashboard/src/app/(dashboard)/hooks/accessGroups/useCreateAccessGroup.ts b/ui/litellm-dashboard/src/app/(dashboard)/hooks/accessGroups/useCreateAccessGroup.ts index 7ea5a813462..5efa2da6557 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/hooks/accessGroups/useCreateAccessGroup.ts +++ b/ui/litellm-dashboard/src/app/(dashboard)/hooks/accessGroups/useCreateAccessGroup.ts @@ -1,10 +1,5 @@ import { useMutation, useQueryClient } from "@tanstack/react-query"; -import { - getProxyBaseUrl, - getGlobalLitellmHeaderName, - deriveErrorMessage, - handleError, -} from "@/components/networking"; +import { getProxyBaseUrl, getGlobalLitellmHeaderName, deriveErrorMessage, handleError } from "@/components/networking"; import useAuthorized from "@/app/(dashboard)/hooks/useAuthorized"; import { AccessGroupResponse, accessGroupKeys } from "./useAccessGroups"; diff --git a/ui/litellm-dashboard/src/app/(dashboard)/hooks/accessGroups/useDeleteAccessGroup.ts b/ui/litellm-dashboard/src/app/(dashboard)/hooks/accessGroups/useDeleteAccessGroup.ts index 5df5960ce0a..01e317f6613 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/hooks/accessGroups/useDeleteAccessGroup.ts +++ b/ui/litellm-dashboard/src/app/(dashboard)/hooks/accessGroups/useDeleteAccessGroup.ts @@ -1,19 +1,11 @@ import { useMutation, useQueryClient } from "@tanstack/react-query"; -import { - getProxyBaseUrl, - getGlobalLitellmHeaderName, - deriveErrorMessage, - handleError, -} from "@/components/networking"; +import { getProxyBaseUrl, getGlobalLitellmHeaderName, deriveErrorMessage, handleError } from "@/components/networking"; import useAuthorized from "@/app/(dashboard)/hooks/useAuthorized"; import { accessGroupKeys } from "./useAccessGroups"; // ── Fetch function ─────────────────────────────────────────────────────────── -const deleteAccessGroup = async ( - accessToken: string, - accessGroupId: string, -): Promise => { +const deleteAccessGroup = async (accessToken: string, accessGroupId: string): Promise => { const baseUrl = getProxyBaseUrl(); const url = `${baseUrl}/v1/access_group/${encodeURIComponent(accessGroupId)}`; diff --git a/ui/litellm-dashboard/src/app/(dashboard)/hooks/accessGroups/useEditAccessGroup.ts b/ui/litellm-dashboard/src/app/(dashboard)/hooks/accessGroups/useEditAccessGroup.ts index 5dc2252f640..7dd85ae93dc 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/hooks/accessGroups/useEditAccessGroup.ts +++ b/ui/litellm-dashboard/src/app/(dashboard)/hooks/accessGroups/useEditAccessGroup.ts @@ -1,10 +1,5 @@ import { useMutation, useQueryClient } from "@tanstack/react-query"; -import { - getProxyBaseUrl, - getGlobalLitellmHeaderName, - deriveErrorMessage, - handleError, -} from "@/components/networking"; +import { getProxyBaseUrl, getGlobalLitellmHeaderName, deriveErrorMessage, handleError } from "@/components/networking"; import useAuthorized from "@/app/(dashboard)/hooks/useAuthorized"; import { AccessGroupResponse, accessGroupKeys } from "./useAccessGroups"; diff --git a/ui/litellm-dashboard/src/app/(dashboard)/hooks/cloudzero/useCloudZeroCreate.test.ts b/ui/litellm-dashboard/src/app/(dashboard)/hooks/cloudzero/useCloudZeroCreate.test.ts index 8334aea56e7..f370e4d6d6e 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/hooks/cloudzero/useCloudZeroCreate.test.ts +++ b/ui/litellm-dashboard/src/app/(dashboard)/hooks/cloudzero/useCloudZeroCreate.test.ts @@ -4,27 +4,22 @@ import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import React, { ReactNode } from "react"; import { useCloudZeroCreate } from "./useCloudZeroCreate"; -const { - mockProxyBaseUrl, - mockAccessToken, - mockHeaderName, - mockGetProxyBaseUrl, - mockGetGlobalLitellmHeaderName, -} = vi.hoisted(() => { - const mockProxyBaseUrl = "https://proxy.example.com"; - const mockAccessToken = "test-access-token"; - const mockHeaderName = "X-LiteLLM-API-Key"; - const mockGetProxyBaseUrl = vi.fn(() => mockProxyBaseUrl); - const mockGetGlobalLitellmHeaderName = vi.fn(() => mockHeaderName); +const { mockProxyBaseUrl, mockAccessToken, mockHeaderName, mockGetProxyBaseUrl, mockGetGlobalLitellmHeaderName } = + vi.hoisted(() => { + const mockProxyBaseUrl = "https://proxy.example.com"; + const mockAccessToken = "test-access-token"; + const mockHeaderName = "X-LiteLLM-API-Key"; + const mockGetProxyBaseUrl = vi.fn(() => mockProxyBaseUrl); + const mockGetGlobalLitellmHeaderName = vi.fn(() => mockHeaderName); - return { - mockProxyBaseUrl, - mockAccessToken, - mockHeaderName, - mockGetProxyBaseUrl, - mockGetGlobalLitellmHeaderName, - }; -}); + return { + mockProxyBaseUrl, + mockAccessToken, + mockHeaderName, + mockGetProxyBaseUrl, + mockGetGlobalLitellmHeaderName, + }; + }); vi.mock("@/components/networking", () => ({ getProxyBaseUrl: mockGetProxyBaseUrl, diff --git a/ui/litellm-dashboard/src/app/(dashboard)/hooks/cloudzero/useCloudZeroDryRun.test.ts b/ui/litellm-dashboard/src/app/(dashboard)/hooks/cloudzero/useCloudZeroDryRun.test.ts index 74d657b3e85..b5b903ea620 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/hooks/cloudzero/useCloudZeroDryRun.test.ts +++ b/ui/litellm-dashboard/src/app/(dashboard)/hooks/cloudzero/useCloudZeroDryRun.test.ts @@ -4,27 +4,22 @@ import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import React, { ReactNode } from "react"; import { useCloudZeroDryRun } from "./useCloudZeroDryRun"; -const { - mockProxyBaseUrl, - mockAccessToken, - mockHeaderName, - mockGetProxyBaseUrl, - mockGetGlobalLitellmHeaderName, -} = vi.hoisted(() => { - const mockProxyBaseUrl = "https://proxy.example.com"; - const mockAccessToken = "test-access-token"; - const mockHeaderName = "X-LiteLLM-API-Key"; - const mockGetProxyBaseUrl = vi.fn(() => mockProxyBaseUrl); - const mockGetGlobalLitellmHeaderName = vi.fn(() => mockHeaderName); +const { mockProxyBaseUrl, mockAccessToken, mockHeaderName, mockGetProxyBaseUrl, mockGetGlobalLitellmHeaderName } = + vi.hoisted(() => { + const mockProxyBaseUrl = "https://proxy.example.com"; + const mockAccessToken = "test-access-token"; + const mockHeaderName = "X-LiteLLM-API-Key"; + const mockGetProxyBaseUrl = vi.fn(() => mockProxyBaseUrl); + const mockGetGlobalLitellmHeaderName = vi.fn(() => mockHeaderName); - return { - mockProxyBaseUrl, - mockAccessToken, - mockHeaderName, - mockGetProxyBaseUrl, - mockGetGlobalLitellmHeaderName, - }; -}); + return { + mockProxyBaseUrl, + mockAccessToken, + mockHeaderName, + mockGetProxyBaseUrl, + mockGetGlobalLitellmHeaderName, + }; + }); vi.mock("@/components/networking", () => ({ getProxyBaseUrl: mockGetProxyBaseUrl, diff --git a/ui/litellm-dashboard/src/app/(dashboard)/hooks/cloudzero/useCloudZeroExport.test.ts b/ui/litellm-dashboard/src/app/(dashboard)/hooks/cloudzero/useCloudZeroExport.test.ts index 72a1cfd24aa..3c44d75dd06 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/hooks/cloudzero/useCloudZeroExport.test.ts +++ b/ui/litellm-dashboard/src/app/(dashboard)/hooks/cloudzero/useCloudZeroExport.test.ts @@ -4,27 +4,22 @@ import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import React, { ReactNode } from "react"; import { useCloudZeroExport } from "./useCloudZeroExport"; -const { - mockProxyBaseUrl, - mockAccessToken, - mockHeaderName, - mockGetProxyBaseUrl, - mockGetGlobalLitellmHeaderName, -} = vi.hoisted(() => { - const mockProxyBaseUrl = "https://proxy.example.com"; - const mockAccessToken = "test-access-token"; - const mockHeaderName = "X-LiteLLM-API-Key"; - const mockGetProxyBaseUrl = vi.fn(() => mockProxyBaseUrl); - const mockGetGlobalLitellmHeaderName = vi.fn(() => mockHeaderName); +const { mockProxyBaseUrl, mockAccessToken, mockHeaderName, mockGetProxyBaseUrl, mockGetGlobalLitellmHeaderName } = + vi.hoisted(() => { + const mockProxyBaseUrl = "https://proxy.example.com"; + const mockAccessToken = "test-access-token"; + const mockHeaderName = "X-LiteLLM-API-Key"; + const mockGetProxyBaseUrl = vi.fn(() => mockProxyBaseUrl); + const mockGetGlobalLitellmHeaderName = vi.fn(() => mockHeaderName); - return { - mockProxyBaseUrl, - mockAccessToken, - mockHeaderName, - mockGetProxyBaseUrl, - mockGetGlobalLitellmHeaderName, - }; -}); + return { + mockProxyBaseUrl, + mockAccessToken, + mockHeaderName, + mockGetProxyBaseUrl, + mockGetGlobalLitellmHeaderName, + }; + }); vi.mock("@/components/networking", () => ({ getProxyBaseUrl: mockGetProxyBaseUrl, diff --git a/ui/litellm-dashboard/src/app/(dashboard)/hooks/common/queryKeysFactory.test.ts b/ui/litellm-dashboard/src/app/(dashboard)/hooks/common/queryKeysFactory.test.ts index 39afd044097..2c1fd29f61c 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/hooks/common/queryKeysFactory.test.ts +++ b/ui/litellm-dashboard/src/app/(dashboard)/hooks/common/queryKeysFactory.test.ts @@ -13,11 +13,7 @@ describe("createQueryKeys", () => { }); it("should generate a list key with params", () => { - expect(keys.list({ page: 1, limit: 10 })).toEqual([ - "books", - "list", - { params: { page: 1, limit: 10 } }, - ]); + expect(keys.list({ page: 1, limit: 10 })).toEqual(["books", "list", { params: { page: 1, limit: 10 } }]); }); it("should generate a list key with undefined params when none provided", () => { diff --git a/ui/litellm-dashboard/src/app/(dashboard)/hooks/configOverrides/hashicorpVaultApi.ts b/ui/litellm-dashboard/src/app/(dashboard)/hooks/configOverrides/hashicorpVaultApi.ts index edf18860ec1..2af0f118500 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/hooks/configOverrides/hashicorpVaultApi.ts +++ b/ui/litellm-dashboard/src/app/(dashboard)/hooks/configOverrides/hashicorpVaultApi.ts @@ -2,9 +2,7 @@ import { getProxyBaseUrl, getGlobalLitellmHeaderName, deriveErrorMessage } from export const getHashicorpVaultConfig = async (accessToken: string) => { const proxyBaseUrl = getProxyBaseUrl(); - const url = proxyBaseUrl - ? `${proxyBaseUrl}/config_overrides/hashicorp_vault` - : `/config_overrides/hashicorp_vault`; + const url = proxyBaseUrl ? `${proxyBaseUrl}/config_overrides/hashicorp_vault` : `/config_overrides/hashicorp_vault`; const response = await fetch(url, { method: "GET", headers: { @@ -20,14 +18,9 @@ export const getHashicorpVaultConfig = async (accessToken: string) => { return data; }; -export const updateHashicorpVaultConfig = async ( - accessToken: string, - config: Record, -) => { +export const updateHashicorpVaultConfig = async (accessToken: string, config: Record) => { const proxyBaseUrl = getProxyBaseUrl(); - const url = proxyBaseUrl - ? `${proxyBaseUrl}/config_overrides/hashicorp_vault` - : `/config_overrides/hashicorp_vault`; + const url = proxyBaseUrl ? `${proxyBaseUrl}/config_overrides/hashicorp_vault` : `/config_overrides/hashicorp_vault`; const response = await fetch(url, { method: "POST", headers: { @@ -47,9 +40,7 @@ export const updateHashicorpVaultConfig = async ( export const deleteHashicorpVaultConfig = async (accessToken: string) => { const proxyBaseUrl = getProxyBaseUrl(); - const url = proxyBaseUrl - ? `${proxyBaseUrl}/config_overrides/hashicorp_vault` - : `/config_overrides/hashicorp_vault`; + const url = proxyBaseUrl ? `${proxyBaseUrl}/config_overrides/hashicorp_vault` : `/config_overrides/hashicorp_vault`; const response = await fetch(url, { method: "DELETE", headers: { diff --git a/ui/litellm-dashboard/src/app/(dashboard)/hooks/guardrails/useGuardrails.test.ts b/ui/litellm-dashboard/src/app/(dashboard)/hooks/guardrails/useGuardrails.test.ts index b1896eda0e6..8db520eecd0 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/hooks/guardrails/useGuardrails.test.ts +++ b/ui/litellm-dashboard/src/app/(dashboard)/hooks/guardrails/useGuardrails.test.ts @@ -289,11 +289,7 @@ describe("useGuardrails", () => { expect(result.current.isSuccess).toBe(true); }); - expect(result.current.data?.globalGuardrailNames).toEqual( - new Set(["global-guard-a", "global-guard-b"]), - ); - expect(result.current.data?.optionalGuardrailNames).toEqual( - new Set(["optional-guard-a", "optional-guard-b"]), - ); + expect(result.current.data?.globalGuardrailNames).toEqual(new Set(["global-guard-a", "global-guard-b"])); + expect(result.current.data?.optionalGuardrailNames).toEqual(new Set(["optional-guard-a", "optional-guard-b"])); }); }); diff --git a/ui/litellm-dashboard/src/app/(dashboard)/hooks/guardrails/useRegisterGuardrail.ts b/ui/litellm-dashboard/src/app/(dashboard)/hooks/guardrails/useRegisterGuardrail.ts index 3135e8326fc..edbcbdfe170 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/hooks/guardrails/useRegisterGuardrail.ts +++ b/ui/litellm-dashboard/src/app/(dashboard)/hooks/guardrails/useRegisterGuardrail.ts @@ -1,10 +1,5 @@ import { useMutation, useQueryClient } from "@tanstack/react-query"; -import { - getProxyBaseUrl, - getGlobalLitellmHeaderName, - deriveErrorMessage, - handleError, -} from "@/components/networking"; +import { getProxyBaseUrl, getGlobalLitellmHeaderName, deriveErrorMessage, handleError } from "@/components/networking"; import useAuthorized from "@/app/(dashboard)/hooks/useAuthorized"; import { createQueryKeys } from "../common/queryKeysFactory"; diff --git a/ui/litellm-dashboard/src/app/(dashboard)/hooks/healthReadiness/useHealthReadinessDetails.ts b/ui/litellm-dashboard/src/app/(dashboard)/hooks/healthReadiness/useHealthReadinessDetails.ts index 5838dbd0ee6..3b79e5c7643 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/hooks/healthReadiness/useHealthReadinessDetails.ts +++ b/ui/litellm-dashboard/src/app/(dashboard)/hooks/healthReadiness/useHealthReadinessDetails.ts @@ -1,8 +1,5 @@ import { useQuery, UseQueryResult } from "@tanstack/react-query"; -import { - getGlobalLitellmHeaderName, - getProxyBaseUrl, -} from "@/components/networking"; +import { getGlobalLitellmHeaderName, getProxyBaseUrl } from "@/components/networking"; import { createQueryKeys } from "../common/queryKeysFactory"; const healthReadinessDetailsKeys = createQueryKeys("healthReadinessDetails"); @@ -18,9 +15,7 @@ export interface HealthReadinessDetailsResponse { is_detailed_debug?: boolean; } -const fetchHealthReadinessDetails = async ( - accessToken: string, -): Promise => { +const fetchHealthReadinessDetails = async (accessToken: string): Promise => { const baseUrl = getProxyBaseUrl(); const response = await fetch(`${baseUrl}/health/readiness/details`, { method: "GET", @@ -30,9 +25,7 @@ const fetchHealthReadinessDetails = async ( }, }); if (!response.ok) { - throw new Error( - `Failed to fetch health readiness details: ${response.statusText}`, - ); + throw new Error(`Failed to fetch health readiness details: ${response.statusText}`); } return response.json(); }; diff --git a/ui/litellm-dashboard/src/app/(dashboard)/hooks/keys/useKeyAliases.test.ts b/ui/litellm-dashboard/src/app/(dashboard)/hooks/keys/useKeyAliases.test.ts index 1e1190b12c8..e0140c6a63a 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/hooks/keys/useKeyAliases.test.ts +++ b/ui/litellm-dashboard/src/app/(dashboard)/hooks/keys/useKeyAliases.test.ts @@ -128,9 +128,7 @@ describe("useInfiniteKeyAliases", () => { }); it("should fetch the next page when fetchNextPage is called", async () => { - mockKeyAliasesCall - .mockResolvedValueOnce(mockPage1) - .mockResolvedValueOnce(mockPage2); + mockKeyAliasesCall.mockResolvedValueOnce(mockPage1).mockResolvedValueOnce(mockPage2); const wrapper = createWrapper(); const { result } = renderHook(() => useInfiniteKeyAliases(2), { wrapper }); @@ -151,10 +149,10 @@ describe("useInfiniteKeyAliases", () => { it("should include search in query key so search changes refetch from page 1", async () => { const wrapper = createWrapper(); - const { result, rerender } = renderHook( - ({ search }: { search?: string }) => useInfiniteKeyAliases(50, search), - { wrapper, initialProps: { search: undefined } }, - ); + const { result, rerender } = renderHook(({ search }: { search?: string }) => useInfiniteKeyAliases(50, search), { + wrapper, + initialProps: { search: undefined }, + }); await waitFor(() => { expect(result.current.isSuccess).toBe(true); diff --git a/ui/litellm-dashboard/src/app/(dashboard)/hooks/keys/useKeyAliases.ts b/ui/litellm-dashboard/src/app/(dashboard)/hooks/keys/useKeyAliases.ts index 03e96fe73c4..2b4583ad6b3 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/hooks/keys/useKeyAliases.ts +++ b/ui/litellm-dashboard/src/app/(dashboard)/hooks/keys/useKeyAliases.ts @@ -5,11 +5,7 @@ import useAuthorized from "../useAuthorized"; const infiniteKeyAliasKeys = createQueryKeys("infiniteKeyAliases"); -export const useInfiniteKeyAliases = ( - size: number = 50, - search?: string, - team_id?: string, -) => { +export const useInfiniteKeyAliases = (size: number = 50, search?: string, team_id?: string) => { const { accessToken } = useAuthorized(); return useInfiniteQuery({ queryKey: infiniteKeyAliasKeys.list({ @@ -20,13 +16,7 @@ export const useInfiniteKeyAliases = ( }, }), queryFn: async ({ pageParam }) => { - return await keyAliasesCall( - accessToken!, - pageParam as number, - size, - search, - team_id, - ); + return await keyAliasesCall(accessToken!, pageParam as number, size, search, team_id); }, initialPageParam: 1, getNextPageParam: (lastPage) => { diff --git a/ui/litellm-dashboard/src/app/(dashboard)/hooks/keys/useKeys.test.ts b/ui/litellm-dashboard/src/app/(dashboard)/hooks/keys/useKeys.test.ts index 80cb69495da..1e700e572d0 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/hooks/keys/useKeys.test.ts +++ b/ui/litellm-dashboard/src/app/(dashboard)/hooks/keys/useKeys.test.ts @@ -410,10 +410,7 @@ describe("useKeys", () => { }), }); - const { result } = renderHook( - () => useKeys(1, 10, { projectID: "project-1" }), - { wrapper }, - ); + const { result } = renderHook(() => useKeys(1, 10, { projectID: "project-1" }), { wrapper }); await waitFor(() => { expect(result.current.isLoading).toBe(false); @@ -436,10 +433,7 @@ describe("useKeys", () => { }), }); - const { result } = renderHook( - () => useKeys(1, 10, { projectID: "project-1", teamID: "team-1" }), - { wrapper }, - ); + const { result } = renderHook(() => useKeys(1, 10, { projectID: "project-1", teamID: "team-1" }), { wrapper }); await waitFor(() => { expect(result.current.isLoading).toBe(false); @@ -456,10 +450,7 @@ describe("useKeys", () => { json: async () => mockKeysResponse, }); - const { result } = renderHook( - () => useKeys(1, 10, { projectID: null }), - { wrapper }, - ); + const { result } = renderHook(() => useKeys(1, 10, { projectID: null }), { wrapper }); await waitFor(() => { expect(result.current.isLoading).toBe(false); diff --git a/ui/litellm-dashboard/src/app/(dashboard)/hooks/keys/useKeys.ts b/ui/litellm-dashboard/src/app/(dashboard)/hooks/keys/useKeys.ts index fbe5eccb75a..4a04c541d1a 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/hooks/keys/useKeys.ts +++ b/ui/litellm-dashboard/src/app/(dashboard)/hooks/keys/useKeys.ts @@ -1,11 +1,6 @@ import { keepPreviousData, useQuery, UseQueryResult } from "@tanstack/react-query"; import { createQueryKeys } from "../common/queryKeysFactory"; -import { - getProxyBaseUrl, - getGlobalLitellmHeaderName, - deriveErrorMessage, - handleError, -} from "@/components/networking"; +import { getProxyBaseUrl, getGlobalLitellmHeaderName, deriveErrorMessage, handleError } from "@/components/networking"; import { KeyResponse } from "@/components/key_team_helpers/key_list"; import useAuthorized from "@/app/(dashboard)/hooks/useAuthorized"; @@ -43,18 +38,13 @@ export interface KeyListCallOptions { status?: string | null; } -const keyListCall = async ( - accessToken: string, - page: number, - pageSize: number, - options: KeyListCallOptions = {}, -) => { +const keyListCall = async (accessToken: string, page: number, pageSize: number, options: KeyListCallOptions = {}) => { /** * Get all available keys on proxy */ try { const baseUrl = getProxyBaseUrl(); - + const params = new URLSearchParams( Object.entries({ team_id: options.teamID, @@ -134,4 +124,4 @@ export const useDeletedKeys = ( staleTime: 30000, // 30 seconds placeholderData: keepPreviousData, }); -}; \ No newline at end of file +}; diff --git a/ui/litellm-dashboard/src/app/(dashboard)/hooks/keys/useResetKeySpend.ts b/ui/litellm-dashboard/src/app/(dashboard)/hooks/keys/useResetKeySpend.ts index a845fc5881a..0265b4dc402 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/hooks/keys/useResetKeySpend.ts +++ b/ui/litellm-dashboard/src/app/(dashboard)/hooks/keys/useResetKeySpend.ts @@ -1,10 +1,5 @@ import { useMutation, useQueryClient } from "@tanstack/react-query"; -import { - getProxyBaseUrl, - getGlobalLitellmHeaderName, - deriveErrorMessage, - handleError, -} from "@/components/networking"; +import { getProxyBaseUrl, getGlobalLitellmHeaderName, deriveErrorMessage, handleError } from "@/components/networking"; import useAuthorized from "@/app/(dashboard)/hooks/useAuthorized"; import { keyKeys } from "./useKeys"; @@ -20,10 +15,7 @@ export interface ResetKeySpendResponse { // ── Fetch function ──────────────────────────────────────────────────────────── -export const resetKeySpend = async ( - accessToken: string, - keyToken: string, -): Promise => { +export const resetKeySpend = async (accessToken: string, keyToken: string): Promise => { const baseUrl = getProxyBaseUrl(); const url = `${baseUrl ? `${baseUrl}/key/${keyToken}/reset_spend` : `/key/${keyToken}/reset_spend`}`; diff --git a/ui/litellm-dashboard/src/app/(dashboard)/hooks/logDetails/useLogDetails.ts b/ui/litellm-dashboard/src/app/(dashboard)/hooks/logDetails/useLogDetails.ts index 6c0f95d5995..5e4757bdb2f 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/hooks/logDetails/useLogDetails.ts +++ b/ui/litellm-dashboard/src/app/(dashboard)/hooks/logDetails/useLogDetails.ts @@ -10,11 +10,7 @@ import { uiSpendLogDetailsCall } from "@/components/networking"; * @param startTime - The formatted start time for the query * @param enabled - Whether the query should be enabled (e.g., drawer is open) */ -export const useLogDetails = ( - requestId: string | undefined, - startTime: string | undefined, - enabled: boolean, -) => { +export const useLogDetails = (requestId: string | undefined, startTime: string | undefined, enabled: boolean) => { const { accessToken } = useAuthorized(); return useQuery({ diff --git a/ui/litellm-dashboard/src/app/(dashboard)/hooks/mcpSemanticFilterSettings/useMCPSemanticFilterSettings.ts b/ui/litellm-dashboard/src/app/(dashboard)/hooks/mcpSemanticFilterSettings/useMCPSemanticFilterSettings.ts index e91f5aa670b..ad9880d8cac 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/hooks/mcpSemanticFilterSettings/useMCPSemanticFilterSettings.ts +++ b/ui/litellm-dashboard/src/app/(dashboard)/hooks/mcpSemanticFilterSettings/useMCPSemanticFilterSettings.ts @@ -3,9 +3,7 @@ import { useQuery } from "@tanstack/react-query"; import { createQueryKeys } from "../common/queryKeysFactory"; import useAuthorized from "../useAuthorized"; -const mcpSemanticFilterSettingsKeys = createQueryKeys( - "mcpSemanticFilterSettings" -); +const mcpSemanticFilterSettingsKeys = createQueryKeys("mcpSemanticFilterSettings"); export const useMCPSemanticFilterSettings = () => { const { accessToken } = useAuthorized(); diff --git a/ui/litellm-dashboard/src/app/(dashboard)/hooks/mcpSemanticFilterSettings/useUpdateMCPSemanticFilterSettings.ts b/ui/litellm-dashboard/src/app/(dashboard)/hooks/mcpSemanticFilterSettings/useUpdateMCPSemanticFilterSettings.ts index 2062b4f4c29..bc7406599b1 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/hooks/mcpSemanticFilterSettings/useUpdateMCPSemanticFilterSettings.ts +++ b/ui/litellm-dashboard/src/app/(dashboard)/hooks/mcpSemanticFilterSettings/useUpdateMCPSemanticFilterSettings.ts @@ -2,9 +2,7 @@ import { updateMCPSemanticFilterSettings } from "@/components/networking"; import { useMutation, useQueryClient } from "@tanstack/react-query"; import { createQueryKeys } from "../common/queryKeysFactory"; -const mcpSemanticFilterSettingsKeys = createQueryKeys( - "mcpSemanticFilterSettings" -); +const mcpSemanticFilterSettingsKeys = createQueryKeys("mcpSemanticFilterSettings"); export const useUpdateMCPSemanticFilterSettings = (accessToken: string) => { const queryClient = useQueryClient(); diff --git a/ui/litellm-dashboard/src/app/(dashboard)/hooks/mcpServers/useMCPAccessGroups.test.ts b/ui/litellm-dashboard/src/app/(dashboard)/hooks/mcpServers/useMCPAccessGroups.test.ts index 9c555ff1234..65dfd6bf4f2 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/hooks/mcpServers/useMCPAccessGroups.test.ts +++ b/ui/litellm-dashboard/src/app/(dashboard)/hooks/mcpServers/useMCPAccessGroups.test.ts @@ -121,4 +121,4 @@ describe("useMCPAccessGroups", () => { expect(result.current.data).toEqual([]); }); -}); \ No newline at end of file +}); diff --git a/ui/litellm-dashboard/src/app/(dashboard)/hooks/mcpServers/useMCPServerHealth.ts b/ui/litellm-dashboard/src/app/(dashboard)/hooks/mcpServers/useMCPServerHealth.ts index 681bf4161ad..9ad8a6f43fa 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/hooks/mcpServers/useMCPServerHealth.ts +++ b/ui/litellm-dashboard/src/app/(dashboard)/hooks/mcpServers/useMCPServerHealth.ts @@ -24,32 +24,32 @@ export const useMCPServerHealth = () => { refetchInterval: 30000, }); - const recheckServerHealth = useCallback(async (serverId: string) => { - if (!accessToken) return; + const recheckServerHealth = useCallback( + async (serverId: string) => { + if (!accessToken) return; - setRecheckingServerIds((prev) => new Set(prev).add(serverId)); + setRecheckingServerIds((prev) => new Set(prev).add(serverId)); - try { - const result: MCPServerHealth[] = await fetchMCPServerHealth(accessToken, [serverId]); + try { + const result: MCPServerHealth[] = await fetchMCPServerHealth(accessToken, [serverId]); - queryClient.setQueriesData( - { queryKey: mcpServerHealthKeys.lists() }, - (oldData) => { + queryClient.setQueriesData({ queryKey: mcpServerHealthKeys.lists() }, (oldData) => { if (!oldData) return result; return oldData.map((h) => { const updated = result.find((r) => r.server_id === h.server_id); return updated ?? h; }); - }, - ); - } finally { - setRecheckingServerIds((prev) => { - const next = new Set(prev); - next.delete(serverId); - return next; - }); - } - }, [accessToken, queryClient]); + }); + } finally { + setRecheckingServerIds((prev) => { + const next = new Set(prev); + next.delete(serverId); + return next; + }); + } + }, + [accessToken, queryClient], + ); return { ...query, diff --git a/ui/litellm-dashboard/src/app/(dashboard)/hooks/mcpServers/useMCPServers.test.ts b/ui/litellm-dashboard/src/app/(dashboard)/hooks/mcpServers/useMCPServers.test.ts index ee03a0ab7c3..52b58f9e318 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/hooks/mcpServers/useMCPServers.test.ts +++ b/ui/litellm-dashboard/src/app/(dashboard)/hooks/mcpServers/useMCPServers.test.ts @@ -131,4 +131,4 @@ describe("useMCPServers", () => { expect(result.current.data).toEqual([]); }); -}); \ No newline at end of file +}); diff --git a/ui/litellm-dashboard/src/app/(dashboard)/hooks/models/useModels.ts b/ui/litellm-dashboard/src/app/(dashboard)/hooks/models/useModels.ts index fe1afdcc39f..c997f679b2e 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/hooks/models/useModels.ts +++ b/ui/litellm-dashboard/src/app/(dashboard)/hooks/models/useModels.ts @@ -28,7 +28,15 @@ const allProxyModelsKeys = createQueryKeys("allProxyModels"); const selectedTeamModelsKeys = createQueryKeys("selectedTeamModels"); const infiniteModelKeys = createQueryKeys("infiniteModels"); -export const useModelsInfo = (page: number = 1, size: number = 50, search?: string, modelId?: string, teamId?: string, sortBy?: string, sortOrder?: string) => { +export const useModelsInfo = ( + page: number = 1, + size: number = 50, + search?: string, + modelId?: string, + teamId?: string, + sortBy?: string, + sortOrder?: string, +) => { const { accessToken, userId, userRole } = useAuthorized(); return useQuery({ queryKey: modelKeys.list({ @@ -44,7 +52,8 @@ export const useModelsInfo = (page: number = 1, size: number = 50, search?: stri ...(sortOrder && { sortOrder }), }, }), - queryFn: async () => await modelInfoCall(accessToken!, userId!, userRole!, page, size, search, modelId, teamId, sortBy, sortOrder), + queryFn: async () => + await modelInfoCall(accessToken!, userId!, userRole!, page, size, search, modelId, teamId, sortBy, sortOrder), enabled: Boolean(accessToken && userId && userRole), }); }; @@ -76,10 +85,7 @@ export const useSelectedTeamModels = (teamID: string | null) => { }); }; -export const useInfiniteModelInfo = ( - size: number = 50, - search?: string, -) => { +export const useInfiniteModelInfo = (size: number = 50, search?: string) => { const { accessToken, userId, userRole } = useAuthorized(); return useInfiniteQuery({ queryKey: infiniteModelKeys.list({ @@ -91,14 +97,7 @@ export const useInfiniteModelInfo = ( }, }), queryFn: async ({ pageParam }) => { - return await modelInfoCall( - accessToken!, - userId!, - userRole!, - pageParam as number, - size, - search, - ); + return await modelInfoCall(accessToken!, userId!, userRole!, pageParam as number, size, search); }, initialPageParam: 1, getNextPageParam: (lastPage) => { diff --git a/ui/litellm-dashboard/src/app/(dashboard)/hooks/projects/useCreateProject.test.ts b/ui/litellm-dashboard/src/app/(dashboard)/hooks/projects/useCreateProject.test.ts index 64d950d59ee..110a704725a 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/hooks/projects/useCreateProject.test.ts +++ b/ui/litellm-dashboard/src/app/(dashboard)/hooks/projects/useCreateProject.test.ts @@ -103,9 +103,7 @@ describe("useCreateProject", () => { const { result } = renderHook(() => useCreateProject(), { wrapper: makeWrapper(queryClient), }); - await expect(result.current.mutateAsync({ team_id: "team-1" })).rejects.toThrow( - "Access token is required" - ); + await expect(result.current.mutateAsync({ team_id: "team-1" })).rejects.toThrow("Access token is required"); expect(global.fetch).not.toHaveBeenCalled(); }); }); diff --git a/ui/litellm-dashboard/src/app/(dashboard)/hooks/projects/useCreateProject.ts b/ui/litellm-dashboard/src/app/(dashboard)/hooks/projects/useCreateProject.ts index e206c770b19..2e67e626936 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/hooks/projects/useCreateProject.ts +++ b/ui/litellm-dashboard/src/app/(dashboard)/hooks/projects/useCreateProject.ts @@ -1,10 +1,5 @@ import { useMutation, useQueryClient } from "@tanstack/react-query"; -import { - getProxyBaseUrl, - getGlobalLitellmHeaderName, - deriveErrorMessage, - handleError, -} from "@/components/networking"; +import { getProxyBaseUrl, getGlobalLitellmHeaderName, deriveErrorMessage, handleError } from "@/components/networking"; import useAuthorized from "@/app/(dashboard)/hooks/useAuthorized"; import { ProjectResponse, projectKeys } from "./useProjects"; @@ -25,10 +20,7 @@ export interface ProjectCreateParams { // ── Fetch function ─────────────────────────────────────────────────────────── -const createProject = async ( - accessToken: string, - params: ProjectCreateParams, -): Promise => { +const createProject = async (accessToken: string, params: ProjectCreateParams): Promise => { const baseUrl = getProxyBaseUrl(); const url = `${baseUrl}/project/new`; diff --git a/ui/litellm-dashboard/src/app/(dashboard)/hooks/projects/useDeleteProject.test.ts b/ui/litellm-dashboard/src/app/(dashboard)/hooks/projects/useDeleteProject.test.ts index 85a9f3e0b10..beaad13ce2a 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/hooks/projects/useDeleteProject.test.ts +++ b/ui/litellm-dashboard/src/app/(dashboard)/hooks/projects/useDeleteProject.test.ts @@ -80,9 +80,7 @@ describe("useDeleteProject", () => { const { result } = renderHook(() => useDeleteProject(), { wrapper: makeWrapper(queryClient), }); - await expect(result.current.mutateAsync(["proj-1"])).rejects.toThrow( - "Access token is required" - ); + await expect(result.current.mutateAsync(["proj-1"])).rejects.toThrow("Access token is required"); expect(global.fetch).not.toHaveBeenCalled(); }); }); diff --git a/ui/litellm-dashboard/src/app/(dashboard)/hooks/projects/useDeleteProject.ts b/ui/litellm-dashboard/src/app/(dashboard)/hooks/projects/useDeleteProject.ts index 5abf9e03be2..04f2c547eef 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/hooks/projects/useDeleteProject.ts +++ b/ui/litellm-dashboard/src/app/(dashboard)/hooks/projects/useDeleteProject.ts @@ -1,19 +1,11 @@ import { useMutation, useQueryClient } from "@tanstack/react-query"; -import { - getProxyBaseUrl, - getGlobalLitellmHeaderName, - deriveErrorMessage, - handleError, -} from "@/components/networking"; +import { getProxyBaseUrl, getGlobalLitellmHeaderName, deriveErrorMessage, handleError } from "@/components/networking"; import useAuthorized from "@/app/(dashboard)/hooks/useAuthorized"; import { projectKeys } from "./useProjects"; // ── Fetch function ─────────────────────────────────────────────────────────── -const deleteProjects = async ( - accessToken: string, - projectIds: string[], -): Promise => { +const deleteProjects = async (accessToken: string, projectIds: string[]): Promise => { const baseUrl = getProxyBaseUrl(); const url = `${baseUrl}/project/delete`; diff --git a/ui/litellm-dashboard/src/app/(dashboard)/hooks/projects/useProjectDetails.ts b/ui/litellm-dashboard/src/app/(dashboard)/hooks/projects/useProjectDetails.ts index 1d35ac1bf70..037baa18692 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/hooks/projects/useProjectDetails.ts +++ b/ui/litellm-dashboard/src/app/(dashboard)/hooks/projects/useProjectDetails.ts @@ -1,20 +1,12 @@ import { useQuery, useQueryClient } from "@tanstack/react-query"; -import { - getProxyBaseUrl, - getGlobalLitellmHeaderName, - deriveErrorMessage, - handleError, -} from "@/components/networking"; +import { getProxyBaseUrl, getGlobalLitellmHeaderName, deriveErrorMessage, handleError } from "@/components/networking"; import { all_admin_roles } from "@/utils/roles"; import useAuthorized from "@/app/(dashboard)/hooks/useAuthorized"; import { ProjectResponse, projectKeys } from "./useProjects"; // ── Fetch function ─────────────────────────────────────────────────────────── -const fetchProjectDetails = async ( - accessToken: string, - projectId: string, -): Promise => { +const fetchProjectDetails = async (accessToken: string, projectId: string): Promise => { const baseUrl = getProxyBaseUrl(); const url = `${baseUrl}/project/info?project_id=${encodeURIComponent(projectId)}`; @@ -45,17 +37,13 @@ export const useProjectDetails = (projectId?: string) => { return useQuery({ queryKey: projectKeys.detail(projectId!), queryFn: async () => fetchProjectDetails(accessToken!, projectId!), - enabled: - Boolean(accessToken && projectId) && - all_admin_roles.includes(userRole || ""), + enabled: Boolean(accessToken && projectId) && all_admin_roles.includes(userRole || ""), // Seed from the list cache when available initialData: () => { if (!projectId) return undefined; - const projects = queryClient.getQueryData( - projectKeys.list({}), - ); + const projects = queryClient.getQueryData(projectKeys.list({})); return projects?.find((p) => p.project_id === projectId); }, diff --git a/ui/litellm-dashboard/src/app/(dashboard)/hooks/projects/useProjects.ts b/ui/litellm-dashboard/src/app/(dashboard)/hooks/projects/useProjects.ts index 79976f54626..7bdc8a4fe6d 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/hooks/projects/useProjects.ts +++ b/ui/litellm-dashboard/src/app/(dashboard)/hooks/projects/useProjects.ts @@ -1,11 +1,6 @@ import { useQuery } from "@tanstack/react-query"; import { createQueryKeys } from "../common/queryKeysFactory"; -import { - getProxyBaseUrl, - getGlobalLitellmHeaderName, - deriveErrorMessage, - handleError, -} from "@/components/networking"; +import { getProxyBaseUrl, getGlobalLitellmHeaderName, deriveErrorMessage, handleError } from "@/components/networking"; import useAuthorized from "@/app/(dashboard)/hooks/useAuthorized"; import { all_admin_roles } from "@/utils/roles"; @@ -49,9 +44,7 @@ export const projectKeys = createQueryKeys("projects"); // ── Fetch function ─────────────────────────────────────────────────────────── -const fetchProjects = async ( - accessToken: string, -): Promise => { +const fetchProjects = async (accessToken: string): Promise => { const baseUrl = getProxyBaseUrl(); const url = `${baseUrl}/project/list`; diff --git a/ui/litellm-dashboard/src/app/(dashboard)/hooks/projects/useUpdateProject.test.ts b/ui/litellm-dashboard/src/app/(dashboard)/hooks/projects/useUpdateProject.test.ts index 31d1a5fb352..9e752ac098a 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/hooks/projects/useUpdateProject.test.ts +++ b/ui/litellm-dashboard/src/app/(dashboard)/hooks/projects/useUpdateProject.test.ts @@ -108,9 +108,9 @@ describe("useUpdateProject", () => { const { result } = renderHook(() => useUpdateProject(), { wrapper: makeWrapper(queryClient), }); - await expect( - result.current.mutateAsync({ projectId: "proj-1", params: {} }) - ).rejects.toThrow("Access token is required"); + await expect(result.current.mutateAsync({ projectId: "proj-1", params: {} })).rejects.toThrow( + "Access token is required", + ); expect(global.fetch).not.toHaveBeenCalled(); }); }); diff --git a/ui/litellm-dashboard/src/app/(dashboard)/hooks/projects/useUpdateProject.ts b/ui/litellm-dashboard/src/app/(dashboard)/hooks/projects/useUpdateProject.ts index 2042c8fc7cd..6d8c2d9d4f8 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/hooks/projects/useUpdateProject.ts +++ b/ui/litellm-dashboard/src/app/(dashboard)/hooks/projects/useUpdateProject.ts @@ -1,10 +1,5 @@ import { useMutation, useQueryClient } from "@tanstack/react-query"; -import { - getProxyBaseUrl, - getGlobalLitellmHeaderName, - deriveErrorMessage, - handleError, -} from "@/components/networking"; +import { getProxyBaseUrl, getGlobalLitellmHeaderName, deriveErrorMessage, handleError } from "@/components/networking"; import useAuthorized from "@/app/(dashboard)/hooks/useAuthorized"; import { ProjectResponse, projectKeys } from "./useProjects"; @@ -58,11 +53,7 @@ export const useUpdateProject = () => { const { accessToken } = useAuthorized(); const queryClient = useQueryClient(); - return useMutation< - ProjectResponse, - Error, - { projectId: string; params: ProjectUpdateParams } - >({ + return useMutation({ mutationFn: async ({ projectId, params }) => { if (!accessToken) { throw new Error("Access token is required"); diff --git a/ui/litellm-dashboard/src/app/(dashboard)/hooks/storeModelInDB/useStoreModelInDB.test.ts b/ui/litellm-dashboard/src/app/(dashboard)/hooks/storeModelInDB/useStoreModelInDB.test.ts index 6ff784ebd90..dd69e8c8791 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/hooks/storeModelInDB/useStoreModelInDB.test.ts +++ b/ui/litellm-dashboard/src/app/(dashboard)/hooks/storeModelInDB/useStoreModelInDB.test.ts @@ -54,7 +54,7 @@ describe("useStoreModelInDB", () => { field_value: true, config_type: "general_settings", }), - }) + }), ); }); @@ -80,15 +80,12 @@ describe("useStoreModelInDB", () => { field_value: false, config_type: "general_settings", }), - }) + }), ); }); it("should throw error when access token is missing", async () => { - vi.spyOn( - await import("../useAuthorized"), - "default" - ).mockReturnValue({ + vi.spyOn(await import("../useAuthorized"), "default").mockReturnValue({ accessToken: null, userRole: null, userId: null, diff --git a/ui/litellm-dashboard/src/app/(dashboard)/hooks/storeModelInDB/useStoreModelInDB.ts b/ui/litellm-dashboard/src/app/(dashboard)/hooks/storeModelInDB/useStoreModelInDB.ts index e6efbd724cd..27e375c265d 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/hooks/storeModelInDB/useStoreModelInDB.ts +++ b/ui/litellm-dashboard/src/app/(dashboard)/hooks/storeModelInDB/useStoreModelInDB.ts @@ -12,7 +12,7 @@ export interface StoreModelInDBResponse { const performStoreModelInDB = async ( accessToken: string, - params: StoreModelInDBParams + params: StoreModelInDBParams, ): Promise => { const proxyBaseUrl = getProxyBaseUrl(); const url = proxyBaseUrl ? `${proxyBaseUrl}/config/field/update` : `/config/field/update`; @@ -41,11 +41,7 @@ const performStoreModelInDB = async ( return data; }; -export const useStoreModelInDB = (): UseMutationResult< - StoreModelInDBResponse, - Error, - StoreModelInDBParams -> => { +export const useStoreModelInDB = (): UseMutationResult => { const { accessToken } = useAuthorized(); return useMutation({ diff --git a/ui/litellm-dashboard/src/app/(dashboard)/hooks/storeRequestInSpendLogs/useStoreRequestInSpendLogs.ts b/ui/litellm-dashboard/src/app/(dashboard)/hooks/storeRequestInSpendLogs/useStoreRequestInSpendLogs.ts index 67b52997a01..88a37b30291 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/hooks/storeRequestInSpendLogs/useStoreRequestInSpendLogs.ts +++ b/ui/litellm-dashboard/src/app/(dashboard)/hooks/storeRequestInSpendLogs/useStoreRequestInSpendLogs.ts @@ -14,7 +14,7 @@ export interface StoreRequestInSpendLogsResponse { const performStoreRequestInSpendLogs = async ( accessToken: string, - params: StoreRequestInSpendLogsParams + params: StoreRequestInSpendLogsParams, ): Promise => { const proxyBaseUrl = getProxyBaseUrl(); const url = proxyBaseUrl ? `${proxyBaseUrl}/config/update` : `/config/update`; diff --git a/ui/litellm-dashboard/src/app/(dashboard)/hooks/teams/useTeams.test.ts b/ui/litellm-dashboard/src/app/(dashboard)/hooks/teams/useTeams.test.ts index 217ca426c25..20f034ada36 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/hooks/teams/useTeams.test.ts +++ b/ui/litellm-dashboard/src/app/(dashboard)/hooks/teams/useTeams.test.ts @@ -423,7 +423,7 @@ describe("useTeam", () => { // This tests the defensive error path in queryFn (lines 111-112) // The enabled check prevents queryFn from running, but we can test the defensive code // by manually constructing and calling the queryFn logic - + // Set up mocks mockUseAuthorized.mockReturnValue({ accessToken: null, // Missing accessToken @@ -438,24 +438,24 @@ describe("useTeam", () => { // Import useQueryClient to get access to query client const { useQueryClient } = await import("@tanstack/react-query"); - + // Manually test the queryFn logic by calling it directly // This simulates what would happen if enabled check was bypassed const testQueryFn = async () => { const { accessToken } = mockUseAuthorized(); const teamId = "team-1"; - + // This is the defensive check from lines 111-112 if (!accessToken || !teamId) { throw new Error("Missing auth or teamId"); } - + return teamInfoCall(accessToken, teamId); }; // Test that the error is thrown await expect(testQueryFn()).rejects.toThrow("Missing auth or teamId"); - + // Also test with missing teamId mockUseAuthorized.mockReturnValue({ accessToken: "test-access-token", @@ -471,11 +471,11 @@ describe("useTeam", () => { const testQueryFnMissingTeamId = async () => { const { accessToken } = mockUseAuthorized(); const teamId = undefined; // Missing teamId - + if (!accessToken || !teamId) { throw new Error("Missing auth or teamId"); } - + return teamInfoCall(accessToken, teamId); }; @@ -736,13 +736,10 @@ describe("useDeletedTeams", () => { json: async () => ({ teams: mockDeletedTeams }), }); - const { result, rerender } = renderHook( - ({ page }) => useDeletedTeams(page, 10, {}), - { - wrapper, - initialProps: { page: 1 }, - }, - ); + const { result, rerender } = renderHook(({ page }) => useDeletedTeams(page, 10, {}), { + wrapper, + initialProps: { page: 1 }, + }); await waitFor(() => { expect(result.current.isSuccess).toBe(true); diff --git a/ui/litellm-dashboard/src/app/(dashboard)/hooks/teams/useTeams.ts b/ui/litellm-dashboard/src/app/(dashboard)/hooks/teams/useTeams.ts index b25b6ce393a..c356434ba04 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/hooks/teams/useTeams.ts +++ b/ui/litellm-dashboard/src/app/(dashboard)/hooks/teams/useTeams.ts @@ -4,12 +4,7 @@ import useAuthorized from "@/app/(dashboard)/hooks/useAuthorized"; import { fetchTeams } from "@/app/(dashboard)/networking"; import { createQueryKeys } from "@/app/(dashboard)/hooks/common/queryKeysFactory"; import { teamInfoCall } from "@/components/networking"; -import { - getProxyBaseUrl, - getGlobalLitellmHeaderName, - deriveErrorMessage, - handleError, -} from "@/components/networking"; +import { getProxyBaseUrl, getGlobalLitellmHeaderName, deriveErrorMessage, handleError } from "@/components/networking"; export interface TeamsResponse { teams: Team[]; @@ -24,7 +19,6 @@ export interface DeletedTeam extends Team { deleted_by: string; } - export interface TeamListCallOptions { organizationID?: string | null; teamID?: string | null; @@ -47,7 +41,7 @@ export const teamListCall = async ( */ try { const baseUrl = getProxyBaseUrl(); - + const params = new URLSearchParams( Object.entries({ team_id: options.teamID, @@ -128,11 +122,7 @@ export const useTeam = (teamId?: string) => { const infiniteTeamKeys = createQueryKeys("infiniteTeams"); -export const useInfiniteTeams = ( - pageSize: number = 50, - search?: string, - organizationId?: string | null, -) => { +export const useInfiniteTeams = (pageSize: number = 50, search?: string, organizationId?: string | null) => { const { accessToken, userId, userRole } = useAuthorized(); const isAdmin = userRole === "Admin" || userRole === "Admin Viewer"; @@ -174,7 +164,7 @@ const deletedTeamListCall = async ( */ try { const baseUrl = getProxyBaseUrl(); - + const params = new URLSearchParams( Object.entries({ team_id: options.teamID, @@ -211,10 +201,10 @@ const deletedTeamListCall = async ( const data = await response.json(); console.log("/team/list?status=deleted API Response:", data); - + // Extract teams array from response if it's wrapped in a response object // Otherwise return the data directly if it's already an array - if (data && typeof data === 'object' && 'teams' in data) { + if (data && typeof data === "object" && "teams" in data) { return data.teams as DeletedTeam[]; } return data as DeletedTeam[]; @@ -239,4 +229,4 @@ export const useDeletedTeams = ( staleTime: 30000, // 30 seconds placeholderData: keepPreviousData, }); -}; \ No newline at end of file +}; diff --git a/ui/litellm-dashboard/src/app/(dashboard)/hooks/useAuthorized.test.ts b/ui/litellm-dashboard/src/app/(dashboard)/hooks/useAuthorized.test.ts index 5178aca0790..94f9d9173f0 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/hooks/useAuthorized.test.ts +++ b/ui/litellm-dashboard/src/app/(dashboard)/hooks/useAuthorized.test.ts @@ -8,7 +8,15 @@ import useAuthorized from "./useAuthorized"; // Unmock useAuthorized to test the actual implementation vi.unmock("@/app/(dashboard)/hooks/useAuthorized"); -const { replaceMock, clearTokenCookiesMock, getProxyBaseUrlMock, getUiConfigMock, decodeTokenMock, checkTokenValidityMock, buildLoginUrlWithReturnMock } = vi.hoisted(() => ({ +const { + replaceMock, + clearTokenCookiesMock, + getProxyBaseUrlMock, + getUiConfigMock, + decodeTokenMock, + checkTokenValidityMock, + buildLoginUrlWithReturnMock, +} = vi.hoisted(() => ({ replaceMock: vi.fn(), clearTokenCookiesMock: vi.fn(), getProxyBaseUrlMock: vi.fn(() => "http://proxy.example"), @@ -102,7 +110,7 @@ describe("useAuthorized", () => { admin_ui_disabled: false, sso_configured: false, }); - + const decodedPayload = { key: "api-key-123", user_id: "user-1", @@ -112,7 +120,7 @@ describe("useAuthorized", () => { disabled_non_admin_personal_key_creation: false, login_method: "username_password", }; - + decodeTokenMock.mockReturnValue(decodedPayload); checkTokenValidityMock.mockReturnValue(true); diff --git a/ui/litellm-dashboard/src/app/(dashboard)/hooks/users/useUsers.test.ts b/ui/litellm-dashboard/src/app/(dashboard)/hooks/users/useUsers.test.ts index b0a96eff0e7..537e2c5378a 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/hooks/users/useUsers.test.ts +++ b/ui/litellm-dashboard/src/app/(dashboard)/hooks/users/useUsers.test.ts @@ -36,11 +36,7 @@ const DEFAULT_AUTH = { showSSOBanner: false, }; -const buildUserListResponse = ( - page: number, - totalPages: number, - userCount = 2, -): UserListResponse => ({ +const buildUserListResponse = (page: number, totalPages: number, userCount = 2): UserListResponse => ({ page, page_size: 50, total: totalPages * userCount, @@ -90,13 +86,7 @@ describe("useInfiniteUsers", () => { expect(result.current.data?.pages).toHaveLength(1); expect(result.current.data?.pages[0]).toEqual(mockResponse); - expect(userListCall).toHaveBeenCalledWith( - "test-access-token", - null, - 1, - 50, - null, - ); + expect(userListCall).toHaveBeenCalledWith("test-access-token", null, 1, 50, null); }); it("should use the default page size of 50", async () => { @@ -109,13 +99,7 @@ describe("useInfiniteUsers", () => { expect(result.current.isSuccess).toBe(true); }); - expect(userListCall).toHaveBeenCalledWith( - "test-access-token", - null, - 1, - 50, - null, - ); + expect(userListCall).toHaveBeenCalledWith("test-access-token", null, 1, 50, null); }); it("should use a custom page size when provided", async () => { @@ -131,13 +115,7 @@ describe("useInfiniteUsers", () => { expect(result.current.isSuccess).toBe(true); }); - expect(userListCall).toHaveBeenCalledWith( - "test-access-token", - null, - 1, - customPageSize, - null, - ); + expect(userListCall).toHaveBeenCalledWith("test-access-token", null, 1, customPageSize, null); }); it("should pass searchEmail to userListCall when provided", async () => { @@ -153,13 +131,7 @@ describe("useInfiniteUsers", () => { expect(result.current.isSuccess).toBe(true); }); - expect(userListCall).toHaveBeenCalledWith( - "test-access-token", - null, - 1, - 50, - searchEmail, - ); + expect(userListCall).toHaveBeenCalledWith("test-access-token", null, 1, 50, searchEmail); }); it("should pass null for searchEmail when not provided", async () => { @@ -174,13 +146,7 @@ describe("useInfiniteUsers", () => { expect(result.current.isSuccess).toBe(true); }); - expect(userListCall).toHaveBeenCalledWith( - "test-access-token", - null, - 1, - 50, - null, - ); + expect(userListCall).toHaveBeenCalledWith("test-access-token", null, 1, 50, null); }); it("should fetch the next page when more pages are available", async () => { @@ -209,13 +175,7 @@ describe("useInfiniteUsers", () => { expect(result.current.data?.pages[1]).toEqual(page2); expect(userListCall).toHaveBeenCalledTimes(2); - expect(userListCall).toHaveBeenLastCalledWith( - "test-access-token", - null, - 2, - 50, - null, - ); + expect(userListCall).toHaveBeenLastCalledWith("test-access-token", null, 2, 50, null); }); it("should not have a next page when on the last page", async () => { @@ -275,13 +235,7 @@ describe("useInfiniteUsers", () => { }); it("should execute query for each admin role", async () => { - const adminRoles = [ - "Admin", - "Admin Viewer", - "proxy_admin", - "proxy_admin_viewer", - "org_admin", - ]; + const adminRoles = ["Admin", "Admin Viewer", "proxy_admin", "proxy_admin_viewer", "org_admin"]; for (const role of adminRoles) { vi.clearAllMocks(); @@ -328,12 +282,6 @@ describe("useInfiniteUsers", () => { expect(result.current.isSuccess).toBe(true); }); - expect(userListCall).toHaveBeenCalledWith( - "test-access-token", - null, - 1, - 50, - null, - ); + expect(userListCall).toHaveBeenCalledWith("test-access-token", null, 1, 50, null); }); }); diff --git a/ui/litellm-dashboard/src/app/(dashboard)/hooks/users/useUsers.ts b/ui/litellm-dashboard/src/app/(dashboard)/hooks/users/useUsers.ts index cb30299f46f..9031de3cb1a 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/hooks/users/useUsers.ts +++ b/ui/litellm-dashboard/src/app/(dashboard)/hooks/users/useUsers.ts @@ -8,10 +8,7 @@ const infiniteUsersKeys = createQueryKeys("infiniteUsers"); const DEFAULT_PAGE_SIZE = 50; -export const useInfiniteUsers = ( - pageSize: number = DEFAULT_PAGE_SIZE, - searchEmail?: string, -) => { +export const useInfiniteUsers = (pageSize: number = DEFAULT_PAGE_SIZE, searchEmail?: string) => { const { accessToken, userRole } = useAuthorized(); return useInfiniteQuery({ queryKey: infiniteUsersKeys.list({ @@ -23,10 +20,10 @@ export const useInfiniteUsers = ( queryFn: async ({ pageParam }) => { return await userListCall( accessToken!, - null, // userIDs - pageParam as number, // page - pageSize, // page_size - searchEmail || null, // userEmail + null, // userIDs + pageParam as number, // page + pageSize, // page_size + searchEmail || null, // userEmail ); }, initialPageParam: 1, diff --git a/ui/litellm-dashboard/src/app/(dashboard)/layout.tsx b/ui/litellm-dashboard/src/app/(dashboard)/layout.tsx index 5bb55ee8d10..a611d619cc1 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/layout.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/layout.tsx @@ -69,17 +69,13 @@ function LayoutContent({ children }: { children: React.ReactNode }) { sidebarCollapsed={sidebarCollapsed} onToggleSidebar={toggleSidebar} proxySettings={undefined} - setProxySettings={() => { }} + setProxySettings={() => {}} accessToken={accessToken} />
- +
{children}
diff --git a/ui/litellm-dashboard/src/app/(dashboard)/models-and-endpoints/ModelsAndEndpointsView.tsx b/ui/litellm-dashboard/src/app/(dashboard)/models-and-endpoints/ModelsAndEndpointsView.tsx index 944c56833e5..88f4382d7dd 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/models-and-endpoints/ModelsAndEndpointsView.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/models-and-endpoints/ModelsAndEndpointsView.tsx @@ -518,7 +518,11 @@ const ModelsAndEndpointsView: React.FC = ({ premiumUser, te ); } return ( - +
{visibleTabs.map((t) => t.tab)}
diff --git a/ui/litellm-dashboard/src/app/(dashboard)/models-and-endpoints/components/AllModelsTab.test.tsx b/ui/litellm-dashboard/src/app/(dashboard)/models-and-endpoints/components/AllModelsTab.test.tsx index 32ab83ea754..045bf0a5f44 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/models-and-endpoints/components/AllModelsTab.test.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/models-and-endpoints/components/AllModelsTab.test.tsx @@ -21,7 +21,7 @@ vi.mock("@/components/molecules/notifications_manager", () => ({ // Mock react-query const mockInvalidateQueries = vi.fn(); vi.mock("@tanstack/react-query", async (importOriginal) => { - const actual = await importOriginal() as any; + const actual = (await importOriginal()) as any; return { ...actual, useQueryClient: () => ({ @@ -178,24 +178,30 @@ describe("AllModelsTab", () => { }), ); - const modelData = createPaginatedModelData([ - { - model_name: "gpt-4-accessible", - model_info: { - id: "model-1", - access_via_team_ids: ["team-456"], - access_groups: [], + const modelData = createPaginatedModelData( + [ + { + model_name: "gpt-4-accessible", + model_info: { + id: "model-1", + access_via_team_ids: ["team-456"], + access_groups: [], + }, }, - }, - { - model_name: "gpt-3.5-turbo-blocked", - model_info: { - id: "model-2", - access_via_team_ids: ["team-789"], - access_groups: [], + { + model_name: "gpt-3.5-turbo-blocked", + model_info: { + id: "model-2", + access_via_team_ids: ["team-789"], + access_groups: [], + }, }, - }, - ], 2, 1, 1, 50); + ], + 2, + 1, + 1, + 50, + ); mockUseModelsInfo.mockReturnValue({ data: modelData, isLoading: false, error: null }); @@ -239,24 +245,30 @@ describe("AllModelsTab", () => { }), ); - const modelData = createPaginatedModelData([ - { - model_name: "gpt-4-sales", - model_info: { - id: "model-sales-1", - access_via_team_ids: [], - access_groups: ["sales-model-group"], + const modelData = createPaginatedModelData( + [ + { + model_name: "gpt-4-sales", + model_info: { + id: "model-sales-1", + access_via_team_ids: [], + access_groups: ["sales-model-group"], + }, }, - }, - { - model_name: "gpt-4-engineering", - model_info: { - id: "model-eng-1", - access_via_team_ids: [], - access_groups: ["engineering-model-group"], + { + model_name: "gpt-4-engineering", + model_info: { + id: "model-eng-1", + access_via_team_ids: [], + access_groups: ["engineering-model-group"], + }, }, - }, - ], 2, 1, 1, 50); + ], + 2, + 1, + 1, + 50, + ); mockUseModelsInfo.mockReturnValue({ data: modelData, isLoading: false, error: null }); @@ -284,26 +296,32 @@ describe("AllModelsTab", () => { }), ); - const modelData = createPaginatedModelData([ - { - model_name: "gpt-4-personal", - model_info: { - id: "model-personal-1", - direct_access: true, - access_via_team_ids: [], - access_groups: [], + const modelData = createPaginatedModelData( + [ + { + model_name: "gpt-4-personal", + model_info: { + id: "model-personal-1", + direct_access: true, + access_via_team_ids: [], + access_groups: [], + }, }, - }, - { - model_name: "gpt-4-team-only", - model_info: { - id: "model-team-1", - direct_access: false, - access_via_team_ids: ["team-123"], - access_groups: [], + { + model_name: "gpt-4-team-only", + model_info: { + id: "model-team-1", + direct_access: false, + access_via_team_ids: ["team-123"], + access_groups: [], + }, }, - }, - ], 2, 1, 1, 50); + ], + 2, + 1, + 1, + 50, + ); mockUseModelsInfo.mockReturnValue({ data: modelData, isLoading: false, error: null }); @@ -330,38 +348,44 @@ describe("AllModelsTab", () => { }), ); - const modelData = createPaginatedModelData([ - { - model_name: "gpt-4-config", - litellm_model_name: "gpt-4-config", - provider: "openai", - model_info: { - id: "model-config-1", - db_model: false, - direct_access: true, - access_via_team_ids: [], - access_groups: [], - created_by: "user-123", - created_at: "2024-01-01", - updated_at: "2024-01-01", + const modelData = createPaginatedModelData( + [ + { + model_name: "gpt-4-config", + litellm_model_name: "gpt-4-config", + provider: "openai", + model_info: { + id: "model-config-1", + db_model: false, + direct_access: true, + access_via_team_ids: [], + access_groups: [], + created_by: "user-123", + created_at: "2024-01-01", + updated_at: "2024-01-01", + }, }, - }, - { - model_name: "gpt-4-db", - litellm_model_name: "gpt-4-db", - provider: "openai", - model_info: { - id: "model-db-1", - db_model: true, - direct_access: true, - access_via_team_ids: [], - access_groups: [], - created_by: "user-123", - created_at: "2024-01-01", - updated_at: "2024-01-01", + { + model_name: "gpt-4-db", + litellm_model_name: "gpt-4-db", + provider: "openai", + model_info: { + id: "model-db-1", + db_model: true, + direct_access: true, + access_via_team_ids: [], + access_groups: [], + created_by: "user-123", + created_at: "2024-01-01", + updated_at: "2024-01-01", + }, }, - }, - ], 2, 1, 1, 50); + ], + 2, + 1, + 1, + 50, + ); mockUseModelsInfo.mockReturnValue({ data: modelData, isLoading: false, error: null }); @@ -387,23 +411,29 @@ describe("AllModelsTab", () => { }), ); - const modelData = createPaginatedModelData([ - { - model_name: "gpt-4-config", - litellm_model_name: "gpt-4-config", - provider: "openai", - model_info: { - id: "model-config-1", - db_model: false, - direct_access: true, - access_via_team_ids: [], - access_groups: [], - created_by: "user-123", - created_at: "2024-01-01", - updated_at: "2024-01-01", + const modelData = createPaginatedModelData( + [ + { + model_name: "gpt-4-config", + litellm_model_name: "gpt-4-config", + provider: "openai", + model_info: { + id: "model-config-1", + db_model: false, + direct_access: true, + access_via_team_ids: [], + access_groups: [], + created_by: "user-123", + created_at: "2024-01-01", + updated_at: "2024-01-01", + }, }, - }, - ], 1, 1, 1, 50); + ], + 1, + 1, + 1, + 50, + ); mockUseModelsInfo.mockReturnValue({ data: modelData, isLoading: false, error: null }); @@ -537,23 +567,29 @@ describe("AllModelsTab", () => { }), ); - const modelData = createPaginatedModelData([ - { - model_name: "gpt-4-delete-test", - litellm_model_name: "gpt-4-delete-test", - provider: "openai", - model_info: { - id: "model-to-delete", - db_model: true, - direct_access: true, - access_via_team_ids: [], - access_groups: [], - created_by: "user-123", - created_at: "2024-01-01", - updated_at: "2024-01-01", + const modelData = createPaginatedModelData( + [ + { + model_name: "gpt-4-delete-test", + litellm_model_name: "gpt-4-delete-test", + provider: "openai", + model_info: { + id: "model-to-delete", + db_model: true, + direct_access: true, + access_via_team_ids: [], + access_groups: [], + created_by: "user-123", + created_at: "2024-01-01", + updated_at: "2024-01-01", + }, }, - }, - ], 1, 1, 1, 50); + ], + 1, + 1, + 1, + 50, + ); mockUseModelsInfo.mockReturnValue({ data: modelData, isLoading: false, error: null, refetch: vi.fn() }); @@ -581,23 +617,29 @@ describe("AllModelsTab", () => { }), ); - const modelData = createPaginatedModelData([ - { - model_name: "gpt-4-clickable", - litellm_model_name: "gpt-4-clickable", - provider: "openai", - model_info: { - id: "clickable-model-id", - db_model: true, - direct_access: true, - access_via_team_ids: [], - access_groups: [], - created_by: "user-123", - created_at: "2024-01-01", - updated_at: "2024-01-01", + const modelData = createPaginatedModelData( + [ + { + model_name: "gpt-4-clickable", + litellm_model_name: "gpt-4-clickable", + provider: "openai", + model_info: { + id: "clickable-model-id", + db_model: true, + direct_access: true, + access_via_team_ids: [], + access_groups: [], + created_by: "user-123", + created_at: "2024-01-01", + updated_at: "2024-01-01", + }, }, - }, - ], 1, 1, 1, 50); + ], + 1, + 1, + 1, + 50, + ); mockUseModelsInfo.mockReturnValue({ data: modelData, isLoading: false, error: null, refetch: vi.fn() }); diff --git a/ui/litellm-dashboard/src/app/(dashboard)/models-and-endpoints/components/AllModelsTab.tsx b/ui/litellm-dashboard/src/app/(dashboard)/models-and-endpoints/components/AllModelsTab.tsx index 2626ace86d5..2aa1eb4c808 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/models-and-endpoints/components/AllModelsTab.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/models-and-endpoints/components/AllModelsTab.tsx @@ -68,7 +68,7 @@ const AllModelsTab = ({ setCurrentPage(1); setPagination((prev: PaginationState) => ({ ...prev, pageIndex: 0 })); }, 200), - [] + [], ); useEffect(() => { @@ -100,15 +100,11 @@ const AllModelsTab = ({ return sort.desc ? "desc" : "asc"; }, [sorting]); - const { data: rawModelData, isLoading: isLoadingModelsInfo, refetch: refetchModels } = useModelsInfo( - currentPage, - pageSize, - debouncedSearch || undefined, - undefined, - teamIdForQuery, - sortBy, - sortOrder - ); + const { + data: rawModelData, + isLoading: isLoadingModelsInfo, + refetch: refetchModels, + } = useModelsInfo(currentPage, pageSize, debouncedSearch || undefined, undefined, teamIdForQuery, sortBy, sortOrder); const isLoading = isLoadingModelsInfo || isLoadingModelCostMap; const getProviderFromModel = (model: string) => { @@ -494,7 +490,7 @@ const AllModelsTab = ({ ) : ( {paginationMeta.total_count > 0 - ? `Showing ${((currentPage - 1) * pageSize) + 1} - ${Math.min(currentPage * pageSize, paginationMeta.total_count)} of ${paginationMeta.total_count} results` + ? `Showing ${(currentPage - 1) * pageSize + 1} - ${Math.min(currentPage * pageSize, paginationMeta.total_count)} of ${paginationMeta.total_count} results` : "Showing 0 results"} )} @@ -510,10 +506,9 @@ const AllModelsTab = ({ setPagination((prev: PaginationState) => ({ ...prev, pageIndex: 0 })); }} disabled={currentPage === 1} - className={`px-3 py-1 text-sm border rounded-md ${currentPage === 1 - ? "bg-gray-100 text-gray-400 cursor-not-allowed" - : "hover:bg-gray-50" - }`} + className={`px-3 py-1 text-sm border rounded-md ${ + currentPage === 1 ? "bg-gray-100 text-gray-400 cursor-not-allowed" : "hover:bg-gray-50" + }`} > Previous @@ -529,10 +524,11 @@ const AllModelsTab = ({ setPagination((prev: PaginationState) => ({ ...prev, pageIndex: 0 })); }} disabled={currentPage >= paginationMeta.total_pages} - className={`px-3 py-1 text-sm border rounded-md ${currentPage >= paginationMeta.total_pages - ? "bg-gray-100 text-gray-400 cursor-not-allowed" - : "hover:bg-gray-50" - }`} + className={`px-3 py-1 text-sm border rounded-md ${ + currentPage >= paginationMeta.total_pages + ? "bg-gray-100 text-gray-400 cursor-not-allowed" + : "hover:bg-gray-50" + }`} > Next @@ -550,8 +546,8 @@ const AllModelsTab = ({ setSelectedModelId, setSelectedTeamId, getDisplayModelName, - () => { }, - () => { }, + () => {}, + () => {}, expandedRows, setExpandedRows, setDeleteModalModelId, @@ -577,24 +573,28 @@ const AllModelsTab = ({ alertMessage="This action cannot be undone." message="Are you sure you want to delete this model?" resourceInformationTitle="Model Information" - resourceInformation={modelToDelete ? [ - { - label: "Model Name", - value: modelToDelete.model_name || "Not Set", - }, - { - label: "LiteLLM Model Name", - value: modelToDelete.litellm_model_name || "Not Set", - }, - { - label: "Provider", - value: modelToDelete.provider || "Not Set", - }, - { - label: "Created By", - value: modelToDelete.model_info?.created_by || "Not Set", - }, - ] : []} + resourceInformation={ + modelToDelete + ? [ + { + label: "Model Name", + value: modelToDelete.model_name || "Not Set", + }, + { + label: "LiteLLM Model Name", + value: modelToDelete.litellm_model_name || "Not Set", + }, + { + label: "Provider", + value: modelToDelete.provider || "Not Set", + }, + { + label: "Created By", + value: modelToDelete.model_info?.created_by || "Not Set", + }, + ] + : [] + } onCancel={() => setDeleteModalModelId(null)} onOk={handleDeleteModel} confirmLoading={deleteLoading} diff --git a/ui/litellm-dashboard/src/app/(dashboard)/playground/page.tsx b/ui/litellm-dashboard/src/app/(dashboard)/playground/page.tsx index 2b4b4ace491..abcbe80a382 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/playground/page.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/playground/page.tsx @@ -36,43 +36,43 @@ export default function PlaygroundPage() { return (
- - - Chat - Compare - Compliance - Agent Builder (Experimental) - - - - - - - - - - - - - - - - + + + Chat + Compare + Compliance + Agent Builder (Experimental) + + + + + + + + + + + + + + + +
); } diff --git a/ui/litellm-dashboard/src/app/login/LoginPage.test.tsx b/ui/litellm-dashboard/src/app/login/LoginPage.test.tsx index 25233725da6..6a61f5ed85f 100644 --- a/ui/litellm-dashboard/src/app/login/LoginPage.test.tsx +++ b/ui/litellm-dashboard/src/app/login/LoginPage.test.tsx @@ -304,8 +304,7 @@ describe("LoginPage", () => { }, writable: true, }); - document.cookie = - "token=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/; SameSite=Lax"; + document.cookie = "token=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/; SameSite=Lax"; }); afterEach(() => { diff --git a/ui/litellm-dashboard/src/app/login/LoginPage.tsx b/ui/litellm-dashboard/src/app/login/LoginPage.tsx index 2db95947305..db3a069902a 100644 --- a/ui/litellm-dashboard/src/app/login/LoginPage.tsx +++ b/ui/litellm-dashboard/src/app/login/LoginPage.tsx @@ -50,13 +50,11 @@ function LoginPageContent() { // Validate the SSO code is a plausible OAuth authorization code (alphanumeric // plus common URL-safe chars) so that arbitrary user input cannot trigger the // exchange endpoint. - const ssoCode = - rawSsoCode && /^[a-zA-Z0-9._~+/=-]+$/.test(rawSsoCode) ? rawSsoCode : null; + const ssoCode = rawSsoCode && /^[a-zA-Z0-9._~+/=-]+$/.test(rawSsoCode) ? rawSsoCode : null; if (ssoCode) { const rawWorkerUrl = localStorage.getItem("litellm_worker_url"); // Validate the stored worker URL: only allow http(s) URLs. - const workerUrl = - rawWorkerUrl && /^https?:\/\/.+/.test(rawWorkerUrl) ? rawWorkerUrl : null; + const workerUrl = rawWorkerUrl && /^https?:\/\/.+/.test(rawWorkerUrl) ? rawWorkerUrl : null; exchangeLoginCode(ssoCode, workerUrl).then(() => { params.delete("code"); const cleanSearch = params.toString(); @@ -277,10 +275,7 @@ function LoginPageContent() { {!uiConfig?.sso_configured ? ( - + @@ -315,7 +310,13 @@ function LoginPageContent() { type="info" showIcon closable - message={Single Sign-On (SSO) is enabled. LiteLLM no longer automatically redirects to the SSO login flow upon loading this page. To re-enable auto-redirect-to-SSO, set AUTO_REDIRECT_UI_LOGIN_TO_SSO=true in your environment configuration.} + message={ + + Single Sign-On (SSO) is enabled. LiteLLM no longer automatically redirects to the SSO login flow upon + loading this page. To re-enable auto-redirect-to-SSO, set{" "} + AUTO_REDIRECT_UI_LOGIN_TO_SSO=true in your environment configuration. + + } /> )} diff --git a/ui/litellm-dashboard/src/app/mcp/oauth/callback/page.tsx b/ui/litellm-dashboard/src/app/mcp/oauth/callback/page.tsx index 3b3729c1ac9..d292925f810 100644 --- a/ui/litellm-dashboard/src/app/mcp/oauth/callback/page.tsx +++ b/ui/litellm-dashboard/src/app/mcp/oauth/callback/page.tsx @@ -80,12 +80,12 @@ const McpOAuthCallbackContent = () => {

LiteLLM MCP OAuth

-

- Authorization complete. You may close this window and return to the LiteLLM dashboard. -

-

- If the window does not close automatically, everything is still saved—you can close it manually. -

+

+ Authorization complete. You may close this window and return to the LiteLLM dashboard. +

+

+ If the window does not close automatically, everything is still saved—you can close it manually. +

); diff --git a/ui/litellm-dashboard/src/app/model_hub_table/page.tsx b/ui/litellm-dashboard/src/app/model_hub_table/page.tsx index f35a6943a63..472cea4c27e 100644 --- a/ui/litellm-dashboard/src/app/model_hub_table/page.tsx +++ b/ui/litellm-dashboard/src/app/model_hub_table/page.tsx @@ -16,9 +16,7 @@ function PublicModelHubTableContent() { setAccessToken(key); }, [key]); - return ( - - ); + return ; } export default function PublicModelHubTable() { diff --git a/ui/litellm-dashboard/src/app/onboarding/OnboardingErrorView.test.tsx b/ui/litellm-dashboard/src/app/onboarding/OnboardingErrorView.test.tsx index d7a7ffb1b15..59071f17bf6 100644 --- a/ui/litellm-dashboard/src/app/onboarding/OnboardingErrorView.test.tsx +++ b/ui/litellm-dashboard/src/app/onboarding/OnboardingErrorView.test.tsx @@ -11,9 +11,7 @@ describe("OnboardingErrorView", () => { it("should show the expiry description", () => { render(); - expect( - screen.getByText("The invitation link may be invalid or expired.") - ).toBeInTheDocument(); + expect(screen.getByText("The invitation link may be invalid or expired.")).toBeInTheDocument(); }); it("should render a Back to Login link pointing to /ui/login", () => { diff --git a/ui/litellm-dashboard/src/app/onboarding/OnboardingForm.tsx b/ui/litellm-dashboard/src/app/onboarding/OnboardingForm.tsx index add58102c57..23a8bc6725a 100644 --- a/ui/litellm-dashboard/src/app/onboarding/OnboardingForm.tsx +++ b/ui/litellm-dashboard/src/app/onboarding/OnboardingForm.tsx @@ -26,9 +26,7 @@ export function OnboardingForm({ variant }: OnboardingFormProps) { const { mutate: claimToken, isPending } = useClaimOnboardingToken(); - const decoded = credentialsData?.token - ? (jwtDecode(credentialsData.token) as { [key: string]: any }) - : null; + const decoded = credentialsData?.token ? (jwtDecode(credentialsData.token) as { [key: string]: any }) : null; const userEmail: string = decoded?.user_email ?? ""; const userId: string | null = decoded?.user_id ?? null; const accessToken: string | null = decoded?.key ?? null; @@ -53,14 +51,12 @@ export function OnboardingForm({ variant }: OnboardingFormProps) { clearTokenCookies(); storeLoginToken(data.token); const proxyBaseUrl = getProxyBaseUrl(); - window.location.href = proxyBaseUrl - ? `${proxyBaseUrl}/ui/?login=success` - : "/ui/?login=success"; + window.location.href = proxyBaseUrl ? `${proxyBaseUrl}/ui/?login=success` : "/ui/?login=success"; }, onError: (error: Error) => { setClaimError(error.message || "Failed to submit. Please try again."); }, - } + }, ); }; diff --git a/ui/litellm-dashboard/src/app/onboarding/OnboardingFormBody.test.tsx b/ui/litellm-dashboard/src/app/onboarding/OnboardingFormBody.test.tsx index f742176d1ba..f3286984706 100644 --- a/ui/litellm-dashboard/src/app/onboarding/OnboardingFormBody.test.tsx +++ b/ui/litellm-dashboard/src/app/onboarding/OnboardingFormBody.test.tsx @@ -74,16 +74,12 @@ describe("OnboardingFormBody", () => { await user.click(screen.getByRole("button", { name: /sign up/i })); await waitFor(() => { - expect(onSubmit).toHaveBeenCalledWith( - expect.objectContaining({ password: "mypassword" }) - ); + expect(onSubmit).toHaveBeenCalledWith(expect.objectContaining({ password: "mypassword" })); }); }); it("should show 'Reset Password' on the submit button for reset_password variant", () => { render(); - expect( - screen.getByRole("button", { name: /reset password/i }) - ).toBeInTheDocument(); + expect(screen.getByRole("button", { name: /reset password/i })).toBeInTheDocument(); }); }); diff --git a/ui/litellm-dashboard/src/app/onboarding/OnboardingFormBody.tsx b/ui/litellm-dashboard/src/app/onboarding/OnboardingFormBody.tsx index c57c7328b61..4aa5e2e6138 100644 --- a/ui/litellm-dashboard/src/app/onboarding/OnboardingFormBody.tsx +++ b/ui/litellm-dashboard/src/app/onboarding/OnboardingFormBody.tsx @@ -9,13 +9,7 @@ type OnboardingFormBodyProps = { onSubmit: (values: { password: string }) => void; }; -export function OnboardingFormBody({ - variant, - userEmail, - isPending, - claimError, - onSubmit, -}: OnboardingFormBodyProps) { +export function OnboardingFormBody({ variant, userEmail, isPending, claimError, onSubmit }: OnboardingFormBodyProps) { const [form] = Form.useForm(); React.useEffect(() => { @@ -28,9 +22,7 @@ export function OnboardingFormBody({ 🚅 LiteLLM - - {variant === "reset_password" ? "Reset Password" : "Sign Up"} - + {variant === "reset_password" ? "Reset Password" : "Sign Up"} {variant === "reset_password" ? "Reset your password to access Admin UI." @@ -45,12 +37,7 @@ export function OnboardingFormBody({ description={
SSO is under the Enterprise Tier. -
@@ -59,7 +46,12 @@ export function OnboardingFormBody({ /> )} -
onSubmit({ password: values.password })}> + onSubmit({ password: values.password })} + > @@ -68,18 +60,12 @@ export function OnboardingFormBody({ label="Password" name="password" rules={[{ required: true, message: "password required to sign up" }]} - help={ - variant === "reset_password" - ? "Enter your new password" - : "Create a password for your account" - } + help={variant === "reset_password" ? "Enter your new password" : "Create a password for your account"} > - {claimError && ( - - )} + {claimError && }
- } - > + Loading...}> ); diff --git a/ui/litellm-dashboard/src/app/page.tsx b/ui/litellm-dashboard/src/app/page.tsx index ce12967c911..da6a0d5a76f 100644 --- a/ui/litellm-dashboard/src/app/page.tsx +++ b/ui/litellm-dashboard/src/app/page.tsx @@ -46,7 +46,13 @@ import SpendLogsTable from "@/components/view_logs"; import ViewUserDashboard from "@/components/view_users"; import { ThemeProvider } from "@/contexts/ThemeContext"; import { useAuth } from "@/contexts/AuthContext"; -import { buildLoginUrlWithReturn, consumeReturnUrl, isValidReturnUrl, normalizeUrlForCompare, storeReturnUrl } from "@/utils/returnUrlUtils"; +import { + buildLoginUrlWithReturn, + consumeReturnUrl, + isValidReturnUrl, + normalizeUrlForCompare, + storeReturnUrl, +} from "@/utils/returnUrlUtils"; import { isAdminRole } from "@/utils/roles"; import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import { useRouter, useSearchParams } from "next/navigation"; @@ -67,17 +73,8 @@ interface ProxySettings { const LEGACY_REDIRECTS: Record = {}; function CreateKeyPageContent() { - const { - authLoading, - token, - userID, - userRole, - userEmail, - accessToken, - premiumUser, - setUserRole, - setUserEmail, - } = useAuth(); + const { authLoading, token, userID, userRole, userEmail, accessToken, premiumUser, setUserRole, setUserEmail } = + useAuth(); const [teams, setTeams] = useState(null); const [keys, setKeys] = useState([]); @@ -129,15 +126,13 @@ function CreateKeyPageContent() { // Validate owned_by against allowed values const validOwnedByValues = ["you", "service_account", "another_user"]; - const validatedOwnedBy = ownedBy && validOwnedByValues.includes(ownedBy) - ? (ownedBy as CreateKeyPrefillData["owned_by"]) - : undefined; + const validatedOwnedBy = + ownedBy && validOwnedByValues.includes(ownedBy) ? (ownedBy as CreateKeyPrefillData["owned_by"]) : undefined; // Validate key_type against allowed values const validKeyTypes = ["default", "llm_api", "management"]; - const validatedKeyType = keyType && validKeyTypes.includes(keyType) - ? (keyType as CreateKeyPrefillData["key_type"]) - : undefined; + const validatedKeyType = + keyType && validKeyTypes.includes(keyType) ? (keyType as CreateKeyPrefillData["key_type"]) : undefined; // Sanitize key_alias (limit length, trim whitespace) const sanitizedKeyAlias = keyAlias @@ -149,8 +144,8 @@ function CreateKeyPageContent() { ? modelsParam .split(",") .slice(0, 100) // Limit number of models to prevent DoS - .map(m => m.trim().slice(0, 256)) // Limit individual model name length - .filter(m => m.length > 0) // Remove empty strings + .map((m) => m.trim().slice(0, 256)) // Limit individual model name length + .filter((m) => m.length > 0) // Remove empty strings : undefined; return { @@ -259,7 +254,9 @@ function CreateKeyPageContent() { if (accessToken && userID && userRole) { v2TeamListCall(accessToken, 1, 100, { userID: userRole !== "Admin" && userRole !== "Admin Viewer" ? userID : null, - }).then((response) => setTeams(response.teams ?? [])).catch(console.error); + }) + .then((response) => setTeams(response.teams ?? [])) + .catch(console.error); } if (accessToken) { fetchOrganizations(accessToken, setOrganizations); @@ -353,235 +350,231 @@ function CreateKeyPageContent() { return ( }> - - - {invitation_id ? ( - + + {invitation_id ? ( + + ) : ( +
+ - ) : ( -
- -
-
+
+
- {page == "api-keys" ? ( - + ) : page == "models" ? ( + + ) : page == "llm-playground" ? ( + + ) : page == "users" ? ( + + ) : page == "teams" ? ( + + ) : page == "organizations" ? ( + + ) : page == "admin-panel" ? ( + + ) : page == "api_ref" || page == "api-reference" ? ( + + ) : page == "logging-and-alerts" ? ( + + ) : page == "budgets" ? ( + + ) : page == "guardrails" ? ( + + ) : page == "policies" ? ( + + ) : page == "agents" ? ( + + ) : page == "prompts" ? ( + + ) : page == "transform-request" ? ( + + ) : page == "router-settings" ? ( + + ) : page == "ui-theme" ? ( + + ) : page == "cost-tracking" ? ( + + ) : page == "model-hub-table" ? ( + isAdminRole(userRole) ? ( + - ) : page == "models" ? ( - - ) : page == "llm-playground" ? ( - - ) : page == "users" ? ( - - ) : page == "teams" ? ( - - ) : page == "organizations" ? ( - - ) : page == "admin-panel" ? ( - - ) : page == "api_ref" || page == "api-reference" ? ( - - ) : page == "logging-and-alerts" ? ( - - ) : page == "budgets" ? ( - - ) : page == "guardrails" ? ( - - ) : page == "policies" ? ( - - ) : page == "agents" ? ( - - ) : page == "prompts" ? ( - - ) : page == "transform-request" ? ( - - ) : page == "router-settings" ? ( - - ) : page == "ui-theme" ? ( - - ) : page == "cost-tracking" ? ( - - ) : page == "model-hub-table" ? ( - isAdminRole(userRole) ? ( - - ) : ( - - ) - ) : page == "caching" ? ( - - ) : page == "pass-through-settings" ? ( - - ) : page == "logs" ? ( - - ) : page == "mcp-servers" ? ( - - ) : page == "search-tools" ? ( - - ) : page == "tag-management" ? ( - - ) : page == "skills" || page == "claude-code-plugins" ? ( - - ) : page == "access-groups" ? ( - - ) : page == "projects" ? ( - - ) : page == "vector-stores" ? ( - - ) : page == "tool-policies" ? ( - - ) : page == "workflows" ? ( - - ) : page == "memory" ? ( - - ) : page == "guardrails-monitor" ? ( - - ) : page == "new_usage" ? ( - ) : ( - - )} -
- - {/* Survey Components */} - - - - {/* Claude Code Components */} - - + + ) + ) : page == "caching" ? ( + + ) : page == "pass-through-settings" ? ( + + ) : page == "logs" ? ( + + ) : page == "mcp-servers" ? ( + + ) : page == "search-tools" ? ( + + ) : page == "tag-management" ? ( + + ) : page == "skills" || page == "claude-code-plugins" ? ( + + ) : page == "access-groups" ? ( + + ) : page == "projects" ? ( + + ) : page == "vector-stores" ? ( + + ) : page == "tool-policies" ? ( + + ) : page == "workflows" ? ( + + ) : page == "memory" ? ( + + ) : page == "guardrails-monitor" ? ( + + ) : page == "new_usage" ? ( + + ) : ( + + )}
- )} - - + + {/* Survey Components */} + + + + {/* Claude Code Components */} + + +
+ )} + + ); } diff --git a/ui/litellm-dashboard/src/components/AIHub/AgentHubTableColumns.test.tsx b/ui/litellm-dashboard/src/components/AIHub/AgentHubTableColumns.test.tsx index 083e67c297a..f980aee3c2c 100644 --- a/ui/litellm-dashboard/src/components/AIHub/AgentHubTableColumns.test.tsx +++ b/ui/litellm-dashboard/src/components/AIHub/AgentHubTableColumns.test.tsx @@ -104,12 +104,10 @@ describe("AgentHubTableColumns", () => { render(); // "In:" and "Out:" are in children; getByText with exact:false // matches against the element's full textContent across child nodes - expect(screen.getByText((_, el) => - el?.tagName === "P" && el.textContent === "In: text" - )).toBeInTheDocument(); - expect(screen.getByText((_, el) => - el?.tagName === "P" && el.textContent === "Out: text, image" - )).toBeInTheDocument(); + expect(screen.getByText((_, el) => el?.tagName === "P" && el.textContent === "In: text")).toBeInTheDocument(); + expect( + screen.getByText((_, el) => el?.tagName === "P" && el.textContent === "Out: text, image"), + ).toBeInTheDocument(); }); it("should display 'Yes' badge for public agents", () => { diff --git a/ui/litellm-dashboard/src/components/AIHub/ClaudeCodeMarketplaceTab.tsx b/ui/litellm-dashboard/src/components/AIHub/ClaudeCodeMarketplaceTab.tsx index 043077c0210..762b0836921 100644 --- a/ui/litellm-dashboard/src/components/AIHub/ClaudeCodeMarketplaceTab.tsx +++ b/ui/litellm-dashboard/src/components/AIHub/ClaudeCodeMarketplaceTab.tsx @@ -2,14 +2,8 @@ import { SearchOutlined } from "@ant-design/icons"; import { Card, Tab, TabGroup, TabList, TabPanel, TabPanels, Text } from "@tremor/react"; import { Input } from "antd"; import React, { useEffect, useMemo, useState } from "react"; -import { - extractCategories, - filterPluginsByCategory, - filterPluginsBySearch, -} from "../claude_code_plugins/helpers"; -import { - MarketplaceResponse -} from "../claude_code_plugins/types"; +import { extractCategories, filterPluginsByCategory, filterPluginsBySearch } from "../claude_code_plugins/helpers"; +import { MarketplaceResponse } from "../claude_code_plugins/types"; import { ModelDataTable } from "../model_dashboard/table"; import NotificationsManager from "../molecules/notifications_manager"; import { getClaudeCodeMarketplace } from "../networking"; @@ -19,11 +13,8 @@ interface ClaudeCodeMarketplaceTabProps { publicPage?: boolean; } -const ClaudeCodeMarketplaceTab: React.FC = ({ - publicPage = false, -}) => { - const [marketplaceData, setMarketplaceData] = - useState(null); +const ClaudeCodeMarketplaceTab: React.FC = ({ publicPage = false }) => { + const [marketplaceData, setMarketplaceData] = useState(null); const [isLoading, setIsLoading] = useState(true); const [searchTerm, setSearchTerm] = useState(""); const [selectedCategoryIndex, setSelectedCategoryIndex] = useState(0); @@ -74,18 +65,13 @@ const ClaudeCodeMarketplaceTab: React.FC = ({ return plugins; }, [marketplaceData, selectedCategory, searchTerm]); - const columns = useMemo( - () => getMarketplaceTableColumns(copyToClipboard, publicPage), - [publicPage] - ); + const columns = useMemo(() => getMarketplaceTableColumns(copyToClipboard, publicPage), [publicPage]); if (!marketplaceData && !isLoading) { return (
- - Failed to load marketplace. Please try again later. - + Failed to load marketplace. Please try again later.
); @@ -110,14 +96,8 @@ const ClaudeCodeMarketplaceTab: React.FC = ({ {categories.map((category) => { // Count plugins in this category - const categoryPlugins = filterPluginsByCategory( - marketplaceData?.plugins || [], - category - ); - const count = filterPluginsBySearch( - categoryPlugins, - searchTerm - ).length; + const categoryPlugins = filterPluginsByCategory(marketplaceData?.plugins || [], category); + const count = filterPluginsBySearch(categoryPlugins, searchTerm).length; return ( @@ -143,8 +123,7 @@ const ClaudeCodeMarketplaceTab: React.FC = ({ {/* Footer Info */}
- Showing {filteredPlugins.length} of{" "} - {marketplaceData?.plugins.length || 0} plugin + Showing {filteredPlugins.length} of {marketplaceData?.plugins.length || 0} plugin {marketplaceData?.plugins.length !== 1 ? "s" : ""} {searchTerm && ` matching "${searchTerm}"`} {selectedCategory !== "All" && ` in ${selectedCategory}`} diff --git a/ui/litellm-dashboard/src/components/AIHub/ModelHubTable.test.tsx b/ui/litellm-dashboard/src/components/AIHub/ModelHubTable.test.tsx index ee59ac84ece..3a22a55298e 100644 --- a/ui/litellm-dashboard/src/components/AIHub/ModelHubTable.test.tsx +++ b/ui/litellm-dashboard/src/components/AIHub/ModelHubTable.test.tsx @@ -48,11 +48,7 @@ describe("ModelHubTable", () => { }); // Reusable helper function to setup mocks for auth redirect tests - const setupAuthRedirectTest = ( - requireAuth: boolean, - tokenValue: string | null, - isTokenValid: boolean - ) => { + const setupAuthRedirectTest = (requireAuth: boolean, tokenValue: string | null, isTokenValid: boolean) => { mockUseUISettings.mockReturnValue({ data: { values: { @@ -87,14 +83,12 @@ describe("ModelHubTable", () => { tokenValue: string | null, isTokenValid: boolean, shouldRedirect: boolean, - description: string + description: string, ) => { it(description, async () => { setupAuthRedirectTest(requireAuth, tokenValue, isTokenValid); - renderWithProviders( - - ); + renderWithProviders(); await waitFor(() => { if (shouldRedirect) { @@ -125,7 +119,9 @@ describe("ModelHubTable", () => { isLoading: false, }); - renderWithProviders(); + renderWithProviders( + , + ); await waitFor(() => { expect(screen.getByText("AI Hub")).toBeInTheDocument(); @@ -172,7 +168,7 @@ describe("ModelHubTable", () => { null, false, true, - "should redirect to login when requireAuth is true and there is no token" + "should redirect to login when requireAuth is true and there is no token", ); testAuthRedirect( @@ -180,7 +176,7 @@ describe("ModelHubTable", () => { "expired-token", false, true, - "should redirect to login when requireAuth is true and token is expired" + "should redirect to login when requireAuth is true and token is expired", ); testAuthRedirect( @@ -188,24 +184,18 @@ describe("ModelHubTable", () => { "malformed-token", false, true, - "should redirect to login when requireAuth is true and token is malformed" + "should redirect to login when requireAuth is true and token is malformed", ); // Test cases where requireAuth is false - should NOT redirect regardless of token state - testAuthRedirect( - false, - null, - false, - false, - "should not redirect when requireAuth is false and there is no token" - ); + testAuthRedirect(false, null, false, false, "should not redirect when requireAuth is false and there is no token"); testAuthRedirect( false, "expired-token", false, false, - "should not redirect when requireAuth is false and token is expired" + "should not redirect when requireAuth is false and token is expired", ); testAuthRedirect( @@ -213,7 +203,7 @@ describe("ModelHubTable", () => { "malformed-token", false, false, - "should not redirect when requireAuth is false and token is malformed" + "should not redirect when requireAuth is false and token is malformed", ); }); }); diff --git a/ui/litellm-dashboard/src/components/AIHub/ModelHubTable.tsx b/ui/litellm-dashboard/src/components/AIHub/ModelHubTable.tsx index 75058157a65..5d171139ab5 100644 --- a/ui/litellm-dashboard/src/components/AIHub/ModelHubTable.tsx +++ b/ui/litellm-dashboard/src/components/AIHub/ModelHubTable.tsx @@ -526,9 +526,7 @@ const ModelHubTable: React.FC = ({ accessToken, publicPage, {publicPage == false && canModify && (
- +
)} = ({ s.description?.toLowerCase().includes(q) || s.domain?.toLowerCase().includes(q) || s.namespace?.toLowerCase().includes(q) || - s.keywords?.some((k) => k.toLowerCase().includes(q)) + s.keywords?.some((k) => k.toLowerCase().includes(q)), ); } return result; @@ -94,9 +94,7 @@ const SkillHubDashboard: React.FC = ({ {/* Search + filters + table */}
-

- All {publicPage ? "Public " : ""}Skills -

+

All {publicPage ? "Public " : ""}Skills

+ - -