mirror of
https://github.com/BerriAI/litellm.git
synced 2025-04-27 11:43:54 +00:00
(ui) create user
This commit is contained in:
parent
c656eaf7a4
commit
02f7b902db
4 changed files with 221 additions and 11 deletions
|
@ -78,15 +78,13 @@ const CreateKey: React.FC<CreateKeyProps> = ({
|
||||||
label="Key Name"
|
label="Key Name"
|
||||||
name="key_alias"
|
name="key_alias"
|
||||||
>
|
>
|
||||||
<Input />
|
<Input />
|
||||||
<p style={{ fontStyle: 'italic', color: 'gray' }}>Optional</p>
|
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
label="Team ID"
|
label="Team ID"
|
||||||
name="team_id"
|
name="team_id"
|
||||||
>
|
>
|
||||||
<Input placeholder="ai_team" />
|
<Input placeholder="ai_team" />
|
||||||
<p style={{ fontStyle: 'italic', color: 'gray' }}>Optional</p>
|
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
label="Models"
|
label="Models"
|
||||||
|
@ -103,44 +101,36 @@ const CreateKey: React.FC<CreateKeyProps> = ({
|
||||||
</Option>
|
</Option>
|
||||||
))}
|
))}
|
||||||
</Select>
|
</Select>
|
||||||
<p style={{ fontStyle: 'italic', color: 'gray' }}>Optional, defaults to all models</p>
|
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
|
|
||||||
<Form.Item
|
<Form.Item
|
||||||
label="Max Budget (USD)"
|
label="Max Budget (USD)"
|
||||||
name="max_budget"
|
name="max_budget"
|
||||||
>
|
>
|
||||||
<InputNumber step={0.01} precision={2} width={200}/>
|
<InputNumber step={0.01} precision={2} width={200}/>
|
||||||
<p style={{ fontStyle: 'italic', color: 'gray' }}>Optional, defaults Unlimited Budget </p>
|
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
label="Tokens per minute Limit (TPM)"
|
label="Tokens per minute Limit (TPM)"
|
||||||
name="tpm_limit"
|
name="tpm_limit"
|
||||||
>
|
>
|
||||||
<InputNumber step={1} width={400}/>
|
<InputNumber step={1} width={400}/>
|
||||||
<p style={{ fontStyle: 'italic', color: 'gray' }}>Optional, defaults to Unlimited TPM</p>
|
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
label="Requests per minute Limit (RPM)"
|
label="Requests per minute Limit (RPM)"
|
||||||
name="rpm_limit"
|
name="rpm_limit"
|
||||||
>
|
>
|
||||||
<InputNumber step={1} width={400}/>
|
<InputNumber step={1} width={400}/>
|
||||||
<p style={{ fontStyle: 'italic', color: 'gray' }}>Optional, defaults to Unlimited RPM</p>
|
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
label="Duration (eg: 30s, 30h, 30d)"
|
label="Duration (eg: 30s, 30h, 30d)"
|
||||||
name="duration"
|
name="duration"
|
||||||
>
|
>
|
||||||
<Input />
|
<Input />
|
||||||
<p style={{ fontStyle: 'italic', color: 'gray' }}>Optional, defaults to never expiring key</p>
|
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
label="Metadata"
|
label="Metadata"
|
||||||
name="metadata"
|
name="metadata"
|
||||||
>
|
>
|
||||||
<Input.TextArea rows={4} placeholder="Enter metadata as JSON" />
|
<Input.TextArea rows={4} placeholder="Enter metadata as JSON" />
|
||||||
<p style={{ fontStyle: 'italic', color: 'gray' }}>Optional, defaults to null</p>
|
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
|
|
148
ui/litellm-dashboard/src/components/create_user_button.tsx
Normal file
148
ui/litellm-dashboard/src/components/create_user_button.tsx
Normal file
|
@ -0,0 +1,148 @@
|
||||||
|
import React, { useState } from "react";
|
||||||
|
import { Button, Modal, Form, Input, message, Select, InputNumber } from "antd";
|
||||||
|
import { Button as Button2 } from "@tremor/react";
|
||||||
|
import { userCreateCall } from "./networking";
|
||||||
|
|
||||||
|
interface CreateuserProps {
|
||||||
|
userID: string;
|
||||||
|
accessToken: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Createuser: React.FC<CreateuserProps> = ({ userID, accessToken }) => {
|
||||||
|
const [form] = Form.useForm();
|
||||||
|
const [isModalVisible, setIsModalVisible] = useState(false);
|
||||||
|
const [apiuser, setApiuser] = useState<string | null>(null);
|
||||||
|
|
||||||
|
const handleOk = () => {
|
||||||
|
setIsModalVisible(false);
|
||||||
|
form.resetFields();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCancel = () => {
|
||||||
|
setIsModalVisible(false);
|
||||||
|
setApiuser(null);
|
||||||
|
form.resetFields();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCreate = async (formValues: { user_id: string }) => {
|
||||||
|
try {
|
||||||
|
message.info("Making API Call");
|
||||||
|
setIsModalVisible(true);
|
||||||
|
console.log("formValues in create user:", formValues);
|
||||||
|
const response = await userCreateCall(accessToken, userID, formValues);
|
||||||
|
console.log("user create Response:", response);
|
||||||
|
setApiuser(response["key"]);
|
||||||
|
message.success("API user Created");
|
||||||
|
form.resetFields();
|
||||||
|
localStorage.removeItem("userData" + userID);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error creating the user:", error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Button2 className="mx-auto" onClick={() => setIsModalVisible(true)}>
|
||||||
|
+ Create New User
|
||||||
|
</Button2>
|
||||||
|
<Modal
|
||||||
|
title="Create User"
|
||||||
|
visible={isModalVisible}
|
||||||
|
width={800}
|
||||||
|
footer={null}
|
||||||
|
onOk={handleOk}
|
||||||
|
onCancel={handleCancel}
|
||||||
|
>
|
||||||
|
<Form form={form} onFinish={handleCreate} labelCol={{ span: 8 }} wrapperCol={{ span: 16 }} labelAlign="left">
|
||||||
|
<Form.Item
|
||||||
|
label="User ID"
|
||||||
|
name="user_id"
|
||||||
|
>
|
||||||
|
<Input placeholder="Enter User ID" />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item
|
||||||
|
label="Team ID"
|
||||||
|
name="team_id"
|
||||||
|
>
|
||||||
|
<Input placeholder="ai_team" />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item
|
||||||
|
label="Models"
|
||||||
|
name="models"
|
||||||
|
>
|
||||||
|
{/* <Select
|
||||||
|
mode="multiple"
|
||||||
|
placeholder="Select models"
|
||||||
|
style={{ width: '100%' }}
|
||||||
|
>
|
||||||
|
{userModels.map((model) => (
|
||||||
|
<Option key={model} value={model}>
|
||||||
|
{model}
|
||||||
|
</Option>
|
||||||
|
))}
|
||||||
|
</Select> */}
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
|
||||||
|
<Form.Item
|
||||||
|
label="Max Budget (USD)"
|
||||||
|
name="max_budget"
|
||||||
|
>
|
||||||
|
<InputNumber step={0.01} precision={2} width={200}/>
|
||||||
|
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item
|
||||||
|
label="Tokens per minute Limit (TPM)"
|
||||||
|
name="tpm_limit"
|
||||||
|
>
|
||||||
|
<InputNumber step={1} width={400}/>
|
||||||
|
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item
|
||||||
|
label="Requests per minute Limit (RPM)"
|
||||||
|
name="rpm_limit"
|
||||||
|
>
|
||||||
|
<InputNumber step={1} width={400}/>
|
||||||
|
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item
|
||||||
|
label="Duration (eg: 30s, 30h, 30d)"
|
||||||
|
name="duration"
|
||||||
|
>
|
||||||
|
<Input />
|
||||||
|
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item
|
||||||
|
label="Metadata"
|
||||||
|
name="metadata"
|
||||||
|
>
|
||||||
|
<Input.TextArea rows={4} placeholder="Enter metadata as JSON" />
|
||||||
|
|
||||||
|
</Form.Item>
|
||||||
|
<Button htmlType="submit">
|
||||||
|
Create User
|
||||||
|
</Button>
|
||||||
|
</Form>
|
||||||
|
</Modal>
|
||||||
|
{apiuser && (
|
||||||
|
<Modal
|
||||||
|
title="Save Your User"
|
||||||
|
visible={isModalVisible}
|
||||||
|
onOk={handleOk}
|
||||||
|
onCancel={handleCancel}
|
||||||
|
footer={null}
|
||||||
|
>
|
||||||
|
<p>
|
||||||
|
Please save this secret user 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 user, you will need to
|
||||||
|
generate a new one.
|
||||||
|
</p>
|
||||||
|
<p>{apiuser != null ? `API user: ${apiuser}` : "User being created, this might take 30s"}</p>
|
||||||
|
</Modal>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Createuser;
|
|
@ -69,6 +69,71 @@ export const keyCreateCall = async (
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
export const userCreateCall = async (
|
||||||
|
accessToken: string,
|
||||||
|
userID: string,
|
||||||
|
formValues: Record<string, any> // Assuming formValues is an object
|
||||||
|
) => {
|
||||||
|
try {
|
||||||
|
console.log("Form Values in keyCreateCall:", formValues); // Log the form values before making the API call
|
||||||
|
|
||||||
|
// check if formValues.description is not undefined, make it a string and add it to formValues.metadata
|
||||||
|
if (formValues.description) {
|
||||||
|
// add to formValues.metadata
|
||||||
|
if (!formValues.metadata) {
|
||||||
|
formValues.metadata = {};
|
||||||
|
}
|
||||||
|
// value needs to be in "", valid JSON
|
||||||
|
formValues.metadata.description = formValues.description;
|
||||||
|
// remove descrption from formValues
|
||||||
|
delete formValues.description;
|
||||||
|
formValues.metadata = JSON.stringify(formValues.metadata);
|
||||||
|
}
|
||||||
|
// if formValues.metadata is not undefined, make it a valid dict
|
||||||
|
if (formValues.metadata) {
|
||||||
|
console.log("formValues.metadata:", formValues.metadata);
|
||||||
|
// if there's an exception JSON.parse, show it in the message
|
||||||
|
try {
|
||||||
|
formValues.metadata = JSON.parse(formValues.metadata);
|
||||||
|
} catch (error) {
|
||||||
|
message.error("Failed to parse metadata: " + error);
|
||||||
|
throw new Error("Failed to parse metadata: " + error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("Form Values after check:", formValues);
|
||||||
|
const url = proxyBaseUrl ? `${proxyBaseUrl}/user/new` : `/user/new`;
|
||||||
|
const response = await fetch(url, {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${accessToken}`,
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
user_id: userID,
|
||||||
|
...formValues, // Include formValues in the request body
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
const errorData = await response.text();
|
||||||
|
message.error("Failed to create key: " + errorData);
|
||||||
|
console.error("Error response from the server:", errorData);
|
||||||
|
throw new Error("Network response was not ok");
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
console.log("API Response:", data);
|
||||||
|
return data;
|
||||||
|
// Handle success - you might want to update some state or UI based on the created key
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Failed to create key:", error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
export const keyDeleteCall = async (accessToken: String, user_key: String) => {
|
export const keyDeleteCall = async (accessToken: String, user_key: String) => {
|
||||||
try {
|
try {
|
||||||
const url = proxyBaseUrl ? `${proxyBaseUrl}/key/delete` : `/key/delete`;
|
const url = proxyBaseUrl ? `${proxyBaseUrl}/key/delete` : `/key/delete`;
|
||||||
|
|
|
@ -3,6 +3,7 @@ import { Card, Title, Subtitle, Table, TableHead, TableRow, TableCell, TableBody
|
||||||
import { userInfoCall } from "./networking";
|
import { userInfoCall } from "./networking";
|
||||||
import { Badge, BadgeDelta, Button } from '@tremor/react';
|
import { Badge, BadgeDelta, Button } from '@tremor/react';
|
||||||
import RequestAccess from "./request_model_access";
|
import RequestAccess from "./request_model_access";
|
||||||
|
import CreateUser from "./create_user_button";
|
||||||
|
|
||||||
interface ViewUserDashboardProps {
|
interface ViewUserDashboardProps {
|
||||||
accessToken: string | null;
|
accessToken: string | null;
|
||||||
|
@ -55,6 +56,12 @@ const ViewUserDashboard: React.FC<ViewUserDashboardProps> = ({
|
||||||
return (
|
return (
|
||||||
<div style={{ width: "100%" }}>
|
<div style={{ width: "100%" }}>
|
||||||
<Grid className="gap-2 p-10 h-[75vh] w-full">
|
<Grid className="gap-2 p-10 h-[75vh] w-full">
|
||||||
|
<CreateUser
|
||||||
|
userID={userID}
|
||||||
|
userRole={userRole}
|
||||||
|
userModels={["litellm-proxy-budget"]}
|
||||||
|
accessToken={accessToken}
|
||||||
|
/>
|
||||||
<Card>
|
<Card>
|
||||||
<Table className="mt-5">
|
<Table className="mt-5">
|
||||||
<TableHead>
|
<TableHead>
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue