diff --git a/ui/litellm-dashboard/src/components/all_keys_table.tsx b/ui/litellm-dashboard/src/components/all_keys_table.tsx index 22a348578c5..02f93b107b7 100644 --- a/ui/litellm-dashboard/src/components/all_keys_table.tsx +++ b/ui/litellm-dashboard/src/components/all_keys_table.tsx @@ -33,8 +33,11 @@ import { TableBody, TableRow, TableCell, + Icon, } from "@tremor/react"; -import { SwitchVerticalIcon, ChevronUpIcon, ChevronDownIcon } from "@heroicons/react/outline"; +import { SwitchVerticalIcon, ChevronUpIcon, ChevronDownIcon, ChevronRightIcon } from "@heroicons/react/outline"; +import { Badge, Text } from "@tremor/react"; +import { getModelDisplayName } from "./key_team_helpers/fetch_available_models_team_key"; interface AllKeysTableProps { keys: KeyResponse[]; @@ -152,6 +155,7 @@ export function AllKeysTable({ desc: true }]; }); + const [expandedAccordions, setExpandedAccordions] = useState>({}); // Use the filter logic hook @@ -363,19 +367,93 @@ export function AllKeysTable({ cell: (info) => { const models = info.getValue() as string[]; return ( -
- {models && models.length > 0 ? ( - models.map((model, index) => ( - - {model} - - )) - ) : ( - "-" - )} +
+ {Array.isArray(models) ? ( +
+ {models.length === 0 ? ( + + All Proxy Models + + ) : ( + <> +
+ {models.length > 3 && ( +
+ { + setExpandedAccordions(prev => ({ + ...prev, + [info.row.id]: !prev[info.row.id] + })); + }} + /> +
+ )} +
+ {models.slice(0, 3).map((model, index) => ( + model === "all-proxy-models" ? ( + + All Proxy Models + + ) : ( + + + {model.length > 30 + ? `${getModelDisplayName(model).slice(0, 30)}...` + : getModelDisplayName(model)} + + + ) + ))} + {models.length > 3 && !expandedAccordions[info.row.id] && ( + + +{models.length - 3} {models.length - 3 === 1 ? 'more model' : 'more models'} + + )} + {expandedAccordions[info.row.id] && ( +
+ {models.slice(3).map((model, index) => ( + model === "all-proxy-models" ? ( + + All Proxy Models + + ) : ( + + + {model.length > 30 + ? `${getModelDisplayName(model).slice(0, 30)}...` + : getModelDisplayName(model)} + + + ) + ))} +
+ )} +
+
+ + )} +
+ ) : null}
); }, @@ -621,11 +699,12 @@ export function AllKeysTable({ {row.getVisibleCells().map((cell) => ( 3 ? "px-0" : ""}`} > {flexRender(cell.column.columnDef.cell, cell.getContext())} diff --git a/ui/litellm-dashboard/src/components/organizations.tsx b/ui/litellm-dashboard/src/components/organizations.tsx index 71b9e5103b7..3ca3b5d9289 100644 --- a/ui/litellm-dashboard/src/components/organizations.tsx +++ b/ui/litellm-dashboard/src/components/organizations.tsx @@ -23,7 +23,7 @@ import NumericalInput from "./shared/numerical_input"; import { Input } from "antd"; import { Modal, Form, Tooltip, Select as Select2 } from "antd"; import { InfoCircleOutlined } from '@ant-design/icons'; -import { PencilAltIcon, TrashIcon, RefreshIcon } from "@heroicons/react/outline"; +import { PencilAltIcon, TrashIcon, RefreshIcon, ChevronDownIcon, ChevronRightIcon } from "@heroicons/react/outline"; import { TextInput } from "@tremor/react"; import { getModelDisplayName } from './key_team_helpers/fetch_available_models_team_key'; import { message } from 'antd'; @@ -65,6 +65,7 @@ const OrganizationsTable: React.FC = ({ const [orgToDelete, setOrgToDelete] = useState(null); const [isOrgModalVisible, setIsOrgModalVisible] = useState(false); const [form] = Form.useForm(); + const [expandedAccordions, setExpandedAccordions] = useState>({}); useEffect(() => { if (organizations.length === 0 && accessToken) { @@ -264,169 +265,248 @@ const OrganizationsTable: React.FC = ({ - - {organizations && organizations.length > 0 - ? organizations - .sort((a, b) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime()) - .map((org: Organization) => ( - - -
- - - -
-
- {org.organization_alias} - - {org.created_at ? new Date(org.created_at).toLocaleDateString() : "N/A"} - - {org.spend.toFixed(4)} - - {org.litellm_budget_table?.max_budget !== null && org.litellm_budget_table?.max_budget !== undefined ? org.litellm_budget_table?.max_budget : "No limit"} - - - {Array.isArray(org.models) && ( -
- {org.models.length === 0 ? ( - - All Proxy Models - - ) : ( - org.models.map((model, index) => - model === "all-proxy-models" ? ( - - All Proxy Models - - ) : ( - - {model.length > 30 - ? `${getModelDisplayName(model).slice(0, 30)}...` - : getModelDisplayName(model)} - - ) - ) - )} -
- )} -
- - - TPM: {org.litellm_budget_table?.tpm_limit ? org.litellm_budget_table?.tpm_limit : "Unlimited"} -
- RPM: {org.litellm_budget_table?.rpm_limit ? org.litellm_budget_table?.rpm_limit : "Unlimited"} -
-
- - {org.members?.length || 0} Members - - - {userRole === "Admin" && ( + + {organizations && organizations.length > 0 + ? organizations + .sort((a, b) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime()) + .map((org: Organization) => ( + + +
+ + + +
+
+ {org.organization_alias} + + {org.created_at ? new Date(org.created_at).toLocaleDateString() : "N/A"} + + {org.spend} + + {org.litellm_budget_table?.max_budget !== null && org.litellm_budget_table?.max_budget !== undefined ? org.litellm_budget_table?.max_budget : "No limit"} + + 3 ? "px-0" : ""} + > +
+ {Array.isArray(org.models) ? ( +
+ {org.models.length === 0 ? ( + + All Proxy Models + + ) : ( <> - { - setSelectedOrgId(org.organization_id); - setEditOrg(true); - }} - /> - handleDelete(org.organization_id)} - icon={TrashIcon} - size="sm" - /> +
+ {org.models.length > 3 && ( +
+ { + setExpandedAccordions(prev => ({ + ...prev, + [org.organization_id || '']: !prev[org.organization_id || ''] + })); + }} + /> +
+ )} +
+ {org.models.slice(0, 3).map((model, index) => ( + model === "all-proxy-models" ? ( + + All Proxy Models + + ) : ( + + + {model.length > 30 + ? `${getModelDisplayName(model).slice(0, 30)}...` + : getModelDisplayName(model)} + + + ) + ))} + {org.models.length > 3 && !expandedAccordions[org.organization_id || ''] && ( + + +{org.models.length - 3} {org.models.length - 3 === 1 ? 'more model' : 'more models'} + + )} + {expandedAccordions[org.organization_id || ''] && ( +
+ {org.models.slice(3).map((model, index) => ( + model === "all-proxy-models" ? ( + + All Proxy Models + + ) : ( + + + {model.length > 30 + ? `${getModelDisplayName(model).slice(0, 30)}...` + : getModelDisplayName(model)} + + + ) + ))} +
+ )} +
+
)} - - - )) - : null} - - - - - {(userRole === "Admin" || userRole === "Org Admin") && ( - - + ) : null} +
+ + + + TPM: {org.litellm_budget_table?.tpm_limit ? org.litellm_budget_table?.tpm_limit : "Unlimited"} +
+ RPM: {org.litellm_budget_table?.rpm_limit ? org.litellm_budget_table?.rpm_limit : "Unlimited"} +
+
+ + {org.members?.length || 0} Members + + + {userRole === "Admin" && ( + <> + { + setSelectedOrgId(org.organization_id); + setEditOrg(true); + }} + /> + handleDelete(org.organization_id)} + icon={TrashIcon} + size="sm" + /> + + )} + + + )) + : null} + + + + + {(userRole === "Admin" || userRole === "Org Admin") && ( + + + +
+ + + + + - - - - - - - - All Proxy Models - - {userModels && userModels.length > 0 && userModels.map((model) => ( - - {getModelDisplayName(model)} - - ))} - - + + All Proxy Models + + {userModels && userModels.length > 0 && userModels.map((model) => ( + + {getModelDisplayName(model)} + + ))} + + - - - - - - daily - weekly - monthly - - - - - - - - + + + + + + daily + weekly + monthly + + + + + + + + - - - + + + -
- -
-
-
- - )} - +
+ +
+ + + + )} + + {isDeleteModalOpen ? (
diff --git a/ui/litellm-dashboard/src/components/teams.tsx b/ui/litellm-dashboard/src/components/teams.tsx index fbcf4948a6b..8989c033e38 100644 --- a/ui/litellm-dashboard/src/components/teams.tsx +++ b/ui/litellm-dashboard/src/components/teams.tsx @@ -11,6 +11,8 @@ import { RefreshIcon, StatusOnlineIcon, TrashIcon, + ChevronDownIcon, + ChevronRightIcon } from "@heroicons/react/outline"; import { Button as Button2, @@ -177,6 +179,7 @@ const Teams: React.FC = ({ // Add this state near the other useState declarations const [guardrailsList, setGuardrailsList] = useState([]); + const [expandedAccordions, setExpandedAccordions] = useState>({}); useEffect(() => { console.log(`currentOrgForCreateTeam: ${currentOrgForCreateTeam}`); @@ -697,48 +700,97 @@ const Teams: React.FC = ({ whiteSpace: "pre-wrap", overflow: "hidden", }} + className={team.models.length > 3 ? "px-0" : ""} > - {Array.isArray(team.models) ? ( -
- {team.models.length === 0 ? ( - - All Proxy Models - - ) : ( - team.models.map( - (model: string, index: number) => - model === "all-proxy-models" ? ( - - All Proxy Models - - ) : ( - - - {model.length > 30 - ? `${getModelDisplayName(model).slice(0, 30)}...` - : getModelDisplayName(model)} - - - ) - ) - )} -
- ) : null} +
+ {Array.isArray(team.models) ? ( +
+ {team.models.length === 0 ? ( + + All Proxy Models + + ) : ( + <> +
+ {team.models.length > 3 && ( +
+ { + setExpandedAccordions(prev => ({ + ...prev, + [team.team_id]: !prev[team.team_id] + })); + }} + /> +
+ )} +
+ {team.models.slice(0, 3).map((model: string, index: number) => ( + model === "all-proxy-models" ? ( + + All Proxy Models + + ) : ( + + + {model.length > 30 + ? `${getModelDisplayName(model).slice(0, 30)}...` + : getModelDisplayName(model)} + + + ) + ))} + {team.models.length > 3 && !expandedAccordions[team.team_id] && ( + + +{team.models.length - 3} {team.models.length - 3 === 1 ? 'more model' : 'more models'} + + )} + {expandedAccordions[team.team_id] && ( +
+ {team.models.slice(3).map((model: string, index: number) => ( + model === "all-proxy-models" ? ( + + All Proxy Models + + ) : ( + + + {model.length > 30 + ? `${getModelDisplayName(model).slice(0, 30)}...` + : getModelDisplayName(model)} + + + ) + ))} +
+ )} +
+
+ + + )} +
+ ) : null} +