diff --git a/ui/litellm-dashboard/src/app/(dashboard)/hooks/useAuthorized.ts b/ui/litellm-dashboard/src/app/(dashboard)/hooks/useAuthorized.ts
index 435000f9c88..0ce0cab183f 100644
--- a/ui/litellm-dashboard/src/app/(dashboard)/hooks/useAuthorized.ts
+++ b/ui/litellm-dashboard/src/app/(dashboard)/hooks/useAuthorized.ts
@@ -5,6 +5,35 @@ import { useRouter } from "next/navigation";
import { jwtDecode } from "jwt-decode";
import { clearTokenCookies, getCookie } from "@/utils/cookieUtils";
+function formatUserRole(userRole: string) {
+ if (!userRole) {
+ return "Undefined Role";
+ }
+ switch (userRole.toLowerCase()) {
+ case "app_owner":
+ return "App Owner";
+ case "demo_app_owner":
+ return "App Owner";
+ case "app_admin":
+ return "Admin";
+ case "proxy_admin":
+ return "Admin";
+ case "proxy_admin_viewer":
+ return "Admin Viewer";
+ case "org_admin":
+ return "Org Admin";
+ case "internal_user":
+ return "Internal User";
+ case "internal_user_viewer":
+ case "internal_viewer": // TODO:remove if deprecated
+ return "Internal Viewer";
+ case "app_user":
+ return "App User";
+ default:
+ return "Unknown Role";
+ }
+}
+
const useAuthorized = () => {
const router = useRouter();
@@ -35,7 +64,7 @@ const useAuthorized = () => {
accessToken: decoded?.key ?? null,
userId: decoded?.user_id ?? null,
userEmail: decoded?.user_email ?? null,
- userRole: decoded?.user_role ?? null,
+ userRole: formatUserRole(decoded?.user_role ?? null),
premiumUser: decoded?.premium_user ?? null,
disabledPersonalKeyCreation: decoded?.disabled_non_admin_personal_key_creation ?? null,
showSSOBanner: decoded?.login_method === "username_password" ?? false,
diff --git a/ui/litellm-dashboard/src/app/(dashboard)/layout.tsx b/ui/litellm-dashboard/src/app/(dashboard)/layout.tsx
index 68ab361356f..97837ff8e0a 100644
--- a/ui/litellm-dashboard/src/app/(dashboard)/layout.tsx
+++ b/ui/litellm-dashboard/src/app/(dashboard)/layout.tsx
@@ -25,7 +25,7 @@ function withBase(path: string): string {
export default function Layout({ children }: { children: React.ReactNode }) {
const router = useRouter();
const searchParams = useSearchParams();
- const { accessToken, userRole } = useAuthorized();
+ const { accessToken, userRole, userId, userEmail, premiumUser } = useAuthorized();
const [sidebarCollapsed, setSidebarCollapsed] = React.useState(false);
const [page, setPage] = useState(() => {
return searchParams.get("page") || "api-keys";
@@ -51,15 +51,13 @@ export default function Layout({ children }: { children: React.ReactNode }) {
isPublicPage={false}
sidebarCollapsed={sidebarCollapsed}
onToggleSidebar={toggleSidebar}
- userID={null}
- userEmail={null}
- userRole={null}
- premiumUser={false}
+ userID={userId}
+ userEmail={userEmail}
+ userRole={userRole}
+ premiumUser={premiumUser}
proxySettings={undefined}
- setProxySettings={function (value: any): void {
- throw new Error("Function not implemented.");
- }}
- accessToken={null}
+ setProxySettings={() => {}}
+ accessToken={accessToken}
/>
diff --git a/ui/litellm-dashboard/src/app/(dashboard)/teams/components/TeamsTable/ModelsCell.tsx b/ui/litellm-dashboard/src/app/(dashboard)/teams/components/TeamsTable/ModelsCell.tsx
new file mode 100644
index 00000000000..0ffd3ae22ea
--- /dev/null
+++ b/ui/litellm-dashboard/src/app/(dashboard)/teams/components/TeamsTable/ModelsCell.tsx
@@ -0,0 +1,101 @@
+import { Badge, Icon, TableCell, Text } from "@tremor/react";
+import { ChevronDownIcon, ChevronRightIcon } from "@heroicons/react/outline";
+import { getModelDisplayName } from "@/components/key_team_helpers/fetch_available_models_team_key";
+import React, { useState } from "react";
+import { Team } from "@/components/key_team_helpers/key_list";
+
+interface ModelsCellProps {
+ team: Team;
+}
+
+const ModelsCell = ({ team }: ModelsCellProps) => {
+ const [expandedAccordions, setExpandedAccordions] = useState
>({});
+
+ return (
+ 3 ? "px-0" : ""}
+ >
+
+ {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}
+
+
+ );
+};
+
+export default ModelsCell;
diff --git a/ui/litellm-dashboard/src/app/(dashboard)/teams/components/TeamsTable/TeamsTable.tsx b/ui/litellm-dashboard/src/app/(dashboard)/teams/components/TeamsTable/TeamsTable.tsx
new file mode 100644
index 00000000000..3ffdbc40629
--- /dev/null
+++ b/ui/litellm-dashboard/src/app/(dashboard)/teams/components/TeamsTable/TeamsTable.tsx
@@ -0,0 +1,172 @@
+import {
+ Badge,
+ Button,
+ Icon,
+ Table,
+ TableBody,
+ TableCell,
+ TableHead,
+ TableHeaderCell,
+ TableRow,
+ Text,
+} from "@tremor/react";
+import { Tooltip } from "antd";
+import { formatNumberWithCommas } from "@/utils/dataUtils";
+import { ChevronDownIcon, ChevronRightIcon, PencilAltIcon, TrashIcon } from "@heroicons/react/outline";
+import { getModelDisplayName } from "@/components/key_team_helpers/fetch_available_models_team_key";
+import React, { useState } from "react";
+import { type KeyResponse, Team } from "@/components/key_team_helpers/key_list";
+import { Member, Organization } from "@/components/networking";
+import ModelsCell from "@/app/(dashboard)/teams/components/TeamsTable/ModelsCell";
+
+type TeamsTableProps = {
+ teams: Team[] | null;
+ currentOrg: Organization | null;
+ perTeamInfo: Record;
+ userRole: string | null;
+ setSelectedTeamId: (teamId: string) => void;
+ setEditTeam: (editTeam: boolean) => void;
+ onDeleteTeam: (teamId: string) => void;
+};
+
+interface TeamInfo {
+ members_with_roles: Member[];
+}
+
+interface PerTeamInfo {
+ keys: KeyResponse[];
+ team_info: TeamInfo;
+}
+
+const TeamsTable = ({
+ teams,
+ currentOrg,
+ setSelectedTeamId,
+ perTeamInfo,
+ userRole,
+ setEditTeam,
+ onDeleteTeam,
+}: TeamsTableProps) => {
+ return (
+
+
+
+ Team Name
+ Team ID
+ Created
+ Spend (USD)
+ Budget (USD)
+ Models
+ Organization
+ Info
+
+
+
+
+ {teams && teams.length > 0
+ ? teams
+ .filter((team) => {
+ if (!currentOrg) return true;
+ return team.organization_id === currentOrg.organization_id;
+ })
+ .sort((a, b) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime())
+ .map((team: any) => (
+
+
+ {team["team_alias"]}
+
+
+
+
+
+
+
+
+
+ {team.created_at ? new Date(team.created_at).toLocaleDateString() : "N/A"}
+
+
+ {formatNumberWithCommas(team["spend"], 4)}
+
+
+ {team["max_budget"] !== null && team["max_budget"] !== undefined ? team["max_budget"] : "No limit"}
+
+
+ {team.organization_id}
+
+
+ {perTeamInfo &&
+ team.team_id &&
+ perTeamInfo[team.team_id] &&
+ perTeamInfo[team.team_id].keys &&
+ perTeamInfo[team.team_id].keys.length}{" "}
+ Keys
+
+
+ {perTeamInfo &&
+ team.team_id &&
+ perTeamInfo[team.team_id] &&
+ perTeamInfo[team.team_id].team_info &&
+ perTeamInfo[team.team_id].team_info.members_with_roles &&
+ perTeamInfo[team.team_id].team_info.members_with_roles.length}{" "}
+ Members
+
+
+
+ {userRole == "Admin" ? (
+ <>
+ {
+ setSelectedTeamId(team.team_id);
+ setEditTeam(true);
+ }}
+ />
+ onDeleteTeam(team.team_id)} icon={TrashIcon} size="sm" />
+ >
+ ) : null}
+
+
+ ))
+ : null}
+
+
+ );
+};
+
+export default TeamsTable;
diff --git a/ui/litellm-dashboard/src/app/(dashboard)/teams/hooks/useFetchTeams.ts b/ui/litellm-dashboard/src/app/(dashboard)/teams/hooks/useFetchTeams.ts
new file mode 100644
index 00000000000..c02787896f9
--- /dev/null
+++ b/ui/litellm-dashboard/src/app/(dashboard)/teams/hooks/useFetchTeams.ts
@@ -0,0 +1,30 @@
+import { useCallback, useEffect, useState } from "react";
+import { fetchTeams } from "@/components/common_components/fetch_teams";
+import useAuthorized from "@/app/(dashboard)/hooks/useAuthorized";
+import { Organization, Team } from "@/components/networking";
+
+interface useFetchTeamsProps {
+ currentOrg: Organization | null;
+ setTeams: (teams: Team[] | null) => void;
+}
+
+const useFetchTeams = ({ currentOrg, setTeams }: useFetchTeamsProps) => {
+ const [lastRefreshed, setLastRefreshed] = useState("");
+ const { accessToken, userId, userRole } = useAuthorized();
+
+ const onRefreshClick = useCallback(() => {
+ const currentDate = new Date();
+ setLastRefreshed(currentDate.toLocaleString());
+ }, []);
+
+ useEffect(() => {
+ if (accessToken) {
+ fetchTeams(accessToken, userId, userRole, currentOrg, setTeams).then();
+ }
+ onRefreshClick();
+ }, [accessToken, currentOrg, lastRefreshed, onRefreshClick, setTeams, userId, userRole]);
+
+ return { lastRefreshed, setLastRefreshed, onRefreshClick };
+};
+
+export default useFetchTeams;
diff --git a/ui/litellm-dashboard/src/app/(dashboard)/teams/page.tsx b/ui/litellm-dashboard/src/app/(dashboard)/teams/page.tsx
index 00f482a6faf..531caabd12a 100644
--- a/ui/litellm-dashboard/src/app/(dashboard)/teams/page.tsx
+++ b/ui/litellm-dashboard/src/app/(dashboard)/teams/page.tsx
@@ -10,9 +10,6 @@ import { fetchOrganizations } from "@/components/organizations";
const TeamsPage = () => {
const { accessToken, userId, userRole } = useAuthorized();
const { teams, setTeams } = useTeams();
- const [searchParams, setSearchParams] = useState(() =>
- typeof window === "undefined" ? new URLSearchParams() : new URLSearchParams(window.location.search),
- );
const [organizations, setOrganizations] = useState([]);
useEffect(() => {
@@ -22,7 +19,6 @@ const TeamsPage = () => {
return (
>;
userID: string | null;
@@ -70,18 +60,6 @@ interface FilterState {
sort_order: "asc" | "desc";
}
-interface EditTeamModalProps {
- visible: boolean;
- onCancel: () => void;
- team: any; // Assuming TeamType is a type representing your team object
- onSubmit: (data: FormData) => void; // Assuming FormData is the type of data to be submitted
-}
-
-import { teamCreateCall, Member, v2TeamListCall } from "@/components/networking";
-import { updateExistingKeys } from "@/utils/dataUtils";
-import TeamsHeaderTabs from "@/app/(dashboard)/teams/components/TeamsHeaderTabs";
-import TeamsFilters from "@/app/(dashboard)/teams/components/TeamsFilters";
-
interface TeamInfo {
members_with_roles: Member[];
}
@@ -112,7 +90,6 @@ const getOrganizationModels = (organization: Organization | null, userModels: st
const Teams: React.FC = ({
teams,
- searchParams,
accessToken,
setTeams,
userID,
@@ -120,7 +97,6 @@ const Teams: React.FC = ({
organizations,
premiumUser = false,
}) => {
- const [lastRefreshed, setLastRefreshed] = useState("");
const [currentOrg, setCurrentOrg] = useState(null);
const [currentOrgForCreateTeam, setCurrentOrgForCreateTeam] = useState(null);
const [showFilters, setShowFilters] = useState(false);
@@ -132,15 +108,6 @@ const Teams: React.FC = ({
sort_order: "desc",
});
- useEffect(() => {
- console.log(`inside useeffect - ${lastRefreshed}`);
- if (accessToken) {
- // Call your function here
- fetchTeams(accessToken, userID, userRole, currentOrg, setTeams);
- }
- handleRefreshClick();
- }, [lastRefreshed]);
-
const [form] = Form.useForm();
const [memberForm] = Form.useForm();
@@ -158,12 +125,12 @@ const Teams: React.FC = ({
// Add this state near the other useState declarations
const [guardrailsList, setGuardrailsList] = useState([]);
- const [expandedAccordions, setExpandedAccordions] = useState>({});
const [loggingSettings, setLoggingSettings] = useState([]);
const [mcpAccessGroups, setMcpAccessGroups] = useState([]);
const [mcpAccessGroupsLoaded, setMcpAccessGroupsLoaded] = useState(false);
const [deleteConfirmInput, setDeleteConfirmInput] = useState("");
const [modelAliases, setModelAliases] = useState<{ [key: string]: string }>({});
+ const { lastRefreshed, onRefreshClick: handleRefreshClick } = useFetchTeams({ currentOrg, setTeams });
useEffect(() => {
console.log(`currentOrgForCreateTeam: ${currentOrgForCreateTeam}`);
@@ -426,12 +393,6 @@ const Teams: React.FC = ({
return false;
};
- const handleRefreshClick = () => {
- // Update the 'lastRefreshed' state to the current date and time
- const currentDate = new Date();
- setLastRefreshed(currentDate.toLocaleString());
- };
-
const handleFilterChange = (key: keyof FilterState, value: string) => {
const newFilters = { ...filters, [key]: value };
setFilters(newFilters);
@@ -565,214 +526,15 @@ const Teams: React.FC = ({
/>
-
-
-
- Team Name
- Team ID
- Created
- Spend (USD)
- Budget (USD)
- Models
- Organization
- Info
-
-
-
-
- {teams && teams.length > 0
- ? teams
- .filter((team) => {
- if (!currentOrg) return true;
- return team.organization_id === currentOrg.organization_id;
- })
- .sort((a, b) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime())
- .map((team: any) => (
-
-
- {team["team_alias"]}
-
-
-
-
-
-
-
-
-
- {team.created_at ? new Date(team.created_at).toLocaleDateString() : "N/A"}
-
-
- {formatNumberWithCommas(team["spend"], 4)}
-
-
- {team["max_budget"] !== null && team["max_budget"] !== undefined
- ? team["max_budget"]
- : "No limit"}
-
- 3 ? "px-0" : ""}
- >
-
- {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}
-
-
-
- {team.organization_id}
-
-
- {perTeamInfo &&
- team.team_id &&
- perTeamInfo[team.team_id] &&
- perTeamInfo[team.team_id].keys &&
- perTeamInfo[team.team_id].keys.length}{" "}
- Keys
-
-
- {perTeamInfo &&
- team.team_id &&
- perTeamInfo[team.team_id] &&
- perTeamInfo[team.team_id].team_info &&
- perTeamInfo[team.team_id].team_info.members_with_roles &&
- perTeamInfo[team.team_id].team_info.members_with_roles.length}{" "}
- Members
-
-
-
- {userRole == "Admin" ? (
- <>
- {
- setSelectedTeamId(team.team_id);
- setEditTeam(true);
- }}
- />
- handleDelete(team.team_id)} icon={TrashIcon} size="sm" />
- >
- ) : null}
-
-
- ))
- : null}
-
-
+
{isDeleteModalOpen &&
(() => {
const team = teams?.find((t) => t.team_id === teamToDelete);
diff --git a/ui/litellm-dashboard/src/app/page.tsx b/ui/litellm-dashboard/src/app/page.tsx
index 4b5e19b60f9..d7262afefb9 100644
--- a/ui/litellm-dashboard/src/app/page.tsx
+++ b/ui/litellm-dashboard/src/app/page.tsx
@@ -379,7 +379,6 @@ export default function CreateKeyPage() {