From 12a55a8a6f8f94d97413a489523b18499a241108 Mon Sep 17 00:00:00 2001 From: Ryan Crabbe Date: Sat, 28 Mar 2026 12:03:03 -0700 Subject: [PATCH 1/3] Fix logs page showing unfiltered results when filter matches zero rows MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The backendFilteredLogs state initialized to { data: [], ... } which was indistinguishable from "API returned empty results". When backend filters were active, the filteredLogs memo fell back to showing unfiltered `logs` because it couldn't tell whether a search had completed or not. Fix: use null as initial state so we can distinguish "not yet searched" (null → show empty placeholder) from "search returned empty" ({ data: [] } → show empty results). This prevents the fallback to unfiltered logs that caused the model filter to display mismatched data. --- .../components/view_logs/log_filter_logic.tsx | 41 +++++++------------ 1 file changed, 15 insertions(+), 26 deletions(-) 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 4e7153b64cd..a4dd05b46e7 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 @@ -69,13 +69,7 @@ export function useLogFilterLogic({ ); const [filters, setFilters] = useState(defaultFilters); - const [backendFilteredLogs, setBackendFilteredLogs] = useState({ - data: [], - total: 0, - page: 1, - page_size: 50, - total_pages: 0, - }); + const [backendFilteredLogs, setBackendFilteredLogs] = useState(null); const lastSearchTimestamp = useRef(0); const performSearch = useCallback( async (filters: LogFilterState, page = 1) => { @@ -227,22 +221,23 @@ export function useLogFilterLogic({ // Choose which filtered logs to expose: backend result when active, otherwise client-derived const filteredLogs: PaginatedResponse = useMemo(() => { if (hasBackendFilters) { - // Prefer backend result if present; otherwise fall back to latest logs - if (backendFilteredLogs && backendFilteredLogs.data) { + // When backend filters are active, only show backend results. + // If search hasn't completed yet (null), show empty state rather than + // falling back to unfiltered logs — that caused filtered views to + // display mismatched data when the filter matched zero rows. + if (backendFilteredLogs !== null) { return backendFilteredLogs; } - return ( - logs || { - data: [], - total: 0, - page: 1, - page_size: 50, - total_pages: 0, - } - ); + return { + data: [], + total: 0, + page: 1, + page_size: 50, + total_pages: 0, + }; } return clientDerivedFilteredLogs; - }, [hasBackendFilters, backendFilteredLogs, clientDerivedFilteredLogs, logs]); + }, [hasBackendFilters, backendFilteredLogs, clientDerivedFilteredLogs]); // Fetch all teams and users for potential filter dropdowns (optional, can be adapted) const { data: allTeams } = useQuery({ @@ -284,13 +279,7 @@ export function useLogFilterLogic({ setFilters(defaultFilters); // Clear backend filtered logs to ensure fresh render - setBackendFilteredLogs({ - data: [], - total: 0, - page: 1, - page_size: 50, - total_pages: 0, - }); + setBackendFilteredLogs(null); // Reset selections debouncedSearch(defaultFilters, 1); From fbddab6178e690c0757d44bc68ac46fe7fa1f721 Mon Sep 17 00:00:00 2001 From: Ryan Crabbe Date: Sat, 28 Mar 2026 13:59:17 -0700 Subject: [PATCH 2/3] Handle search error state and cancel wasted API call on filter reset - Set backendFilteredLogs to empty response on performSearch error so the UI shows "0 results" instead of appearing stuck in a loading state - Cancel debounced search on filter reset instead of firing a request whose result would be ignored (hasBackendFilters is false after reset) - Clear backendFilteredLogs immediately on filter change to prevent stale results from previous filter showing during debounce window - Normalize response.data with nullish coalescing to handle missing data - Use `not team_object.models` for None-safe emptiness check --- litellm/proxy/proxy_server.py | 2 +- .../components/view_logs/log_filter_logic.tsx | 19 +++++++++++++++---- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/litellm/proxy/proxy_server.py b/litellm/proxy/proxy_server.py index 853ceb63c9d..59a234d09c8 100644 --- a/litellm/proxy/proxy_server.py +++ b/litellm/proxy/proxy_server.py @@ -9215,7 +9215,7 @@ def _add_team_models_to_all_models( for team_object in team_db_objects_typed: if ( - len(team_object.models) == 0 # empty list = all model access + not team_object.models # None or empty list = all model access or SpecialModelNames.all_proxy_models.value in team_object.models ): model_list = llm_router.get_model_list() 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 a4dd05b46e7..9acff32d63c 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 @@ -107,11 +107,21 @@ export function useLogFilterLogic({ }, }); - if (currentTimestamp === lastSearchTimestamp.current && response.data) { - setBackendFilteredLogs(response); + if (currentTimestamp === lastSearchTimestamp.current) { + setBackendFilteredLogs({ + ...response, + data: response.data ?? [], + }); } } catch (error) { console.error("Error searching users:", error); + setBackendFilteredLogs({ + data: [], + total: 0, + page: 1, + page_size: pageSize, + total_pages: 0, + }); } }, [accessToken, startTime, endTime, isCustomDate, pageSize, sortBy, sortOrder], @@ -267,6 +277,7 @@ export function useLogFilterLogic({ // Only call debouncedSearch if filters have actually changed if (JSON.stringify(updatedFilters) !== JSON.stringify(prev)) { setCurrentPage(1); + setBackendFilteredLogs(null); debouncedSearch(updatedFilters, 1); } @@ -281,8 +292,8 @@ export function useLogFilterLogic({ // Clear backend filtered logs to ensure fresh render setBackendFilteredLogs(null); - // Reset selections - debouncedSearch(defaultFilters, 1); + // Cancel any in-flight debounced search + debouncedSearch.cancel(); }; return { From 8a2186a654b67fff101e5f8ac5a68167a2ca52cc Mon Sep 17 00:00:00 2001 From: Ryan Crabbe Date: Sat, 28 Mar 2026 16:19:03 -0700 Subject: [PATCH 3/3] [Fix] Reset page to 1 on filter reset and use pageSize param in fallbacks Add setCurrentPage(1) to handleFilterReset in the hook so page resets regardless of which component consumes it. Replace hardcoded page_size: 50 in empty-state fallbacks with the pageSize parameter for consistency. --- .../src/components/view_logs/log_filter_logic.tsx | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) 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 9acff32d63c..8c88de49d0d 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 @@ -174,7 +174,7 @@ export function useLogFilterLogic({ data: [], total: 0, page: 1, - page_size: 50, + page_size: pageSize, total_pages: 0, }; } @@ -242,7 +242,7 @@ export function useLogFilterLogic({ data: [], total: 0, page: 1, - page_size: 50, + page_size: pageSize, total_pages: 0, }; } @@ -294,6 +294,9 @@ export function useLogFilterLogic({ // Cancel any in-flight debounced search debouncedSearch.cancel(); + + // Reset to first page so the unfiltered view starts at page 1 + setCurrentPage(1); }; return {