"use client"; import React, { useEffect, useState, useMemo } from "react"; import { keyDeleteCall, modelAvailableCall, getGuardrailsList, Organization, } from "./networking"; import { add } from "date-fns"; 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, TableHead, TableHeaderCell, TableRow, Dialog, DialogPanel, Text, Title, Subtitle, Icon, BarChart, TextInput, Textarea, Select, SelectItem, } from "@tremor/react"; import { InfoCircleOutlined } from "@ant-design/icons"; import { fetchAvailableModelsForTeamOrKey, getModelDisplayName, } from "./key_team_helpers/fetch_available_models_team_key"; import { MultiSelect, MultiSelectItem, } from "@tremor/react"; import { Button as Button2, Modal, Form, Input, Select as Select2, InputNumber, message, Tooltip, DatePicker, } from "antd"; import { CopyToClipboard } from "react-copy-to-clipboard"; import TextArea from "antd/es/input/TextArea"; import useKeyList from "./key_team_helpers/key_list"; import { KeyResponse } from "./key_team_helpers/key_list"; import { AllKeysTable } from "./all_keys_table"; import { Team } from "./key_team_helpers/key_list"; import { Setter } from "@/types"; const isLocal = process.env.NODE_ENV === "development"; const proxyBaseUrl = isLocal ? "http://localhost:4000" : null; if (isLocal != true) { console.log = function () {}; } interface EditKeyModalProps { visible: boolean; onCancel: () => void; token: any; // Assuming TeamType is a type representing your team object onSubmit: (data: FormData) => void; // Assuming FormData is the type of data to be submitted } interface ModelLimitModalProps { visible: boolean; onCancel: () => void; token: KeyResponse; onSubmit: (updatedMetadata: any) => void; accessToken: string; } // Define the props type interface ViewKeyTableProps { userID: string; userRole: string | null; accessToken: string; selectedTeam: any | null; setSelectedTeam: React.Dispatch>; data: any[] | null; setData: React.Dispatch>; teams: Team[] | null; premiumUser: boolean; currentOrg: Organization | null; organizations: Organization[] | null; setCurrentOrg: React.Dispatch>; selectedKeyAlias: string | null; setSelectedKeyAlias: Setter; } interface ItemData { key_alias: string | null; key_name: string; spend: string; max_budget: string | null; models: string[]; tpm_limit: string | null; rpm_limit: string | null; token: string; token_id: string | null; id: number; team_id: string; metadata: any; user_id: string | null; expires: any; budget_duration: string | null; budget_reset_at: string | null; // Add any other properties that exist in the item data } interface ModelLimits { [key: string]: number; // Index signature allowing string keys } interface CombinedLimit { tpm: number; rpm: number; } interface CombinedLimits { [key: string]: CombinedLimit; // Index signature allowing string keys } const ViewKeyTable: React.FC = ({ userID, userRole, accessToken, selectedTeam, setSelectedTeam, data, setData, teams, premiumUser, currentOrg, organizations, setCurrentOrg, selectedKeyAlias, setSelectedKeyAlias }) => { const [isButtonClicked, setIsButtonClicked] = useState(false); const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false); const [keyToDelete, setKeyToDelete] = useState(null); const [selectedItem, setSelectedItem] = useState(null); const [spendData, setSpendData] = useState< { day: string; spend: number }[] | null >(null); // NEW: Declare filter states for team and key alias. const [teamFilter, setTeamFilter] = useState(selectedTeam?.team_id || ""); const [keyAliasFilter, setKeyAliasFilter] = useState(""); // Keep the team filter in sync with the incoming prop. useEffect(() => { setTeamFilter(selectedTeam?.team_id || ""); }, [selectedTeam]); // Build a memoized filters object for the backend call. // Pass filters into the hook so the API call includes these query parameters. const { keys, isLoading, error, pagination, refresh, setKeys } = useKeyList({ selectedTeam, currentOrg, selectedKeyAlias, accessToken, }); // Make both refresh and addKey functions available globally if (typeof window !== 'undefined') { window.refreshKeysList = refresh; window.addNewKeyToList = (newKey) => { // Add the new key to the keys list without making an API call setKeys((prevKeys) => [newKey, ...prevKeys]); }; } const handlePageChange = (newPage: number) => { refresh({ page: newPage }); }; const [editModalVisible, setEditModalVisible] = useState(false); const [infoDialogVisible, setInfoDialogVisible] = useState(false); const [selectedToken, setSelectedToken] = useState(null); 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 [regenerateFormData, setRegenerateFormData] = useState(null); const [regenerateForm] = Form.useForm(); const [newExpiryTime, setNewExpiryTime] = useState(null); const [knownTeamIDs, setKnownTeamIDs] = useState(initialKnownTeamIDs); const [guardrailsList, setGuardrailsList] = useState([]); 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("en-US", { year: "numeric", month: "numeric", day: "numeric", hour: "numeric", minute: "numeric", second: "numeric", hour12: true, }); } catch (error) { return null; } }; console.log("in calculateNewExpiryTime for selectedToken", selectedToken); // When a new duration is entered if (regenerateFormData?.duration) { setNewExpiryTime(calculateNewExpiryTime(regenerateFormData.duration)); } else { setNewExpiryTime(null); } console.log("calculateNewExpiryTime:", newExpiryTime); }, [selectedToken, regenerateFormData?.duration]); useEffect(() => { const fetchUserModels = async () => { try { if (userID === null || userRole === null || accessToken === null) { return; } const models = await fetchAvailableModelsForTeamOrKey( userID, userRole, accessToken ); if (models) { setUserModels(models); } } catch (error) { console.error("Error fetching user models:", error); } }; fetchUserModels(); }, [accessToken, userID, userRole]); const handleModelLimitClick = (token: KeyResponse) => { setSelectedToken(token); setModelLimitModalVisible(true); }; const handleModelLimitSubmit = async (updatedMetadata: any) => { if (accessToken == null || selectedToken == null) { return; } const formValues = { ...selectedToken, metadata: updatedMetadata, key: selectedToken.token, }; try { let newKeyValues = await keyUpdateCall(accessToken, formValues); console.log("Model limits updated:", newKeyValues); // Update the keys with the updated key if (data) { const updatedData = data.map((key) => key.token === selectedToken.token ? newKeyValues : key ); setData(updatedData); } message.success("Model-specific limits updated successfully"); } catch (error) { console.error("Error updating model-specific limits:", error); message.error("Failed to update model-specific limits"); } setModelLimitModalVisible(false); setSelectedToken(null); }; useEffect(() => { if (teams) { const teamIDSet: Set = new Set(); teams.forEach((team: any, index: number) => { const team_obj: string = team.team_id; teamIDSet.add(team_obj); }); setKnownTeamIDs(teamIDSet); } }, [teams]); const confirmDelete = async () => { if (keyToDelete == null || data == null) { return; } try { await keyDeleteCall(accessToken, keyToDelete); // Successfully completed the deletion. Update the state to trigger a rerender. const filteredData = data.filter((item) => item.token !== keyToDelete); setData(filteredData); } catch (error) { console.error("Error deleting the key:", error); // Handle any error situations, such as displaying an error message to the user. } // Close the confirmation modal and reset the keyToDelete setIsDeleteModalOpen(false); setKeyToDelete(null); }; const cancelDelete = () => { // Close the confirmation modal and reset the keyToDelete setIsDeleteModalOpen(false); setKeyToDelete(null); }; const handleRegenerateClick = (token: any) => { setSelectedToken(token); setNewExpiryTime(null); regenerateForm.setFieldsValue({ key_alias: token.key_alias, max_budget: token.max_budget, tpm_limit: token.tpm_limit, rpm_limit: token.rpm_limit, duration: token.duration || "", }); setRegenerateDialogVisible(true); }; const handleRegenerateFormChange = (field: string, value: any) => { setRegenerateFormData((prev: any) => ({ ...prev, [field]: value, })); }; const handleRegenerateKey = async () => { if (!premiumUser) { message.error( "Regenerate API Key is an Enterprise feature. Please upgrade to use this feature." ); return; } if (selectedToken == null) { return; } try { const formValues = await regenerateForm.validateFields(); const response = await regenerateKeyCall( accessToken, selectedToken.token, formValues ); setRegeneratedKey(response.key); // Update the data state with the new key_name if (data) { const updatedData = data.map((item) => item.token === selectedToken?.token ? { ...item, key_name: response.key_name, ...formValues } : item ); setData(updatedData); } setRegenerateDialogVisible(false); regenerateForm.resetFields(); message.success("API Key regenerated successfully"); } catch (error) { console.error("Error regenerating key:", error); message.error("Failed to regenerate API Key"); } }; return (
{isDeleteModalOpen && (
{/* Modal Panel */} {/* Confirmation Modal Content */}

Delete Key

Are you sure you want to delete this key ?

)} {/* Regenerate Key Form Modal */} { setRegenerateDialogVisible(false); regenerateForm.resetFields(); }} footer={[ , , ]} > {premiumUser ?
{ if ("duration" in changedValues) { handleRegenerateFormChange("duration", changedValues.duration); } }} >
Current expiry:{" "} {selectedToken?.expires != null ? new Date(selectedToken.expires).toLocaleString() : "Never"}
{newExpiryTime && (
New expiry: {newExpiryTime}
)}
:

Upgrade to use this feature

}
{/* Regenerated Key Display Modal */} {regeneratedKey && ( setRegeneratedKey(null)} footer={[ , ]} > 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:
                  {regeneratedKey}
                
message.success("API Key copied to clipboard")} >
)}
); }; // Update the type declaration to include the new function declare global { interface Window { refreshKeysList?: () => void; addNewKeyToList?: (newKey: any) => void; } } export default ViewKeyTable;