diff --git a/ui/litellm-dashboard/src/components/key_info_view.tsx b/ui/litellm-dashboard/src/components/key_info_view.tsx
index 1c7826fe856..e76aea61eb8 100644
--- a/ui/litellm-dashboard/src/components/key_info_view.tsx
+++ b/ui/litellm-dashboard/src/components/key_info_view.tsx
@@ -15,11 +15,12 @@ import {
TextInput,
Select as TremorSelect
} from "@tremor/react";
-import { ArrowLeftIcon, TrashIcon } from "@heroicons/react/outline";
+import { ArrowLeftIcon, TrashIcon, RefreshIcon } from "@heroicons/react/outline";
import { keyDeleteCall, keyUpdateCall } from "./networking";
import { KeyResponse } from "./key_team_helpers/key_list";
import { Form, Input, InputNumber, message, Select } from "antd";
import { KeyEditView } from "./key_edit_view";
+import { RegenerateKeyModal } from "./regenerate_key_modal";
interface KeyInfoViewProps {
keyId: string;
@@ -29,12 +30,14 @@ interface KeyInfoViewProps {
userID: string | null;
userRole: string | null;
teams: any[] | null;
+ premiumUser: boolean;
}
-export default function KeyInfoView({ keyId, onClose, keyData, accessToken, userID, userRole, teams }: KeyInfoViewProps) {
+export default function KeyInfoView({ keyId, onClose, keyData, accessToken, userID, userRole, teams, premiumUser }: KeyInfoViewProps) {
const [isEditing, setIsEditing] = useState(false);
const [form] = Form.useForm();
const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
+ const [isRegenerateModalOpen, setIsRegenerateModalOpen] = useState(false);
if (!keyData) {
return (
@@ -126,16 +129,38 @@ export default function KeyInfoView({ keyId, onClose, keyData, accessToken, user
{keyData.key_alias || "API Key"}
{keyData.token}
- setIsDeleteModalOpen(true)}
- >
- Delete Key
-
+
+ setIsRegenerateModalOpen(true)}
+ className="flex items-center"
+ >
+ Regenerate Key
+
+ setIsDeleteModalOpen(true)}
+ className="flex items-center"
+ >
+ Delete Key
+
+
+ {/* Add RegenerateKeyModal */}
+ setIsRegenerateModalOpen(false)}
+ accessToken={accessToken}
+ onSuccess={(newKeyData) => {
+ // Handle the updated key data here if needed
+ setIsRegenerateModalOpen(false);
+ }}
+ />
+
{/* Delete Confirmation Modal */}
{isDeleteModalOpen && (
diff --git a/ui/litellm-dashboard/src/components/key_team_helpers/key_list.tsx b/ui/litellm-dashboard/src/components/key_team_helpers/key_list.tsx
index 0f36c86dc5c..946f4d39509 100644
--- a/ui/litellm-dashboard/src/components/key_team_helpers/key_list.tsx
+++ b/ui/litellm-dashboard/src/components/key_team_helpers/key_list.tsx
@@ -29,6 +29,7 @@ export interface KeyResponse {
metadata: Record
;
tpm_limit: number;
rpm_limit: number;
+ duration: string;
budget_duration: string;
budget_reset_at: string;
allowed_cache_controls: string[];
diff --git a/ui/litellm-dashboard/src/components/regenerate_key_modal.tsx b/ui/litellm-dashboard/src/components/regenerate_key_modal.tsx
new file mode 100644
index 00000000000..463a63dd816
--- /dev/null
+++ b/ui/litellm-dashboard/src/components/regenerate_key_modal.tsx
@@ -0,0 +1,182 @@
+import React, { useEffect, useState } from "react";
+import { Button, Text, TextInput, Title, Grid, Col } from "@tremor/react";
+import { Modal, Form, InputNumber, message } from "antd";
+import { add } from "date-fns";
+import { regenerateKeyCall } from "./networking";
+import { KeyResponse } from "./key_team_helpers/key_list";
+import { CopyToClipboard } from "react-copy-to-clipboard";
+
+interface RegenerateKeyModalProps {
+ selectedToken: KeyResponse | null;
+ visible: boolean;
+ onClose: () => void;
+ accessToken: string | null;
+ onSuccess?: (newKeyData: any) => void;
+}
+
+export function RegenerateKeyModal({
+ selectedToken,
+ visible,
+ onClose,
+ accessToken,
+ onSuccess,
+}: RegenerateKeyModalProps) {
+ const [form] = Form.useForm();
+ const [regeneratedKey, setRegeneratedKey] = useState(null);
+ const [regenerateFormData, setRegenerateFormData] = useState(null);
+ const [newExpiryTime, setNewExpiryTime] = useState(null);
+
+ useEffect(() => {
+ if (visible && selectedToken) {
+ form.setFieldsValue({
+ key_alias: selectedToken.key_alias,
+ max_budget: selectedToken.max_budget,
+ tpm_limit: selectedToken.tpm_limit,
+ rpm_limit: selectedToken.rpm_limit,
+ duration: selectedToken.duration || "",
+ });
+ }
+ }, [visible, selectedToken, form]);
+
+ useEffect(() => {
+ const calculateNewExpiryTime = (duration: string | undefined) => {
+ if (!duration) return null;
+
+ try {
+ const now = new Date();
+ let newExpiry: Date;
+
+ if (duration.endsWith("s")) {
+ newExpiry = add(now, { seconds: parseInt(duration) });
+ } else if (duration.endsWith("h")) {
+ newExpiry = add(now, { hours: parseInt(duration) });
+ } else if (duration.endsWith("d")) {
+ newExpiry = add(now, { days: parseInt(duration) });
+ } else {
+ throw new Error("Invalid duration format");
+ }
+
+ return newExpiry.toLocaleString();
+ } catch (error) {
+ return null;
+ }
+ };
+
+ if (regenerateFormData?.duration) {
+ setNewExpiryTime(calculateNewExpiryTime(regenerateFormData.duration));
+ } else {
+ setNewExpiryTime(null);
+ }
+ }, [regenerateFormData?.duration]);
+
+ const handleRegenerateKey = async () => {
+
+ if (!selectedToken || !accessToken) return;
+
+ try {
+ const formValues = await form.validateFields();
+ const response = await regenerateKeyCall(accessToken, selectedToken.token, formValues);
+ setRegeneratedKey(response.key);
+ if (onSuccess) {
+ onSuccess({ ...selectedToken, key_name: response.key_name, ...formValues });
+ }
+ message.success("API Key regenerated successfully");
+ } catch (error) {
+ console.error("Error regenerating key:", error);
+ message.error("Failed to regenerate API Key");
+ }
+ };
+
+ const handleClose = () => {
+ setRegeneratedKey(null);
+ form.resetFields();
+ onClose();
+ };
+
+ return (
+
+ Close
+ ,
+ ] : [
+
+ Cancel
+ ,
+
+ Regenerate
+ ,
+ ]}
+ >
+ {regeneratedKey ? (
+
+ Regenerated Key
+
+
+ Please replace your old key with the new key generated. 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.
+
+
+
+ Key Alias:
+
+
+ {selectedToken?.key_alias || "No alias set"}
+
+
+ New API Key:
+
+ message.success("API Key copied to clipboard")}
+ >
+ Copy API Key
+
+
+
+ ) : (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Current expiry: {selectedToken?.expires ? new Date(selectedToken.expires).toLocaleString() : "Never"}
+
+ {newExpiryTime && (
+
+ New expiry: {newExpiryTime}
+
+ )}
+
+ )
+ }
+
+ );
+}
\ No newline at end of file