Spaces:
Paused
Paused
| import React, { useState, useEffect } from "react"; | |
| import { useRouter } from "next/navigation"; | |
| import { | |
| Button, | |
| Modal, | |
| Form, | |
| Input, | |
| message, | |
| Select, | |
| InputNumber, | |
| Select as Select2, | |
| } from "antd"; | |
| import { Button as Button2, Text, TextInput, SelectItem, Accordion, AccordionHeader, AccordionBody, Title, } from "@tremor/react"; | |
| import OnboardingModal from "./onboarding_link"; | |
| import { InvitationLink } from "./onboarding_link"; | |
| import { | |
| userCreateCall, | |
| modelAvailableCall, | |
| invitationCreateCall, | |
| getProxyUISettings, | |
| } from "./networking"; | |
| import BulkCreateUsers from "./bulk_create_users_button"; | |
| const { Option } = Select; | |
| import { Tooltip } from "antd"; | |
| import { InfoCircleOutlined } from '@ant-design/icons'; | |
| import { getModelDisplayName } from "./key_team_helpers/fetch_available_models_team_key"; | |
| import { useQueryClient } from "@tanstack/react-query"; | |
| interface CreateuserProps { | |
| userID: string; | |
| 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 | |
| interface UISettings { | |
| PROXY_BASE_URL: string | null; | |
| PROXY_LOGOUT_URL: string | null; | |
| DEFAULT_TEAM_DISABLED: boolean; | |
| SSO_ENABLED: boolean; | |
| } | |
| const Createuser: React.FC<CreateuserProps> = ({ | |
| userID, | |
| accessToken, | |
| teams, | |
| possibleUIRoles, | |
| onUserCreated, | |
| isEmbedded = false, | |
| }) => { | |
| const queryClient = useQueryClient(); | |
| const [uiSettings, setUISettings] = useState<UISettings | null>(null); | |
| const [form] = Form.useForm(); | |
| const [isModalVisible, setIsModalVisible] = useState(false); | |
| const [apiuser, setApiuser] = useState<boolean>(false); | |
| const [userModels, setUserModels] = useState<string[]>([]); | |
| const [isInvitationLinkModalVisible, setIsInvitationLinkModalVisible] = | |
| useState(false); | |
| const [invitationLinkData, setInvitationLinkData] = | |
| useState<InvitationLink | null>(null); | |
| const router = useRouter(); | |
| const isLocal = process.env.NODE_ENV === "development"; | |
| const [baseUrl, setBaseUrl] = useState("http://localhost:4000"); | |
| // get all models | |
| useEffect(() => { | |
| const fetchData = async () => { | |
| try { | |
| const userRole = "any"; // You may need to get the user role dynamically | |
| const modelDataResponse = await modelAvailableCall( | |
| accessToken, | |
| userID, | |
| userRole, | |
| ); | |
| // Assuming modelDataResponse.data contains an array of model objects with a 'model_name' property | |
| const availableModels = []; | |
| for (let i = 0; i < modelDataResponse.data.length; i++) { | |
| const model = modelDataResponse.data[i]; | |
| availableModels.push(model.id); | |
| } | |
| console.log("Model data response:", modelDataResponse.data); | |
| console.log("Available models:", availableModels); | |
| // Assuming modelDataResponse.data contains an array of model names | |
| setUserModels(availableModels); | |
| // get ui settings | |
| const uiSettingsResponse = await getProxyUISettings(accessToken); | |
| console.log("uiSettingsResponse:", uiSettingsResponse); | |
| setUISettings(uiSettingsResponse); | |
| } catch (error) { | |
| console.error("Error fetching model data:", error); | |
| } | |
| }; | |
| fetchData(); // Call the function to fetch model data when the component mounts | |
| }, []); // Empty dependency array to run only once | |
| useEffect(() => { | |
| if (!router) { | |
| return; | |
| } | |
| const base = new URL("/", window.location.href); | |
| setBaseUrl(base.toString()); | |
| }, [router]); | |
| const handleOk = () => { | |
| setIsModalVisible(false); | |
| form.resetFields(); | |
| }; | |
| const handleCancel = () => { | |
| setIsModalVisible(false); | |
| setApiuser(false); | |
| form.resetFields(); | |
| }; | |
| const handleCreate = async (formValues: { user_id: string, models?: string[], user_role: string }) => { | |
| try { | |
| message.info("Making API Call"); | |
| if (!isEmbedded) { | |
| setIsModalVisible(true); | |
| } | |
| if ((!formValues.models || formValues.models.length === 0) && formValues.user_role !== "proxy_admin") { | |
| console.log("formValues.user_role", formValues.user_role) | |
| // If models is empty or undefined, set it to "no-default-models" | |
| formValues.models = ["no-default-models"]; | |
| } | |
| console.log("formValues in create user:", formValues); | |
| const response = await userCreateCall(accessToken, null, formValues); | |
| await queryClient.invalidateQueries({ queryKey: ['userList'] }) | |
| console.log("user create Response:", response); | |
| setApiuser(true); | |
| 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) => { | |
| data.has_user_setup_sso = false; | |
| setInvitationLinkData(data); | |
| setIsInvitationLinkModalVisible(true); | |
| }); | |
| } else { | |
| // create an InvitationLink Object for this user for the SSO flow | |
| // for SSO the invite link is the proxy base url since the User just needs to login | |
| const invitationLink: InvitationLink = { | |
| id: crypto.randomUUID(), // Generate a unique ID | |
| user_id: user_id, | |
| is_accepted: false, | |
| accepted_at: null, | |
| expires_at: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000), // Set expiry to 7 days from now | |
| created_at: new Date(), | |
| created_by: userID, // Assuming userID is the current user creating the invitation | |
| updated_at: new Date(), | |
| updated_by: userID, | |
| has_user_setup_sso: true, | |
| }; | |
| setInvitationLinkData(invitationLink); | |
| setIsInvitationLinkModalVisible(true); | |
| } | |
| message.success("API user Created"); | |
| form.resetFields(); | |
| localStorage.removeItem("userData" + userID); | |
| } catch (error: any) { | |
| const errorMessage = error.response?.data?.detail || error?.message || "Error creating the user"; | |
| message.error(errorMessage); | |
| console.error("Error creating the user:", error); | |
| } | |
| }; | |
| // 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)}> | |
| + Invite User | |
| </Button2> | |
| <BulkCreateUsers | |
| accessToken={accessToken} | |
| teams={teams} | |
| possibleUIRoles={possibleUIRoles} | |
| /> | |
| <Modal | |
| title="Invite User" | |
| visible={isModalVisible} | |
| width={800} | |
| footer={null} | |
| onOk={handleOk} | |
| onCancel={handleCancel} | |
| > | |
| <Text className="mb-1">Create a User who can own keys</Text> | |
| <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={ | |
| <span> | |
| Global Proxy Role{' '} | |
| <Tooltip title="This is the role that the user will globally on the proxy. This role is independent of any team/org specific roles."> | |
| <InfoCircleOutlined/> | |
| </Tooltip> | |
| </span> | |
| } | |
| 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" className="gap-2" name="team_id" help="If selected, user will be added as a 'user' role to the team."> | |
| <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> | |
| <Accordion> | |
| <AccordionHeader> | |
| <Title>Personal Key Creation</Title> | |
| </AccordionHeader> | |
| <AccordionBody> | |
| <Form.Item className="gap-2" label={ | |
| <span> | |
| Models{' '} | |
| <Tooltip title="Models user has access to, outside of team scope."> | |
| <InfoCircleOutlined style={{ marginLeft: '4px' }} /> | |
| </Tooltip> | |
| </span> | |
| } name="models" help="Models user has access to, outside of team scope."> | |
| <Select2 | |
| mode="multiple" | |
| placeholder="Select models" | |
| style={{ width: "100%" }} | |
| > | |
| <Select2.Option | |
| key="all-proxy-models" | |
| value="all-proxy-models" | |
| > | |
| All Proxy Models | |
| </Select2.Option> | |
| {userModels.map((model) => ( | |
| <Select2.Option key={model} value={model}> | |
| {getModelDisplayName(model)} | |
| </Select2.Option> | |
| ))} | |
| </Select2> | |
| </Form.Item> | |
| </AccordionBody> | |
| </Accordion> | |
| <div style={{ textAlign: "right", marginTop: "10px" }}> | |
| <Button htmlType="submit">Create User</Button> | |
| </div> | |
| </Form> | |
| </Modal> | |
| {apiuser && ( | |
| <OnboardingModal | |
| isInvitationLinkModalVisible={isInvitationLinkModalVisible} | |
| setIsInvitationLinkModalVisible={setIsInvitationLinkModalVisible} | |
| baseUrl={baseUrl} | |
| invitationLinkData={invitationLinkData} | |
| /> | |
| )} | |
| </div> | |
| ); | |
| }; | |
| export default Createuser; | |