From 342e4470c48ffa81cf3227d3e3095d6dda8b56ed Mon Sep 17 00:00:00 2001 From: yuneng-jiang Date: Thu, 3 Sep 2026 09:47:45 -0700 Subject: [PATCH] fix(ui): replace the key detail URL entry when a virtual key is rotated (#39471) Regenerating a key repointed ?key= at the rotated hash with a pushed history entry, so pressing the browser Back button landed on the hash that had just been revoked. /key/info answers 404 for it and the page shows "Key not found in database". The rotated hash now replaces the current entry instead of pushing a new one, so Back from a just-regenerated key returns to the key list. Opening a key from the table still pushes, so Back from a normally opened key is unchanged. --- .../src/components/VirtualKeysPage/VirtualKeysTable.test.tsx | 4 ++++ .../src/components/VirtualKeysPage/VirtualKeysTable.tsx | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/ui/litellm-dashboard/src/components/VirtualKeysPage/VirtualKeysTable.test.tsx b/ui/litellm-dashboard/src/components/VirtualKeysPage/VirtualKeysTable.test.tsx index 93f860333d8..c69662b69dc 100644 --- a/ui/litellm-dashboard/src/components/VirtualKeysPage/VirtualKeysTable.test.tsx +++ b/ui/litellm-dashboard/src/components/VirtualKeysPage/VirtualKeysTable.test.tsx @@ -182,6 +182,8 @@ const lastSearchParam = (onUrlUpdate: Mock, name: string) = const lastKeyParam = (onUrlUpdate: Mock) => lastSearchParam(onUrlUpdate, "key"); +const lastHistoryMode = (onUrlUpdate: Mock) => onUrlUpdate.mock.calls.at(-1)?.[0].options.history; + beforeEach(() => { vi.clearAllMocks(); @@ -377,6 +379,7 @@ it("clicking the key cell deep-links via ?key=", async () => { await waitFor(() => { expect(lastKeyParam(onUrlUpdate)).toBe(mockKey.token); }); + expect(lastHistoryMode(onUrlUpdate)).toBe("push"); }); it("renders KeyInfoView when the URL has ?key= for a key on the current page, without refetching it", async () => { @@ -417,6 +420,7 @@ it("repoints ?key= to the rotated hash once the regenerate dialog is dismissed", await waitFor(() => { expect(lastKeyParam(onUrlUpdate)).toBe("rotated-hash-456"); }); + expect(lastHistoryMode(onUrlUpdate)).toBe("replace"); }); it("fetches the key by id when the URL has ?key= for a key not in the loaded page", async () => { diff --git a/ui/litellm-dashboard/src/components/VirtualKeysPage/VirtualKeysTable.tsx b/ui/litellm-dashboard/src/components/VirtualKeysPage/VirtualKeysTable.tsx index 8ec8b6d0c3f..c424966a0a3 100644 --- a/ui/litellm-dashboard/src/components/VirtualKeysPage/VirtualKeysTable.tsx +++ b/ui/litellm-dashboard/src/components/VirtualKeysPage/VirtualKeysTable.tsx @@ -217,7 +217,7 @@ export function VirtualKeysTable({ headerActions }: VirtualKeysTableProps) { (updated: Partial) => { const rotatedToken = updated.token ?? updated.token_id; if (!rotatedToken || rotatedToken === selectedKeyId) return; - void setSelectedKeyId(rotatedToken); + void setSelectedKeyId(rotatedToken, { history: "replace" }); void refetch(); }, [refetch, selectedKeyId, setSelectedKeyId],