mirror of
https://github.com/tiennm99/litellm.git
synced 2026-08-06 22:22:23 +00:00
Revert "Regenerate Key State Management and Authentication Issues (#12729)"
This reverts commit 663abbe275.
This commit is contained in:
@@ -13,37 +13,19 @@ import {
|
||||
Title,
|
||||
Badge,
|
||||
TextInput,
|
||||
Select as TremorSelect,
|
||||
Select as TremorSelect
|
||||
} from "@tremor/react";
|
||||
import {
|
||||
ArrowLeftIcon,
|
||||
TrashIcon,
|
||||
RefreshIcon,
|
||||
} 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,
|
||||
Tooltip,
|
||||
Button as AntdButton,
|
||||
} from "antd";
|
||||
import { Form, Input, InputNumber, message, Select, Tooltip, Button as AntdButton } from "antd";
|
||||
import { KeyEditView } from "./key_edit_view";
|
||||
import { RegenerateKeyModal } from "./regenerate_key_modal";
|
||||
import { rolesWithWriteAccess } from "../utils/roles";
|
||||
import { rolesWithWriteAccess } from '../utils/roles';
|
||||
import ObjectPermissionsView from "./object_permissions_view";
|
||||
import LoggingSettingsView from "./logging_settings_view";
|
||||
import {
|
||||
copyToClipboard as utilCopyToClipboard,
|
||||
formatNumberWithCommas,
|
||||
} from "@/utils/dataUtils";
|
||||
import {
|
||||
extractLoggingSettings,
|
||||
formatMetadataForDisplay,
|
||||
} from "./key_info_utils";
|
||||
import { copyToClipboard as utilCopyToClipboard, formatNumberWithCommas } from "@/utils/dataUtils";
|
||||
import { extractLoggingSettings, formatMetadataForDisplay } from "./key_info_utils";
|
||||
import { CopyIcon, CheckIcon } from "lucide-react";
|
||||
|
||||
interface KeyInfoViewProps {
|
||||
@@ -57,38 +39,20 @@ interface KeyInfoViewProps {
|
||||
userRole: string | null;
|
||||
teams: any[] | null;
|
||||
premiumUser: boolean;
|
||||
setAccessToken: (token: string) => void;
|
||||
}
|
||||
|
||||
export default function KeyInfoView({
|
||||
keyId,
|
||||
onClose,
|
||||
keyData,
|
||||
accessToken,
|
||||
userID,
|
||||
userRole,
|
||||
teams,
|
||||
onKeyDataUpdate,
|
||||
onDelete,
|
||||
premiumUser,
|
||||
setAccessToken,
|
||||
}: KeyInfoViewProps) {
|
||||
export default function KeyInfoView({ keyId, onClose, keyData, accessToken, userID, userRole, teams, onKeyDataUpdate, onDelete, premiumUser }: KeyInfoViewProps) {
|
||||
const [isEditing, setIsEditing] = useState(false);
|
||||
const [form] = Form.useForm();
|
||||
const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
|
||||
const [isRegenerateModalOpen, setIsRegenerateModalOpen] = useState(false);
|
||||
const [copiedStates, setCopiedStates] = useState<Record<string, boolean>>({});
|
||||
const [currentKeyData, setCurrentKeyData] = useState(keyData);
|
||||
|
||||
React.useEffect(() => {
|
||||
setCurrentKeyData(keyData);
|
||||
}, [keyData]);
|
||||
|
||||
if (!currentKeyData) {
|
||||
if (!keyData) {
|
||||
return (
|
||||
<div className="p-4">
|
||||
<Button
|
||||
icon={ArrowLeftIcon}
|
||||
<Button
|
||||
icon={ArrowLeftIcon}
|
||||
variant="light"
|
||||
onClick={onClose}
|
||||
className="mb-4"
|
||||
@@ -110,22 +74,19 @@ export default function KeyInfoView({
|
||||
// Handle object_permission updates
|
||||
if (formValues.vector_stores !== undefined) {
|
||||
formValues.object_permission = {
|
||||
...currentKeyData.object_permission,
|
||||
vector_stores: formValues.vector_stores || [],
|
||||
...keyData.object_permission,
|
||||
vector_stores: formValues.vector_stores || []
|
||||
};
|
||||
// Remove vector_stores from the top level as it should be in object_permission
|
||||
delete formValues.vector_stores;
|
||||
}
|
||||
|
||||
if (formValues.mcp_servers_and_groups !== undefined) {
|
||||
const { servers, accessGroups } = formValues.mcp_servers_and_groups || {
|
||||
servers: [],
|
||||
accessGroups: [],
|
||||
};
|
||||
const { servers, accessGroups } = formValues.mcp_servers_and_groups || { servers: [], accessGroups: [] };
|
||||
formValues.object_permission = {
|
||||
...currentKeyData.object_permission,
|
||||
...keyData.object_permission,
|
||||
mcp_servers: servers || [],
|
||||
mcp_access_groups: accessGroups || [],
|
||||
mcp_access_groups: accessGroups || []
|
||||
};
|
||||
// Remove mcp_servers_and_groups from the top level as it should be in object_permission
|
||||
delete formValues.mcp_servers_and_groups;
|
||||
@@ -137,12 +98,8 @@ export default function KeyInfoView({
|
||||
const parsedMetadata = JSON.parse(formValues.metadata);
|
||||
formValues.metadata = {
|
||||
...parsedMetadata,
|
||||
...(formValues.guardrails?.length > 0
|
||||
? { guardrails: formValues.guardrails }
|
||||
: {}),
|
||||
...(formValues.logging_settings
|
||||
? { logging: formValues.logging_settings }
|
||||
: {}),
|
||||
...(formValues.guardrails?.length > 0 ? { guardrails: formValues.guardrails } : {}),
|
||||
...(formValues.logging_settings ? { logging: formValues.logging_settings } : {})
|
||||
};
|
||||
} catch (error) {
|
||||
console.error("Error parsing metadata JSON:", error);
|
||||
@@ -152,12 +109,8 @@ export default function KeyInfoView({
|
||||
} else {
|
||||
formValues.metadata = {
|
||||
...(formValues.metadata || {}),
|
||||
...(formValues.guardrails?.length > 0
|
||||
? { guardrails: formValues.guardrails }
|
||||
: {}),
|
||||
...(formValues.logging_settings
|
||||
? { logging: formValues.logging_settings }
|
||||
: {}),
|
||||
...(formValues.guardrails?.length > 0 ? { guardrails: formValues.guardrails } : {}),
|
||||
...(formValues.logging_settings ? { logging: formValues.logging_settings } : {})
|
||||
};
|
||||
}
|
||||
|
||||
@@ -168,46 +121,31 @@ export default function KeyInfoView({
|
||||
const durationMap: Record<string, string> = {
|
||||
daily: "24h",
|
||||
weekly: "7d",
|
||||
monthly: "30d",
|
||||
monthly: "30d"
|
||||
};
|
||||
formValues.budget_duration = durationMap[formValues.budget_duration];
|
||||
}
|
||||
|
||||
const newKeyValues = await keyUpdateCall(accessToken, formValues);
|
||||
|
||||
setCurrentKeyData((prev) =>
|
||||
prev ? { ...prev, ...newKeyValues } : newKeyValues,
|
||||
);
|
||||
|
||||
if (onKeyDataUpdate) {
|
||||
onKeyDataUpdate(newKeyValues);
|
||||
onKeyDataUpdate(newKeyValues)
|
||||
}
|
||||
|
||||
message.success("Key updated successfully");
|
||||
setIsEditing(false);
|
||||
// Refresh key data here if needed
|
||||
} catch (error) {
|
||||
message.error("Failed to update key");
|
||||
console.error("Error updating key:", error);
|
||||
}
|
||||
};
|
||||
|
||||
const handleRegenerateKeyUpdate = (updatedKeyData: Partial<KeyResponse>) => {
|
||||
setCurrentKeyData((prev) =>
|
||||
prev ? { ...prev, ...updatedKeyData } : (updatedKeyData as KeyResponse),
|
||||
);
|
||||
|
||||
if (onKeyDataUpdate) {
|
||||
onKeyDataUpdate(updatedKeyData);
|
||||
}
|
||||
};
|
||||
|
||||
const handleDelete = async () => {
|
||||
try {
|
||||
if (!accessToken) return;
|
||||
await keyDeleteCall(accessToken as string, currentKeyData.token);
|
||||
await keyDeleteCall(accessToken as string, keyData.token);
|
||||
message.success("Key deleted successfully");
|
||||
if (onDelete) {
|
||||
onDelete();
|
||||
onDelete()
|
||||
}
|
||||
onClose();
|
||||
} catch (error) {
|
||||
@@ -230,47 +168,34 @@ export default function KeyInfoView({
|
||||
<div className="w-full h-screen p-4">
|
||||
<div className="flex justify-between items-center mb-6">
|
||||
<div>
|
||||
<Button
|
||||
icon={ArrowLeftIcon}
|
||||
<Button
|
||||
icon={ArrowLeftIcon}
|
||||
variant="light"
|
||||
onClick={onClose}
|
||||
className="mb-4"
|
||||
>
|
||||
Back to Keys
|
||||
</Button>
|
||||
<Title>{currentKeyData.key_alias || "API Key"}</Title>
|
||||
<div className="flex items-center cursor-pointer">
|
||||
<Text className="text-gray-500 font-mono">
|
||||
{currentKeyData.token}
|
||||
</Text>
|
||||
<Title>{keyData.key_alias || "API Key"}</Title>
|
||||
<div className="flex items-center cursor-pointer"
|
||||
>
|
||||
<Text className="text-gray-500 font-mono">{keyData.token}</Text>
|
||||
<AntdButton
|
||||
type="text"
|
||||
size="small"
|
||||
icon={
|
||||
copiedStates["key-id"] ? (
|
||||
<CheckIcon size={12} />
|
||||
) : (
|
||||
<CopyIcon size={12} />
|
||||
)
|
||||
}
|
||||
onClick={() => copyToClipboard(currentKeyData.token, "key-id")}
|
||||
icon={copiedStates["key-id"] ? <CheckIcon size={12} /> : <CopyIcon size={12} />}
|
||||
onClick={() => copyToClipboard(keyData.token, "key-id")}
|
||||
className={`left-2 z-10 transition-all duration-200 ${
|
||||
copiedStates["key-id"]
|
||||
? "text-green-600 bg-green-50 border-green-200"
|
||||
: "text-gray-500 hover:text-gray-700 hover:bg-gray-100"
|
||||
copiedStates["key-id"]
|
||||
? 'text-green-600 bg-green-50 border-green-200'
|
||||
: 'text-gray-500 hover:text-gray-700 hover:bg-gray-100'
|
||||
}`}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{userRole && rolesWithWriteAccess.includes(userRole) && (
|
||||
<div className="flex gap-2">
|
||||
<Tooltip
|
||||
title={
|
||||
!premiumUser
|
||||
? "This is a LiteLLM Enterprise feature, and requires a valid key to use."
|
||||
: ""
|
||||
}
|
||||
>
|
||||
<Tooltip title={!premiumUser ? "This is a LiteLLM Enterprise feature, and requires a valid key to use." : ""}>
|
||||
<span className="inline-block">
|
||||
<Button
|
||||
icon={RefreshIcon}
|
||||
@@ -297,32 +222,22 @@ export default function KeyInfoView({
|
||||
|
||||
{/* Add RegenerateKeyModal */}
|
||||
<RegenerateKeyModal
|
||||
selectedToken={currentKeyData}
|
||||
selectedToken={keyData}
|
||||
visible={isRegenerateModalOpen}
|
||||
onClose={() => setIsRegenerateModalOpen(false)}
|
||||
accessToken={accessToken}
|
||||
setAccessToken={setAccessToken}
|
||||
premiumUser={premiumUser}
|
||||
onKeyUpdate={handleRegenerateKeyUpdate}
|
||||
/>
|
||||
|
||||
{/* Delete Confirmation Modal */}
|
||||
{isDeleteModalOpen && (
|
||||
<div className="fixed z-10 inset-0 overflow-y-auto">
|
||||
<div className="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0">
|
||||
<div
|
||||
className="fixed inset-0 transition-opacity"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<div className="fixed inset-0 transition-opacity" aria-hidden="true">
|
||||
<div className="absolute inset-0 bg-gray-500 opacity-75"></div>
|
||||
</div>
|
||||
|
||||
<span
|
||||
className="hidden sm:inline-block sm:align-middle sm:h-screen"
|
||||
aria-hidden="true"
|
||||
>
|
||||
​
|
||||
</span>
|
||||
<span className="hidden sm:inline-block sm:align-middle sm:h-screen" aria-hidden="true">​</span>
|
||||
|
||||
<div className="inline-block align-bottom bg-white rounded-lg text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-lg sm:w-full">
|
||||
<div className="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4">
|
||||
@@ -340,7 +255,11 @@ export default function KeyInfoView({
|
||||
</div>
|
||||
</div>
|
||||
<div className="bg-gray-50 px-4 py-3 sm:px-6 sm:flex sm:flex-row-reverse">
|
||||
<Button onClick={handleDelete} color="red" className="ml-2">
|
||||
<Button
|
||||
onClick={handleDelete}
|
||||
color="red"
|
||||
className="ml-2"
|
||||
>
|
||||
Delete
|
||||
</Button>
|
||||
<Button onClick={() => setIsDeleteModalOpen(false)}>
|
||||
@@ -365,41 +284,24 @@ export default function KeyInfoView({
|
||||
<Card>
|
||||
<Text>Spend</Text>
|
||||
<div className="mt-2">
|
||||
<Title>
|
||||
${formatNumberWithCommas(currentKeyData.spend, 4)}
|
||||
</Title>
|
||||
<Text>
|
||||
of{" "}
|
||||
{currentKeyData.max_budget !== null
|
||||
? `$${formatNumberWithCommas(currentKeyData.max_budget)}`
|
||||
: "Unlimited"}
|
||||
</Text>
|
||||
<Title>${formatNumberWithCommas(keyData.spend, 4)}</Title>
|
||||
<Text>of {keyData.max_budget !== null ? `$${formatNumberWithCommas(keyData.max_budget)}` : "Unlimited"}</Text>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<Text>Rate Limits</Text>
|
||||
<div className="mt-2">
|
||||
<Text>
|
||||
TPM:{" "}
|
||||
{currentKeyData.tpm_limit !== null
|
||||
? currentKeyData.tpm_limit
|
||||
: "Unlimited"}
|
||||
</Text>
|
||||
<Text>
|
||||
RPM:{" "}
|
||||
{currentKeyData.rpm_limit !== null
|
||||
? currentKeyData.rpm_limit
|
||||
: "Unlimited"}
|
||||
</Text>
|
||||
<Text>TPM: {keyData.tpm_limit !== null ? keyData.tpm_limit : "Unlimited"}</Text>
|
||||
<Text>RPM: {keyData.rpm_limit !== null ? keyData.rpm_limit : "Unlimited"}</Text>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<Text>Models</Text>
|
||||
<div className="mt-2 flex flex-wrap gap-2">
|
||||
{currentKeyData.models && currentKeyData.models.length > 0 ? (
|
||||
currentKeyData.models.map((model, index) => (
|
||||
{keyData.models && keyData.models.length > 0 ? (
|
||||
keyData.models.map((model, index) => (
|
||||
<Badge key={index} color="red">
|
||||
{model}
|
||||
</Badge>
|
||||
@@ -412,14 +314,14 @@ export default function KeyInfoView({
|
||||
|
||||
<Card>
|
||||
<ObjectPermissionsView
|
||||
objectPermission={currentKeyData.object_permission}
|
||||
objectPermission={keyData.object_permission}
|
||||
variant="inline"
|
||||
accessToken={accessToken}
|
||||
/>
|
||||
</Card>
|
||||
|
||||
<LoggingSettingsView
|
||||
loggingConfigs={extractLoggingSettings(currentKeyData.metadata)}
|
||||
loggingConfigs={extractLoggingSettings(keyData.metadata)}
|
||||
variant="card"
|
||||
/>
|
||||
</Grid>
|
||||
@@ -430,18 +332,16 @@ export default function KeyInfoView({
|
||||
<Card className="overflow-y-auto max-h-[65vh]">
|
||||
<div className="flex justify-between items-center mb-4">
|
||||
<Title>Key Settings</Title>
|
||||
{!isEditing &&
|
||||
userRole &&
|
||||
rolesWithWriteAccess.includes(userRole) && (
|
||||
<Button variant="light" onClick={() => setIsEditing(true)}>
|
||||
Edit Settings
|
||||
</Button>
|
||||
)}
|
||||
{!isEditing && userRole && rolesWithWriteAccess.includes(userRole) && (
|
||||
<Button variant="light" onClick={() => setIsEditing(true)}>
|
||||
Edit Settings
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{isEditing ? (
|
||||
<KeyEditView
|
||||
keyData={currentKeyData}
|
||||
keyData={keyData}
|
||||
onCancel={() => setIsEditing(false)}
|
||||
onSubmit={handleKeyUpdate}
|
||||
teams={teams}
|
||||
@@ -453,57 +353,49 @@ export default function KeyInfoView({
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<Text className="font-medium">Key ID</Text>
|
||||
<Text className="font-mono">{currentKeyData.token}</Text>
|
||||
<Text className="font-mono">{keyData.token}</Text>
|
||||
</div>
|
||||
|
||||
|
||||
<div>
|
||||
<Text className="font-medium">Key Alias</Text>
|
||||
<Text>{currentKeyData.key_alias || "Not Set"}</Text>
|
||||
<Text>{keyData.key_alias || "Not Set"}</Text>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Text className="font-medium">Secret Key</Text>
|
||||
<Text className="font-mono">{currentKeyData.key_name}</Text>
|
||||
<Text className="font-mono">{keyData.key_name}</Text>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Text className="font-medium">Team ID</Text>
|
||||
<Text>{currentKeyData.team_id || "Not Set"}</Text>
|
||||
<Text>{keyData.team_id || "Not Set"}</Text>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Text className="font-medium">Organization</Text>
|
||||
<Text>{currentKeyData.organization_id || "Not Set"}</Text>
|
||||
<Text>{keyData.organization_id || "Not Set"}</Text>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Text className="font-medium">Created</Text>
|
||||
<Text>
|
||||
{new Date(currentKeyData.created_at).toLocaleString()}
|
||||
</Text>
|
||||
<Text>{new Date(keyData.created_at).toLocaleString()}</Text>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Text className="font-medium">Expires</Text>
|
||||
<Text>
|
||||
{currentKeyData.expires
|
||||
? new Date(currentKeyData.expires).toLocaleString()
|
||||
: "Never"}
|
||||
</Text>
|
||||
<Text>{keyData.expires ? new Date(keyData.expires).toLocaleString() : "Never"}</Text>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Text className="font-medium">Spend</Text>
|
||||
<Text>
|
||||
${formatNumberWithCommas(currentKeyData.spend, 4)} USD
|
||||
</Text>
|
||||
<Text>${formatNumberWithCommas(keyData.spend, 4)} USD</Text>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Text className="font-medium">Budget</Text>
|
||||
<Text>
|
||||
{currentKeyData.max_budget !== null
|
||||
? `$${formatNumberWithCommas(currentKeyData.max_budget, 2)}`
|
||||
{keyData.max_budget !== null
|
||||
? `$${formatNumberWithCommas(keyData.max_budget, 2)}`
|
||||
: "Unlimited"}
|
||||
</Text>
|
||||
</div>
|
||||
@@ -511,9 +403,8 @@ export default function KeyInfoView({
|
||||
<div>
|
||||
<Text className="font-medium">Models</Text>
|
||||
<div className="flex flex-wrap gap-2 mt-1">
|
||||
{currentKeyData.models &&
|
||||
currentKeyData.models.length > 0 ? (
|
||||
currentKeyData.models.map((model, index) => (
|
||||
{keyData.models && keyData.models.length > 0 ? (
|
||||
keyData.models.map((model, index) => (
|
||||
<span
|
||||
key={index}
|
||||
className="px-2 py-1 bg-blue-100 rounded text-xs"
|
||||
@@ -529,60 +420,29 @@ export default function KeyInfoView({
|
||||
|
||||
<div>
|
||||
<Text className="font-medium">Rate Limits</Text>
|
||||
<Text>
|
||||
TPM:{" "}
|
||||
{currentKeyData.tpm_limit !== null
|
||||
? currentKeyData.tpm_limit
|
||||
: "Unlimited"}
|
||||
</Text>
|
||||
<Text>
|
||||
RPM:{" "}
|
||||
{currentKeyData.rpm_limit !== null
|
||||
? currentKeyData.rpm_limit
|
||||
: "Unlimited"}
|
||||
</Text>
|
||||
<Text>
|
||||
Max Parallel Requests:{" "}
|
||||
{currentKeyData.max_parallel_requests !== null
|
||||
? currentKeyData.max_parallel_requests
|
||||
: "Unlimited"}
|
||||
</Text>
|
||||
<Text>
|
||||
Model TPM Limits:{" "}
|
||||
{currentKeyData.metadata?.model_tpm_limit
|
||||
? JSON.stringify(
|
||||
currentKeyData.metadata.model_tpm_limit,
|
||||
)
|
||||
: "Unlimited"}
|
||||
</Text>
|
||||
<Text>
|
||||
Model RPM Limits:{" "}
|
||||
{currentKeyData.metadata?.model_rpm_limit
|
||||
? JSON.stringify(
|
||||
currentKeyData.metadata.model_rpm_limit,
|
||||
)
|
||||
: "Unlimited"}
|
||||
</Text>
|
||||
<Text>TPM: {keyData.tpm_limit !== null ? keyData.tpm_limit : "Unlimited"}</Text>
|
||||
<Text>RPM: {keyData.rpm_limit !== null ? keyData.rpm_limit : "Unlimited"}</Text>
|
||||
<Text>Max Parallel Requests: {keyData.max_parallel_requests !== null ? keyData.max_parallel_requests : "Unlimited"}</Text>
|
||||
<Text>Model TPM Limits: {keyData.metadata?.model_tpm_limit ? JSON.stringify(keyData.metadata.model_tpm_limit) : "Unlimited"}</Text>
|
||||
<Text>Model RPM Limits: {keyData.metadata?.model_rpm_limit ? JSON.stringify(keyData.metadata.model_rpm_limit) : "Unlimited"}</Text>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Text className="font-medium">Metadata</Text>
|
||||
<pre className="bg-gray-100 p-2 rounded text-xs overflow-auto mt-1">
|
||||
{formatMetadataForDisplay(currentKeyData.metadata)}
|
||||
{formatMetadataForDisplay(keyData.metadata)}
|
||||
</pre>
|
||||
</div>
|
||||
|
||||
<ObjectPermissionsView
|
||||
objectPermission={currentKeyData.object_permission}
|
||||
objectPermission={keyData.object_permission}
|
||||
variant="inline"
|
||||
className="pt-4 border-t border-gray-200"
|
||||
accessToken={accessToken}
|
||||
/>
|
||||
|
||||
<LoggingSettingsView
|
||||
loggingConfigs={extractLoggingSettings(
|
||||
currentKeyData.metadata,
|
||||
)}
|
||||
loggingConfigs={extractLoggingSettings(keyData.metadata)}
|
||||
variant="inline"
|
||||
className="pt-4 border-t border-gray-200"
|
||||
/>
|
||||
@@ -594,4 +454,4 @@ export default function KeyInfoView({
|
||||
</TabGroup>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -11,9 +11,7 @@ interface RegenerateKeyModalProps {
|
||||
visible: boolean;
|
||||
onClose: () => void;
|
||||
accessToken: string | null;
|
||||
setAccessToken: (token: string) => void;
|
||||
premiumUser: boolean;
|
||||
onKeyUpdate?: (updatedKeyData: Partial<KeyResponse>) => void;
|
||||
}
|
||||
|
||||
export function RegenerateKeyModal({
|
||||
@@ -21,9 +19,7 @@ export function RegenerateKeyModal({
|
||||
visible,
|
||||
onClose,
|
||||
accessToken,
|
||||
setAccessToken,
|
||||
premiumUser,
|
||||
onKeyUpdate,
|
||||
}: RegenerateKeyModalProps) {
|
||||
const [form] = Form.useForm();
|
||||
const [regeneratedKey, setRegeneratedKey] = useState<string | null>(null);
|
||||
@@ -86,59 +82,19 @@ export function RegenerateKeyModal({
|
||||
const handleRegenerateKey = async () => {
|
||||
if (!selectedToken || !accessToken) return;
|
||||
|
||||
const isOwnKey = selectedToken.token === accessToken;
|
||||
|
||||
setIsRegenerating(true);
|
||||
try {
|
||||
const formValues = await form.validateFields();
|
||||
const response = await regenerateKeyCall(accessToken, selectedToken.token, formValues);
|
||||
setRegeneratedKey(response.key);
|
||||
|
||||
if (onKeyUpdate) {
|
||||
const updatedKeyData = {
|
||||
...selectedToken,
|
||||
token: response.key,
|
||||
key_name: response.key,
|
||||
max_budget: formValues.max_budget,
|
||||
tpm_limit: formValues.tpm_limit,
|
||||
rpm_limit: formValues.rpm_limit,
|
||||
expires: formValues.duration
|
||||
? calculateNewExpiryTime(formValues.duration) || undefined
|
||||
: selectedToken.expires,
|
||||
};
|
||||
onKeyUpdate(updatedKeyData);
|
||||
}
|
||||
|
||||
// If user regenerated their own authentication key, update accessToken
|
||||
if (isOwnKey) {
|
||||
setAccessToken(response.key);
|
||||
}
|
||||
|
||||
message.success("API Key regenerated successfully");
|
||||
} catch (error) {
|
||||
console.error("Error regenerating key:", error);
|
||||
message.error("Failed to regenerate API Key");
|
||||
setIsRegenerating(false);
|
||||
setIsRegenerating(false); // Reset regenerating state on error
|
||||
}
|
||||
};
|
||||
|
||||
const calculateNewExpiryTime = (duration: string) => {
|
||||
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 {
|
||||
return null;
|
||||
}
|
||||
|
||||
return newExpiry.toISOString();
|
||||
};
|
||||
|
||||
const handleClose = () => {
|
||||
setRegeneratedKey(null);
|
||||
setIsRegenerating(false);
|
||||
@@ -151,43 +107,49 @@ export function RegenerateKeyModal({
|
||||
title="Regenerate API Key"
|
||||
open={visible}
|
||||
onCancel={handleClose}
|
||||
footer={
|
||||
regeneratedKey
|
||||
? [
|
||||
<Button key="close" onClick={handleClose}>
|
||||
Close
|
||||
</Button>,
|
||||
]
|
||||
: [
|
||||
<Button key="cancel" onClick={handleClose} className="mr-2">
|
||||
Cancel
|
||||
</Button>,
|
||||
<Button key="regenerate" onClick={handleRegenerateKey} disabled={isRegenerating}>
|
||||
{isRegenerating ? "Regenerating..." : "Regenerate"}
|
||||
</Button>,
|
||||
]
|
||||
}
|
||||
footer={regeneratedKey ? [
|
||||
<Button key="close" onClick={handleClose}>
|
||||
Close
|
||||
</Button>,
|
||||
] : [
|
||||
<Button key="cancel" onClick={handleClose} className="mr-2">
|
||||
Cancel
|
||||
</Button>,
|
||||
<Button
|
||||
key="regenerate"
|
||||
onClick={handleRegenerateKey}
|
||||
disabled={isRegenerating}
|
||||
>
|
||||
{isRegenerating ? "Regenerating..." : "Regenerate"}
|
||||
</Button>,
|
||||
]}
|
||||
>
|
||||
{regeneratedKey ? (
|
||||
<Grid numItems={1} className="gap-2 w-full">
|
||||
<Title>Regenerated Key</Title>
|
||||
<Col numColSpan={1}>
|
||||
<p>
|
||||
Please replace your old key with the new key generated. For security reasons,{" "}
|
||||
<b>you will not be able to view it again</b> through your LiteLLM account. If you lose this secret key,
|
||||
you will need to generate a new one.
|
||||
Please replace your old key with the new key generated. For
|
||||
security reasons, <b>you will not be able to view it again</b>{" "}
|
||||
through your LiteLLM account. If you lose this secret key, you
|
||||
will need to generate a new one.
|
||||
</p>
|
||||
</Col>
|
||||
<Col numColSpan={1}>
|
||||
<Text className="mt-3">Key Alias:</Text>
|
||||
<div className="bg-gray-100 p-2 rounded mb-2">
|
||||
<pre className="break-words whitespace-normal">{selectedToken?.key_alias || "No alias set"}</pre>
|
||||
<pre className="break-words whitespace-normal">
|
||||
{selectedToken?.key_alias || "No alias set"}
|
||||
</pre>
|
||||
</div>
|
||||
<Text className="mt-3">New API Key:</Text>
|
||||
<div className="bg-gray-100 p-2 rounded mb-2">
|
||||
<pre className="break-words whitespace-normal">{regeneratedKey}</pre>
|
||||
</div>
|
||||
<CopyToClipboard text={regeneratedKey} onCopy={() => message.success("API Key copied to clipboard")}>
|
||||
<CopyToClipboard
|
||||
text={regeneratedKey}
|
||||
onCopy={() => message.success("API Key copied to clipboard")}
|
||||
>
|
||||
<Button className="mt-3">Copy API Key</Button>
|
||||
</CopyToClipboard>
|
||||
</Col>
|
||||
@@ -220,9 +182,13 @@ export function RegenerateKeyModal({
|
||||
<div className="mt-2 text-sm text-gray-500">
|
||||
Current expiry: {selectedToken?.expires ? new Date(selectedToken.expires).toLocaleString() : "Never"}
|
||||
</div>
|
||||
{newExpiryTime && <div className="mt-2 text-sm text-green-600">New expiry: {newExpiryTime}</div>}
|
||||
{newExpiryTime && (
|
||||
<div className="mt-2 text-sm text-green-600">
|
||||
New expiry: {newExpiryTime}
|
||||
</div>
|
||||
)}
|
||||
</Form>
|
||||
)}
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user