diff --git a/ui/litellm-dashboard/src/components/networking.tsx b/ui/litellm-dashboard/src/components/networking.tsx index f5507647898..a6bd5d32cb0 100644 --- a/ui/litellm-dashboard/src/components/networking.tsx +++ b/ui/litellm-dashboard/src/components/networking.tsx @@ -770,6 +770,37 @@ export const claimOnboardingToken = async ( throw error; } }; + +export const regenerateKeyCall = async (accessToken: string, keyToRegenerate: string) => { + try { + const url = proxyBaseUrl + ? `${proxyBaseUrl}/key/${keyToRegenerate}/regenerate` + : `/key/${keyToRegenerate}/regenerate`; + + const response = await fetch(url, { + method: "POST", + headers: { + [globalLitellmHeaderName]: `Bearer ${accessToken}`, + "Content-Type": "application/json", + }, + body: JSON.stringify({}), + }); + + if (!response.ok) { + const errorData = await response.text(); + handleError(errorData); + throw new Error("Network response was not ok"); + } + + const data = await response.json(); + console.log("Regenerate key Response:", data); + return data; + } catch (error) { + console.error("Failed to regenerate key:", error); + throw error; + } +}; + let ModelListerrorShown = false; let errorTimer: NodeJS.Timeout | null = null; diff --git a/ui/litellm-dashboard/src/components/view_key_table.tsx b/ui/litellm-dashboard/src/components/view_key_table.tsx index 44a1f6ece84..752aad37f98 100644 --- a/ui/litellm-dashboard/src/components/view_key_table.tsx +++ b/ui/litellm-dashboard/src/components/view_key_table.tsx @@ -1,12 +1,14 @@ "use client"; import React, { useEffect, useState } from "react"; import { keyDeleteCall, modelAvailableCall } from "./networking"; -import { InformationCircleIcon, StatusOnlineIcon, TrashIcon, PencilAltIcon } from "@heroicons/react/outline"; -import { keySpendLogsCall, PredictedSpendLogsCall, keyUpdateCall, modelInfoCall } from "./networking"; +import { InformationCircleIcon, StatusOnlineIcon, TrashIcon, PencilAltIcon, RefreshIcon } from "@heroicons/react/outline"; +import { keySpendLogsCall, PredictedSpendLogsCall, keyUpdateCall, modelInfoCall, regenerateKeyCall } from "./networking"; import { Badge, Card, Table, + Grid, + Col, Button, TableBody, TableCell, @@ -33,6 +35,8 @@ import { Select, } from "antd"; +import { CopyToClipboard } from "react-copy-to-clipboard"; + const { Option } = Select; const isLocal = process.env.NODE_ENV === "development"; const proxyBaseUrl = isLocal ? "http://localhost:4000" : null; @@ -109,6 +113,8 @@ const ViewKeyTable: React.FC = ({ const [userModels, setUserModels] = useState([]); const initialKnownTeamIDs: Set = new Set(); const [modelLimitModalVisible, setModelLimitModalVisible] = useState(false); + const [regenerateDialogVisible, setRegenerateDialogVisible] = useState(false); + const [regeneratedKey, setRegeneratedKey] = useState(null); const [knownTeamIDs, setKnownTeamIDs] = useState(initialKnownTeamIDs); @@ -612,6 +618,18 @@ const ViewKeyTable: React.FC = ({ setKeyToDelete(null); }; + const handleRegenerateKey = async () => { + try { + const response = await regenerateKeyCall(accessToken, selectedToken.token); + setRegeneratedKey(response.key); + setRegenerateDialogVisible(false); + message.success("API Key regenerated successfully"); + } catch (error) { + console.error("Error regenerating key:", error); + message.error("Failed to regenerate API Key"); + } + }; + if (data == null) { return; } @@ -768,6 +786,7 @@ const ViewKeyTable: React.FC = ({ size="sm" /> + = ({ size="sm" onClick={() => handleEditClick(item)} /> + { + setSelectedToken(item); + setRegenerateDialogVisible(true); + }} + icon={RefreshIcon} + size="sm" + /> handleDelete(item)} icon={TrashIcon} @@ -942,6 +969,58 @@ const ViewKeyTable: React.FC = ({ accessToken={accessToken} /> )} + + {/* Regenerate Key Confirmation Dialog */} + setRegenerateDialogVisible(false)} + > +

Are you sure you want to regenerate this key?

+

Key Alias:

+
{selectedToken?.key_alias || 'No alias set'}
+
+ + {/* Regenerated Key Display Modal */} + {regeneratedKey && ( + setRegeneratedKey(null)} + onCancel={() => setRegeneratedKey(null)} + footer={null} + > + + Save your New Key + +

+ Please save this new secret key somewhere safe and accessible. For + security reasons, you will not be able to view it again through + your LiteLLM account. If you lose this secret key, you will need to + generate a new one. +

+ + + New API Key: +
+
+                {regeneratedKey}
+              
+
+ message.success("API Key copied to clipboard")}> + + + +
+
+ )} ); };