diff --git a/ui/litellm-dashboard/src/components/common_components/AutoRotationView.tsx b/ui/litellm-dashboard/src/components/common_components/AutoRotationView.tsx index d6445d1472d..48f2e7ebed8 100644 --- a/ui/litellm-dashboard/src/components/common_components/AutoRotationView.tsx +++ b/ui/litellm-dashboard/src/components/common_components/AutoRotationView.tsx @@ -1,6 +1,7 @@ import React from "react"; -import { Text, Badge } from "@tremor/react"; -import { RefreshIcon, ClockIcon } from "@heroicons/react/outline"; +import { Badge } from "@/components/ui/badge"; +import { Clock, RefreshCw } from "lucide-react"; +import { cn } from "@/lib/utils"; interface AutoRotationViewProps { autoRotate?: boolean; @@ -38,75 +39,97 @@ const AutoRotationView: React.FC = ({ const content = (
- {/* Status Section */}
- - Auto-Rotation - + + Auto-Rotation + {autoRotate ? "Enabled" : "Disabled"} {autoRotate && rotationInterval && ( <> - • - Every {rotationInterval} + • + + Every {rotationInterval} + )}
- {/* Rotation History - Show if there's any rotation data OR if auto-rotation is enabled */} {(autoRotate || lastRotationAt || keyRotationAt || nextRotationAt) && (
- {/* Last Rotation - Show when available */} {lastRotationAt && ( -
- +
+
- Last Rotation - {formatTimestamp(lastRotationAt)} +
Last Rotation
+
+ {formatTimestamp(lastRotationAt)} +
)} - {/* Next Scheduled Rotation - Show when available */} {(keyRotationAt || nextRotationAt) && ( -
- +
+
- Next Scheduled Rotation - {formatTimestamp(nextRotationAt || keyRotationAt || "")} +
+ Next Scheduled Rotation +
+
+ {formatTimestamp(nextRotationAt || keyRotationAt || "")} +
)} - {/* No rotation data message - Only show if auto-rotation is enabled but no data */} - {autoRotate && !lastRotationAt && !keyRotationAt && !nextRotationAt && ( -
- - No rotation history available -
- )} + {autoRotate && + !lastRotationAt && + !keyRotationAt && + !nextRotationAt && ( +
+ + + No rotation history available + +
+ )}
)} - {/* Disabled State - Only show if auto-rotation is disabled AND there's no rotation history */} - {!autoRotate && !lastRotationAt && !keyRotationAt && !nextRotationAt && ( -
- - Auto-rotation is not enabled for this key -
- )} + {!autoRotate && + !lastRotationAt && + !keyRotationAt && + !nextRotationAt && ( +
+ + + Auto-rotation is not enabled for this key + +
+ )}
); if (variant === "card") { return ( -
+
- Auto-Rotation - Automatic key rotation settings and status for this key +

Auto-Rotation

+

+ Automatic key rotation settings and status for this key +

{content} @@ -116,7 +139,7 @@ const AutoRotationView: React.FC = ({ return (
- Auto-Rotation +

Auto-Rotation

{content}
); diff --git a/ui/litellm-dashboard/src/components/common_components/team_multi_select.tsx b/ui/litellm-dashboard/src/components/common_components/team_multi_select.tsx index 6f00a4d1f7b..8477431bf3c 100644 --- a/ui/litellm-dashboard/src/components/common_components/team_multi_select.tsx +++ b/ui/litellm-dashboard/src/components/common_components/team_multi_select.tsx @@ -1,11 +1,16 @@ -import React, { useMemo, useState, type UIEvent } from "react"; -import { Select, Typography } from "antd"; -import { LoadingOutlined } from "@ant-design/icons"; +import React, { useMemo, useRef, useState } from "react"; +import { Loader2, X } from "lucide-react"; import { useDebouncedState } from "@tanstack/react-pacer/debouncer"; import { useInfiniteTeams } from "@/app/(dashboard)/hooks/teams/useTeams"; import { Team } from "../key_team_helpers/key_list"; - -const { Text } = Typography; +import { Badge } from "@/components/ui/badge"; +import { Input } from "@/components/ui/input"; +import { + Popover, + PopoverContent, + PopoverTrigger, +} from "@/components/ui/popover"; +import { cn } from "@/lib/utils"; interface TeamMultiSelectProps { value?: string[]; @@ -27,22 +32,15 @@ const TeamMultiSelect: React.FC = ({ pageSize = 20, placeholder = "Search teams by alias...", }) => { + const [open, setOpen] = useState(false); const [searchInput, setSearchInput] = useState(""); const [debouncedSearch, setDebouncedSearch] = useDebouncedState("", { wait: DEBOUNCE_MS, }); + const listRef = useRef(null); - const { - data, - fetchNextPage, - hasNextPage, - isFetchingNextPage, - isLoading, - } = useInfiniteTeams( - pageSize, - debouncedSearch || undefined, - organizationId, - ); + const { data, fetchNextPage, hasNextPage, isFetchingNextPage, isLoading } = + useInfiniteTeams(pageSize, debouncedSearch || undefined, organizationId); const teams = useMemo(() => { if (!data?.pages) return []; @@ -58,7 +56,15 @@ const TeamMultiSelect: React.FC = ({ return result; }, [data]); - const handlePopupScroll = (e: UIEvent) => { + const teamMap = useMemo(() => { + const m = new Map(); + for (const team of teams) m.set(team.team_id, team); + return m; + }, [teams]); + + const labelFor = (id: string) => teamMap.get(id)?.team_alias ?? id; + + const handleScroll = (e: React.UIEvent) => { const target = e.currentTarget; const scrollRatio = (target.scrollTop + target.clientHeight) / target.scrollHeight; @@ -72,40 +78,106 @@ const TeamMultiSelect: React.FC = ({ setDebouncedSearch(val); }; + const toggle = (teamId: string) => { + if (value.includes(teamId)) { + onChange?.(value.filter((v) => v !== teamId)); + } else { + onChange?.([...value, teamId]); + } + }; + return ( - handleSearch(e.target.value)} + className="h-8" + /> +
+
+ {isLoading && teams.length === 0 ? ( +
+ +
+ ) : teams.length === 0 ? ( +
+ No teams found +
+ ) : ( + teams.map((team) => { + const selected = value.includes(team.team_id); + return ( + + ); + }) + )} {isFetchingNextPage && ( -
- +
+
)} - - )} - > - {teams.map((team) => ( - - {team.team_alias}{" "} - ({team.team_id}) - - ))} - +
+ + ); };