mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-07 08:26:10 +00:00
(UI) - Create Key flow for existing users (#8844)
* working create user button * working create user for a key flow * allow searching users * working create user + key * use clear sections on create key * better search for users * fix create key * ui fix create key button - make it neater / cleaner * ui fix all keys table
This commit is contained in:
parent
335ba30467
commit
cb8d5e5917
4 changed files with 453 additions and 50 deletions
|
|
@ -186,6 +186,18 @@ export function AllKeysTable({
|
|||
accessorKey: "organization_id",
|
||||
cell: (info) => info.getValue() ? info.renderValue() : "Not Set",
|
||||
},
|
||||
{
|
||||
header: "User ID",
|
||||
accessorKey: "user_id",
|
||||
cell: (info) => {
|
||||
const userId = info.getValue() as string;
|
||||
return userId ? (
|
||||
<Tooltip title={userId}>
|
||||
<span>{userId.slice(0, 5)}...</span>
|
||||
</Tooltip>
|
||||
) : "Not Set";
|
||||
},
|
||||
},
|
||||
{
|
||||
header: "Created",
|
||||
accessorKey: "created_at",
|
||||
|
|
|
|||
|
|
@ -0,0 +1,74 @@
|
|||
import React from "react";
|
||||
import { Form, Select, TextInput } from "@tremor/react";
|
||||
import { Form as AntForm, Radio } from "antd";
|
||||
import TeamDropdown from "./team_dropdown";
|
||||
import { getPossibleUserRoles } from "../networking";
|
||||
|
||||
interface UserFormProps {
|
||||
form: any;
|
||||
teams: any[] | null;
|
||||
possibleUIRoles: null | Record<string, Record<string, string>>;
|
||||
setPossibleUIRoles?: (roles: any) => void;
|
||||
accessToken?: string;
|
||||
}
|
||||
|
||||
const UserForm: React.FC<UserFormProps> = ({
|
||||
form,
|
||||
teams,
|
||||
possibleUIRoles,
|
||||
setPossibleUIRoles,
|
||||
accessToken
|
||||
}) => {
|
||||
React.useEffect(() => {
|
||||
// Fetch roles if they're not available and we have a setter
|
||||
if (!possibleUIRoles && setPossibleUIRoles && accessToken) {
|
||||
getPossibleUserRoles(accessToken).then(roles => {
|
||||
setPossibleUIRoles(roles);
|
||||
});
|
||||
}
|
||||
}, [possibleUIRoles, setPossibleUIRoles, accessToken]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Form.Item
|
||||
label="User Email"
|
||||
name="user_email"
|
||||
rules={[{ required: true, message: "Please input user email" }]}
|
||||
>
|
||||
<TextInput placeholder="" />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
label="User Role"
|
||||
name="user_role"
|
||||
rules={[{ required: true, message: "Please select a role" }]}
|
||||
>
|
||||
<Select>
|
||||
{possibleUIRoles &&
|
||||
Object.entries(possibleUIRoles).map(
|
||||
([role, { ui_label, description }]) => (
|
||||
<Select.Option key={role} value={role} title={ui_label}>
|
||||
<div className="flex">
|
||||
{ui_label}{" "}
|
||||
<p className="ml-2" style={{ color: "gray", fontSize: "12px" }}>
|
||||
{description}
|
||||
</p>
|
||||
</div>
|
||||
</Select.Option>
|
||||
)
|
||||
)}
|
||||
</Select>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item label="Team" name="team_id">
|
||||
<TeamDropdown teams={teams} />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item label="Metadata" name="metadata">
|
||||
<TextInput.TextArea rows={4} placeholder="Enter metadata as JSON" />
|
||||
</Form.Item>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default UserForm;
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
"use client";
|
||||
import React, { useState, useEffect, useRef } from "react";
|
||||
import React, { useState, useEffect, useRef, useCallback } from "react";
|
||||
import { Button, TextInput, Grid, Col } from "@tremor/react";
|
||||
import {
|
||||
Card,
|
||||
|
|
@ -30,11 +30,15 @@ import {
|
|||
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';
|
||||
|
||||
const { Option } = Select;
|
||||
|
||||
|
|
@ -48,6 +52,18 @@ interface CreateKeyProps {
|
|||
teams: Team[] | null;
|
||||
}
|
||||
|
||||
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 = [];
|
||||
|
||||
|
|
@ -138,6 +154,13 @@ const CreateKey: React.FC<CreateKeyProps> = ({
|
|||
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);
|
||||
|
|
@ -172,6 +195,29 @@ const CreateKey: React.FC<CreateKeyProps> = ({
|
|||
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 ?? "";
|
||||
|
|
@ -233,6 +279,60 @@ const CreateKey: React.FC<CreateKeyProps> = ({
|
|||
form.setFieldValue('models', []);
|
||||
}, [selectedCreateKeyTeam, userModels]);
|
||||
|
||||
// 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>
|
||||
<Button className="mx-auto" onClick={() => setIsModalVisible(true)}>
|
||||
|
|
@ -241,7 +341,7 @@ const CreateKey: React.FC<CreateKeyProps> = ({
|
|||
<Modal
|
||||
title="Create Key"
|
||||
visible={isModalVisible}
|
||||
width={800}
|
||||
width={1000}
|
||||
footer={null}
|
||||
onOk={handleOk}
|
||||
onCancel={handleCancel}
|
||||
|
|
@ -253,8 +353,20 @@ const CreateKey: React.FC<CreateKeyProps> = ({
|
|||
wrapperCol={{ span: 16 }}
|
||||
labelAlign="left"
|
||||
>
|
||||
<>
|
||||
<Form.Item label="Owned By" className="mb-4">
|
||||
{/* Section 1: Key Ownership */}
|
||||
<div className="mb-8">
|
||||
<Title level={5} className="mb-4">1. 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}
|
||||
|
|
@ -265,34 +377,59 @@ const CreateKey: React.FC<CreateKeyProps> = ({
|
|||
</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="User ID"
|
||||
name="user_id"
|
||||
hidden={keyOwner !== "another_user"}
|
||||
valuePropName="user_id"
|
||||
className="mt-8"
|
||||
rules={[{ required: keyOwner === "another_user", message: `Please input the user ID of the user you are assigning the key to` }]}
|
||||
help={"Get User ID - Click on the 'Users' tab in the sidebar."}
|
||||
>
|
||||
<TextInput
|
||||
placeholder="User ID"
|
||||
onChange={(e) => form.setFieldValue('user_id', e.target.value)}
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
label={keyOwner === "you" || keyOwner === "another_user" ? "Key Name" : "Service Account ID"}
|
||||
name="key_alias"
|
||||
rules={[{ required: true, message: `Please input a ${keyOwner === "you" ? "key name" : "service account ID"}` }]}
|
||||
help={keyOwner === "you" ? "required" : "IDs can include letters, numbers, and hyphens"}
|
||||
>
|
||||
<TextInput placeholder="" />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label="Team"
|
||||
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-8"
|
||||
className="mt-4"
|
||||
>
|
||||
<TeamDropdown
|
||||
teams={teams}
|
||||
|
|
@ -303,11 +440,34 @@ const CreateKey: React.FC<CreateKeyProps> = ({
|
|||
/>
|
||||
</Form.Item>
|
||||
|
||||
</div>
|
||||
|
||||
{/* Section 2: Key Details */}
|
||||
<div className="mb-8">
|
||||
<Title level={5} className="mb-4">2. 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="These are the models that your selected team has access to">
|
||||
<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>
|
||||
|
|
@ -315,6 +475,7 @@ const CreateKey: React.FC<CreateKeyProps> = ({
|
|||
name="models"
|
||||
rules={[{ required: true, message: "Please select a model" }]}
|
||||
help="required"
|
||||
className="mt-4"
|
||||
>
|
||||
<Select
|
||||
mode="multiple"
|
||||
|
|
@ -336,14 +497,25 @@ const CreateKey: React.FC<CreateKeyProps> = ({
|
|||
))}
|
||||
</Select>
|
||||
</Form.Item>
|
||||
<Accordion className="mt-20 mb-8">
|
||||
</div>
|
||||
|
||||
{/* Section 3: Optional Settings */}
|
||||
<div className="mb-8">
|
||||
<Accordion className="mt-4 mb-4">
|
||||
<AccordionHeader>
|
||||
<b>Optional Settings</b>
|
||||
<Title level={5} className="m-0">3. Optional Settings</Title>
|
||||
</AccordionHeader>
|
||||
<AccordionBody>
|
||||
<Form.Item
|
||||
className="mt-8"
|
||||
label="Max Budget (USD)"
|
||||
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={[
|
||||
|
|
@ -366,8 +538,15 @@ const CreateKey: React.FC<CreateKeyProps> = ({
|
|||
<InputNumber step={0.01} precision={2} width={200} />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
className="mt-8"
|
||||
label="Reset Budget"
|
||||
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"}`}
|
||||
>
|
||||
|
|
@ -378,8 +557,15 @@ const CreateKey: React.FC<CreateKeyProps> = ({
|
|||
</Select>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
className="mt-8"
|
||||
label="Tokens per minute Limit (TPM)"
|
||||
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={[
|
||||
|
|
@ -402,8 +588,15 @@ const CreateKey: React.FC<CreateKeyProps> = ({
|
|||
<InputNumber step={1} width={400} />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
className="mt-8"
|
||||
label="Requests per minute Limit (RPM)"
|
||||
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={[
|
||||
|
|
@ -426,17 +619,24 @@ const CreateKey: React.FC<CreateKeyProps> = ({
|
|||
<InputNumber step={1} width={400} />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label="Expire Key (eg: 30s, 30h, 30d)"
|
||||
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-8"
|
||||
className="mt-4"
|
||||
>
|
||||
<TextInput placeholder="" />
|
||||
<TextInput placeholder="e.g., 30d" />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={
|
||||
<span>
|
||||
Guardrails{' '}
|
||||
<Tooltip title="Setup your first guardrail">
|
||||
<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"
|
||||
|
|
@ -449,7 +649,7 @@ const CreateKey: React.FC<CreateKeyProps> = ({
|
|||
</span>
|
||||
}
|
||||
name="guardrails"
|
||||
className="mt-8"
|
||||
className="mt-4"
|
||||
help="Select existing guardrails or enter new ones"
|
||||
>
|
||||
<Select
|
||||
|
|
@ -460,13 +660,36 @@ const CreateKey: React.FC<CreateKeyProps> = ({
|
|||
/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item label="Metadata" name="metadata" className="mt-8">
|
||||
<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="Tags" name="tags" className="mt-8" help={`Tags for tracking spend and/or doing tag-based routing.`}>
|
||||
<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%' }}
|
||||
|
|
@ -475,7 +698,7 @@ const CreateKey: React.FC<CreateKeyProps> = ({
|
|||
options={predefinedTags}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Accordion className="mt-20 mb-8">
|
||||
<Accordion className="mt-4 mb-4">
|
||||
<AccordionHeader>
|
||||
<div className="flex items-center gap-2">
|
||||
|
||||
|
|
@ -507,13 +730,34 @@ const CreateKey: React.FC<CreateKeyProps> = ({
|
|||
</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}
|
||||
|
|
|
|||
|
|
@ -27,6 +27,8 @@ interface CreateuserProps {
|
|||
accessToken: string;
|
||||
teams: any[] | null;
|
||||
possibleUIRoles: null | Record<string, Record<string, string>>;
|
||||
onUserCreated?: (userId: string) => void;
|
||||
isEmbedded?: boolean;
|
||||
}
|
||||
|
||||
// Define an interface for the UI settings
|
||||
|
|
@ -42,6 +44,8 @@ const Createuser: React.FC<CreateuserProps> = ({
|
|||
accessToken,
|
||||
teams,
|
||||
possibleUIRoles,
|
||||
onUserCreated,
|
||||
isEmbedded = false,
|
||||
}) => {
|
||||
const [uiSettings, setUISettings] = useState<UISettings | null>(null);
|
||||
const [form] = Form.useForm();
|
||||
|
|
@ -113,13 +117,22 @@ const Createuser: React.FC<CreateuserProps> = ({
|
|||
const handleCreate = async (formValues: { user_id: string }) => {
|
||||
try {
|
||||
message.info("Making API Call");
|
||||
setIsModalVisible(true);
|
||||
if (!isEmbedded) {
|
||||
setIsModalVisible(true);
|
||||
}
|
||||
console.log("formValues in create user:", formValues);
|
||||
const response = await userCreateCall(accessToken, null, formValues);
|
||||
console.log("user create Response:", response);
|
||||
setApiuser(response["key"]);
|
||||
const user_id = response.data?.user_id || response.user_id;
|
||||
|
||||
// Call the callback if provided (for embedded mode)
|
||||
if (onUserCreated && isEmbedded) {
|
||||
onUserCreated(user_id);
|
||||
form.resetFields();
|
||||
return; // Skip the invitation flow when embedded
|
||||
}
|
||||
|
||||
// only do invite link flow if sso is not enabled
|
||||
if (!uiSettings?.SSO_ENABLED) {
|
||||
invitationCreateCall(accessToken, user_id).then((data) => {
|
||||
|
|
@ -156,6 +169,66 @@ const Createuser: React.FC<CreateuserProps> = ({
|
|||
}
|
||||
};
|
||||
|
||||
// Modify the return statement to handle embedded mode
|
||||
if (isEmbedded) {
|
||||
return (
|
||||
<Form
|
||||
form={form}
|
||||
onFinish={handleCreate}
|
||||
labelCol={{ span: 8 }}
|
||||
wrapperCol={{ span: 16 }}
|
||||
labelAlign="left"
|
||||
>
|
||||
<Form.Item label="User Email" name="user_email">
|
||||
<TextInput placeholder="" />
|
||||
</Form.Item>
|
||||
<Form.Item label="User Role" name="user_role">
|
||||
<Select2>
|
||||
{possibleUIRoles &&
|
||||
Object.entries(possibleUIRoles).map(
|
||||
([role, { ui_label, description }]) => (
|
||||
<SelectItem key={role} value={role} title={ui_label}>
|
||||
<div className="flex">
|
||||
{ui_label}{" "}
|
||||
<p
|
||||
className="ml-2"
|
||||
style={{ color: "gray", fontSize: "12px" }}
|
||||
>
|
||||
{description}
|
||||
</p>
|
||||
</div>
|
||||
</SelectItem>
|
||||
),
|
||||
)}
|
||||
</Select2>
|
||||
</Form.Item>
|
||||
<Form.Item label="Team ID" name="team_id">
|
||||
<Select placeholder="Select Team ID" style={{ width: "100%" }}>
|
||||
{teams ? (
|
||||
teams.map((team: any) => (
|
||||
<Option key={team.team_id} value={team.team_id}>
|
||||
{team.team_alias}
|
||||
</Option>
|
||||
))
|
||||
) : (
|
||||
<Option key="default" value={null}>
|
||||
Default Team
|
||||
</Option>
|
||||
)}
|
||||
</Select>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item label="Metadata" name="metadata">
|
||||
<Input.TextArea rows={4} placeholder="Enter metadata as JSON" />
|
||||
</Form.Item>
|
||||
<div style={{ textAlign: "right", marginTop: "10px" }}>
|
||||
<Button htmlType="submit">Create User</Button>
|
||||
</div>
|
||||
</Form>
|
||||
);
|
||||
}
|
||||
|
||||
// Original return for standalone mode
|
||||
return (
|
||||
<div className="flex gap-2">
|
||||
<Button2 className="mx-auto mb-0" onClick={() => setIsModalVisible(true)}>
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue