mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-07 08:26:10 +00:00
useAuthorized fix, teams refactor progress
This commit is contained in:
parent
d94e27e3c2
commit
7c6e48c780
8 changed files with 358 additions and 271 deletions
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
/>
|
||||
<div className="flex flex-1 overflow-auto">
|
||||
<div className="mt-2">
|
||||
|
|
|
|||
|
|
@ -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<Record<string, boolean>>({});
|
||||
|
||||
return (
|
||||
<TableCell
|
||||
style={{
|
||||
maxWidth: "8-x",
|
||||
whiteSpace: "pre-wrap",
|
||||
overflow: "hidden",
|
||||
}}
|
||||
className={team.models.length > 3 ? "px-0" : ""}
|
||||
>
|
||||
<div className="flex flex-col">
|
||||
{Array.isArray(team.models) ? (
|
||||
<div className="flex flex-col">
|
||||
{team.models.length === 0 ? (
|
||||
<Badge size={"xs"} className="mb-1" color="red">
|
||||
<Text>All Proxy Models</Text>
|
||||
</Badge>
|
||||
) : (
|
||||
<>
|
||||
<div className="flex items-start">
|
||||
{team.models.length > 3 && (
|
||||
<div>
|
||||
<Icon
|
||||
icon={expandedAccordions[team.team_id] ? ChevronDownIcon : ChevronRightIcon}
|
||||
className="cursor-pointer"
|
||||
size="xs"
|
||||
onClick={() => {
|
||||
setExpandedAccordions((prev) => ({
|
||||
...prev,
|
||||
[team.team_id]: !prev[team.team_id],
|
||||
}));
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<div className="flex flex-wrap gap-1">
|
||||
{team.models.slice(0, 3).map((model: string, index: number) =>
|
||||
model === "all-proxy-models" ? (
|
||||
<Badge key={index} size={"xs"} color="red">
|
||||
<Text>All Proxy Models</Text>
|
||||
</Badge>
|
||||
) : (
|
||||
<Badge key={index} size={"xs"} color="blue">
|
||||
<Text>
|
||||
{model.length > 30
|
||||
? `${getModelDisplayName(model).slice(0, 30)}...`
|
||||
: getModelDisplayName(model)}
|
||||
</Text>
|
||||
</Badge>
|
||||
),
|
||||
)}
|
||||
{team.models.length > 3 && !expandedAccordions[team.team_id] && (
|
||||
<Badge size={"xs"} color="gray" className="cursor-pointer">
|
||||
<Text>
|
||||
+{team.models.length - 3} {team.models.length - 3 === 1 ? "more model" : "more models"}
|
||||
</Text>
|
||||
</Badge>
|
||||
)}
|
||||
{expandedAccordions[team.team_id] && (
|
||||
<div className="flex flex-wrap gap-1">
|
||||
{team.models.slice(3).map((model: string, index: number) =>
|
||||
model === "all-proxy-models" ? (
|
||||
<Badge key={index + 3} size={"xs"} color="red">
|
||||
<Text>All Proxy Models</Text>
|
||||
</Badge>
|
||||
) : (
|
||||
<Badge key={index + 3} size={"xs"} color="blue">
|
||||
<Text>
|
||||
{model.length > 30
|
||||
? `${getModelDisplayName(model).slice(0, 30)}...`
|
||||
: getModelDisplayName(model)}
|
||||
</Text>
|
||||
</Badge>
|
||||
),
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
</TableCell>
|
||||
);
|
||||
};
|
||||
|
||||
export default ModelsCell;
|
||||
|
|
@ -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<string, PerTeamInfo>;
|
||||
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 (
|
||||
<Table>
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableHeaderCell>Team Name</TableHeaderCell>
|
||||
<TableHeaderCell>Team ID</TableHeaderCell>
|
||||
<TableHeaderCell>Created</TableHeaderCell>
|
||||
<TableHeaderCell>Spend (USD)</TableHeaderCell>
|
||||
<TableHeaderCell>Budget (USD)</TableHeaderCell>
|
||||
<TableHeaderCell>Models</TableHeaderCell>
|
||||
<TableHeaderCell>Organization</TableHeaderCell>
|
||||
<TableHeaderCell>Info</TableHeaderCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
|
||||
<TableBody>
|
||||
{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) => (
|
||||
<TableRow key={team.team_id}>
|
||||
<TableCell
|
||||
style={{
|
||||
maxWidth: "4px",
|
||||
whiteSpace: "pre-wrap",
|
||||
overflow: "hidden",
|
||||
}}
|
||||
>
|
||||
{team["team_alias"]}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<div className="overflow-hidden">
|
||||
<Tooltip title={team.team_id}>
|
||||
<Button
|
||||
size="xs"
|
||||
variant="light"
|
||||
className="font-mono text-blue-500 bg-blue-50 hover:bg-blue-100 text-xs font-normal px-2 py-0.5 text-left overflow-hidden truncate max-w-[200px]"
|
||||
onClick={() => {
|
||||
// Add click handler
|
||||
setSelectedTeamId(team.team_id);
|
||||
}}
|
||||
>
|
||||
{team.team_id.slice(0, 7)}...
|
||||
</Button>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell
|
||||
style={{
|
||||
maxWidth: "4px",
|
||||
whiteSpace: "pre-wrap",
|
||||
overflow: "hidden",
|
||||
}}
|
||||
>
|
||||
{team.created_at ? new Date(team.created_at).toLocaleDateString() : "N/A"}
|
||||
</TableCell>
|
||||
<TableCell
|
||||
style={{
|
||||
maxWidth: "4px",
|
||||
whiteSpace: "pre-wrap",
|
||||
overflow: "hidden",
|
||||
}}
|
||||
>
|
||||
{formatNumberWithCommas(team["spend"], 4)}
|
||||
</TableCell>
|
||||
<TableCell
|
||||
style={{
|
||||
maxWidth: "4px",
|
||||
whiteSpace: "pre-wrap",
|
||||
overflow: "hidden",
|
||||
}}
|
||||
>
|
||||
{team["max_budget"] !== null && team["max_budget"] !== undefined ? team["max_budget"] : "No limit"}
|
||||
</TableCell>
|
||||
<ModelsCell team={team} />
|
||||
<TableCell>{team.organization_id}</TableCell>
|
||||
<TableCell>
|
||||
<Text>
|
||||
{perTeamInfo &&
|
||||
team.team_id &&
|
||||
perTeamInfo[team.team_id] &&
|
||||
perTeamInfo[team.team_id].keys &&
|
||||
perTeamInfo[team.team_id].keys.length}{" "}
|
||||
Keys
|
||||
</Text>
|
||||
<Text>
|
||||
{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
|
||||
</Text>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
{userRole == "Admin" ? (
|
||||
<>
|
||||
<Icon
|
||||
icon={PencilAltIcon}
|
||||
size="sm"
|
||||
onClick={() => {
|
||||
setSelectedTeamId(team.team_id);
|
||||
setEditTeam(true);
|
||||
}}
|
||||
/>
|
||||
<Icon onClick={() => onDeleteTeam(team.team_id)} icon={TrashIcon} size="sm" />
|
||||
</>
|
||||
) : null}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))
|
||||
: null}
|
||||
</TableBody>
|
||||
</Table>
|
||||
);
|
||||
};
|
||||
|
||||
export default TeamsTable;
|
||||
|
|
@ -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;
|
||||
|
|
@ -10,9 +10,6 @@ import { fetchOrganizations } from "@/components/organizations";
|
|||
const TeamsPage = () => {
|
||||
const { accessToken, userId, userRole } = useAuthorized();
|
||||
const { teams, setTeams } = useTeams();
|
||||
const [searchParams, setSearchParams] = useState<URLSearchParams>(() =>
|
||||
typeof window === "undefined" ? new URLSearchParams() : new URLSearchParams(window.location.search),
|
||||
);
|
||||
const [organizations, setOrganizations] = useState<Organization[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
|
|
@ -22,7 +19,6 @@ const TeamsPage = () => {
|
|||
return (
|
||||
<Teams
|
||||
teams={teams}
|
||||
searchParams={searchParams}
|
||||
accessToken={accessToken}
|
||||
setTeams={setTeams}
|
||||
userID={userId}
|
||||
|
|
|
|||
|
|
@ -1,59 +1,49 @@
|
|||
import React, { useState, useEffect } from "react";
|
||||
import { Typography } from "antd";
|
||||
import { teamDeleteCall, Organization, fetchMCPAccessGroups } from "@/components/networking";
|
||||
import { fetchTeams } from "@/components/common_components/fetch_teams";
|
||||
import { PencilAltIcon, RefreshIcon, TrashIcon, ChevronDownIcon, ChevronRightIcon } from "@heroicons/react/outline";
|
||||
import { Button as Button2, Modal, Form, Input, Select as Select2, message, Tooltip } from "antd";
|
||||
import { Button as Button2, Modal, Form, Input, Select as Select2, Tooltip } from "antd";
|
||||
import NumericalInput from "../../../components/shared/numerical_input";
|
||||
import {
|
||||
fetchAvailableModelsForTeamOrKey,
|
||||
getModelDisplayName,
|
||||
unfurlWildcardModelsInList,
|
||||
} from "@/components/key_team_helpers/fetch_available_models_team_key";
|
||||
import { Select, SelectItem } from "@tremor/react";
|
||||
import { InfoCircleOutlined } from "@ant-design/icons";
|
||||
import { getGuardrailsList } from "@/components/networking";
|
||||
import TeamInfoView from "@/components/team/team_info";
|
||||
import TeamSSOSettings from "@/components/TeamSSOSettings";
|
||||
import { isAdminRole } from "@/utils/roles";
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableHead,
|
||||
TableHeaderCell,
|
||||
TableRow,
|
||||
TextInput,
|
||||
Card,
|
||||
Icon,
|
||||
Button,
|
||||
Badge,
|
||||
Col,
|
||||
Text,
|
||||
Grid,
|
||||
Accordion,
|
||||
AccordionHeader,
|
||||
AccordionBody,
|
||||
TabGroup,
|
||||
TabList,
|
||||
TabPanel,
|
||||
TabPanels,
|
||||
Tab,
|
||||
} from "@tremor/react";
|
||||
import AvailableTeamsPanel from "@/components/team/available_teams";
|
||||
import VectorStoreSelector from "../../../components/vector_store_management/VectorStoreSelector";
|
||||
import PremiumLoggingSettings from "../../../components/common_components/PremiumLoggingSettings";
|
||||
import type { KeyResponse, Team } from "@/components/key_team_helpers/key_list";
|
||||
import { formatNumberWithCommas } from "@/utils/dataUtils";
|
||||
import { AlertTriangleIcon, XIcon } from "lucide-react";
|
||||
import MCPServerSelector from "./mcp_server_management/MCPServerSelector";
|
||||
import MCPToolPermissions from "./mcp_server_management/MCPToolPermissions";
|
||||
import ModelAliasManager from "./common_components/ModelAliasManager";
|
||||
import NotificationsManager from "./molecules/notifications_manager";
|
||||
|
||||
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";
|
||||
import useFetchTeams from "@/app/(dashboard)/teams/hooks/useFetchTeams";
|
||||
import TeamsTable from "@/app/(dashboard)/teams/components/TeamsTable/TeamsTable";
|
||||
|
||||
interface TeamProps {
|
||||
teams: Team[] | null;
|
||||
searchParams: any;
|
||||
accessToken: string | null;
|
||||
setTeams: React.Dispatch<React.SetStateAction<Team[] | null>>;
|
||||
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<TeamProps> = ({
|
||||
teams,
|
||||
searchParams,
|
||||
accessToken,
|
||||
setTeams,
|
||||
userID,
|
||||
|
|
@ -120,7 +97,6 @@ const Teams: React.FC<TeamProps> = ({
|
|||
organizations,
|
||||
premiumUser = false,
|
||||
}) => {
|
||||
const [lastRefreshed, setLastRefreshed] = useState("");
|
||||
const [currentOrg, setCurrentOrg] = useState<Organization | null>(null);
|
||||
const [currentOrgForCreateTeam, setCurrentOrgForCreateTeam] = useState<Organization | null>(null);
|
||||
const [showFilters, setShowFilters] = useState(false);
|
||||
|
|
@ -132,15 +108,6 @@ const Teams: React.FC<TeamProps> = ({
|
|||
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<TeamProps> = ({
|
|||
|
||||
// Add this state near the other useState declarations
|
||||
const [guardrailsList, setGuardrailsList] = useState<string[]>([]);
|
||||
const [expandedAccordions, setExpandedAccordions] = useState<Record<string, boolean>>({});
|
||||
const [loggingSettings, setLoggingSettings] = useState<any[]>([]);
|
||||
const [mcpAccessGroups, setMcpAccessGroups] = useState<string[]>([]);
|
||||
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<TeamProps> = ({
|
|||
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<TeamProps> = ({
|
|||
/>
|
||||
</div>
|
||||
</div>
|
||||
<Table>
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableHeaderCell>Team Name</TableHeaderCell>
|
||||
<TableHeaderCell>Team ID</TableHeaderCell>
|
||||
<TableHeaderCell>Created</TableHeaderCell>
|
||||
<TableHeaderCell>Spend (USD)</TableHeaderCell>
|
||||
<TableHeaderCell>Budget (USD)</TableHeaderCell>
|
||||
<TableHeaderCell>Models</TableHeaderCell>
|
||||
<TableHeaderCell>Organization</TableHeaderCell>
|
||||
<TableHeaderCell>Info</TableHeaderCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
|
||||
<TableBody>
|
||||
{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) => (
|
||||
<TableRow key={team.team_id}>
|
||||
<TableCell
|
||||
style={{
|
||||
maxWidth: "4px",
|
||||
whiteSpace: "pre-wrap",
|
||||
overflow: "hidden",
|
||||
}}
|
||||
>
|
||||
{team["team_alias"]}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<div className="overflow-hidden">
|
||||
<Tooltip title={team.team_id}>
|
||||
<Button
|
||||
size="xs"
|
||||
variant="light"
|
||||
className="font-mono text-blue-500 bg-blue-50 hover:bg-blue-100 text-xs font-normal px-2 py-0.5 text-left overflow-hidden truncate max-w-[200px]"
|
||||
onClick={() => {
|
||||
// Add click handler
|
||||
setSelectedTeamId(team.team_id);
|
||||
}}
|
||||
>
|
||||
{team.team_id.slice(0, 7)}...
|
||||
</Button>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell
|
||||
style={{
|
||||
maxWidth: "4px",
|
||||
whiteSpace: "pre-wrap",
|
||||
overflow: "hidden",
|
||||
}}
|
||||
>
|
||||
{team.created_at ? new Date(team.created_at).toLocaleDateString() : "N/A"}
|
||||
</TableCell>
|
||||
<TableCell
|
||||
style={{
|
||||
maxWidth: "4px",
|
||||
whiteSpace: "pre-wrap",
|
||||
overflow: "hidden",
|
||||
}}
|
||||
>
|
||||
{formatNumberWithCommas(team["spend"], 4)}
|
||||
</TableCell>
|
||||
<TableCell
|
||||
style={{
|
||||
maxWidth: "4px",
|
||||
whiteSpace: "pre-wrap",
|
||||
overflow: "hidden",
|
||||
}}
|
||||
>
|
||||
{team["max_budget"] !== null && team["max_budget"] !== undefined
|
||||
? team["max_budget"]
|
||||
: "No limit"}
|
||||
</TableCell>
|
||||
<TableCell
|
||||
style={{
|
||||
maxWidth: "8-x",
|
||||
whiteSpace: "pre-wrap",
|
||||
overflow: "hidden",
|
||||
}}
|
||||
className={team.models.length > 3 ? "px-0" : ""}
|
||||
>
|
||||
<div className="flex flex-col">
|
||||
{Array.isArray(team.models) ? (
|
||||
<div className="flex flex-col">
|
||||
{team.models.length === 0 ? (
|
||||
<Badge size={"xs"} className="mb-1" color="red">
|
||||
<Text>All Proxy Models</Text>
|
||||
</Badge>
|
||||
) : (
|
||||
<>
|
||||
<div className="flex items-start">
|
||||
{team.models.length > 3 && (
|
||||
<div>
|
||||
<Icon
|
||||
icon={
|
||||
expandedAccordions[team.team_id]
|
||||
? ChevronDownIcon
|
||||
: ChevronRightIcon
|
||||
}
|
||||
className="cursor-pointer"
|
||||
size="xs"
|
||||
onClick={() => {
|
||||
setExpandedAccordions((prev) => ({
|
||||
...prev,
|
||||
[team.team_id]: !prev[team.team_id],
|
||||
}));
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<div className="flex flex-wrap gap-1">
|
||||
{team.models.slice(0, 3).map((model: string, index: number) =>
|
||||
model === "all-proxy-models" ? (
|
||||
<Badge key={index} size={"xs"} color="red">
|
||||
<Text>All Proxy Models</Text>
|
||||
</Badge>
|
||||
) : (
|
||||
<Badge key={index} size={"xs"} color="blue">
|
||||
<Text>
|
||||
{model.length > 30
|
||||
? `${getModelDisplayName(model).slice(0, 30)}...`
|
||||
: getModelDisplayName(model)}
|
||||
</Text>
|
||||
</Badge>
|
||||
),
|
||||
)}
|
||||
{team.models.length > 3 && !expandedAccordions[team.team_id] && (
|
||||
<Badge size={"xs"} color="gray" className="cursor-pointer">
|
||||
<Text>
|
||||
+{team.models.length - 3}{" "}
|
||||
{team.models.length - 3 === 1 ? "more model" : "more models"}
|
||||
</Text>
|
||||
</Badge>
|
||||
)}
|
||||
{expandedAccordions[team.team_id] && (
|
||||
<div className="flex flex-wrap gap-1">
|
||||
{team.models.slice(3).map((model: string, index: number) =>
|
||||
model === "all-proxy-models" ? (
|
||||
<Badge key={index + 3} size={"xs"} color="red">
|
||||
<Text>All Proxy Models</Text>
|
||||
</Badge>
|
||||
) : (
|
||||
<Badge key={index + 3} size={"xs"} color="blue">
|
||||
<Text>
|
||||
{model.length > 30
|
||||
? `${getModelDisplayName(model).slice(0, 30)}...`
|
||||
: getModelDisplayName(model)}
|
||||
</Text>
|
||||
</Badge>
|
||||
),
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
</TableCell>
|
||||
|
||||
<TableCell>{team.organization_id}</TableCell>
|
||||
<TableCell>
|
||||
<Text>
|
||||
{perTeamInfo &&
|
||||
team.team_id &&
|
||||
perTeamInfo[team.team_id] &&
|
||||
perTeamInfo[team.team_id].keys &&
|
||||
perTeamInfo[team.team_id].keys.length}{" "}
|
||||
Keys
|
||||
</Text>
|
||||
<Text>
|
||||
{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
|
||||
</Text>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
{userRole == "Admin" ? (
|
||||
<>
|
||||
<Icon
|
||||
icon={PencilAltIcon}
|
||||
size="sm"
|
||||
onClick={() => {
|
||||
setSelectedTeamId(team.team_id);
|
||||
setEditTeam(true);
|
||||
}}
|
||||
/>
|
||||
<Icon onClick={() => handleDelete(team.team_id)} icon={TrashIcon} size="sm" />
|
||||
</>
|
||||
) : null}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))
|
||||
: null}
|
||||
</TableBody>
|
||||
</Table>
|
||||
<TeamsTable
|
||||
teams={teams}
|
||||
currentOrg={currentOrg}
|
||||
perTeamInfo={perTeamInfo}
|
||||
userRole={userRole}
|
||||
setSelectedTeamId={setSelectedTeamId}
|
||||
setEditTeam={setEditTeam}
|
||||
onDeleteTeam={handleDelete}
|
||||
/>
|
||||
{isDeleteModalOpen &&
|
||||
(() => {
|
||||
const team = teams?.find((t) => t.team_id === teamToDelete);
|
||||
|
|
|
|||
|
|
@ -379,7 +379,6 @@ export default function CreateKeyPage() {
|
|||
<Teams
|
||||
teams={teams}
|
||||
setTeams={setTeams}
|
||||
searchParams={searchParams}
|
||||
accessToken={accessToken}
|
||||
userID={userID}
|
||||
userRole={userRole}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue