diff --git a/ui/litellm-dashboard/src/components/organisms/RegenerateKeyModal.test.tsx b/ui/litellm-dashboard/src/components/organisms/RegenerateKeyModal.test.tsx
index f77cf787a3e..a69ae779249 100644
--- a/ui/litellm-dashboard/src/components/organisms/RegenerateKeyModal.test.tsx
+++ b/ui/litellm-dashboard/src/components/organisms/RegenerateKeyModal.test.tsx
@@ -219,6 +219,54 @@ describe("RegenerateKeyModal", () => {
expect(updateCall.key_name).toBe("sk-new-regenerated-key");
});
+ it.each([
+ ["30s", /New expiry:/],
+ ["15m", /New expiry:/],
+ ["2h", /New expiry:/],
+ ["7d", /New expiry:/],
+ ["2w", /New expiry:/],
+ ["1mo", /New expiry:/],
+ ])("should compute a new expiry preview for duration '%s'", async (durationInput, expected) => {
+ const user = userEvent.setup();
+ renderWithProviders();
+
+ const durationField = screen.getByPlaceholderText("e.g. 30s, 30h, 30d");
+ await user.clear(durationField);
+ await user.type(durationField, durationInput);
+
+ await waitFor(() => {
+ expect(screen.getByText(expected)).toBeInTheDocument();
+ });
+ });
+
+ it("should fall back to the previous expiry when duration is unparseable", async () => {
+ // Regression: if calculateNewExpiryTime returns null (unrecognised suffix),
+ // the payload should fall back to the previous expires rather than null.
+ const user = userEvent.setup();
+ const previousExpires = "2026-12-31T00:00:00Z";
+ mockRegenerateKeyCall.mockResolvedValue({
+ key: "sk-new-regenerated-key",
+ token: "new-token-hash",
+ });
+
+ renderWithProviders(
+ ,
+ );
+
+ const durationField = screen.getByPlaceholderText("e.g. 30s, 30h, 30d");
+ await user.clear(durationField);
+ await user.type(durationField, "bogus");
+
+ await user.click(screen.getByRole("button", { name: /Regenerate/ }));
+
+ await waitFor(() => {
+ expect(mockOnKeyUpdate).toHaveBeenCalledOnce();
+ });
+
+ const updateCall = mockOnKeyUpdate.mock.calls[0][0];
+ expect(updateCall.expires).toBe(previousExpires);
+ });
+
it("should pass form values to onKeyUpdate even when the API echoes back different limits", async () => {
// Regression: when the regenerate endpoint returns GenerateKeyResponse, it echoes
// back the existing max_budget / tpm_limit / rpm_limit. The modal must prefer the
diff --git a/ui/litellm-dashboard/src/components/organisms/RegenerateKeyModal.tsx b/ui/litellm-dashboard/src/components/organisms/RegenerateKeyModal.tsx
index c714a15eb98..babbf9989e6 100644
--- a/ui/litellm-dashboard/src/components/organisms/RegenerateKeyModal.tsx
+++ b/ui/litellm-dashboard/src/components/organisms/RegenerateKeyModal.tsx
@@ -68,15 +68,25 @@ export function RegenerateKeyModal({ selectedToken, visible, onClose, onKeyUpdat
if (!duration) return null;
try {
+ const amount = parseInt(duration);
+ if (Number.isNaN(amount)) {
+ throw new Error("Invalid duration format");
+ }
const now = new Date();
+ // Check "mo" before "m" to avoid a false prefix match (e.g. "1mo" → minutes).
let newExpiry: Date;
-
- if (duration.endsWith("s")) {
- newExpiry = add(now, { seconds: parseInt(duration) });
+ if (duration.endsWith("mo")) {
+ newExpiry = add(now, { months: amount });
+ } else if (duration.endsWith("s")) {
+ newExpiry = add(now, { seconds: amount });
+ } else if (duration.endsWith("m")) {
+ newExpiry = add(now, { minutes: amount });
} else if (duration.endsWith("h")) {
- newExpiry = add(now, { hours: parseInt(duration) });
+ newExpiry = add(now, { hours: amount });
} else if (duration.endsWith("d")) {
- newExpiry = add(now, { days: parseInt(duration) });
+ newExpiry = add(now, { days: amount });
+ } else if (duration.endsWith("w")) {
+ newExpiry = add(now, { weeks: amount });
} else {
throw new Error("Invalid duration format");
}
@@ -122,7 +132,9 @@ export function RegenerateKeyModal({ selectedToken, visible, onClose, onKeyUpdat
max_budget: formValues.max_budget,
tpm_limit: formValues.tpm_limit,
rpm_limit: formValues.rpm_limit,
- expires: formValues.duration ? calculateNewExpiryTime(formValues.duration) : selectedToken.expires,
+ expires: formValues.duration
+ ? (calculateNewExpiryTime(formValues.duration) ?? selectedToken.expires)
+ : selectedToken.expires,
};
// Update the parent component with new key data