"use client"; import React, { useEffect, useState } from "react"; import { keyDeleteCall, modelAvailableCall } 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, } from "@tremor/react"; import { Select as Select3, SelectItem, MultiSelect, MultiSelectItem } from "@tremor/react"; import { Button as Button2, Modal, Form, Input, Select as Select2, InputNumber, message, Select, Tooltip, DatePicker, } from "antd"; import { CopyToClipboard } from "react-copy-to-clipboard"; import TextArea from "antd/es/input/TextArea"; const { Option } = Select; 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: ItemData; onSubmit: (updatedMetadata: any) => void; accessToken: string; } // Define the props type interface ViewKeyTableProps { userID: string; userRole: string | null; accessToken: string; selectedTeam: any | null; data: any[] | null; setData: React.Dispatch>; teams: any[] | null; premiumUser: boolean; } 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 } const ViewKeyTable: React.FC = ({ userID, userRole, accessToken, selectedTeam, data, setData, teams, premiumUser }) => { 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 ); const [predictedSpendString, setPredictedSpendString] = useState(""); 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); // Function to check if user is admin of a team const isUserTeamAdmin = (team: any) => { if (!team.members_with_roles) return false; return team.members_with_roles.some( (member: any) => member.role === "admin" && member.user_id === userID ); }; // Combine all keys that user should have access to const all_keys_to_display = React.useMemo(() => { let allKeys: any[] = []; // If no teams, return personal keys if (!teams || teams.length === 0) { return data; } teams.forEach(team => { // For default team or when user is not admin, use personal keys (data) if (team.team_id === "default-team" || !isUserTeamAdmin(team)) { if (selectedTeam && selectedTeam.team_id === team.team_id && data) { allKeys = [...allKeys, ...data.filter(key => key.team_id === team.team_id)]; } } // For teams where user is admin, use team keys else if (isUserTeamAdmin(team)) { if (selectedTeam && selectedTeam.team_id === team.team_id) { allKeys = [...allKeys, ...(team.keys || [])]; } } }); // If no team is selected, show all accessible keys if (!selectedTeam && data) { const personalKeys = data.filter(key => !key.team_id || key.team_id === "default-team"); const adminTeamKeys = teams .filter(team => isUserTeamAdmin(team)) .flatMap(team => team.keys || []); allKeys = [...personalKeys, ...adminTeamKeys]; } // Filter out litellm-dashboard keys allKeys = allKeys.filter(key => key.team_id !== "litellm-dashboard"); // Remove duplicates based on token const uniqueKeys = Array.from( new Map(allKeys.map(key => [key.token, key])).values() ); return uniqueKeys; }, [data, teams, selectedTeam, userID]); 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) { return; } if (accessToken !== null && userRole !== null) { const model_available = await modelAvailableCall(accessToken, userID, userRole); let available_model_names = model_available["data"].map( (element: { id: string }) => element.id ); console.log("available_model_names:", available_model_names); setUserModels(available_model_names); } } catch (error) { console.error("Error fetching user models:", error); } }; fetchUserModels(); }, [accessToken, userID, userRole]); const handleModelLimitClick = (token: ItemData) => { 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 EditKeyModal: React.FC = ({ visible, onCancel, token, onSubmit }) => { const [form] = Form.useForm(); const [keyTeam, setKeyTeam] = useState(selectedTeam); const [errorModels, setErrorModels] = useState([]); const [errorBudget, setErrorBudget] = useState(false); let metadataString = ''; try { metadataString = JSON.stringify(token.metadata, null, 2); } catch (error) { console.error("Error stringifying metadata:", error); // You can choose a fallback, such as an empty string or a warning message metadataString = ''; } // Ensure token is defined and handle gracefully if not const initialValues = token ? { ...token, budget_duration: token.budget_duration, metadata: metadataString } : { metadata: metadataString }; const handleOk = () => { form .validateFields() .then((values) => { // const updatedValues = {...values, team_id: team.team_id}; // onSubmit(updatedValues); form.resetFields(); }) .catch((error) => { console.error("Validation failed:", error); }); }; return (
<> { const errorModels = value.filter((model: string) => ( !keyTeam.models.includes(model) && model !== "all-team-models" && model !== "all-proxy-models" && !keyTeam.models.includes("all-proxy-models") )); console.log(`errorModels: ${errorModels}`) if (errorModels.length > 0) { return Promise.reject(`Some models are not part of the new team\'s models - ${errorModels}Team models: ${keyTeam.models}`); } else { return Promise.resolve(); } } } ]}> { if (value && keyTeam && keyTeam.max_budget !== null && value > keyTeam.max_budget) { console.log(`keyTeam.max_budget: ${keyTeam.max_budget}`) throw new Error(`Budget cannot exceed team max budget: $${keyTeam.max_budget}`); } }, }, ]} > {teams?.map((team_obj, index) => ( setKeyTeam(team_obj)} > {team_obj.team_alias} ))} { if (value && keyTeam && keyTeam.tpm_limit !== null && value > keyTeam.tpm_limit) { console.log(`keyTeam.tpm_limit: ${keyTeam.tpm_limit}`) throw new Error(`tpm_limit cannot exceed team max tpm_limit: $${keyTeam.tpm_limit}`); } }, }, ]} > { if (value && keyTeam && keyTeam.rpm_limit !== null && value > keyTeam.rpm_limit) { console.log(`keyTeam.rpm_limit: ${keyTeam.rpm_limit}`) throw new Error(`rpm_limit cannot exceed team max rpm_limit: $${keyTeam.rpm_limit}`); } }, }, ]} >