Skip to content

Commit d4e5da8

Browse files
authored
Reflect key and team update in UI (BerriAI#9825)
* Reflect updates to keys in UI instantly * Reflect updates to teams in UI instantly
1 parent cc7d59a commit d4e5da8

9 files changed

Lines changed: 76 additions & 5 deletions

File tree

ui/litellm-dashboard/src/components/all_keys_table.tsx

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,12 @@ import { Organization, userListCall } from "./networking";
1313
import { createTeamSearchFunction } from "./key_team_helpers/team_search_fn";
1414
import { createOrgSearchFunction } from "./key_team_helpers/organization_search_fn";
1515
import { useFilterLogic } from "./key_team_helpers/filter_logic";
16+
import { Setter } from "@/types";
17+
import { updateExistingKeys } from "@/utils/dataUtils";
1618

1719
interface AllKeysTableProps {
1820
keys: KeyResponse[];
21+
setKeys: Setter<KeyResponse[]>;
1922
isLoading?: boolean;
2023
pagination: {
2124
currentPage: number;
@@ -87,6 +90,7 @@ const TeamFilter = ({
8790
*/
8891
export function AllKeysTable({
8992
keys,
93+
setKeys,
9094
isLoading = false,
9195
pagination,
9296
onPageChange,
@@ -364,6 +368,23 @@ export function AllKeysTable({
364368
keyId={selectedKeyId}
365369
onClose={() => setSelectedKeyId(null)}
366370
keyData={keys.find(k => k.token === selectedKeyId)}
371+
onKeyDataUpdate={(updatedKeyData) => {
372+
setKeys(keys => keys.map(key => {
373+
if (key.token === updatedKeyData.token) {
374+
// The shape of key is different from that of
375+
// updatedKeyData(received from keyUpdateCall in networking.tsx).
376+
// Hence, we can't replace key with updatedKeys since it might lead
377+
// to unintended bugs/behaviors.
378+
// So instead, we only update fields that are present in both.
379+
return updateExistingKeys(key, updatedKeyData)
380+
}
381+
382+
return key
383+
}))
384+
}}
385+
onDelete={() => {
386+
setKeys(keys => keys.filter(key => key.token !== selectedKeyId))
387+
}}
367388
accessToken={accessToken}
368389
userID={userID}
369390
userRole={userRole}

ui/litellm-dashboard/src/components/key_info_view.tsx

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,13 +27,15 @@ interface KeyInfoViewProps {
2727
keyId: string;
2828
onClose: () => void;
2929
keyData: KeyResponse | undefined;
30+
onKeyDataUpdate?: (data: Partial<KeyResponse>) => void;
31+
onDelete?: () => void;
3032
accessToken: string | null;
3133
userID: string | null;
3234
userRole: string | null;
3335
teams: any[] | null;
3436
}
3537

36-
export default function KeyInfoView({ keyId, onClose, keyData, accessToken, userID, userRole, teams }: KeyInfoViewProps) {
38+
export default function KeyInfoView({ keyId, onClose, keyData, accessToken, userID, userRole, teams, onKeyDataUpdate, onDelete }: KeyInfoViewProps) {
3739
const [isEditing, setIsEditing] = useState(false);
3840
const [form] = Form.useForm();
3941
const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
@@ -93,6 +95,9 @@ export default function KeyInfoView({ keyId, onClose, keyData, accessToken, user
9395
}
9496

9597
const newKeyValues = await keyUpdateCall(accessToken, formValues);
98+
if (onKeyDataUpdate) {
99+
onKeyDataUpdate(newKeyValues)
100+
}
96101
message.success("Key updated successfully");
97102
setIsEditing(false);
98103
// Refresh key data here if needed
@@ -107,6 +112,9 @@ export default function KeyInfoView({ keyId, onClose, keyData, accessToken, user
107112
if (!accessToken) return;
108113
await keyDeleteCall(accessToken as string, keyData.token);
109114
message.success("Key deleted successfully");
115+
if (onDelete) {
116+
onDelete()
117+
}
110118
onClose();
111119
} catch (error) {
112120
console.error("Error deleting the key:", error);

ui/litellm-dashboard/src/components/key_team_helpers/key_list.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { useState, useEffect } from 'react';
22
import { keyListCall, Organization } from '../networking';
3+
import { Setter } from '@/types';
34

45
export interface Team {
56
team_id: string;
@@ -94,13 +95,14 @@ totalPages: number;
9495
totalCount: number;
9596
}
9697

98+
9799
interface UseKeyListReturn {
98100
keys: KeyResponse[];
99101
isLoading: boolean;
100102
error: Error | null;
101103
pagination: PaginationData;
102104
refresh: (params?: Record<string, unknown>) => Promise<void>;
103-
setKeys: (newKeysOrUpdater: KeyResponse[] | ((prevKeys: KeyResponse[]) => KeyResponse[])) => void;
105+
setKeys: Setter<KeyResponse[]>;
104106
}
105107

106108
const useKeyList = ({

ui/litellm-dashboard/src/components/networking.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import { all_admin_roles } from "@/utils/roles";
55
import { message } from "antd";
66
import { TagNewRequest, TagUpdateRequest, TagDeleteRequest, TagInfoRequest, TagListResponse, TagInfoResponse } from "./tag_management/types";
7+
import { Team } from "./key_team_helpers/key_list";
78

89
const isLocal = process.env.NODE_ENV === "development";
910
export const proxyBaseUrl = isLocal ? "http://localhost:4000" : null;
@@ -2983,7 +2984,7 @@ export const teamUpdateCall = async (
29832984
console.error("Error response from the server:", errorData);
29842985
throw new Error("Network response was not ok");
29852986
}
2986-
const data = await response.json();
2987+
const data = await response.json() as { data: Team, team_id: string };
29872988
console.log("Update Team Response:", data);
29882989
return data;
29892990
// Handle success - you might want to update some state or UI based on the created key

ui/litellm-dashboard/src/components/team/team_info.tsx

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import { PencilAltIcon, PlusIcon, TrashIcon } from "@heroicons/react/outline";
3030
import MemberModal from "./edit_membership";
3131
import UserSearchModal from "@/components/common_components/user_search_modal";
3232
import { getModelDisplayName } from "../key_team_helpers/fetch_available_models_team_key";
33+
import { Team } from "../key_team_helpers/key_list";
3334

3435

3536
interface TeamData {
@@ -69,6 +70,7 @@ interface TeamInfoProps {
6970
is_proxy_admin: boolean;
7071
userModels: string[];
7172
editTeam: boolean;
73+
onUpdate?: (team: Team) => void
7274
}
7375

7476
const TeamInfoView: React.FC<TeamInfoProps> = ({
@@ -78,7 +80,8 @@ const TeamInfoView: React.FC<TeamInfoProps> = ({
7880
is_team_admin,
7981
is_proxy_admin,
8082
userModels,
81-
editTeam
83+
editTeam,
84+
onUpdate
8285
}) => {
8386
const [teamData, setTeamData] = useState<TeamData | null>(null);
8487
const [loading, setLoading] = useState(true);
@@ -199,7 +202,10 @@ const TeamInfoView: React.FC<TeamInfoProps> = ({
199202
};
200203

201204
const response = await teamUpdateCall(accessToken, updateData);
202-
205+
if (onUpdate) {
206+
onUpdate(response.data)
207+
}
208+
203209
message.success("Team settings updated successfully");
204210
setIsEditing(false);
205211
fetchTeamInfo();

ui/litellm-dashboard/src/components/teams.tsx

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ import {
8484
modelAvailableCall,
8585
teamListCall
8686
} from "./networking";
87+
import { updateExistingKeys } from "@/utils/dataUtils";
8788

8889
const getOrganizationModels = (organization: Organization | null, userModels: string[]) => {
8990
let tempModelsToPick = [];
@@ -321,6 +322,22 @@ const Teams: React.FC<TeamProps> = ({
321322
{selectedTeamId ? (
322323
<TeamInfoView
323324
teamId={selectedTeamId}
325+
onUpdate={data => {
326+
setTeams(teams => {
327+
if (teams == null) {
328+
return teams;
329+
}
330+
331+
return teams.map(team => {
332+
if (data.team_id === team.team_id) {
333+
return updateExistingKeys(team, data)
334+
}
335+
336+
return team
337+
})
338+
})
339+
340+
}}
324341
onClose={() => {
325342
setSelectedTeamId(null);
326343
setEditTeam(false);

ui/litellm-dashboard/src/components/view_key_table.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -418,6 +418,7 @@ const ViewKeyTable: React.FC<ViewKeyTableProps> = ({
418418
<div>
419419
<AllKeysTable
420420
keys={keys}
421+
setKeys={setKeys}
421422
isLoading={isLoading}
422423
pagination={pagination}
423424
onPageChange={handlePageChange}

ui/litellm-dashboard/src/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export type Setter<T> = (newValueOrUpdater: T | ((previousValue: T) => T)) => void
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
export function updateExistingKeys<Source extends Object>(
2+
target: Source,
3+
source: Object
4+
): Source {
5+
const clonedTarget = structuredClone(target);
6+
7+
for (const [key, value] of Object.entries(source)) {
8+
if (key in clonedTarget) {
9+
(clonedTarget as any)[key] = value;
10+
}
11+
}
12+
13+
return clonedTarget;
14+
}

0 commit comments

Comments
 (0)