Spaces:
Sleeping
Sleeping
"use client"; | |
import React, { useState, useEffect, useRef, useCallback } from "react"; | |
import { Button, TextInput, Grid, Col } from "@tremor/react"; | |
import { | |
Card, | |
Metric, | |
Text, | |
Title, | |
Subtitle, | |
Accordion, | |
AccordionHeader, | |
AccordionBody, | |
} from "@tremor/react"; | |
import { CopyToClipboard } from "react-copy-to-clipboard"; | |
import { | |
Button as Button2, | |
Modal, | |
Form, | |
Input, | |
Select, | |
message, | |
Radio, | |
} from "antd"; | |
import NumericalInput from "./shared/numerical_input"; | |
import { unfurlWildcardModelsInList, getModelDisplayName } from "./key_team_helpers/fetch_available_models_team_key"; | |
import SchemaFormFields from './common_components/check_openapi_schema'; | |
import { | |
keyCreateCall, | |
slackBudgetAlertsHealthCheck, | |
modelAvailableCall, | |
getGuardrailsList, | |
proxyBaseUrl, | |
getPossibleUserRoles, | |
userFilterUICall, | |
} from "./networking"; | |
import { Team } from "./key_team_helpers/key_list"; | |
import TeamDropdown from "./common_components/team_dropdown"; | |
import { InfoCircleOutlined } from '@ant-design/icons'; | |
import { Tooltip } from 'antd'; | |
import Createuser from "./create_user_button"; | |
import debounce from 'lodash/debounce'; | |
import { rolesWithWriteAccess } from '../utils/roles'; | |
import BudgetDurationDropdown from "./common_components/budget_duration_dropdown"; | |
const { Option } = Select; | |
interface CreateKeyProps { | |
userID: string; | |
team: Team | null; | |
userRole: string | null; | |
accessToken: string; | |
data: any[] | null; | |
teams: Team[] | null; | |
addKey: (data: any) => void; | |
} | |
interface User { | |
user_id: string; | |
user_email: string; | |
role?: string; | |
} | |
interface UserOption { | |
label: string; | |
value: string; | |
user: User; | |
} | |
const getPredefinedTags = (data: any[] | null) => { | |
let allTags = []; | |
console.log("data:", JSON.stringify(data)); | |
if (data) { | |
for (let key of data) { | |
if (key["metadata"] && key["metadata"]["tags"]) { | |
allTags.push(...key["metadata"]["tags"]); | |
} | |
} | |
} | |
// Deduplicate using Set | |
const uniqueTags = Array.from(new Set(allTags)).map(tag => ({ | |
value: tag, | |
label: tag, | |
})); | |
console.log("uniqueTags:", uniqueTags); | |
return uniqueTags; | |
} | |
export const fetchTeamModels = async (userID: string, userRole: string, accessToken: string, teamID: string | null): Promise<string[]> => { | |
try { | |
if (userID === null || userRole === null) { | |
return []; | |
} | |
if (accessToken !== null) { | |
const model_available = await modelAvailableCall( | |
accessToken, | |
userID, | |
userRole, | |
true, | |
teamID | |
); | |
let available_model_names = model_available["data"].map( | |
(element: { id: string }) => element.id | |
); | |
console.log("available_model_names:", available_model_names); | |
return available_model_names; | |
} | |
return []; | |
} catch (error) { | |
console.error("Error fetching user models:", error); | |
return []; | |
} | |
}; | |
export const fetchUserModels = async (userID: string, userRole: string, accessToken: string, setUserModels: (models: string[]) => void) => { | |
try { | |
if (userID === null || userRole === null) { | |
return; | |
} | |
if (accessToken !== 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); | |
} | |
}; | |
const CreateKey: React.FC<CreateKeyProps> = ({ | |
userID, | |
team, | |
teams, | |
userRole, | |
accessToken, | |
data, | |
addKey, | |
}) => { | |
const [form] = Form.useForm(); | |
const [isModalVisible, setIsModalVisible] = useState(false); | |
const [apiKey, setApiKey] = useState(null); | |
const [softBudget, setSoftBudget] = useState(null); | |
const [userModels, setUserModels] = useState<string[]>([]); | |
const [modelsToPick, setModelsToPick] = useState<string[]>([]); | |
const [keyOwner, setKeyOwner] = useState("you"); | |
const [predefinedTags, setPredefinedTags] = useState(getPredefinedTags(data)); | |
const [guardrailsList, setGuardrailsList] = useState<string[]>([]); | |
const [selectedCreateKeyTeam, setSelectedCreateKeyTeam] = useState<Team | null>(team); | |
const [isCreateUserModalVisible, setIsCreateUserModalVisible] = useState(false); | |
const [newlyCreatedUserId, setNewlyCreatedUserId] = useState<string | null>(null); | |
const [possibleUIRoles, setPossibleUIRoles] = useState< | |
Record<string, Record<string, string>> | |
>({}); | |
const [userOptions, setUserOptions] = useState<UserOption[]>([]); | |
const [userSearchLoading, setUserSearchLoading] = useState<boolean>(false); | |
const handleOk = () => { | |
setIsModalVisible(false); | |
form.resetFields(); | |
}; | |
const handleCancel = () => { | |
setIsModalVisible(false); | |
setApiKey(null); | |
setSelectedCreateKeyTeam(null); | |
form.resetFields(); | |
}; | |
useEffect(() => { | |
if (userID && userRole && accessToken) { | |
fetchUserModels(userID, userRole, accessToken, setUserModels); | |
} | |
}, [accessToken, userID, userRole]); | |
useEffect(() => { | |
const fetchGuardrails = async () => { | |
try { | |
const response = await getGuardrailsList(accessToken); | |
const guardrailNames = response.guardrails.map( | |
(g: { guardrail_name: string }) => g.guardrail_name | |
); | |
setGuardrailsList(guardrailNames); | |
} catch (error) { | |
console.error("Failed to fetch guardrails:", error); | |
} | |
}; | |
fetchGuardrails(); | |
}, [accessToken]); | |
// Fetch possible user roles when component mounts | |
useEffect(() => { | |
const fetchPossibleRoles = async () => { | |
try { | |
if (accessToken) { | |
// Check if roles are cached in session storage | |
const cachedRoles = sessionStorage.getItem('possibleUserRoles'); | |
if (cachedRoles) { | |
setPossibleUIRoles(JSON.parse(cachedRoles)); | |
} else { | |
const availableUserRoles = await getPossibleUserRoles(accessToken); | |
sessionStorage.setItem('possibleUserRoles', JSON.stringify(availableUserRoles)); | |
setPossibleUIRoles(availableUserRoles); | |
} | |
} | |
} catch (error) { | |
console.error("Error fetching possible user roles:", error); | |
} | |
}; | |
fetchPossibleRoles(); | |
}, [accessToken]); | |
const handleCreate = async (formValues: Record<string, any>) => { | |
try { | |
const newKeyAlias = formValues?.key_alias ?? ""; | |
const newKeyTeamId = formValues?.team_id ?? null; | |
const existingKeyAliases = | |
data | |
?.filter((k) => k.team_id === newKeyTeamId) | |
.map((k) => k.key_alias) ?? []; | |
if (existingKeyAliases.includes(newKeyAlias)) { | |
throw new Error( | |
`Key alias ${newKeyAlias} already exists for team with ID ${newKeyTeamId}, please provide another key alias` | |
); | |
} | |
message.info("Making API Call"); | |
setIsModalVisible(true); | |
if(keyOwner === "you"){ | |
formValues.user_id = userID | |
} | |
// If it's a service account, add the service_account_id to the metadata | |
if (keyOwner === "service_account") { | |
// Parse existing metadata or create an empty object | |
let metadata: Record<string, any> = {}; | |
try { | |
metadata = JSON.parse(formValues.metadata || "{}"); | |
} catch (error) { | |
console.error("Error parsing metadata:", error); | |
} | |
metadata["service_account_id"] = formValues.key_alias; | |
// Update the formValues with the new metadata | |
formValues.metadata = JSON.stringify(metadata); | |
} | |
const response = await keyCreateCall(accessToken, userID, formValues); | |
console.log("key create Response:", response); | |
// Add the data to the state in the parent component | |
// Also directly update the keys list in AllKeysTable without an API call | |
addKey(response) | |
setApiKey(response["key"]); | |
setSoftBudget(response["soft_budget"]); | |
message.success("API Key Created"); | |
form.resetFields(); | |
localStorage.removeItem("userData" + userID); | |
} catch (error) { | |
console.log("error in create key:", error); | |
message.error(`Error creating the key: ${error}`); | |
} | |
}; | |
const handleCopy = () => { | |
message.success("API Key copied to clipboard"); | |
}; | |
useEffect(() => { | |
if (userID && userRole && accessToken) { | |
fetchTeamModels(userID, userRole, accessToken, selectedCreateKeyTeam?.team_id ?? null).then((models) => { | |
let allModels = Array.from(new Set([...(selectedCreateKeyTeam?.models ?? []), ...models])); | |
setModelsToPick(allModels); | |
}); | |
} | |
form.setFieldValue('models', []); | |
}, [selectedCreateKeyTeam, accessToken, userID, userRole]); | |
// Add a callback function to handle user creation | |
const handleUserCreated = (userId: string) => { | |
setNewlyCreatedUserId(userId); | |
form.setFieldsValue({ user_id: userId }); | |
setIsCreateUserModalVisible(false); | |
}; | |
const fetchUsers = async (searchText: string): Promise<void> => { | |
if (!searchText) { | |
setUserOptions([]); | |
return; | |
} | |
setUserSearchLoading(true); | |
try { | |
const params = new URLSearchParams(); | |
params.append('user_email', searchText); // Always search by email | |
if (accessToken == null) { | |
return; | |
} | |
const response = await userFilterUICall(accessToken, params); | |
const data: User[] = response; | |
const options: UserOption[] = data.map(user => ({ | |
label: `${user.user_email} (${user.user_id})`, | |
value: user.user_id, | |
user | |
})); | |
setUserOptions(options); | |
} catch (error) { | |
console.error('Error fetching users:', error); | |
message.error('Failed to search for users'); | |
} finally { | |
setUserSearchLoading(false); | |
} | |
}; | |
const debouncedSearch = useCallback( | |
debounce((text: string) => fetchUsers(text), 300), | |
[accessToken] | |
); | |
const handleUserSearch = (value: string): void => { | |
debouncedSearch(value); | |
}; | |
const handleUserSelect = (_value: string, option: UserOption): void => { | |
const selectedUser = option.user; | |
form.setFieldsValue({ | |
user_id: selectedUser.user_id | |
}); | |
}; | |
return ( | |
<div> | |
{userRole && rolesWithWriteAccess.includes(userRole) && ( | |
<Button className="mx-auto" onClick={() => setIsModalVisible(true)}> | |
+ Create New Key | |
</Button> | |
)} | |
<Modal | |
// title="Create Key" | |
visible={isModalVisible} | |
width={1000} | |
footer={null} | |
onOk={handleOk} | |
onCancel={handleCancel} | |
> | |
<Form | |
form={form} | |
onFinish={handleCreate} | |
labelCol={{ span: 8 }} | |
wrapperCol={{ span: 16 }} | |
labelAlign="left" | |
> | |
{/* Section 1: Key Ownership */} | |
<div className="mb-8"> | |
<Title className="mb-4">Key Ownership</Title> | |
<Form.Item | |
label={ | |
<span> | |
Owned By{' '} | |
<Tooltip title="Select who will own this API key"> | |
<InfoCircleOutlined style={{ marginLeft: '4px' }} /> | |
</Tooltip> | |
</span> | |
} | |
className="mb-4" | |
> | |
<Radio.Group | |
onChange={(e) => setKeyOwner(e.target.value)} | |
value={keyOwner} | |
> | |
<Radio value="you">You</Radio> | |
<Radio value="service_account">Service Account</Radio> | |
{userRole === "Admin" && <Radio value="another_user">Another User</Radio>} | |
</Radio.Group> | |
</Form.Item> | |
{keyOwner === "another_user" && ( | |
<Form.Item | |
label={ | |
<span> | |
User ID{' '} | |
<Tooltip title="The user who will own this key and be responsible for its usage"> | |
<InfoCircleOutlined style={{ marginLeft: '4px' }} /> | |
</Tooltip> | |
</span> | |
} | |
name="user_id" | |
className="mt-4" | |
rules={[{ required: keyOwner === "another_user", message: `Please input the user ID of the user you are assigning the key to` }]} | |
> | |
<div> | |
<div style={{ display: 'flex', marginBottom: '8px' }}> | |
<Select | |
showSearch | |
placeholder="Type email to search for users" | |
filterOption={false} | |
onSearch={handleUserSearch} | |
onSelect={(value, option) => handleUserSelect(value, option as UserOption)} | |
options={userOptions} | |
loading={userSearchLoading} | |
allowClear | |
style={{ width: '100%' }} | |
notFoundContent={userSearchLoading ? 'Searching...' : 'No users found'} | |
/> | |
<Button2 | |
onClick={() => setIsCreateUserModalVisible(true)} | |
style={{ marginLeft: '8px' }} | |
> | |
Create User | |
</Button2> | |
</div> | |
<div className="text-xs text-gray-500"> | |
Search by email to find users | |
</div> | |
</div> | |
</Form.Item> | |
)} | |
<Form.Item | |
label={ | |
<span> | |
Team{' '} | |
<Tooltip title="The team this key belongs to, which determines available models and budget limits"> | |
<InfoCircleOutlined style={{ marginLeft: '4px' }} /> | |
</Tooltip> | |
</span> | |
} | |
name="team_id" | |
initialValue={team ? team.team_id : null} | |
className="mt-4" | |
> | |
<TeamDropdown | |
teams={teams} | |
onChange={(teamId) => { | |
const selectedTeam = teams?.find(t => t.team_id === teamId) || null; | |
setSelectedCreateKeyTeam(selectedTeam); | |
}} | |
/> | |
</Form.Item> | |
</div> | |
{/* Section 2: Key Details */} | |
<div className="mb-8"> | |
<Title className="mb-4">Key Details</Title> | |
<Form.Item | |
label={ | |
<span> | |
{keyOwner === "you" || keyOwner === "another_user" ? "Key Name" : "Service Account ID"}{' '} | |
<Tooltip title={keyOwner === "you" || keyOwner === "another_user" ? | |
"A descriptive name to identify this key" : | |
"Unique identifier for this service account"}> | |
<InfoCircleOutlined style={{ marginLeft: '4px' }} /> | |
</Tooltip> | |
</span> | |
} | |
name="key_alias" | |
rules={[{ required: true, message: `Please input a ${keyOwner === "you" ? "key name" : "service account ID"}` }]} | |
help="required" | |
> | |
<TextInput placeholder="" /> | |
</Form.Item> | |
<Form.Item | |
label={ | |
<span> | |
Models{' '} | |
<Tooltip title="Select which models this key can access. Choose 'All Team Models' to grant access to all models available to the team"> | |
<InfoCircleOutlined style={{ marginLeft: '4px' }} /> | |
</Tooltip> | |
</span> | |
} | |
name="models" | |
rules={[{ required: true, message: "Please select a model" }]} | |
help="required" | |
className="mt-4" | |
> | |
<Select | |
mode="multiple" | |
placeholder="Select models" | |
style={{ width: "100%" }} | |
onChange={(values) => { | |
if (values.includes("all-team-models")) { | |
form.setFieldsValue({ models: ["all-team-models"] }); | |
} | |
}} | |
> | |
<Option key="all-team-models" value="all-team-models"> | |
All Team Models | |
</Option> | |
{modelsToPick.map((model: string) => ( | |
<Option key={model} value={model}> | |
{getModelDisplayName(model)} | |
</Option> | |
))} | |
</Select> | |
</Form.Item> | |
</div> | |
{/* Section 3: Optional Settings */} | |
<div className="mb-8"> | |
<Accordion className="mt-4 mb-4"> | |
<AccordionHeader> | |
<Title className="m-0">Optional Settings</Title> | |
</AccordionHeader> | |
<AccordionBody> | |
<Form.Item | |
className="mt-4" | |
label={ | |
<span> | |
Max Budget (USD){' '} | |
<Tooltip title="Maximum amount in USD this key can spend. When reached, the key will be blocked from making further requests"> | |
<InfoCircleOutlined style={{ marginLeft: '4px' }} /> | |
</Tooltip> | |
</span> | |
} | |
name="max_budget" | |
help={`Budget cannot exceed team max budget: $${team?.max_budget !== null && team?.max_budget !== undefined ? team?.max_budget : "unlimited"}`} | |
rules={[ | |
{ | |
validator: async (_, value) => { | |
if ( | |
value && | |
team && | |
team.max_budget !== null && | |
value > team.max_budget | |
) { | |
throw new Error( | |
`Budget cannot exceed team max budget: $${team.max_budget}` | |
); | |
} | |
}, | |
}, | |
]} | |
> | |
<NumericalInput step={0.01} precision={2} width={200} /> | |
</Form.Item> | |
<Form.Item | |
className="mt-4" | |
label={ | |
<span> | |
Reset Budget{' '} | |
<Tooltip title="How often the budget should reset. For example, setting 'daily' will reset the budget every 24 hours"> | |
<InfoCircleOutlined style={{ marginLeft: '4px' }} /> | |
</Tooltip> | |
</span> | |
} | |
name="budget_duration" | |
help={`Team Reset Budget: ${team?.budget_duration !== null && team?.budget_duration !== undefined ? team?.budget_duration : "None"}`} | |
> | |
<BudgetDurationDropdown onChange={(value) => form.setFieldValue('budget_duration', value)} /> | |
</Form.Item> | |
<Form.Item | |
className="mt-4" | |
label={ | |
<span> | |
Tokens per minute Limit (TPM){' '} | |
<Tooltip title="Maximum number of tokens this key can process per minute. Helps control usage and costs"> | |
<InfoCircleOutlined style={{ marginLeft: '4px' }} /> | |
</Tooltip> | |
</span> | |
} | |
name="tpm_limit" | |
help={`TPM cannot exceed team TPM limit: ${team?.tpm_limit !== null && team?.tpm_limit !== undefined ? team?.tpm_limit : "unlimited"}`} | |
rules={[ | |
{ | |
validator: async (_, value) => { | |
if ( | |
value && | |
team && | |
team.tpm_limit !== null && | |
value > team.tpm_limit | |
) { | |
throw new Error( | |
`TPM limit cannot exceed team TPM limit: ${team.tpm_limit}` | |
); | |
} | |
}, | |
}, | |
]} | |
> | |
<NumericalInput step={1} width={400} /> | |
</Form.Item> | |
<Form.Item | |
className="mt-4" | |
label={ | |
<span> | |
Requests per minute Limit (RPM){' '} | |
<Tooltip title="Maximum number of API requests this key can make per minute. Helps prevent abuse and manage load"> | |
<InfoCircleOutlined style={{ marginLeft: '4px' }} /> | |
</Tooltip> | |
</span> | |
} | |
name="rpm_limit" | |
help={`RPM cannot exceed team RPM limit: ${team?.rpm_limit !== null && team?.rpm_limit !== undefined ? team?.rpm_limit : "unlimited"}`} | |
rules={[ | |
{ | |
validator: async (_, value) => { | |
if ( | |
value && | |
team && | |
team.rpm_limit !== null && | |
value > team.rpm_limit | |
) { | |
throw new Error( | |
`RPM limit cannot exceed team RPM limit: ${team.rpm_limit}` | |
); | |
} | |
}, | |
}, | |
]} | |
> | |
<NumericalInput step={1} width={400} /> | |
</Form.Item> | |
<Form.Item | |
label={ | |
<span> | |
Expire Key{' '} | |
<Tooltip title="Set when this key should expire. Format: 30s (seconds), 30m (minutes), 30h (hours), 30d (days)"> | |
<InfoCircleOutlined style={{ marginLeft: '4px' }} /> | |
</Tooltip> | |
</span> | |
} | |
name="duration" | |
className="mt-4" | |
> | |
<TextInput placeholder="e.g., 30d" /> | |
</Form.Item> | |
<Form.Item | |
label={ | |
<span> | |
Guardrails{' '} | |
<Tooltip title="Apply safety guardrails to this key to filter content or enforce policies"> | |
<a | |
href="https://docs.litellm.ai/docs/proxy/guardrails/quick_start" | |
target="_blank" | |
rel="noopener noreferrer" | |
onClick={(e) => e.stopPropagation()} // Prevent accordion from collapsing when clicking link | |
> | |
<InfoCircleOutlined style={{ marginLeft: '4px' }} /> | |
</a> | |
</Tooltip> | |
</span> | |
} | |
name="guardrails" | |
className="mt-4" | |
help="Select existing guardrails or enter new ones" | |
> | |
<Select | |
mode="tags" | |
style={{ width: '100%' }} | |
placeholder="Select or enter guardrails" | |
options={guardrailsList.map(name => ({ value: name, label: name }))} | |
/> | |
</Form.Item> | |
<Form.Item | |
label={ | |
<span> | |
Metadata{' '} | |
<Tooltip title="JSON object with additional information about this key. Used for tracking or custom logic"> | |
<InfoCircleOutlined style={{ marginLeft: '4px' }} /> | |
</Tooltip> | |
</span> | |
} | |
name="metadata" | |
className="mt-4" | |
> | |
<Input.TextArea | |
rows={4} | |
placeholder="Enter metadata as JSON" | |
/> | |
</Form.Item> | |
<Form.Item | |
label={ | |
<span> | |
Tags{' '} | |
<Tooltip title="Tags for tracking spend and/or doing tag-based routing. Used for analytics and filtering"> | |
<InfoCircleOutlined style={{ marginLeft: '4px' }} /> | |
</Tooltip> | |
</span> | |
} | |
name="tags" | |
className="mt-4" | |
help={`Tags for tracking spend and/or doing tag-based routing.`} | |
> | |
<Select | |
mode="tags" | |
style={{ width: '100%' }} | |
placeholder="Enter tags" | |
tokenSeparators={[',']} | |
options={predefinedTags} | |
/> | |
</Form.Item> | |
<Accordion className="mt-4 mb-4"> | |
<AccordionHeader> | |
<div className="flex items-center gap-2"> | |
<b>Advanced Settings</b> | |
<Tooltip title={ | |
<span> | |
Learn more about advanced settings in our{' '} | |
<a | |
href={proxyBaseUrl ? `${proxyBaseUrl}/#/key%20management/generate_key_fn_key_generate_post`: `/#/key%20management/generate_key_fn_key_generate_post`} | |
target="_blank" | |
rel="noopener noreferrer" | |
className="text-blue-400 hover:text-blue-300" | |
> | |
documentation | |
</a> | |
</span> | |
}> | |
<InfoCircleOutlined className="text-gray-400 hover:text-gray-300 cursor-help" /> | |
</Tooltip> | |
</div> | |
</AccordionHeader> | |
<AccordionBody> | |
<SchemaFormFields | |
schemaComponent="GenerateKeyRequest" | |
form={form} | |
excludedFields={['key_alias', 'team_id', 'models', 'duration', 'metadata', 'tags', 'guardrails', "max_budget", "budget_duration", "tpm_limit", "rpm_limit"]} | |
/> | |
</AccordionBody> | |
</Accordion> | |
</AccordionBody> | |
</Accordion> | |
</div> | |
<div style={{ textAlign: "right", marginTop: "10px" }}> | |
<Button2 htmlType="submit">Create Key</Button2> | |
</div> | |
</Form> | |
</Modal> | |
{/* Add the Create User Modal */} | |
{isCreateUserModalVisible && ( | |
<Modal | |
title="Create New User" | |
visible={isCreateUserModalVisible} | |
onCancel={() => setIsCreateUserModalVisible(false)} | |
footer={null} | |
width={800} | |
> | |
<Createuser | |
userID={userID} | |
accessToken={accessToken} | |
teams={teams} | |
possibleUIRoles={possibleUIRoles} | |
onUserCreated={handleUserCreated} | |
isEmbedded={true} | |
/> | |
</Modal> | |
)} | |
{apiKey && ( | |
<Modal | |
visible={isModalVisible} | |
onOk={handleOk} | |
onCancel={handleCancel} | |
footer={null} | |
> | |
<Grid numItems={1} className="gap-2 w-full"> | |
<Title>Save your Key</Title> | |
<Col numColSpan={1}> | |
<p> | |
Please save this secret key somewhere safe and accessible. 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}> | |
{apiKey != null ? ( | |
<div> | |
<Text className="mt-3">API Key:</Text> | |
<div | |
style={{ | |
background: "#f8f8f8", | |
padding: "10px", | |
borderRadius: "5px", | |
marginBottom: "10px", | |
}} | |
> | |
<pre | |
style={{ wordWrap: "break-word", whiteSpace: "normal" }} | |
> | |
{apiKey} | |
</pre> | |
</div> | |
<CopyToClipboard text={apiKey} onCopy={handleCopy}> | |
<Button className="mt-3">Copy API Key</Button> | |
</CopyToClipboard> | |
{/* <Button className="mt-3" onClick={sendSlackAlert}> | |
Test Key | |
</Button> */} | |
</div> | |
) : ( | |
<Text>Key being created, this might take 30s</Text> | |
)} | |
</Col> | |
</Grid> | |
</Modal> | |
)} | |
</div> | |
); | |
}; | |
export default CreateKey; | |