From b6d155108e81a6f6f672aa2acb452d18ce3d061f Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Thu, 13 Mar 2025 18:54:05 -0700 Subject: [PATCH 01/12] working hover --- .../src/components/top_key_view.tsx | 76 +++++++++++++++++++ ui/litellm-dashboard/src/components/usage.tsx | 18 ++--- 2 files changed, 82 insertions(+), 12 deletions(-) create mode 100644 ui/litellm-dashboard/src/components/top_key_view.tsx diff --git a/ui/litellm-dashboard/src/components/top_key_view.tsx b/ui/litellm-dashboard/src/components/top_key_view.tsx new file mode 100644 index 00000000000..97a0c8ead6f --- /dev/null +++ b/ui/litellm-dashboard/src/components/top_key_view.tsx @@ -0,0 +1,76 @@ +import React, { useState } from "react"; +import { BarChart } from "@tremor/react"; +import KeyInfoView from "./key_info_view"; +import { keyInfoCall } from "./networking"; + +interface TopKeyViewProps { + topKeys: any[]; + accessToken: string | null; + userID: string | null; + userRole: string | null; + teams: any[] | null; +} + +const TopKeyView: React.FC = ({ + topKeys, + accessToken, + userID, + userRole, + teams +}) => { + const [selectedKey, setSelectedKey] = useState(null); + const [keyData, setKeyData] = useState(undefined); + + const handleKeyClick = async (item: any) => { + if (!accessToken || !item.key) return; + + try { + // Get the full key data using the API key + const keyInfo = await keyInfoCall(accessToken, [item.key]); + if (keyInfo && keyInfo.keys && keyInfo.keys.length > 0) { + setKeyData(keyInfo.keys[0]); + setSelectedKey(item.key); + } + } catch (error) { + console.error("Error fetching key info:", error); + } + }; + + const handleClose = () => { + setSelectedKey(null); + setKeyData(undefined); + }; + + if (selectedKey && keyData) { + return ( + + ); + } + + return ( + `$${value.toFixed(2)}`} + onValueChange={(item) => handleKeyClick(item)} + /> + ); +}; + +export default TopKeyView; diff --git a/ui/litellm-dashboard/src/components/usage.tsx b/ui/litellm-dashboard/src/components/usage.tsx index 053f69c256f..d95f8bd9e16 100644 --- a/ui/litellm-dashboard/src/components/usage.tsx +++ b/ui/litellm-dashboard/src/components/usage.tsx @@ -39,6 +39,7 @@ import { getProxyUISettings } from "./networking"; import { start } from "repl"; +import TopKeyView from "./top_key_view"; console.log("process.env.NODE_ENV", process.env.NODE_ENV); const isLocal = process.env.NODE_ENV === "development"; const proxyBaseUrl = isLocal ? "http://localhost:4000" : null; @@ -656,18 +657,11 @@ const UsagePage: React.FC = ({ Top API Keys - `$${value.toFixed(2)}`} + From 33cf03722d9663b8428f44a52447b4034c195f31 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Thu, 13 Mar 2025 19:16:46 -0700 Subject: [PATCH 02/12] keyInfoV1Call --- .../src/components/networking.tsx | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/ui/litellm-dashboard/src/components/networking.tsx b/ui/litellm-dashboard/src/components/networking.tsx index 46099906ac3..19589400a0e 100644 --- a/ui/litellm-dashboard/src/components/networking.tsx +++ b/ui/litellm-dashboard/src/components/networking.tsx @@ -2273,6 +2273,37 @@ export const keyInfoCall = async (accessToken: String, keys: String[]) => { } }; + +export const keyInfoV1Call = async (accessToken: string, key: string) => { + try { + let url = proxyBaseUrl ? `${proxyBaseUrl}/key/info` : `/key/info`; + url = `${url}?key=${key}`; // Add key as query parameter + + const response = await fetch(url, { + method: "GET", + headers: { + [globalLitellmHeaderName]: `Bearer ${accessToken}`, + "Content-Type": "application/json", + }, + // Remove body since this is a GET request + }); + + if (!response.ok) { + const errorData = await response.text(); + handleError(errorData); + throw new Error("Network response was not ok"); + } + + const data = await response.json(); + return data; + } catch (error) { + console.error("Failed to fetch key info:", error); + throw error; + } +}; + + + export const keyListCall = async ( accessToken: String, organizationID: string | null, From 063aedc6c675d8e3840064216886d2c4baf60c35 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Thu, 13 Mar 2025 19:16:56 -0700 Subject: [PATCH 03/12] fix api_key --- ui/litellm-dashboard/src/components/usage.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/ui/litellm-dashboard/src/components/usage.tsx b/ui/litellm-dashboard/src/components/usage.tsx index d95f8bd9e16..b351553d3dc 100644 --- a/ui/litellm-dashboard/src/components/usage.tsx +++ b/ui/litellm-dashboard/src/components/usage.tsx @@ -401,6 +401,7 @@ const UsagePage: React.FC = ({ const top_keys = await adminTopKeysCall(accessToken); return top_keys.map((k: any) => ({ key: (k["key_alias"] || k["key_name"] || k["api_key"]).substring(0, 10), + api_key: k["api_key"], spend: Number(k["total_spend"].toFixed(2)), })); }, From 2c4dd2994445d3bac715cbe9ac374717a271c5bd Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Thu, 13 Mar 2025 19:17:14 -0700 Subject: [PATCH 04/12] allow clicking into keys --- .../key_team_helpers/transform_key_info.tsx | 28 +++++++++++++++++++ .../src/components/top_key_view.tsx | 17 +++++------ 2 files changed, 37 insertions(+), 8 deletions(-) create mode 100644 ui/litellm-dashboard/src/components/key_team_helpers/transform_key_info.tsx diff --git a/ui/litellm-dashboard/src/components/key_team_helpers/transform_key_info.tsx b/ui/litellm-dashboard/src/components/key_team_helpers/transform_key_info.tsx new file mode 100644 index 00000000000..49e248e2bc4 --- /dev/null +++ b/ui/litellm-dashboard/src/components/key_team_helpers/transform_key_info.tsx @@ -0,0 +1,28 @@ +import { KeyResponse } from "./key_list"; + +export const transformKeyInfo = (apiResponse: any): KeyResponse => { + const { key, info } = apiResponse; + + return { + token: key, + key_name: info.key_name, + key_alias: info.key_alias, + spend: info.spend, + expires: info.expires, + models: info.models, + aliases: info.aliases, + config: info.config, + user_id: info.user_id, + team_id: info.team_id, + permissions: info.permissions, + max_parallel_requests: info.max_parallel_requests, + metadata: info.metadata, + tpm_limit: info.tpm_limit, + rpm_limit: info.rpm_limit, + max_budget: info.max_budget, + budget_duration: info.budget_duration, + organization_id: info.organization_id, + created_at: info.created_at, + litellm_budget_table: info.litellm_budget_table, + }; +}; \ No newline at end of file diff --git a/ui/litellm-dashboard/src/components/top_key_view.tsx b/ui/litellm-dashboard/src/components/top_key_view.tsx index 97a0c8ead6f..b031ef1fa3e 100644 --- a/ui/litellm-dashboard/src/components/top_key_view.tsx +++ b/ui/litellm-dashboard/src/components/top_key_view.tsx @@ -1,7 +1,8 @@ import React, { useState } from "react"; import { BarChart } from "@tremor/react"; import KeyInfoView from "./key_info_view"; -import { keyInfoCall } from "./networking"; +import { keyInfoV1Call } from "./networking"; +import { transformKeyInfo } from "../components/key_team_helpers/transform_key_info"; interface TopKeyViewProps { topKeys: any[]; @@ -22,15 +23,15 @@ const TopKeyView: React.FC = ({ const [keyData, setKeyData] = useState(undefined); const handleKeyClick = async (item: any) => { - if (!accessToken || !item.key) return; + if (!accessToken) return; + console.log("Clicked item:", item); try { - // Get the full key data using the API key - const keyInfo = await keyInfoCall(accessToken, [item.key]); - if (keyInfo && keyInfo.keys && keyInfo.keys.length > 0) { - setKeyData(keyInfo.keys[0]); - setSelectedKey(item.key); - } + // Use item.key instead of item.api_key + const keyInfo = await keyInfoV1Call(accessToken, item.api_key); + const transformedKeyData = transformKeyInfo(keyInfo); + setKeyData(transformedKeyData); + setSelectedKey(item.key); } catch (error) { console.error("Error fetching key info:", error); } From 2f7d118c5433efd9f29eb75f3bbdcbc66dc05621 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Thu, 13 Mar 2025 19:21:59 -0700 Subject: [PATCH 05/12] working modal --- .../src/components/key_page_wrapper.tsx | 50 ++++++++++++++ .../src/components/top_key_view.tsx | 65 ++++++++++--------- 2 files changed, 85 insertions(+), 30 deletions(-) create mode 100644 ui/litellm-dashboard/src/components/key_page_wrapper.tsx diff --git a/ui/litellm-dashboard/src/components/key_page_wrapper.tsx b/ui/litellm-dashboard/src/components/key_page_wrapper.tsx new file mode 100644 index 00000000000..5870e00fe73 --- /dev/null +++ b/ui/litellm-dashboard/src/components/key_page_wrapper.tsx @@ -0,0 +1,50 @@ +import React, { useState } from "react"; +import TopKeyView from "./top_key_view"; +import KeyInfoView from "./key_info_view"; +import { KeyResponse } from "./key_team_helpers/key_list"; + +interface KeyPageWrapperProps { + topKeys: any[]; + accessToken: string | null; + userID: string | null; + userRole: string | null; + teams: any[] | null; +} + +const KeyPageWrapper: React.FC = ({ + topKeys, + accessToken, + userID, + userRole, + teams, +}) => { + const [selectedKeyData, setSelectedKeyData] = useState(null); + + if (selectedKeyData) { + return ( +
+ setSelectedKeyData(null)} + accessToken={accessToken} + userID={userID} + userRole={userRole} + teams={teams} + /> +
+ ); + } + + return ( + + ); +}; + +export default KeyPageWrapper; \ No newline at end of file diff --git a/ui/litellm-dashboard/src/components/top_key_view.tsx b/ui/litellm-dashboard/src/components/top_key_view.tsx index b031ef1fa3e..ab943a6cb10 100644 --- a/ui/litellm-dashboard/src/components/top_key_view.tsx +++ b/ui/litellm-dashboard/src/components/top_key_view.tsx @@ -19,58 +19,63 @@ const TopKeyView: React.FC = ({ userRole, teams }) => { + const [isModalOpen, setIsModalOpen] = useState(false); const [selectedKey, setSelectedKey] = useState(null); const [keyData, setKeyData] = useState(undefined); const handleKeyClick = async (item: any) => { if (!accessToken) return; - console.log("Clicked item:", item); try { - // Use item.key instead of item.api_key const keyInfo = await keyInfoV1Call(accessToken, item.api_key); const transformedKeyData = transformKeyInfo(keyInfo); setKeyData(transformedKeyData); setSelectedKey(item.key); + setIsModalOpen(true); // Open modal when key is clicked } catch (error) { console.error("Error fetching key info:", error); } }; const handleClose = () => { + setIsModalOpen(false); setSelectedKey(null); setKeyData(undefined); }; - if (selectedKey && keyData) { - return ( - - ); - } - return ( - `$${value.toFixed(2)}`} - onValueChange={(item) => handleKeyClick(item)} - /> + <> + `$${value.toFixed(2)}`} + onValueChange={(item) => handleKeyClick(item)} + /> + + {isModalOpen && selectedKey && keyData && ( +
+
+ +
+
+ )} + ); }; From 3e12b065ddd8a5ddec4ad0b621f0ee9a66aad0f8 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Thu, 13 Mar 2025 19:33:19 -0700 Subject: [PATCH 06/12] working modal view --- .../src/components/top_key_view.tsx | 58 +++++++++++++++---- 1 file changed, 47 insertions(+), 11 deletions(-) diff --git a/ui/litellm-dashboard/src/components/top_key_view.tsx b/ui/litellm-dashboard/src/components/top_key_view.tsx index ab943a6cb10..c88c308d7f1 100644 --- a/ui/litellm-dashboard/src/components/top_key_view.tsx +++ b/ui/litellm-dashboard/src/components/top_key_view.tsx @@ -43,6 +43,25 @@ const TopKeyView: React.FC = ({ setKeyData(undefined); }; + // Handle clicking outside the modal + const handleOutsideClick = (e: React.MouseEvent) => { + if (e.target === e.currentTarget) { + handleClose(); + } + }; + + // Handle escape key + React.useEffect(() => { + const handleEscapeKey = (e: KeyboardEvent) => { + if (e.key === 'Escape' && isModalOpen) { + handleClose(); + } + }; + + document.addEventListener('keydown', handleEscapeKey); + return () => document.removeEventListener('keydown', handleEscapeKey); + }, [isModalOpen]); + return ( <> = ({ /> {isModalOpen && selectedKey && keyData && ( -
-
- +
+
+ {/* Close button */} + + + {/* Content */} +
+ +
)} From 193ed3b53734eac924ed7efff671e90656d1ef98 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Thu, 13 Mar 2025 19:44:00 -0700 Subject: [PATCH 07/12] use table view --- .../src/components/top_key_view.tsx | 97 ++++++++++++++++--- ui/litellm-dashboard/src/components/usage.tsx | 3 +- 2 files changed, 85 insertions(+), 15 deletions(-) diff --git a/ui/litellm-dashboard/src/components/top_key_view.tsx b/ui/litellm-dashboard/src/components/top_key_view.tsx index c88c308d7f1..11c7877f9c5 100644 --- a/ui/litellm-dashboard/src/components/top_key_view.tsx +++ b/ui/litellm-dashboard/src/components/top_key_view.tsx @@ -3,6 +3,9 @@ import { BarChart } from "@tremor/react"; import KeyInfoView from "./key_info_view"; import { keyInfoV1Call } from "./networking"; import { transformKeyInfo } from "../components/key_team_helpers/transform_key_info"; +import { DataTable } from "./view_logs/table"; +import { Tooltip } from "antd"; +import { Button } from "@tremor/react"; interface TopKeyViewProps { topKeys: any[]; @@ -22,6 +25,7 @@ const TopKeyView: React.FC = ({ const [isModalOpen, setIsModalOpen] = useState(false); const [selectedKey, setSelectedKey] = useState(null); const [keyData, setKeyData] = useState(undefined); + const [viewMode, setViewMode] = useState<'chart' | 'table'>('table'); const handleKeyClick = async (item: any) => { if (!accessToken) return; @@ -62,22 +66,87 @@ const TopKeyView: React.FC = ({ return () => document.removeEventListener('keydown', handleEscapeKey); }, [isModalOpen]); + // Define columns for the table view + const columns = [ + { + header: "Key", + accessorKey: "api_key", + cell: (info: any) => ( +
+ + + +
+ ), + }, + { + header: "Key Alias", + accessorKey: "key_alias", + cell: (info: any) => info.getValue() || "-", + }, + { + header: "Spend (USD)", + accessorKey: "spend", + cell: (info: any) => `$${Number(info.getValue()).toFixed(2)}`, + }, + ]; + return ( <> - `$${value.toFixed(2)}`} - onValueChange={(item) => handleKeyClick(item)} - /> +
+
+ + +
+
+ + {viewMode === 'chart' ? ( +
+
Click on any key to view detailed information
+ `$${value.toFixed(2)}`} + onValueChange={(item) => handleKeyClick(item)} + showTooltip={true} + /> +
+ ) : ( +
+ <>} + getRowCanExpand={() => false} + isLoading={false} + /> +
+ )} {isModalOpen && selectedKey && keyData && (
= ({ async () => { const top_keys = await adminTopKeysCall(accessToken); return top_keys.map((k: any) => ({ - key: (k["key_alias"] || k["key_name"] || k["api_key"]).substring(0, 10), + key: (k["api_key"]).substring(0, 10), api_key: k["api_key"], + key_alias: k["key_alias"], spend: Number(k["total_spend"].toFixed(2)), })); }, From b504e3b40662894e944057c4833294a3274f6945 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Thu, 13 Mar 2025 19:49:11 -0700 Subject: [PATCH 08/12] working button toggle --- .../src/components/top_key_view.tsx | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/ui/litellm-dashboard/src/components/top_key_view.tsx b/ui/litellm-dashboard/src/components/top_key_view.tsx index 11c7877f9c5..9de9985908f 100644 --- a/ui/litellm-dashboard/src/components/top_key_view.tsx +++ b/ui/litellm-dashboard/src/components/top_key_view.tsx @@ -100,20 +100,20 @@ const TopKeyView: React.FC = ({ return ( <> -
+
+ -
From 6a4cce7687b409677315f732db4c6ba50b5e707d Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Thu, 13 Mar 2025 19:51:16 -0700 Subject: [PATCH 09/12] fix top key view --- ui/litellm-dashboard/src/components/top_key_view.tsx | 1 - ui/litellm-dashboard/src/components/usage.tsx | 5 ++--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/ui/litellm-dashboard/src/components/top_key_view.tsx b/ui/litellm-dashboard/src/components/top_key_view.tsx index 9de9985908f..f9a04859d58 100644 --- a/ui/litellm-dashboard/src/components/top_key_view.tsx +++ b/ui/litellm-dashboard/src/components/top_key_view.tsx @@ -119,7 +119,6 @@ const TopKeyView: React.FC = ({ {viewMode === 'chart' ? (
-
Click on any key to view detailed information
= ({ - + Top API Keys = ({ - + Top Models = ({ valueFormatter={(value) => `$${value.toFixed(2)}`} /> - From 636083b886317532852e3ea080ec019e42921d6d Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Thu, 13 Mar 2025 19:53:20 -0700 Subject: [PATCH 10/12] ui fix top key view --- ui/litellm-dashboard/src/components/top_key_view.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ui/litellm-dashboard/src/components/top_key_view.tsx b/ui/litellm-dashboard/src/components/top_key_view.tsx index f9a04859d58..a66afcdec5c 100644 --- a/ui/litellm-dashboard/src/components/top_key_view.tsx +++ b/ui/litellm-dashboard/src/components/top_key_view.tsx @@ -152,7 +152,7 @@ const TopKeyView: React.FC = ({ className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50" onClick={handleOutsideClick} > -
+
{/* Close button */} {/* Content */} -
+
Date: Thu, 13 Mar 2025 20:05:59 -0700 Subject: [PATCH 11/12] Key ID --- ui/litellm-dashboard/src/components/top_key_view.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/litellm-dashboard/src/components/top_key_view.tsx b/ui/litellm-dashboard/src/components/top_key_view.tsx index a66afcdec5c..d687e499d36 100644 --- a/ui/litellm-dashboard/src/components/top_key_view.tsx +++ b/ui/litellm-dashboard/src/components/top_key_view.tsx @@ -69,7 +69,7 @@ const TopKeyView: React.FC = ({ // Define columns for the table view const columns = [ { - header: "Key", + header: "Key ID", accessorKey: "api_key", cell: (info: any) => (
From 8274c8fe36bd04cfa3a95478bfe09a4e68b83914 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Thu, 13 Mar 2025 20:25:08 -0700 Subject: [PATCH 12/12] fix ui linting --- .../src/components/key_page_wrapper.tsx | 50 ------------------- 1 file changed, 50 deletions(-) delete mode 100644 ui/litellm-dashboard/src/components/key_page_wrapper.tsx diff --git a/ui/litellm-dashboard/src/components/key_page_wrapper.tsx b/ui/litellm-dashboard/src/components/key_page_wrapper.tsx deleted file mode 100644 index 5870e00fe73..00000000000 --- a/ui/litellm-dashboard/src/components/key_page_wrapper.tsx +++ /dev/null @@ -1,50 +0,0 @@ -import React, { useState } from "react"; -import TopKeyView from "./top_key_view"; -import KeyInfoView from "./key_info_view"; -import { KeyResponse } from "./key_team_helpers/key_list"; - -interface KeyPageWrapperProps { - topKeys: any[]; - accessToken: string | null; - userID: string | null; - userRole: string | null; - teams: any[] | null; -} - -const KeyPageWrapper: React.FC = ({ - topKeys, - accessToken, - userID, - userRole, - teams, -}) => { - const [selectedKeyData, setSelectedKeyData] = useState(null); - - if (selectedKeyData) { - return ( -
- setSelectedKeyData(null)} - accessToken={accessToken} - userID={userID} - userRole={userRole} - teams={teams} - /> -
- ); - } - - return ( - - ); -}; - -export default KeyPageWrapper; \ No newline at end of file