Input Cost:
- {formatCost(costBreakdown.input_cost)}
+
+ {formatCost(inputCost)}
+ {promptTokens !== undefined && (
+
+ ({promptTokens.toLocaleString()} prompt tokens)
+
+ )}
+
Output Cost:
- {formatCost(costBreakdown.output_cost)}
+
+ {formatCost(outputCost)}
+ {completionTokens !== undefined && (
+
+ ({completionTokens.toLocaleString()} completion tokens)
+
+ )}
+
- {costBreakdown.tool_usage_cost !== undefined && costBreakdown.tool_usage_cost > 0 && (
+ {costBreakdown?.tool_usage_cost !== undefined && costBreakdown.tool_usage_cost > 0 && (
Tool Usage Cost:
{formatCost(costBreakdown.tool_usage_cost)}
)}
{/* Additional Costs (free-form) */}
- {costBreakdown.additional_costs && Object.keys(costBreakdown.additional_costs).length > 0 && (
+ {costBreakdown?.additional_costs && Object.keys(costBreakdown.additional_costs).length > 0 && (
<>
{Object.entries(costBreakdown.additional_costs).map(([key, value]) => (
@@ -106,13 +140,15 @@ export const CostBreakdownViewer: React.FC = ({
)}
- {/* Subtotal / Original Cost */}
-
-
-
Original LLM Cost:
-
{formatCost(costBreakdown.original_cost)}
+ {/* Subtotal / Original Cost - hide when cached since it would be $0 */}
+ {!isCached && (
+
+
+ Original LLM Cost:
+ {formatCost(originalCost)}
+
-
+ )}
{/* Step 2: Adjustments (Discount & Margin) */}
{(hasDiscount || hasMargin) && (
@@ -160,7 +196,8 @@ export const CostBreakdownViewer: React.FC
= ({
Final Calculated Cost:
- {formatCost(costBreakdown.total_cost ?? totalSpend)}
+ {formatCost(totalCost)}
+ {isCached && " (Cached)"}
diff --git a/ui/litellm-dashboard/src/components/view_logs/GuardrailViewer/GuardrailViewer.test.tsx b/ui/litellm-dashboard/src/components/view_logs/GuardrailViewer/GuardrailViewer.test.tsx
index 95120f60570..28991ebfa03 100644
--- a/ui/litellm-dashboard/src/components/view_logs/GuardrailViewer/GuardrailViewer.test.tsx
+++ b/ui/litellm-dashboard/src/components/view_logs/GuardrailViewer/GuardrailViewer.test.tsx
@@ -19,68 +19,62 @@ describe("GuardrailViewer", () => {
vi.resetModules();
});
- it("shows header, status pill color, duration rounding, and time labels", () => {
+ it("shows header, status pill, and duration", () => {
const data = makeGuardrailInformation({ duration: 1.23456, guardrail_status: "success" });
renderWithProviders(
);
- expect(screen.getByText("Guardrail Information")).toBeInTheDocument();
- // header status pill (success => green)
- const statusBadges = screen.getAllByText("success");
- // there are two status locations: header chip and grid "Status"
- expect(statusBadges.length).toBeGreaterThanOrEqual(1);
- // Quick class assertion for at least one of them
- expect(statusBadges[0].className).toMatch(/bg-green-100/);
+ expect(screen.getByText("Guardrails & Policy Compliance")).toBeInTheDocument();
+ // header shows passed count
+ expect(screen.getByText(/1 Passed/)).toBeInTheDocument();
+ // The PASSED badge in the evaluation card
+ expect(screen.getByText("PASSED")).toBeInTheDocument();
- // duration displays with 4 decimals
- expect(screen.getByText(/1\.2346s/)).toBeInTheDocument();
-
- // time labels exist
- expect(screen.getByText("Start Time:")).toBeInTheDocument();
- expect(screen.getByText("End Time:")).toBeInTheDocument();
+ // duration displays in ms format: Math.round(1.23456 * 1000) = 1235
+ expect(screen.getByText("1235ms")).toBeInTheDocument();
});
- it("calculates and displays masked entity totals with pluralization", () => {
+ it("calculates and displays masked entity totals", async () => {
+ const user = userEvent.setup();
const data = makeGuardrailInformation({
masked_entity_count: { EMAIL_ADDRESS: 2, PHONE_NUMBER: 1 },
});
renderWithProviders(
);
- expect(screen.getByText("3 masked entities")).toBeInTheDocument();
- // summary chips for each entry
+ // In collapsed state, the match count badge is visible
+ expect(screen.getByText("3 matched")).toBeInTheDocument();
+
+ // Expand the evaluation card to see entity details
+ await user.click(screen.getByText("pii-rail"));
+ // summary chips for each entry inside expanded card
expect(screen.getByText("EMAIL_ADDRESS: 2")).toBeInTheDocument();
expect(screen.getByText("PHONE_NUMBER: 1")).toBeInTheDocument();
});
- it("hides masked badge & summary when count is zero/empty", () => {
+ it("hides matched badge when count is zero/empty", () => {
const data = makeGuardrailInformation({ masked_entity_count: {} });
renderWithProviders(
);
- expect(screen.queryByText(/masked entity/)).not.toBeInTheDocument();
- expect(screen.queryByText("Masked Entity Summary")).not.toBeInTheDocument();
+ expect(screen.queryByText(/matched/)).not.toBeInTheDocument();
});
- it("toggles main section open/closed and chevron rotation class", async () => {
+ it("toggles evaluation card open/closed on click", async () => {
const user = userEvent.setup();
- const data = makeGuardrailInformation();
- const { container } = renderWithProviders(
);
-
- const header = screen.getByText("Guardrail Information").closest(".ant-collapse-header")!;
- // Initially expanded (content is visible)
- expect(screen.getByText("Masked Entity Summary")).toBeInTheDocument();
-
- // Click to collapse
- await user.click(header);
- // Wait for collapse animation and content to be hidden
- await waitFor(() => {
- const contentBox = container.querySelector(".ant-collapse-content-box");
- expect(contentBox).not.toBeVisible();
+ const data = makeGuardrailInformation({
+ masked_entity_count: { EMAIL_ADDRESS: 2 },
});
+ renderWithProviders(
);
- // Click to expand again
- await user.click(header);
- // Wait for expand animation
+ // Initially collapsed — masked entity details not visible
+ expect(screen.queryByText("EMAIL_ADDRESS: 2")).not.toBeInTheDocument();
+
+ // Click to expand
+ await user.click(screen.getByText("pii-rail"));
+ expect(screen.getByText("EMAIL_ADDRESS: 2")).toBeInTheDocument();
+
+ // Click again to collapse
+ await user.click(screen.getByText("pii-rail"));
await waitFor(() => {
- expect(screen.getByText("Masked Entity Summary")).toBeVisible();
+ expect(screen.queryByText("EMAIL_ADDRESS: 2")).not.toBeInTheDocument();
});
});
@@ -97,6 +91,9 @@ describe("GuardrailViewer", () => {
});
renderWithProviders(
);
+ // Expand the card to see provider-specific content
+ const user = userEvent.setup();
+ await user.click(screen.getByText("pii-rail"));
expect(screen.getByTestId("presidio-mock")).toHaveTextContent("presidio 2");
});
@@ -112,6 +109,10 @@ describe("GuardrailViewer", () => {
guardrail_response: [makeEntity()],
});
renderWithProviders(
);
+
+ // Expand the card to see provider-specific content
+ const user = userEvent.setup();
+ await user.click(screen.getByText("pii-rail"));
expect(screen.getByTestId("presidio-mock")).toHaveTextContent("count:1");
});
@@ -127,22 +128,31 @@ describe("GuardrailViewer", () => {
guardrail_response: makeBedrockResponse({ action: "GUARDRAIL_INTERVENED" }),
});
renderWithProviders(
);
+
+ // Expand the card to see provider-specific content
+ const user = userEvent.setup();
+ await user.click(screen.getByText("pii-rail"));
expect(screen.getByTestId("bedrock-mock")).toHaveTextContent("GUARDRAIL_INTERVENED");
});
- it("unknown provider renders neither Presidio nor Bedrock details", () => {
+ it("unknown provider renders neither Presidio nor Bedrock details", async () => {
+ const user = userEvent.setup();
const data = makeGuardrailInformation({
guardrail_provider: "unknown",
});
renderWithProviders(
);
- // Summary still present
- expect(screen.getByText("Guardrail Information")).toBeInTheDocument();
- // No provider sections
+ // Header still present
+ expect(screen.getByText("Guardrails & Policy Compliance")).toBeInTheDocument();
+
+ // Expand the card
+ await user.click(screen.getByText("pii-rail"));
+ // No Presidio or Bedrock sections
expect(screen.queryByText(/Detected Entities/)).not.toBeInTheDocument();
expect(screen.queryByText(/Raw Bedrock Guardrail Response/)).not.toBeInTheDocument();
});
- it("integration: renders with real Bedrock details without mocks", () => {
+ it("integration: renders with real Bedrock details without mocks", async () => {
+ const user = userEvent.setup();
const data = makeGuardrailInformation({
guardrail_provider: "bedrock",
guardrail_response: makeBedrockResponse({
@@ -152,6 +162,9 @@ describe("GuardrailViewer", () => {
});
renderWithProviders(
);
+ // Expand the card to reveal Bedrock details
+ await user.click(screen.getByText("pii-rail"));
+
// Bedrock summary bits
expect(screen.getByText("Outputs")).toBeInTheDocument();
expect(screen.getByText("ok")).toBeInTheDocument();
diff --git a/ui/litellm-dashboard/src/components/view_logs/LogDetailsDrawer/LogDetailContent.test.tsx b/ui/litellm-dashboard/src/components/view_logs/LogDetailsDrawer/LogDetailContent.test.tsx
new file mode 100644
index 00000000000..33de54991da
--- /dev/null
+++ b/ui/litellm-dashboard/src/components/view_logs/LogDetailsDrawer/LogDetailContent.test.tsx
@@ -0,0 +1,346 @@
+import { render, screen } from "@testing-library/react";
+import userEvent from "@testing-library/user-event";
+import { describe, expect, it, vi } from "vitest";
+import { LogDetailContent } from "./LogDetailContent";
+import type { LogEntry } from "../columns";
+
+vi.mock("../GuardrailViewer/GuardrailViewer", () => ({
+ default: ({ data }: { data: unknown }) =>
{JSON.stringify(data)}
,
+}));
+
+const createLogEntry = (overrides: Partial
= {}): LogEntry =>
+ ({
+ request_id: "chatcmpl-test-id",
+ api_key: "api-key",
+ team_id: "team-id",
+ model: "gpt-4",
+ model_id: "gpt-4",
+ call_type: "chat",
+ spend: 0,
+ total_tokens: 10,
+ prompt_tokens: 5,
+ completion_tokens: 5,
+ startTime: "2025-11-14T00:00:00Z",
+ endTime: "2025-11-14T00:00:01Z",
+ cache_hit: "miss",
+ duration: 1,
+ messages: [{ role: "user", content: "hello" }],
+ response: { choices: [{ message: { content: "hi" } }] },
+ metadata: { status: "success" },
+ request_tags: {},
+ custom_llm_provider: "openai",
+ api_base: "https://api.example.com",
+ ...overrides,
+ }) as LogEntry;
+
+describe("LogDetailContent", () => {
+ it("should render the component successfully", () => {
+ render();
+
+ expect(screen.getByText("Request Details")).toBeInTheDocument();
+ });
+
+ it("should display Request Details with model, provider, and call type", () => {
+ render(
+ ,
+ );
+
+ expect(screen.getByText("gpt-4o")).toBeInTheDocument();
+ expect(screen.getByText("anthropic")).toBeInTheDocument();
+ expect(screen.getByText("completion")).toBeInTheDocument();
+ });
+
+ it("should display error alert when request has failed", () => {
+ render(
+ ,
+ );
+
+ expect(screen.getByText("Request Failed")).toBeInTheDocument();
+ expect(screen.getByText("rate_limit")).toBeInTheDocument();
+ expect(screen.getByText("Too many requests")).toBeInTheDocument();
+ });
+
+ it("should display tags section when request_tags has entries", () => {
+ render(
+ ,
+ );
+
+ expect(screen.getByText("Tags")).toBeInTheDocument();
+ expect(screen.getByText("env: prod")).toBeInTheDocument();
+ expect(screen.getByText("version: 1.0")).toBeInTheDocument();
+ });
+
+ it("should not display tags section when request_tags is empty", () => {
+ render();
+
+ expect(screen.queryByText("Tags")).not.toBeInTheDocument();
+ });
+
+ it("should display Metrics section with tokens and cost", () => {
+ render(
+ ,
+ );
+
+ expect(screen.getByText("Metrics")).toBeInTheDocument();
+ expect(screen.getAllByText("$0.00200000").length).toBeGreaterThanOrEqual(1);
+ });
+
+ it("should display ConfigInfoMessage when no messages, response, or error and not loading", () => {
+ render(
+ ,
+ );
+
+ expect(screen.getByText("Request/Response Data Not Available")).toBeInTheDocument();
+ });
+
+ it("should not display ConfigInfoMessage when isLoadingDetails is true even without data", () => {
+ render(
+ ,
+ );
+
+ expect(screen.queryByText("Request/Response Data Not Available")).not.toBeInTheDocument();
+ });
+
+ it("should call onOpenSettings when user clicks open settings in ConfigInfoMessage", async () => {
+ const onOpenSettings = vi.fn();
+ const user = userEvent.setup();
+
+ render(
+ ,
+ );
+
+ const settingsButton = screen.getByRole("button", { name: /open the settings/i });
+ await user.click(settingsButton);
+
+ expect(onOpenSettings).toHaveBeenCalledTimes(1);
+ });
+
+ it("should display loading state when isLoadingDetails is true", () => {
+ render(
+ ,
+ );
+
+ expect(screen.getByText("Loading request & response data...")).toBeInTheDocument();
+ });
+
+ it("should display Request & Response section with Pretty and JSON view modes", () => {
+ render();
+
+ expect(screen.getByText("Request & Response")).toBeInTheDocument();
+ expect(screen.getByRole("radio", { name: "Pretty" })).toBeInTheDocument();
+ expect(screen.getByRole("radio", { name: "JSON" })).toBeInTheDocument();
+ });
+
+ it("should display Request and Response tabs when JSON view is selected", async () => {
+ const user = userEvent.setup();
+ render();
+
+ await user.click(screen.getByText("JSON"));
+
+ expect(screen.getByRole("tab", { name: "Request" })).toBeInTheDocument();
+ expect(screen.getByRole("tab", { name: "Response" })).toBeInTheDocument();
+ });
+
+ it("should display response not available message when no response and Response tab is selected", async () => {
+ const user = userEvent.setup();
+ render(
+ ,
+ );
+
+ await user.click(screen.getByText("JSON"));
+ await user.click(screen.getByRole("tab", { name: "Response" }));
+
+ expect(screen.getByText("Response data not available")).toBeInTheDocument();
+ });
+
+ it("should display Metadata section when metadata has keys", () => {
+ render(
+ ,
+ );
+
+ expect(screen.getByText("Metadata")).toBeInTheDocument();
+ });
+
+ it("should display IP address when requester_ip_address is present", () => {
+ render(
+ ,
+ );
+
+ expect(screen.getByText("192.168.1.1")).toBeInTheDocument();
+ });
+
+ it("should display guardrail label when guardrail data exists", () => {
+ render(
+ ,
+ );
+
+ expect(screen.getByText("PII Filter")).toBeInTheDocument();
+ expect(screen.getByText("2 masked")).toBeInTheDocument();
+ });
+
+ it("should display cache hit information when cache_hit is true", () => {
+ render(
+ ,
+ );
+
+ expect(screen.getByText("Cache Hit")).toBeInTheDocument();
+ expect(screen.getByText("true")).toBeInTheDocument();
+ expect(screen.getByText("Cache Read Tokens")).toBeInTheDocument();
+ expect(screen.getByText("100")).toBeInTheDocument();
+ });
+
+ it("should display LiteLLM Overhead when litellm_overhead_time_ms is in metadata", () => {
+ render(
+ ,
+ );
+
+ expect(screen.getByText("LiteLLM Overhead")).toBeInTheDocument();
+ expect(screen.getByText("42.50 ms")).toBeInTheDocument();
+ });
+
+ it("should display start and end time in ISO format", () => {
+ render(
+ ,
+ );
+
+ expect(screen.getByText("Start Time")).toBeInTheDocument();
+ expect(screen.getByText("End Time")).toBeInTheDocument();
+ const dateElements = screen.getAllByText((content) => content.includes("2025-11-14"));
+ expect(dateElements.length).toBeGreaterThanOrEqual(2);
+ });
+
+ it("should display Vector Store Requests when vector store data exists", () => {
+ render(
+ ,
+ );
+
+ expect(screen.getByText("Vector Store Requests")).toBeInTheDocument();
+ });
+
+ it("should display provider as dash when custom_llm_provider is absent", () => {
+ render(
+ ,
+ );
+
+ const descriptions = screen.getByText("Provider").closest(".ant-descriptions-item");
+ expect(descriptions).toBeInTheDocument();
+ expect(screen.getByText("-")).toBeInTheDocument();
+ });
+});
diff --git a/ui/litellm-dashboard/src/components/view_logs/LogDetailsDrawer/LogDetailContent.tsx b/ui/litellm-dashboard/src/components/view_logs/LogDetailsDrawer/LogDetailContent.tsx
index 913634d388f..8ff4f53bdd3 100644
--- a/ui/litellm-dashboard/src/components/view_logs/LogDetailsDrawer/LogDetailContent.tsx
+++ b/ui/litellm-dashboard/src/components/view_logs/LogDetailsDrawer/LogDetailContent.tsx
@@ -137,7 +137,13 @@ export function LogDetailContent({ logEntry, onOpenSettings, isLoadingDetails =
{/* Cost Breakdown */}
-
+
{/* Tools */}
@@ -258,9 +264,17 @@ function MetricsSection({ logEntry, metadata }: { logEntry: LogEntry; metadata:
(metadata?.additional_usage_values?.cache_read_input_tokens &&
metadata.additional_usage_values.cache_read_input_tokens > 0);
+ const cacheHitValue = String(logEntry.cache_hit ?? "None");
+ const cacheHitColor =
+ cacheHitValue.toLowerCase() === "true"
+ ? "green"
+ : cacheHitValue.toLowerCase() === "false"
+ ? "red"
+ : "default";
+
return (
-
+
- {logEntry.cache_hit || "None"}
+ {cacheHitValue}
{metadata?.additional_usage_values?.cache_read_input_tokens > 0 && (
@@ -296,6 +310,14 @@ function MetricsSection({ logEntry, metadata }: { logEntry: LogEntry; metadata:
)}
+
+ {metadata?.attempted_retries !== undefined && metadata?.attempted_retries !== null
+ ? metadata.attempted_retries > 0
+ ? <>{metadata.attempted_retries}{metadata.max_retries !== undefined && metadata.max_retries !== null ? ` / ${metadata.max_retries}` : ''}>
+ : None
+ : "-"}
+
+
{moment(logEntry.startTime).format("YYYY-MM-DDTHH:mm:ss.SSS[Z]")}
@@ -331,12 +353,24 @@ function RequestResponseSection({
return JSON.stringify(data, null, 2);
};
- const totalSpend = logEntry.spend || 0;
+ const totalSpend = logEntry.spend ?? 0;
const promptTokens = logEntry.prompt_tokens || 0;
const completionTokens = logEntry.completion_tokens || 0;
const totalTokens = promptTokens + completionTokens;
- const inputCost = totalTokens > 0 ? (totalSpend * promptTokens) / totalTokens : 0;
- const outputCost = totalTokens > 0 ? (totalSpend * completionTokens) / totalTokens : 0;
+ const costBreakdown = logEntry.metadata?.cost_breakdown;
+ const useCostBreakdown =
+ costBreakdown?.input_cost !== undefined &&
+ costBreakdown?.output_cost !== undefined;
+ const inputCost = useCostBreakdown
+ ? (costBreakdown!.input_cost ?? 0)
+ : totalTokens > 0
+ ? (totalSpend * promptTokens) / totalTokens
+ : 0;
+ const outputCost = useCostBreakdown
+ ? (costBreakdown!.output_cost ?? 0)
+ : totalTokens > 0
+ ? (totalSpend * completionTokens) / totalTokens
+ : 0;
return (
diff --git a/ui/litellm-dashboard/src/components/view_logs/index.test.tsx b/ui/litellm-dashboard/src/components/view_logs/index.test.tsx
index a19e772e340..7d4fc98111d 100644
--- a/ui/litellm-dashboard/src/components/view_logs/index.test.tsx
+++ b/ui/litellm-dashboard/src/components/view_logs/index.test.tsx
@@ -123,6 +123,55 @@ describe("Request Viewer", () => {
expect(screen.queryByText("LiteLLM Overhead:")).not.toBeInTheDocument();
});
+
+ it("should display retry count when attempted_retries > 0 in metadata", () => {
+ render(
+ ,
+ );
+
+ expect(screen.getByText("Retries:")).toBeInTheDocument();
+ expect(screen.getByText("2 / 3")).toBeInTheDocument();
+ });
+
+ it("should display green 'None' tag when attempted_retries is 0", () => {
+ render(
+ ,
+ );
+
+ expect(screen.getByText("Retries:")).toBeInTheDocument();
+ expect(screen.getByText("None")).toBeInTheDocument();
+ });
+
+ it("should display '-' for Retries when attempted_retries is not present in metadata", () => {
+ render();
+
+ expect(screen.getByText("Retries:")).toBeInTheDocument();
+ expect(screen.getByText("-")).toBeInTheDocument();
+ });
});
describe("SpendLogsTable", () => {
diff --git a/ui/litellm-dashboard/src/components/view_logs/index.tsx b/ui/litellm-dashboard/src/components/view_logs/index.tsx
index a14a263a3fe..9f199ec8ac9 100644
--- a/ui/litellm-dashboard/src/components/view_logs/index.tsx
+++ b/ui/litellm-dashboard/src/components/view_logs/index.tsx
@@ -7,7 +7,7 @@ import { truncateString } from "@/utils/textUtils";
import { SettingOutlined, SyncOutlined } from "@ant-design/icons";
import { Row } from "@tanstack/react-table";
import { Switch, Tab, TabGroup, TabList, TabPanel, TabPanels } from "@tremor/react";
-import { Button, Tooltip } from "antd";
+import { Button, Tag, Tooltip } from "antd";
import { internalUserRoles } from "../../utils/roles";
import DeletedKeysPage from "../DeletedKeysPage/DeletedKeysPage";
import DeletedTeamsPage from "../DeletedTeamsPage/DeletedTeamsPage";
@@ -91,6 +91,11 @@ export default function SpendLogsTable({
const [sortBy, setSortBy] = useState("startTime");
const [sortOrder, setSortOrder] = useState<"asc" | "desc">("desc");
+ // Tracks whether any filter that uses performSearch (backend) is active.
+ // Used to disable the main query so it doesn't fire redundant unfiltered requests
+ // when time range / sort / page changes while a backend filter is in effect.
+ const [isMainQueryEnabled, setIsMainQueryEnabled] = useState(true);
+
const queryClient = useQueryClient();
const [isLiveTail, setIsLiveTail] = useState(() => {
@@ -212,7 +217,7 @@ export default function SpendLogsTable({
return response;
},
- enabled: !!accessToken && !!token && !!userRole && !!userID && activeTab === "request logs",
+ enabled: !!accessToken && !!token && !!userRole && !!userID && activeTab === "request logs" && isMainQueryEnabled,
refetchInterval: isLiveTail && currentPage === 1 ? 15000 : false,
placeholderData: keepPreviousData,
refetchIntervalInBackground: true,
@@ -235,6 +240,7 @@ export default function SpendLogsTable({
const {
filters,
filteredLogs,
+ hasBackendFilters,
allTeams: hookAllTeams,
allKeyAliases,
handleFilterChange,
@@ -254,25 +260,6 @@ export default function SpendLogsTable({
currentPage,
});
- 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],
- );
-
const handleFilterReset = useCallback(() => {
handleFilterResetFromHook();
// Reset custom time range to default (last 24 hours)
@@ -283,7 +270,13 @@ export default function SpendLogsTable({
setCurrentPage(1);
}, [handleFilterResetFromHook]);
- // Add this effect to update selected filters when filter changes
+ // Disable the main query whenever backend filters are active so it doesn't fire
+ // redundant unfiltered requests when time range / sort / page changes.
+ useEffect(() => {
+ setIsMainQueryEnabled(!hasBackendFilters);
+ }, [hasBackendFilters]);
+
+ // Sync filter state into the individual selectedX state variables used by the main query
useEffect(() => {
if (!accessToken) return;
@@ -296,14 +289,11 @@ export default function SpendLogsTable({
setSelectedModelId(filters["Model"] || "");
setSelectedEndUser(filters["End User"] || "");
- if (filters["Key Hash"]) {
- setSelectedKeyHash(filters["Key Hash"]);
- } else if (filters["Key Alias"]) {
- fetchKeyHashForAlias(filters["Key Alias"]);
- } else {
- setSelectedKeyHash("");
- }
- }, [filters, accessToken, fetchKeyHashForAlias]);
+ // Key Alias filtering is handled server-side by performSearch via the key_alias param.
+ // We intentionally do not translate the alias to a hash here to avoid firing a
+ // redundant main-query request (api_key=hash) alongside performSearch's key_alias request.
+ setSelectedKeyHash(filters["Key Hash"] || "");
+ }, [filters, accessToken]);
if (!accessToken || !token || !userRole || !userID) {
return null;
@@ -592,6 +582,7 @@ export default function SpendLogsTable({
className={`w-full px-3 py-2 text-left text-sm hover:bg-gray-50 rounded-md ${displayLabel === option.label ? "bg-blue-50 text-blue-600" : ""
}`}
onClick={() => {
+ setCurrentPage(1);
setEndTime(moment().format("YYYY-MM-DDTHH:mm"));
setStartTime(
moment()
@@ -694,7 +685,7 @@ export default function SpendLogsTable({
- {isLiveTail && currentPage === 1 && (
+ {isLiveTail && currentPage === 1 && isMainQueryEnabled && (