mirror of
https://github.com/BerriAI/litellm.git
synced 2025-04-25 02:34:29 +00:00
Org UI Improvements (#8436)
* feat(team_endpoints.py): support returning teams filtered by organization_id allows user to just get teams they belong to, within the org Enables org admin to see filtered list of teams on UI * fix(teams.tsx): simple filter for team on ui - just filter team based on selected org id * feat(ui/organizations): show 'default org' in switcher, filter teams based on selected org * feat(user_dashboard.tsx): update team in switcher when org changes * feat(schema.prisma): add new 'organization_id' value to key table allow org admin to directly issue keys to a user within their org * fix(view_key_table.tsx): fix regression where admin couldn't see keys caused by bad console log statement * fix(team_endpoints.py): handle default org value in /team/list * fix(key_management_endpoints.py): allow proxy admin to create keys for team they're not in * fix(team_endpoints.py): fix team endpoint to handle org id not being passed in * build(config.yml): investigate what pkg is installing posthog in ci/cd * ci(config.yml): uninstall posthog prevent it from being added in ci/cd * ci: auto-install ci
This commit is contained in:
parent
e26d7df91b
commit
13a3e8630e
15 changed files with 195 additions and 127 deletions
|
@ -72,6 +72,7 @@ jobs:
|
|||
pip install "jsonschema==4.22.0"
|
||||
pip install "pytest-xdist==3.6.1"
|
||||
pip install "websockets==10.4"
|
||||
pip uninstall posthog -y
|
||||
- save_cache:
|
||||
paths:
|
||||
- ./venv
|
||||
|
|
|
@ -1042,6 +1042,9 @@ class LiteLLM_TeamTable(TeamBase):
|
|||
"model_aliases",
|
||||
]
|
||||
|
||||
if isinstance(values, BaseModel):
|
||||
values = values.model_dump()
|
||||
|
||||
if (
|
||||
isinstance(values.get("members_with_roles"), dict)
|
||||
and not values["members_with_roles"]
|
||||
|
@ -1489,6 +1492,7 @@ class LiteLLM_OrganizationTable(LiteLLMPydanticObjectBase):
|
|||
|
||||
class LiteLLM_OrganizationTableWithMembers(LiteLLM_OrganizationTable):
|
||||
members: List[LiteLLM_OrganizationMembershipTable]
|
||||
teams: List[LiteLLM_TeamTable]
|
||||
|
||||
|
||||
class NewOrganizationResponse(LiteLLM_OrganizationTable):
|
||||
|
@ -2424,3 +2428,7 @@ class PrismaCompatibleUpdateDBModel(TypedDict, total=False):
|
|||
model_info: str
|
||||
updated_at: str
|
||||
updated_by: str
|
||||
|
||||
|
||||
class SpecialManagementEndpointEnums(enum.Enum):
|
||||
DEFAULT_ORGANIZATION = "default_organization"
|
||||
|
|
|
@ -168,6 +168,8 @@ def _team_key_generation_check(
|
|||
user_api_key_dict: UserAPIKeyAuth,
|
||||
data: GenerateKeyRequest,
|
||||
):
|
||||
if user_api_key_dict.user_role == LitellmUserRoles.PROXY_ADMIN.value:
|
||||
return True
|
||||
if (
|
||||
litellm.key_generation_settings is not None
|
||||
and "team_key_generation" in litellm.key_generation_settings
|
||||
|
|
|
@ -227,7 +227,7 @@ async def list_organization(
|
|||
# if proxy admin - get all orgs
|
||||
if user_api_key_dict.user_role == LitellmUserRoles.PROXY_ADMIN:
|
||||
response = await prisma_client.db.litellm_organizationtable.find_many(
|
||||
include={"members": True}
|
||||
include={"members": True, "teams": True}
|
||||
)
|
||||
# if internal user - get orgs they are a member of
|
||||
else:
|
||||
|
@ -242,7 +242,7 @@ async def list_organization(
|
|||
"in": [membership.organization_id for membership in org_memberships]
|
||||
}
|
||||
},
|
||||
include={"members": True},
|
||||
include={"members": True, "teams": True},
|
||||
)
|
||||
|
||||
response = org_objects
|
||||
|
|
|
@ -39,6 +39,7 @@ from litellm.proxy._types import (
|
|||
NewTeamRequest,
|
||||
ProxyErrorTypes,
|
||||
ProxyException,
|
||||
SpecialManagementEndpointEnums,
|
||||
TeamAddMemberResponse,
|
||||
TeamInfoResponseObject,
|
||||
TeamListResponseObject,
|
||||
|
@ -1482,6 +1483,7 @@ async def list_team(
|
|||
user_id: Optional[str] = fastapi.Query(
|
||||
default=None, description="Only return teams which this 'user_id' belongs to"
|
||||
),
|
||||
organization_id: Optional[str] = None,
|
||||
user_api_key_dict: UserAPIKeyAuth = Depends(user_api_key_auth),
|
||||
):
|
||||
"""
|
||||
|
@ -1492,6 +1494,7 @@ async def list_team(
|
|||
|
||||
Parameters:
|
||||
- user_id: str - Optional. If passed will only return teams that the user_id is a member of.
|
||||
- organization_id: str - Optional. If passed will only return teams that belong to the organization_id. Pass 'default_organization' to get all teams without organization_id.
|
||||
"""
|
||||
from litellm.proxy.proxy_server import prisma_client
|
||||
|
||||
|
@ -1565,6 +1568,19 @@ async def list_team(
|
|||
continue
|
||||
# Sort the responses by team_alias
|
||||
returned_responses.sort(key=lambda x: (getattr(x, "team_alias", "") or ""))
|
||||
|
||||
if organization_id is not None:
|
||||
if organization_id == SpecialManagementEndpointEnums.DEFAULT_ORGANIZATION.value:
|
||||
returned_responses = [
|
||||
team for team in returned_responses if team.organization_id is None
|
||||
]
|
||||
else:
|
||||
returned_responses = [
|
||||
team
|
||||
for team in returned_responses
|
||||
if team.organization_id == organization_id
|
||||
]
|
||||
|
||||
return returned_responses
|
||||
|
||||
|
||||
|
|
|
@ -56,6 +56,7 @@ model LiteLLM_OrganizationTable {
|
|||
litellm_budget_table LiteLLM_BudgetTable? @relation(fields: [budget_id], references: [budget_id])
|
||||
teams LiteLLM_TeamTable[]
|
||||
users LiteLLM_UserTable[]
|
||||
keys LiteLLM_VerificationToken[]
|
||||
members LiteLLM_OrganizationMembership[] @relation("OrganizationToMembership")
|
||||
}
|
||||
|
||||
|
@ -158,9 +159,11 @@ model LiteLLM_VerificationToken {
|
|||
model_spend Json @default("{}")
|
||||
model_max_budget Json @default("{}")
|
||||
budget_id String?
|
||||
organization_id String?
|
||||
created_at DateTime? @default(now()) @map("created_at")
|
||||
updated_at DateTime? @default(now()) @updatedAt @map("updated_at")
|
||||
litellm_budget_table LiteLLM_BudgetTable? @relation(fields: [budget_id], references: [budget_id])
|
||||
litellm_organization_table LiteLLM_OrganizationTable? @relation(fields: [organization_id], references: [organization_id])
|
||||
}
|
||||
|
||||
model LiteLLM_EndUserTable {
|
||||
|
|
|
@ -56,6 +56,7 @@ model LiteLLM_OrganizationTable {
|
|||
litellm_budget_table LiteLLM_BudgetTable? @relation(fields: [budget_id], references: [budget_id])
|
||||
teams LiteLLM_TeamTable[]
|
||||
users LiteLLM_UserTable[]
|
||||
keys LiteLLM_VerificationToken[]
|
||||
members LiteLLM_OrganizationMembership[] @relation("OrganizationToMembership")
|
||||
}
|
||||
|
||||
|
@ -158,9 +159,11 @@ model LiteLLM_VerificationToken {
|
|||
model_spend Json @default("{}")
|
||||
model_max_budget Json @default("{}")
|
||||
budget_id String?
|
||||
organization_id String?
|
||||
created_at DateTime? @default(now()) @map("created_at")
|
||||
updated_at DateTime? @default(now()) @updatedAt @map("updated_at")
|
||||
litellm_budget_table LiteLLM_BudgetTable? @relation(fields: [budget_id], references: [budget_id])
|
||||
litellm_organization_table LiteLLM_OrganizationTable? @relation(fields: [organization_id], references: [organization_id])
|
||||
}
|
||||
|
||||
model LiteLLM_EndUserTable {
|
||||
|
|
|
@ -183,6 +183,7 @@ export default function CreateKeyPage() {
|
|||
}
|
||||
}
|
||||
}
|
||||
setTeams(null);
|
||||
}
|
||||
|
||||
return (
|
||||
|
@ -201,6 +202,7 @@ export default function CreateKeyPage() {
|
|||
setTeams={setTeams}
|
||||
setKeys={setKeys}
|
||||
setOrganizations={setOrganizations}
|
||||
currentOrg={currentOrg}
|
||||
/>
|
||||
) : (
|
||||
<div className="flex flex-col min-h-screen">
|
||||
|
@ -237,6 +239,7 @@ export default function CreateKeyPage() {
|
|||
setTeams={setTeams}
|
||||
setKeys={setKeys}
|
||||
setOrganizations={setOrganizations}
|
||||
currentOrg={currentOrg}
|
||||
/>
|
||||
) : page == "models" ? (
|
||||
<ModelDashboard
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
import { teamListCall, DEFAULT_ORGANIZATION, Organization } from "../networking";
|
||||
|
||||
export const fetchTeams = async (accessToken: string, userID: string | null, userRole: string | null, currentOrg: Organization | null, setTeams: (teams: any[]) => void) => {
|
||||
let givenTeams;
|
||||
if (userRole != "Admin" && userRole != "Admin Viewer") {
|
||||
givenTeams = await teamListCall(accessToken, currentOrg?.organization_id || DEFAULT_ORGANIZATION, userID)
|
||||
} else {
|
||||
givenTeams = await teamListCall(accessToken, currentOrg?.organization_id || DEFAULT_ORGANIZATION)
|
||||
}
|
||||
|
||||
console.log(`givenTeams: ${givenTeams}`)
|
||||
|
||||
setTeams(givenTeams)
|
||||
}
|
|
@ -44,7 +44,6 @@ export const fetchAvailableModelsForTeamOrKey = async (
|
|||
};
|
||||
|
||||
export const getModelDisplayName = (model: string) => {
|
||||
console.log("getModelDisplayName", model);
|
||||
if (model.endsWith('/*')) {
|
||||
const provider = model.replace('/*', '');
|
||||
return `All ${provider} models`;
|
||||
|
|
|
@ -59,46 +59,45 @@ const Navbar: React.FC<NavbarProps> = ({
|
|||
];
|
||||
|
||||
const orgMenuItems: MenuProps["items"] = [
|
||||
...(userRole === "Admin"
|
||||
? [{
|
||||
key: 'global',
|
||||
label: (
|
||||
<div className="flex items-center justify-between py-1">
|
||||
<span className="text-sm">Global View</span>
|
||||
</div>
|
||||
),
|
||||
onClick: () => onOrgChange({ organization_id: "global", organization_alias: "Global View" } as Organization)
|
||||
}]
|
||||
: [
|
||||
{
|
||||
key: 'header',
|
||||
label: 'Organizations',
|
||||
type: 'group' as const,
|
||||
style: {
|
||||
color: '#6B7280',
|
||||
fontSize: '0.875rem'
|
||||
}
|
||||
},
|
||||
...organizations.map(org => ({
|
||||
key: org.organization_id,
|
||||
label: (
|
||||
<div className="flex items-center justify-between py-1">
|
||||
<span className="text-sm">{org.organization_alias}</span>
|
||||
</div>
|
||||
),
|
||||
onClick: () => onOrgChange(org)
|
||||
})),
|
||||
{
|
||||
key: "note",
|
||||
label: (
|
||||
<div className="flex items-center justify-between py-1 px-2 bg-gray-50 text-gray-500 text-xs italic">
|
||||
<span>Switching between organizations on the UI is currently in beta.</span>
|
||||
</div>
|
||||
),
|
||||
disabled: true
|
||||
}
|
||||
]
|
||||
)
|
||||
{
|
||||
key: 'header',
|
||||
label: 'Organizations',
|
||||
type: 'group' as const,
|
||||
style: {
|
||||
color: '#6B7280',
|
||||
fontSize: '0.875rem'
|
||||
}
|
||||
},
|
||||
{
|
||||
key: "default",
|
||||
label: (
|
||||
<div className="flex items-center justify-between py-1">
|
||||
<span className="text-sm">Default Organization</span>
|
||||
</div>
|
||||
),
|
||||
onClick: () => onOrgChange({
|
||||
organization_id: null,
|
||||
organization_alias: "Default Organization"
|
||||
} as Organization)
|
||||
},
|
||||
...organizations.filter(org => org.organization_id !== null).map(org => ({
|
||||
key: org.organization_id ?? "default",
|
||||
label: (
|
||||
<div className="flex items-center justify-between py-1">
|
||||
<span className="text-sm">{org.organization_alias}</span>
|
||||
</div>
|
||||
),
|
||||
onClick: () => onOrgChange(org)
|
||||
})),
|
||||
{
|
||||
key: "note",
|
||||
label: (
|
||||
<div className="flex items-center justify-between py-1 px-2 bg-gray-50 text-gray-500 text-xs italic">
|
||||
<span>Switching between organizations on the UI is currently in beta.</span>
|
||||
</div>
|
||||
),
|
||||
disabled: true
|
||||
}
|
||||
];
|
||||
|
||||
return (
|
||||
|
|
|
@ -9,6 +9,8 @@ if (isLocal != true) {
|
|||
console.log = function() {};
|
||||
}
|
||||
|
||||
export const DEFAULT_ORGANIZATION = "default_organization";
|
||||
|
||||
export interface Model {
|
||||
model_name: string;
|
||||
litellm_params: Object;
|
||||
|
@ -16,7 +18,7 @@ export interface Model {
|
|||
}
|
||||
|
||||
export interface Organization {
|
||||
organization_id: string;
|
||||
organization_id: string | null;
|
||||
organization_alias: string;
|
||||
budget_id: string;
|
||||
metadata: Record<string, any>;
|
||||
|
@ -724,7 +726,8 @@ export const teamInfoCall = async (
|
|||
|
||||
export const teamListCall = async (
|
||||
accessToken: String,
|
||||
userID: String | null = null
|
||||
organizationID: string | null,
|
||||
userID: String | null = null,
|
||||
) => {
|
||||
/**
|
||||
* Get all available teams on proxy
|
||||
|
@ -732,9 +735,21 @@ export const teamListCall = async (
|
|||
try {
|
||||
let url = proxyBaseUrl ? `${proxyBaseUrl}/team/list` : `/team/list`;
|
||||
console.log("in teamInfoCall");
|
||||
const queryParams = new URLSearchParams();
|
||||
|
||||
if (userID) {
|
||||
url += `?user_id=${userID}`;
|
||||
queryParams.append('user_id', userID.toString());
|
||||
}
|
||||
|
||||
if (organizationID) {
|
||||
queryParams.append('organization_id', organizationID.toString());
|
||||
}
|
||||
|
||||
const queryString = queryParams.toString();
|
||||
if (queryString) {
|
||||
url += `?${queryString}`;
|
||||
}
|
||||
|
||||
const response = await fetch(url, {
|
||||
method: "GET",
|
||||
headers: {
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
import React, { useState, useEffect } from "react";
|
||||
import Link from "next/link";
|
||||
import { Typography } from "antd";
|
||||
import { teamDeleteCall, teamUpdateCall, teamInfoCall, Organization } from "./networking";
|
||||
import { teamDeleteCall, teamUpdateCall, teamInfoCall, Organization, DEFAULT_ORGANIZATION } from "./networking";
|
||||
import TeamMemberModal from "@/components/team/edit_membership";
|
||||
import { fetchTeams } from "./common_components/fetch_teams";
|
||||
import {
|
||||
InformationCircleIcon,
|
||||
PencilAltIcon,
|
||||
|
@ -95,23 +96,12 @@ const Team: React.FC<TeamProps> = ({
|
|||
}) => {
|
||||
const [lastRefreshed, setLastRefreshed] = useState("");
|
||||
|
||||
const fetchTeams = async (accessToken: string, userID: string | null, userRole: string | null) => {
|
||||
let givenTeams;
|
||||
if (userRole != "Admin" && userRole != "Admin Viewer") {
|
||||
givenTeams = await teamListCall(accessToken, userID)
|
||||
} else {
|
||||
givenTeams = await teamListCall(accessToken)
|
||||
}
|
||||
|
||||
console.log(`givenTeams: ${givenTeams}`)
|
||||
|
||||
setTeams(givenTeams)
|
||||
}
|
||||
useEffect(() => {
|
||||
console.log(`inside useeffect - ${teams}`)
|
||||
if (teams === null && accessToken) {
|
||||
// Call your function here
|
||||
fetchTeams(accessToken, userID, userRole)
|
||||
fetchTeams(accessToken, userID, userRole, currentOrg, setTeams)
|
||||
}
|
||||
}, [teams]);
|
||||
|
||||
|
@ -119,7 +109,7 @@ const Team: React.FC<TeamProps> = ({
|
|||
console.log(`inside useeffect - ${lastRefreshed}`)
|
||||
if (accessToken) {
|
||||
// Call your function here
|
||||
fetchTeams(accessToken, userID, userRole)
|
||||
fetchTeams(accessToken, userID, userRole, currentOrg, setTeams)
|
||||
}
|
||||
handleRefreshClick()
|
||||
}, [lastRefreshed]);
|
||||
|
@ -257,9 +247,9 @@ const Team: React.FC<TeamProps> = ({
|
|||
let _team_id_to_info: Record<string, any> = {};
|
||||
let teamList;
|
||||
if (userRole != "Admin" && userRole != "Admin Viewer") {
|
||||
teamList = await teamListCall(accessToken, userID)
|
||||
teamList = await teamListCall(accessToken, currentOrg?.organization_id || DEFAULT_ORGANIZATION, userID)
|
||||
} else {
|
||||
teamList = await teamListCall(accessToken)
|
||||
teamList = await teamListCall(accessToken, currentOrg?.organization_id || DEFAULT_ORGANIZATION)
|
||||
}
|
||||
|
||||
for (let i = 0; i < teamList.length; i++) {
|
||||
|
@ -405,6 +395,10 @@ const Team: React.FC<TeamProps> = ({
|
|||
<TableBody>
|
||||
{teams && teams.length > 0
|
||||
? teams
|
||||
.filter((team) => {
|
||||
const targetOrgId = currentOrg ? currentOrg.organization_id : null;
|
||||
return team.organization_id === targetOrgId;
|
||||
})
|
||||
.sort((a, b) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime())
|
||||
.map((team: any) => (
|
||||
<TableRow key={team.team_id}>
|
||||
|
|
|
@ -7,8 +7,10 @@ import {
|
|||
getProxyUISettings,
|
||||
teamListCall,
|
||||
Organization,
|
||||
organizationListCall
|
||||
organizationListCall,
|
||||
DEFAULT_ORGANIZATION
|
||||
} from "./networking";
|
||||
import { fetchTeams } from "./common_components/fetch_teams";
|
||||
import { Grid, Col, Card, Text, Title } from "@tremor/react";
|
||||
import CreateKey from "./create_key_button";
|
||||
import ViewKeyTable from "./view_key_table";
|
||||
|
@ -62,6 +64,7 @@ interface UserDashboardProps {
|
|||
setKeys: React.Dispatch<React.SetStateAction<Object[] | null>>;
|
||||
setOrganizations: React.Dispatch<React.SetStateAction<Organization[]>>;
|
||||
premiumUser: boolean;
|
||||
currentOrg: Organization | null;
|
||||
}
|
||||
|
||||
type TeamInterface = {
|
||||
|
@ -82,15 +85,15 @@ const UserDashboard: React.FC<UserDashboardProps> = ({
|
|||
setKeys,
|
||||
setOrganizations,
|
||||
premiumUser,
|
||||
currentOrg
|
||||
}) => {
|
||||
console.log(`currentOrg in user dashboard: ${JSON.stringify(currentOrg)}`)
|
||||
const [userSpendData, setUserSpendData] = useState<UserInfo | null>(
|
||||
null
|
||||
);
|
||||
|
||||
// Assuming useSearchParams() hook exists and works in your setup
|
||||
const searchParams = useSearchParams()!;
|
||||
const viewSpend = searchParams.get("viewSpend");
|
||||
const router = useRouter();
|
||||
|
||||
const token = getCookie('token');
|
||||
|
||||
|
@ -173,22 +176,11 @@ const UserDashboard: React.FC<UserDashboardProps> = ({
|
|||
}
|
||||
}
|
||||
if (userID && accessToken && userRole && !keys && !userSpendData) {
|
||||
// const cachedUserModels = sessionStorage.getItem("userModels" + userID);
|
||||
// if (cachedUserModels) {
|
||||
// setUserModels(JSON.parse(cachedUserModels));
|
||||
// } else {
|
||||
const fetchTeams = async () => {
|
||||
let givenTeams;
|
||||
if (userRole != "Admin" && userRole != "Admin Viewer") {
|
||||
givenTeams = await teamListCall(accessToken, userID)
|
||||
} else {
|
||||
givenTeams = await teamListCall(accessToken)
|
||||
}
|
||||
|
||||
console.log(`givenTeams: ${givenTeams}`)
|
||||
|
||||
setTeams(givenTeams)
|
||||
}
|
||||
const cachedUserModels = sessionStorage.getItem("userModels" + userID);
|
||||
if (cachedUserModels) {
|
||||
setUserModels(JSON.parse(cachedUserModels));
|
||||
} else {
|
||||
console.log(`currentOrg: ${JSON.stringify(currentOrg)}`)
|
||||
const fetchData = async () => {
|
||||
try {
|
||||
const proxy_settings: ProxySettings = await getProxyUISettings(accessToken);
|
||||
|
@ -205,12 +197,10 @@ const UserDashboard: React.FC<UserDashboardProps> = ({
|
|||
|
||||
setUserSpendData(response["user_info"]);
|
||||
console.log(`userSpendData: ${JSON.stringify(userSpendData)}`)
|
||||
|
||||
console.log(`response["user_info"]["organization_memberships"]: ${JSON.stringify(response["user_info"]["organization_memberships"])}`)
|
||||
|
||||
|
||||
// set keys for admin and users
|
||||
if (!response?.teams?.[0]?.keys) {
|
||||
if (!response?.teams[0].keys) {
|
||||
setKeys(response["keys"]);
|
||||
} else {
|
||||
setKeys(
|
||||
|
@ -270,12 +260,20 @@ const UserDashboard: React.FC<UserDashboardProps> = ({
|
|||
setOrganizations(organizations);
|
||||
}
|
||||
fetchData();
|
||||
fetchTeams();
|
||||
fetchTeams(accessToken, userID, userRole, currentOrg, setTeams);
|
||||
fetchOrganizations();
|
||||
}
|
||||
// }
|
||||
}
|
||||
}, [userID, token, accessToken, keys, userRole]);
|
||||
|
||||
useEffect(() => {
|
||||
console.log(`currentOrg: ${JSON.stringify(currentOrg)}, accessToken: ${accessToken}, userID: ${userID}, userRole: ${userRole}`)
|
||||
if (accessToken) {
|
||||
console.log(`fetching teams`)
|
||||
fetchTeams(accessToken, userID, userRole, currentOrg, setTeams);
|
||||
}
|
||||
}, [currentOrg]);
|
||||
|
||||
useEffect(() => {
|
||||
// This code will run every time selectedTeam changes
|
||||
if (
|
||||
|
@ -375,6 +373,7 @@ const UserDashboard: React.FC<UserDashboardProps> = ({
|
|||
setData={setKeys}
|
||||
premiumUser={premiumUser}
|
||||
teams={teams}
|
||||
currentOrg={currentOrg}
|
||||
/>
|
||||
<CreateKey
|
||||
key={selectedTeam ? selectedTeam.team_id : null}
|
||||
|
|
|
@ -4,6 +4,7 @@ import {
|
|||
keyDeleteCall,
|
||||
modelAvailableCall,
|
||||
getGuardrailsList,
|
||||
Organization,
|
||||
} from "./networking";
|
||||
import { add } from "date-fns";
|
||||
import {
|
||||
|
@ -101,6 +102,7 @@ interface ViewKeyTableProps {
|
|||
setData: React.Dispatch<React.SetStateAction<any[] | null>>;
|
||||
teams: any[] | null;
|
||||
premiumUser: boolean;
|
||||
currentOrg: Organization | null;
|
||||
}
|
||||
|
||||
interface ItemData {
|
||||
|
@ -132,6 +134,7 @@ const ViewKeyTable: React.FC<ViewKeyTableProps> = ({
|
|||
setData,
|
||||
teams,
|
||||
premiumUser,
|
||||
currentOrg
|
||||
}) => {
|
||||
const [isButtonClicked, setIsButtonClicked] = useState(false);
|
||||
const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
|
||||
|
@ -167,55 +170,64 @@ const ViewKeyTable: React.FC<ViewKeyTableProps> = ({
|
|||
|
||||
// Combine all keys that user should have access to
|
||||
const all_keys_to_display = React.useMemo(() => {
|
||||
if (!data) return [];
|
||||
|
||||
// Helper function for default team org check
|
||||
const matchesDefaultTeamOrg = (key: any) => {
|
||||
console.log(`Checking if key matches default team org: ${JSON.stringify(key)}, currentOrg: ${JSON.stringify(currentOrg)}`)
|
||||
if (!currentOrg || currentOrg.organization_id === null) {
|
||||
return !('organization_id' in key) || key.organization_id === null;
|
||||
}
|
||||
return key.organization_id === currentOrg.organization_id;
|
||||
};
|
||||
|
||||
let allKeys: any[] = [];
|
||||
|
||||
// If no teams, return personal keys
|
||||
if (!teams || teams.length === 0) {
|
||||
return data;
|
||||
}
|
||||
// Handle no team selected or Default Team case
|
||||
if (!selectedTeam || selectedTeam.team_alias === "Default Team") {
|
||||
|
||||
teams.forEach((team) => {
|
||||
// For default team or when user is not admin, use personal keys (data)
|
||||
if (team.team_id === "default-team" || !isUserTeamAdmin(team)) {
|
||||
if (selectedTeam && selectedTeam.team_id === team.team_id && data) {
|
||||
allKeys = [
|
||||
...allKeys,
|
||||
...data.filter((key) => key.team_id === team.team_id),
|
||||
];
|
||||
}
|
||||
}
|
||||
// For teams where user is admin, use team keys
|
||||
else if (isUserTeamAdmin(team)) {
|
||||
if (selectedTeam && selectedTeam.team_id === team.team_id) {
|
||||
allKeys = [
|
||||
...allKeys,
|
||||
...(data?.filter((key) => key?.team_id === team?.team_id) || []),
|
||||
];
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// If no team is selected, show all accessible keys
|
||||
if ((!selectedTeam || selectedTeam.team_alias === "Default Team") && data) {
|
||||
const personalKeys = data.filter(
|
||||
(key) => !key.team_id || key.team_id === "default-team"
|
||||
console.log(`inside personal keys`)
|
||||
// Get personal keys (with org check)
|
||||
const personalKeys = data.filter(key =>
|
||||
key.team_id == null &&
|
||||
matchesDefaultTeamOrg(key)
|
||||
);
|
||||
const adminTeamKeys = teams
|
||||
.filter((team) => isUserTeamAdmin(team))
|
||||
.flatMap((team) => team.keys || []);
|
||||
|
||||
console.log(`personalKeys: ${JSON.stringify(personalKeys)}`)
|
||||
|
||||
// Get admin team keys (no org check)
|
||||
const adminTeamKeys = data.filter(key => {
|
||||
const keyTeam = teams?.find(team => team.team_id === key.team_id);
|
||||
return keyTeam && isUserTeamAdmin(keyTeam) && key.team_id !== "default-team";
|
||||
});
|
||||
|
||||
console.log(`adminTeamKeys: ${JSON.stringify(adminTeamKeys)}`)
|
||||
|
||||
allKeys = [...personalKeys, ...adminTeamKeys];
|
||||
}
|
||||
// Handle specific team selected
|
||||
else {
|
||||
const selectedTeamData = teams?.find(t => t.team_id === selectedTeam.team_id);
|
||||
if (selectedTeamData) {
|
||||
const teamKeys = data.filter(key => {
|
||||
if (selectedTeamData.team_id === "default-team") {
|
||||
return key.team_id == null && matchesDefaultTeamOrg(key);
|
||||
}
|
||||
return key.team_id === selectedTeamData.team_id;
|
||||
});
|
||||
allKeys = teamKeys;
|
||||
}
|
||||
}
|
||||
|
||||
// Filter out litellm-dashboard keys
|
||||
allKeys = allKeys.filter((key) => key.team_id !== "litellm-dashboard");
|
||||
|
||||
// Remove duplicates based on token
|
||||
const uniqueKeys = Array.from(
|
||||
new Map(allKeys.map((key) => [key.token, key])).values()
|
||||
// Final filtering and deduplication
|
||||
return Array.from(
|
||||
new Map(
|
||||
allKeys
|
||||
.filter(key => key.team_id !== "litellm-dashboard")
|
||||
.map(key => [key.token, key])
|
||||
).values()
|
||||
);
|
||||
|
||||
return uniqueKeys;
|
||||
}, [data, teams, selectedTeam, userID]);
|
||||
}, [data, teams, selectedTeam, currentOrg]);
|
||||
|
||||
useEffect(() => {
|
||||
const calculateNewExpiryTime = (duration: string | undefined) => {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue