diff --git a/ui/litellm-dashboard/src/components/view_logs/index.tsx b/ui/litellm-dashboard/src/components/view_logs/index.tsx index b5946bc67c..2c05c3138d 100644 --- a/ui/litellm-dashboard/src/components/view_logs/index.tsx +++ b/ui/litellm-dashboard/src/components/view_logs/index.tsx @@ -1,9 +1,9 @@ import moment from "moment"; import { useQuery } from "@tanstack/react-query"; -import { useState, useRef, useEffect } from "react"; +import { useState, useRef, useEffect, useCallback } from "react"; import { useQueryClient } from "@tanstack/react-query"; -import { uiSpendLogsCall, keyInfoV1Call, sessionSpendLogsCall } from "../networking"; +import { uiSpendLogsCall, keyInfoV1Call, sessionSpendLogsCall, keyListCall } from "../networking"; import { DataTable } from "./table"; import { columns, LogEntry } from "./columns"; import { Row } from "@tanstack/react-table"; @@ -21,6 +21,7 @@ import { GuardrailViewer } from './GuardrailViewer'; import FilterComponent from "../common_components/filter"; import { FilterOption } from "../common_components/filter"; import { useLogFilterLogic } from "./log_filter_logic"; +import { fetchAllKeyAliases } from "../key_team_helpers/filter_helpers"; interface SpendLogsTableProps { accessToken: string | null; @@ -154,7 +155,6 @@ export default function SpendLogsTable({ ], queryFn: async () => { if (!accessToken || !token || !userRole || !userID) { - console.log("Missing required auth parameters"); return { data: [], total: 0, @@ -241,18 +241,53 @@ export default function SpendLogsTable({ userRole }) - // Add this effect to update selectedTeamId and selectedStatus when team filter changes + const fetchKeyHashForAlias = useCallback(async (keyAlias: string) => { + if (!accessToken) return; + + try { + const response = await keyListCall( + accessToken, + null, + null, + keyAlias, + null, + null, + currentPage, + pageSize + ); + + const selectedKey = response.keys.find( + (key: any) => key.key_alias === keyAlias + ); + + if (selectedKey) { + setSelectedKeyHash(selectedKey.token); + } + } catch (error) { + console.error("Error fetching key hash for alias:", error); + } + }, [accessToken, currentPage, pageSize]); + + // Add this effect to update selected filters when filter changes useEffect(() => { + if(!accessToken) return; + if (filters['Team ID']) { setSelectedTeamId(filters['Team ID']); - } else { setSelectedTeamId(""); } setSelectedStatus(filters['Status'] || ""); setSelectedModel(filters['Model'] || ""); - setSelectedKeyHash(filters['Key Hash'] || ""); - }, [filters]); + + if (filters['Key Hash']) { + setSelectedKeyHash(filters['Key Hash']); + } else if (filters['Key Alias']) { + fetchKeyHashForAlias(filters['Key Alias']); + } else { + setSelectedKeyHash(""); + } + }, [filters, accessToken, fetchKeyHashForAlias]); // Fetch logs for a session if selected const sessionLogs = useQuery({ @@ -354,7 +389,7 @@ export default function SpendLogsTable({ const filtered = allTeams.filter((team: Team) =>{ return team.team_id.toLowerCase().includes(searchText.toLowerCase()) || (team.team_alias && team.team_alias.toLowerCase().includes(searchText.toLowerCase())) - }); + }); return filtered.map((team: Team) => ({ label: `${team.team_alias || team.team_id} (${team.team_id})`, value: team.team_id @@ -385,6 +420,22 @@ export default function SpendLogsTable({ })); } }, + { + name: 'Key Alias', + label: 'Key Alias', + isSearchable: true, + searchFn: async (searchText: string) => { + if (!accessToken) return []; + const keyAliases = await fetchAllKeyAliases(accessToken); + const filtered = keyAliases.filter(alias => + alias.toLowerCase().includes(searchText.toLowerCase()) + ); + return filtered.map(alias => ({ + label: alias, + value: alias + })); + } + }, { name: 'Key Hash', label: 'Key Hash', diff --git a/ui/litellm-dashboard/src/components/view_logs/log_filter_logic.tsx b/ui/litellm-dashboard/src/components/view_logs/log_filter_logic.tsx index 9e8ce094c9..1f2b765ded 100644 --- a/ui/litellm-dashboard/src/components/view_logs/log_filter_logic.tsx +++ b/ui/litellm-dashboard/src/components/view_logs/log_filter_logic.tsx @@ -15,6 +15,7 @@ export const FILTER_KEYS = { MODEL: "Model", USER_ID: "User ID", STATUS: "Status", + KEY_ALIAS: "Key Alias", } as const; export type FilterKey = keyof typeof FILTER_KEYS; @@ -47,7 +48,8 @@ export function useLogFilterLogic({ [FILTER_KEYS.REQUEST_ID]: "", [FILTER_KEYS.MODEL]: "", [FILTER_KEYS.USER_ID]: "", - [FILTER_KEYS.STATUS]: "" + [FILTER_KEYS.STATUS]: "", + [FILTER_KEYS.KEY_ALIAS]: "" }), []); const [filters, setFilters] = useState(defaultFilters); @@ -96,6 +98,16 @@ export function useLogFilterLogic({ return () => debouncedSearch.cancel(); }, [debouncedSearch]); + const queryAllKeysQuery = useQuery({ + queryKey: ['allKeys'], + queryFn: async () => { + if (!accessToken) throw new Error('Access token required'); + return await fetchAllKeyAliases(accessToken); + }, + enabled: !!accessToken + }); + const allKeyAliases = queryAllKeysQuery.data || [] + // Apply filters to keys whenever logs or filters change useEffect(() => { if (!logs || !logs.data) { @@ -140,6 +152,24 @@ export function useLogFilterLogic({ ); } + // Add key alias filtering + if (filters[FILTER_KEYS.KEY_ALIAS]) { + // We need to fetch the key info to get the key hash for the selected alias + try { + // Get the key hash for the selected alias + const selectedKey = filters[FILTER_KEYS.KEY_ALIAS] + + if (selectedKey) { + // Filter logs by the key hash + filteredData = filteredData.filter( + log => log.metadata?.user_api_key_alias === selectedKey + ); + } + } catch (error) { + console.error("Error fetching key info for alias:", error); + } + } + const newFilteredLogs: PaginatedResponse = { data: filteredData, total: logs.total, @@ -151,17 +181,9 @@ export function useLogFilterLogic({ if (JSON.stringify(newFilteredLogs) !== JSON.stringify(filteredLogs)) { setFilteredLogs(newFilteredLogs); } - }, [logs, filters, filteredLogs]); + }, [logs, filters, filteredLogs, accessToken]); - const queryAllKeysQuery = useQuery({ - queryKey: ['allKeys'], - queryFn: async () => { - if (!accessToken) throw new Error('Access token required'); - return await fetchAllKeyAliases(accessToken); - }, - enabled: !!accessToken - }); - const allKeyAliases = queryAllKeysQuery.data || [] + // Fetch all teams and users for potential filter dropdowns (optional, can be adapted) const { data: allTeams } = useQuery({