diff --git a/ui/litellm-dashboard/eslint-suppressions.json b/ui/litellm-dashboard/eslint-suppressions.json index 31e8f81f3b3..d625cca0c60 100644 --- a/ui/litellm-dashboard/eslint-suppressions.json +++ b/ui/litellm-dashboard/eslint-suppressions.json @@ -542,11 +542,6 @@ "count": 1 } }, - "src/app/(dashboard)/mcp-servers/_components/EnvVarsSection.tsx": { - "no-restricted-imports": { - "count": 1 - } - }, "src/app/(dashboard)/mcp-servers/_components/IdJagFormFields.tsx": { "no-restricted-imports": { "count": 1 @@ -647,9 +642,6 @@ "src/app/(dashboard)/mcp-servers/_components/UserEnvVarsModal.tsx": { "no-nested-ternary": { "count": 2 - }, - "no-restricted-imports": { - "count": 1 } }, "src/app/(dashboard)/mcp-servers/_components/index.tsx": { @@ -661,9 +653,6 @@ "local/filename-pascal-case": { "count": 1 }, - "no-restricted-imports": { - "count": 1 - }, "react-hooks/static-components": { "count": 4 } @@ -1475,11 +1464,6 @@ "count": 1 } }, - "src/components/Settings/AdminSettings/LoggingSettings/LoggingSettings.tsx": { - "no-restricted-imports": { - "count": 1 - } - }, "src/components/Settings/AdminSettings/MCPSemanticFilterSettings/MCPSemanticFilterSettings.tsx": { "no-nested-ternary": { "count": 1 @@ -1506,11 +1490,6 @@ "count": 1 } }, - "src/components/Settings/AdminSettings/SSOSettings/RoleMappings.tsx": { - "no-restricted-imports": { - "count": 1 - } - }, "src/components/Settings/AdminSettings/UISettings/PageVisibilitySettings.tsx": { "react-hooks/set-state-in-render": { "count": 1 @@ -1590,9 +1569,6 @@ "src/components/VirtualKeysPage/keyTableColumns.tsx": { "local/filename-pascal-case": { "count": 1 - }, - "no-restricted-imports": { - "count": 1 } }, "src/components/activity_metrics.tsx": { @@ -1603,11 +1579,6 @@ "count": 1 } }, - "src/components/add_model/AdaptiveRoutingConfig.tsx": { - "no-restricted-imports": { - "count": 1 - } - }, "src/components/add_model/AddModelForm.test.tsx": { "no-restricted-imports": { "count": 1 @@ -1620,26 +1591,6 @@ "no-nested-ternary": { "count": 1 }, - "no-restricted-imports": { - "count": 2 - } - }, - "src/components/add_model/ClassificationMethodConfig.tsx": { - "no-restricted-imports": { - "count": 1 - } - }, - "src/components/add_model/ComplexityRouterConfig.tsx": { - "no-restricted-imports": { - "count": 1 - } - }, - "src/components/add_model/EscalationKeywords.tsx": { - "no-restricted-imports": { - "count": 1 - } - }, - "src/components/add_model/KeywordTierRules.tsx": { "no-restricted-imports": { "count": 1 } @@ -1649,11 +1600,6 @@ "count": 1 } }, - "src/components/add_model/SemanticKeywordMatching.tsx": { - "no-restricted-imports": { - "count": 1 - } - }, "src/components/add_model/add_auto_router_tab.tsx": { "local/filename-pascal-case": { "count": 1 @@ -1672,7 +1618,7 @@ "count": 1 }, "no-restricted-imports": { - "count": 3 + "count": 1 } }, "src/components/add_model/auto_router_connection_test.tsx": { @@ -2078,7 +2024,7 @@ }, "src/components/model_add/CredentialModal.tsx": { "no-restricted-imports": { - "count": 2 + "count": 1 } }, "src/components/model_add/reuse_credentials.tsx": { diff --git a/ui/litellm-dashboard/src/app/(dashboard)/usage/_components/components/UsagePageView.test.tsx b/ui/litellm-dashboard/src/app/(dashboard)/usage/_components/components/UsagePageView.test.tsx index a6f46ee8242..118371aa9ac 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/usage/_components/components/UsagePageView.test.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/usage/_components/components/UsagePageView.test.tsx @@ -971,8 +971,8 @@ describe("UsagePage", () => { expect(mockUserDailyActivityCall).toHaveBeenCalled(); }); - // Should still render the data from the paginated fallback - expect(screen.getByText("1,500")).toBeInTheDocument(); + // Should still render the data from the paginated fallback, which lands a render after the call + expect(await screen.findByText("1,500")).toBeInTheDocument(); }); it("should stop showing the previous range's paginated pages while a new range is in flight", async () => { diff --git a/ui/litellm-dashboard/src/components/common_components/AccessGroupSelector.tsx b/ui/litellm-dashboard/src/components/common_components/AccessGroupSelector.tsx index 96f21f30385..e522e7c7e62 100644 --- a/ui/litellm-dashboard/src/components/common_components/AccessGroupSelector.tsx +++ b/ui/litellm-dashboard/src/components/common_components/AccessGroupSelector.tsx @@ -1,6 +1,7 @@ import React from "react"; -import { Select, Skeleton } from "antd"; import { Users } from "lucide-react"; +import { Skeleton } from "@/components/ui/skeleton"; +import { MultiSelect, type MultiSelectOption } from "@/components/shared/MultiSelect"; import { useAccessGroups, AccessGroupResponse } from "@/app/(dashboard)/hooks/accessGroups/useAccessGroups"; export interface AccessGroupSelectorProps { @@ -12,8 +13,6 @@ export interface AccessGroupSelectorProps { className?: string; showLabel?: boolean; labelText?: string; - /** Allow clearing the selection */ - allowClear?: boolean; } /** @@ -32,7 +31,6 @@ const AccessGroupSelector: React.FC = ({ className, showLabel = false, labelText = "Access Group", - allowClear = true, }) => { const { data: accessGroups, isLoading, isError } = useAccessGroups(); @@ -45,22 +43,16 @@ const AccessGroupSelector: React.FC = ({ {labelText}

)} - + ); } // ── Build options ──────────────────────────────────────────────────────── - const options = (accessGroups ?? []).map((group: AccessGroupResponse) => ({ - label: ( - - {group.access_group_name}{" "} - ({group.access_group_id}) - - ), + const options: MultiSelectOption[] = (accessGroups ?? []).map((group: AccessGroupResponse) => ({ + label: group.access_group_name, value: group.access_group_id, - selectedLabel: group.access_group_name, - searchText: `${group.access_group_name} ${group.access_group_id}`, + description: group.access_group_id, })); // ── Render ─────────────────────────────────────────────────────────────── @@ -71,30 +63,17 @@ const AccessGroupSelector: React.FC = ({ {labelText}

)} - - {property.enum.map((value) => ( - - {value} - - ))} + ); } if (type === "number" || type === "integer") { return ( - control.onChange(toSchemaNumber(event.target.value, type === "integer"))} + className="w-full" /> ); } diff --git a/ui/litellm-dashboard/src/components/common_components/user_search_modal.test.tsx b/ui/litellm-dashboard/src/components/common_components/user_search_modal.test.tsx index cf4461a2b7a..e320a90f01a 100644 --- a/ui/litellm-dashboard/src/components/common_components/user_search_modal.test.tsx +++ b/ui/litellm-dashboard/src/components/common_components/user_search_modal.test.tsx @@ -74,7 +74,7 @@ describe("UserSearchModal", () => { expect(notice).toHaveTextContent(/users that already exist/i); expect(notice).toHaveTextContent(/ask a proxy admin to create their account first/i); // info, not warning: a warning here would read as an error state on an empty form - expect(notice.className).toMatch(/text-info/); + expect(notice).toHaveAttribute("data-variant", "info"); }); }); diff --git a/ui/litellm-dashboard/src/components/key_team_helpers/BudgetFallbacksEditor.tsx b/ui/litellm-dashboard/src/components/key_team_helpers/BudgetFallbacksEditor.tsx index 1d327b87346..4e1c17a4c68 100644 --- a/ui/litellm-dashboard/src/components/key_team_helpers/BudgetFallbacksEditor.tsx +++ b/ui/litellm-dashboard/src/components/key_team_helpers/BudgetFallbacksEditor.tsx @@ -1,5 +1,5 @@ -import { SimpleTooltip } from "@/components/ui/tooltip"; -import { Select } from "antd"; +import { MultiSelect } from "@/components/shared/MultiSelect"; +import { SearchSelect } from "@/components/shared/SearchSelect"; import { Button } from "@/components/ui/button"; import { ArrowDown, Plus, X } from "lucide-react"; import React, { useState } from "react"; @@ -94,18 +94,15 @@ export function BudgetFallbacksEditor({ value, onChange, availableModels }: Budg
- updateEntry(entry.id, { fallbackModels: values })} - disabled={!entry.primaryModel} - showSearch - filterOption={(input, option) => (option?.label ?? "").toLowerCase().includes(input.toLowerCase())} + ({ label: m, value: m }))} - getPopupContainer={(trigger) => trigger.parentElement || document.body} - maxTagCount="responsive" - maxTagPlaceholder={(omittedValues) => ( - v).join(", ")}> - +{omittedValues.length} more - - )} + value={entry.fallbackModels} + onValueChange={(values) => updateEntry(entry.id, { fallbackModels: values })} + placeholder={entry.primaryModel ? "Select fallback models" : "Select a primary model first"} + emptyText="No models found" + disabled={!entry.primaryModel} + className="w-full" /> {entry.fallbackModels.length > 1 && (
diff --git a/ui/litellm-dashboard/src/components/key_team_helpers/BudgetWindowsEditor.tsx b/ui/litellm-dashboard/src/components/key_team_helpers/BudgetWindowsEditor.tsx index c9a422dcb85..43861d43c0d 100644 --- a/ui/litellm-dashboard/src/components/key_team_helpers/BudgetWindowsEditor.tsx +++ b/ui/litellm-dashboard/src/components/key_team_helpers/BudgetWindowsEditor.tsx @@ -1,5 +1,6 @@ -import { InputNumber, Select } from "antd"; import { Button } from "@/components/ui/button"; +import { InputGroup, InputGroupAddon, InputGroupInput, InputGroupText } from "@/components/ui/input-group"; +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import React from "react"; export interface BudgetWindowEntry { @@ -41,21 +42,43 @@ export function BudgetWindowsEditor({ value, onChange }: BudgetWindowsEditorProp
+ + + $ + + { + const typed = event.target.valueAsNumber; + updateWindow(idx, "max_budget", Number.isNaN(typed) ? null : typed); + }} + onBlur={(event) => { + const typed = event.target.valueAsNumber; + if (!Number.isNaN(typed)) { + updateWindow(idx, "max_budget", Number(typed.toFixed(2))); + } + }} + placeholder="Max spend ($)" + /> +