Merge pull request #3787 from BerriAI/litellm_set_budgets_ui_2

feat(proxy_server.py): enable admin to set tpm/rpm limits for end-users via UI
This commit is contained in:
Krish Dholakia 2024-05-22 18:22:51 -07:00 committed by GitHub
commit 3400596dd2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 931 additions and 122 deletions

View file

@ -706,6 +706,10 @@ class BudgetRequest(LiteLLMBase):
budgets: List[str]
class BudgetDeleteRequest(LiteLLMBase):
id: str
class KeyManagementSystem(enum.Enum):
GOOGLE_KMS = "google_kms"
AZURE_KEY_VAULT = "azure_key_vault"

View file

@ -8289,6 +8289,142 @@ async def info_budget(data: BudgetRequest):
return response
@router.get(
"/budget/settings",
tags=["budget management"],
dependencies=[Depends(user_api_key_auth)],
)
async def budget_settings(
budget_id: str,
user_api_key_dict: UserAPIKeyAuth = Depends(user_api_key_auth),
):
"""
Get list of configurable params + current value for a budget item + description of each field
Used on Admin UI.
"""
if prisma_client is None:
raise HTTPException(
status_code=400,
detail={"error": CommonProxyErrors.db_not_connected_error.value},
)
if user_api_key_dict.user_role != "proxy_admin":
raise HTTPException(
status_code=400,
detail={
"error": "{}, your role={}".format(
CommonProxyErrors.not_allowed_access.value,
user_api_key_dict.user_role,
)
},
)
## get budget item from db
db_budget_row = await prisma_client.db.litellm_budgettable.find_first(
where={"budget_id": budget_id}
)
if db_budget_row is not None:
db_budget_row_dict = db_budget_row.model_dump(exclude_none=True)
else:
db_budget_row_dict = {}
allowed_args = {
"max_parallel_requests": {"type": "Integer"},
"tpm_limit": {"type": "Integer"},
"rpm_limit": {"type": "Integer"},
"budget_duration": {"type": "String"},
"max_budget": {"type": "Float"},
"soft_budget": {"type": "Float"},
}
return_val = []
for field_name, field_info in BudgetNew.model_fields.items():
if field_name in allowed_args:
_stored_in_db = True
_response_obj = ConfigList(
field_name=field_name,
field_type=allowed_args[field_name]["type"],
field_description=field_info.description or "",
field_value=db_budget_row_dict.get(field_name, None),
stored_in_db=_stored_in_db,
)
return_val.append(_response_obj)
return return_val
@router.get(
"/budget/list",
tags=["budget management"],
dependencies=[Depends(user_api_key_auth)],
)
async def list_budget(
user_api_key_dict: UserAPIKeyAuth = Depends(user_api_key_auth),
):
"""List all the created budgets in proxy db. Used on Admin UI."""
if prisma_client is None:
raise HTTPException(
status_code=400,
detail={"error": CommonProxyErrors.db_not_connected_error.value},
)
if user_api_key_dict.user_role != "proxy_admin":
raise HTTPException(
status_code=400,
detail={
"error": "{}, your role={}".format(
CommonProxyErrors.not_allowed_access.value,
user_api_key_dict.user_role,
)
},
)
response = await prisma_client.db.litellm_budgettable.find_many()
return response
@router.post(
"/budget/delete",
tags=["budget management"],
dependencies=[Depends(user_api_key_auth)],
)
async def delete_budget(
data: BudgetDeleteRequest,
user_api_key_dict: UserAPIKeyAuth = Depends(user_api_key_auth),
):
"""Delete budget"""
global prisma_client
if prisma_client is None:
raise HTTPException(
status_code=500,
detail={"error": CommonProxyErrors.db_not_connected_error.value},
)
if user_api_key_dict.user_role != "proxy_admin":
raise HTTPException(
status_code=400,
detail={
"error": "{}, your role={}".format(
CommonProxyErrors.not_allowed_access.value,
user_api_key_dict.user_role,
)
},
)
response = await prisma_client.db.litellm_budgettable.delete(
where={"budget_id": data.id}
)
return response
#### MODEL MANAGEMENT ####

View file

@ -9,6 +9,7 @@ import Teams from "@/components/teams";
import AdminPanel from "@/components/admins";
import Settings from "@/components/settings";
import GeneralSettings from "@/components/general_settings";
import BudgetPanel from "@/components/budgets/budget_panel";
import APIRef from "@/components/api_ref";
import ChatUI from "@/components/chat_ui";
import Sidebar from "../components/leftnav";
@ -111,7 +112,6 @@ const CreateKeyPage = () => {
userRole={userRole}
defaultSelectedKey={null}
/>
</div>
{page == "api-keys" ? (
@ -169,13 +169,15 @@ const CreateKeyPage = () => {
showSSOBanner={showSSOBanner}
/>
) : page == "api_ref" ? (
<APIRef/>
<APIRef />
) : page == "settings" ? (
<Settings
userID={userID}
userRole={userRole}
accessToken={accessToken}
/>
) : page == "budgets" ? (
<BudgetPanel accessToken={accessToken} />
) : page == "general-settings" ? (
<GeneralSettings
userID={userID}

View file

@ -0,0 +1,140 @@
import React from "react";
import {
Button,
TextInput,
Grid,
Col,
Accordion,
AccordionHeader,
AccordionBody,
} from "@tremor/react";
import {
Button as Button2,
Modal,
Form,
Input,
InputNumber,
Select,
message,
} from "antd";
import { budgetCreateCall } from "../networking";
interface BudgetModalProps {
isModalVisible: boolean;
accessToken: string | null;
setIsModalVisible: React.Dispatch<React.SetStateAction<boolean>>;
setBudgetList: React.Dispatch<React.SetStateAction<any[]>>;
}
const BudgetModal: React.FC<BudgetModalProps> = ({
isModalVisible,
accessToken,
setIsModalVisible,
setBudgetList,
}) => {
const [form] = Form.useForm();
const handleOk = () => {
setIsModalVisible(false);
form.resetFields();
};
const handleCancel = () => {
setIsModalVisible(false);
form.resetFields();
};
const handleCreate = async (formValues: Record<string, any>) => {
if (accessToken == null || accessToken == undefined) {
return;
}
try {
message.info("Making API Call");
// setIsModalVisible(true);
const response = await budgetCreateCall(accessToken, formValues);
console.log("key create Response:", response);
setBudgetList((prevData) =>
prevData ? [...prevData, response] : [response]
); // Check if prevData is null
message.success("API Key Created");
form.resetFields();
} catch (error) {
console.error("Error creating the key:", error);
message.error(`Error creating the key: ${error}`, 20);
}
};
return (
<Modal
title="Create Budget"
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="Budget ID"
name="budget_id"
rules={[
{
required: true,
message: "Please input a human-friendly name for the budget",
},
]}
help="A human-friendly name for the budget"
>
<TextInput placeholder="" />
</Form.Item>
<Form.Item
label="Max Tokens per minute"
name="tpm_limit"
help="Default is model limit."
>
<InputNumber step={1} precision={2} width={200} />
</Form.Item>
<Form.Item
label="Max Requests per minute"
name="rpm_limit"
help="Default is model limit."
>
<InputNumber step={1} precision={2} width={200} />
</Form.Item>
<Accordion className="mt-20 mb-8">
<AccordionHeader>
<b>Optional Settings</b>
</AccordionHeader>
<AccordionBody>
<Form.Item label="Max Budget (USD)" name="max_budget">
<InputNumber step={0.01} precision={2} width={200} />
</Form.Item>
<Form.Item
className="mt-8"
label="Reset Budget"
name="budget_duration"
>
<Select defaultValue={null} placeholder="n/a">
<Select.Option value="24h">daily</Select.Option>
<Select.Option value="30d">monthly</Select.Option>
</Select>
</Form.Item>
</AccordionBody>
</Accordion>
</>
<div style={{ textAlign: "right", marginTop: "10px" }}>
<Button2 htmlType="submit">Create Budget</Button2>
</div>
</Form>
</Modal>
);
};
export default BudgetModal;

View file

@ -0,0 +1,201 @@
/**
* The parent pane, showing list of budgets
*
*/
import React, { useState, useEffect } from "react";
import BudgetSettings from "./budget_settings";
import BudgetModal from "./budget_modal";
import {
Table,
TableBody,
TableCell,
TableFoot,
TableHead,
TableHeaderCell,
TableRow,
Card,
Button,
Icon,
Text,
Tab,
TabGroup,
TabList,
TabPanel,
TabPanels,
Grid,
} from "@tremor/react";
import {
InformationCircleIcon,
PencilAltIcon,
PencilIcon,
StatusOnlineIcon,
TrashIcon,
RefreshIcon,
CheckCircleIcon,
XCircleIcon,
QuestionMarkCircleIcon,
} from "@heroicons/react/outline";
import { Prism as SyntaxHighlighter } from "react-syntax-highlighter";
import { getBudgetList, budgetDeleteCall } from "../networking";
import { message } from "antd";
interface BudgetSettingsPageProps {
accessToken: string | null;
}
interface budgetItem {
budget_id: string;
max_budget: string | null;
rpm_limit: number | null;
tpm_limit: number | null;
}
const BudgetPanel: React.FC<BudgetSettingsPageProps> = ({ accessToken }) => {
const [isModalVisible, setIsModalVisible] = useState(false);
const [budgetList, setBudgetList] = useState<budgetItem[]>([]);
useEffect(() => {
if (!accessToken) {
return;
}
getBudgetList(accessToken).then((data) => {
setBudgetList(data);
});
}, [accessToken]);
const handleDeleteCall = async (budget_id: string, index: number) => {
if (accessToken == null) {
return;
}
message.info("Request made");
await budgetDeleteCall(accessToken, budget_id);
const newBudgetList = [...budgetList];
newBudgetList.splice(index, 1);
setBudgetList(newBudgetList);
message.success("Budget Deleted.");
};
return (
<div className="w-full mx-auto flex-auto overflow-y-auto m-8 p-2">
<Button
size="sm"
variant="primary"
className="mb-2"
onClick={() => setIsModalVisible(true)}
>
+ Create Budget
</Button>
<BudgetModal
accessToken={accessToken}
isModalVisible={isModalVisible}
setIsModalVisible={setIsModalVisible}
setBudgetList={setBudgetList}
/>
<Card>
<Text>Create a budget to assign to customers.</Text>
<Table>
<TableHead>
<TableRow>
<TableHeaderCell>Budget ID</TableHeaderCell>
<TableHeaderCell>Max Budget</TableHeaderCell>
<TableHeaderCell>TPM</TableHeaderCell>
<TableHeaderCell>RPM</TableHeaderCell>
</TableRow>
</TableHead>
<TableBody>
{budgetList.map((value: budgetItem, index: number) => (
<TableRow>
<TableCell>{value.budget_id}</TableCell>
<TableCell>
{value.max_budget ? value.max_budget : "n/a"}
</TableCell>
<TableCell>
{value.tpm_limit ? value.tpm_limit : "n/a"}
</TableCell>
<TableCell>
{value.rpm_limit ? value.rpm_limit : "n/a"}
</TableCell>
<Icon
icon={TrashIcon}
size="sm"
onClick={() => handleDeleteCall(value.budget_id, index)}
/>
</TableRow>
))}
</TableBody>
</Table>
</Card>
<div className="mt-5">
<Text className="text-base">How to use budget id</Text>
<TabGroup>
<TabList>
<Tab>Assign Budget to Customer</Tab>
<Tab>Test it (Curl)</Tab>
<Tab>Test it (OpenAI SDK)</Tab>
</TabList>
<TabPanels>
<TabPanel>
<SyntaxHighlighter language="bash">
{`
curl -X POST --location '<your_proxy_base_url>/end_user/new' \
-H 'Authorization: Bearer <your-master-key>' \
-H 'Content-Type: application/json' \
-d '{"user_id": "my-customer-id', "budget_id": "<BUDGET_ID>"}' # 👈 KEY CHANGE
`}
</SyntaxHighlighter>
</TabPanel>
<TabPanel>
<SyntaxHighlighter language="bash">
{`
curl -X POST --location '<your_proxy_base_url>/chat/completions' \
-H 'Authorization: Bearer <your-master-key>' \
-H 'Content-Type: application/json' \
-d '{
"model": "gpt-3.5-turbo',
"messages":[{"role": "user", "content": "Hey, how's it going?"}],
"user": "my-customer-id"
}' # 👈 KEY CHANGE
`}
</SyntaxHighlighter>
</TabPanel>
<TabPanel>
<SyntaxHighlighter language="python">
{`from openai import OpenAI
client = OpenAI(
base_url="<your_proxy_base_url",
api_key="<your_proxy_key>"
)
completion = client.chat.completions.create(
model="gpt-3.5-turbo",
messages=[
{"role": "system", "content": "You are a helpful assistant."},
{"role": "user", "content": "Hello!"}
],
user="my-customer-id"
)
print(completion.choices[0].message)`}
</SyntaxHighlighter>
</TabPanel>
</TabPanels>
</TabGroup>
</div>
</div>
);
};
export default BudgetPanel;

View file

@ -0,0 +1,195 @@
import React, { useState, useEffect } from "react";
import {
Card,
Title,
Subtitle,
Table,
TableHead,
TableRow,
Badge,
TableHeaderCell,
TableCell,
TableBody,
Metric,
Text,
Grid,
Button,
TextInput,
Select as Select2,
SelectItem,
Col,
Accordion,
AccordionBody,
AccordionHeader,
AccordionList,
} from "@tremor/react";
import {
TabPanel,
TabPanels,
TabGroup,
TabList,
Tab,
Icon,
} from "@tremor/react";
import { getBudgetSettings } from "../networking";
import {
Modal,
Form,
Input,
Select,
Button as Button2,
message,
InputNumber,
} from "antd";
import {
InformationCircleIcon,
PencilAltIcon,
PencilIcon,
StatusOnlineIcon,
TrashIcon,
RefreshIcon,
CheckCircleIcon,
XCircleIcon,
QuestionMarkCircleIcon,
} from "@heroicons/react/outline";
import StaticGenerationSearchParamsBailoutProvider from "next/dist/client/components/static-generation-searchparams-bailout-provider";
import AddFallbacks from "../add_fallbacks";
import openai from "openai";
import Paragraph from "antd/es/skeleton/Paragraph";
interface BudgetSettingsPageProps {
accessToken: string | null;
}
interface budgetSettingsItem {
field_name: string;
field_type: string;
field_value: any;
field_description: string;
}
const BudgetSettings: React.FC<BudgetSettingsPageProps> = ({ accessToken }) => {
const [budgetSettings, setBudgetSettings] = useState<budgetSettingsItem[]>(
[]
);
useEffect(() => {
if (!accessToken) {
return;
}
getBudgetSettings(accessToken).then((data) => {
console.log("budget settings", data);
let budget_settings = data.budget_settings;
setBudgetSettings(budget_settings);
});
}, [accessToken]);
const handleInputChange = (fieldName: string, newValue: any) => {
// Update the value in the state
const updatedSettings = budgetSettings.map((setting) =>
setting.field_name === fieldName
? { ...setting, field_value: newValue }
: setting
);
setBudgetSettings(updatedSettings);
};
const handleUpdateField = (fieldName: string, idx: number) => {
if (!accessToken) {
return;
}
let fieldValue = budgetSettings[idx].field_value;
if (fieldValue == null || fieldValue == undefined) {
return;
}
try {
const updatedSettings = budgetSettings.map((setting) =>
setting.field_name === fieldName
? { ...setting, stored_in_db: true }
: setting
);
setBudgetSettings(updatedSettings);
} catch (error) {
// do something
}
};
const handleResetField = (fieldName: string, idx: number) => {
if (!accessToken) {
return;
}
try {
const updatedSettings = budgetSettings.map((setting) =>
setting.field_name === fieldName
? { ...setting, stored_in_db: null, field_value: null }
: setting
);
setBudgetSettings(updatedSettings);
} catch (error) {
// do something
}
};
return (
<div className="w-full mx-4">
<Card>
<Table>
<TableHead>
<TableRow>
<TableHeaderCell>Setting</TableHeaderCell>
<TableHeaderCell>Value</TableHeaderCell>
</TableRow>
</TableHead>
<TableBody>
{budgetSettings.map((value, index) => (
<TableRow key={index}>
<TableCell>
<Text>{value.field_name}</Text>
<p
style={{
fontSize: "0.65rem",
color: "#808080",
fontStyle: "italic",
}}
className="mt-1"
>
{value.field_description}
</p>
</TableCell>
<TableCell>
{value.field_type == "Integer" ? (
<InputNumber
step={1}
value={value.field_value}
onChange={(newValue) =>
handleInputChange(value.field_name, newValue)
} // Handle value change
/>
) : null}
</TableCell>
<TableCell>
<Button
onClick={() => handleUpdateField(value.field_name, index)}
>
Update
</Button>
<Icon
icon={TrashIcon}
color="red"
onClick={() => handleResetField(value.field_name, index)}
>
Reset
</Icon>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</Card>
</div>
);
};
export default BudgetSettings;

View file

@ -10,7 +10,11 @@ interface CreateuserProps {
teams: any[] | null;
}
const Createuser: React.FC<CreateuserProps> = ({ userID, accessToken, teams }) => {
const Createuser: React.FC<CreateuserProps> = ({
userID,
accessToken,
teams,
}) => {
const [form] = Form.useForm();
const [isModalVisible, setIsModalVisible] = useState(false);
const [apiuser, setApiuser] = useState<string | null>(null);
@ -73,7 +77,7 @@ const Createuser: React.FC<CreateuserProps> = ({ userID, accessToken, teams }) =
return (
<div>
<Button2 className="mx-auto" onClick={() => setIsModalVisible(true)}>
<Button2 className="mx-auto mb-0" onClick={() => setIsModalVisible(true)}>
+ Invite User
</Button2>
<Modal
@ -84,8 +88,12 @@ const Createuser: React.FC<CreateuserProps> = ({ userID, accessToken, teams }) =
onOk={handleOk}
onCancel={handleCancel}
>
<Text className="mb-1">Invite a user to login to the Admin UI and create Keys</Text>
<Text className="mb-6"><b>Note: SSO Setup Required for this</b></Text>
<Text className="mb-1">
Invite a user to login to the Admin UI and create Keys
</Text>
<Text className="mb-6">
<b>Note: SSO Setup Required for this</b>
</Text>
<Form
form={form}
onFinish={handleCreate}
@ -97,10 +105,7 @@ const Createuser: React.FC<CreateuserProps> = ({ userID, accessToken, teams }) =
<TextInput placeholder="" />
</Form.Item>
<Form.Item label="Team ID" name="team_id">
<Select
placeholder="Select Team ID"
style={{ width: "100%" }}
>
<Select placeholder="Select Team ID" style={{ width: "100%" }}>
{teams ? (
teams.map((team: any) => (
<Option key={team.team_id} value={team.team_id}>
@ -131,12 +136,16 @@ const Createuser: React.FC<CreateuserProps> = ({ userID, accessToken, teams }) =
footer={null}
>
<p>
User has been created to access your proxy. Please Ask them to Log In.
User has been created to access your proxy. Please Ask them to Log
In.
</p>
<br></br>
<p><b>Note: This Feature is only supported through SSO on the Admin UI</b></p>
<p>
<b>
Note: This Feature is only supported through SSO on the Admin UI
</b>
</p>
</Modal>
)}
</div>

View file

@ -1,7 +1,7 @@
import { Layout, Menu } from "antd";
import Link from "next/link";
import { List } from "postcss/lib/list";
import { Text } from "@tremor/react"
import { Text } from "@tremor/react";
const { Sider } = Layout;
@ -54,87 +54,60 @@ const Sidebar: React.FC<SidebarProps> = ({
style={{ height: "100%", borderRight: 0 }}
>
<Menu.Item key="1" onClick={() => setPage("api-keys")}>
<Text>
API Keys
</Text>
<Text>API Keys</Text>
</Menu.Item>
<Menu.Item key="3" onClick={() => setPage("llm-playground")}>
<Text>
Test Key
</Text>
<Text>Test Key</Text>
</Menu.Item>
{
userRole == "Admin" ? (
{userRole == "Admin" ? (
<Menu.Item key="2" onClick={() => setPage("models")}>
<Text>
Models
</Text>
<Text>Models</Text>
</Menu.Item>
) : null
}
{
userRole == "Admin" ? (
) : null}
{userRole == "Admin" ? (
<Menu.Item key="4" onClick={() => setPage("usage")}>
<Text>
Usage
</Text>
<Text>Usage</Text>
</Menu.Item>
) : null
}
) : null}
{userRole == "Admin" ? (
<Menu.Item key="6" onClick={() => setPage("teams")}>
<Text>
Teams
</Text>
<Text>Teams</Text>
</Menu.Item>
) : null}
{userRole == "Admin" ? (
<Menu.Item key="5" onClick={() => setPage("users")}>
<Text>
Users
</Text>
<Text>Users</Text>
</Menu.Item>
) : null}
{
userRole == "Admin" ? (
<Menu.Item key="8" onClick={() => setPage("settings")}>
<Text>
Logging & Alerts
</Text>
</Menu.Item>
) : null
}
{
userRole == "Admin" ? (
<Menu.Item key="9" onClick={() => setPage("general-settings")}>
<Text>
Router Settings
</Text>
</Menu.Item>
) : null
}
{userRole == "Admin" ? (
<Menu.Item key="7" onClick={() => setPage("admin-panel")}>
<Text>
Admin
</Text>
<Menu.Item key="8" onClick={() => setPage("settings")}>
<Text>Logging & Alerts</Text>
</Menu.Item>
) : null}
<Menu.Item key="11" onClick={() => setPage("api_ref")}>
<Text>
API Reference
</Text>
{userRole == "Admin" ? (
<Menu.Item key="9" onClick={() => setPage("budgets")}>
<Text>Rate Limits</Text>
</Menu.Item>
) : null}
{userRole == "Admin" ? (
<Menu.Item key="10" onClick={() => setPage("general-settings")}>
<Text>Router Settings</Text>
</Menu.Item>
) : null}
{userRole == "Admin" ? (
<Menu.Item key="11" onClick={() => setPage("admin-panel")}>
<Text>Admin</Text>
</Menu.Item>
) : null}
<Menu.Item key="12" onClick={() => setPage("api_ref")}>
<Text>API Reference</Text>
</Menu.Item>
</Menu>
</Sider>

View file

@ -97,6 +97,82 @@ export const modelDeleteCall = async (
}
};
export const budgetDeleteCall = async (
accessToken: string | null,
budget_id: string
) => {
console.log(`budget_id in budget delete call: ${budget_id}`);
if (accessToken == null) {
return;
}
try {
const url = proxyBaseUrl
? `${proxyBaseUrl}/budget/delete`
: `/budget/delete`;
const response = await fetch(url, {
method: "POST",
headers: {
Authorization: `Bearer ${accessToken}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
id: budget_id,
}),
});
if (!response.ok) {
const errorData = await response.text();
message.error("Failed to create key: " + errorData, 10);
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;
} catch (error) {
console.error("Failed to create key:", error);
throw error;
}
};
export const budgetCreateCall = async (
accessToken: string,
formValues: Record<string, any> // Assuming formValues is an object
) => {
try {
console.log("Form Values in budgetCreateCall:", formValues); // Log the form values before making the API call
console.log("Form Values after check:", formValues);
const url = proxyBaseUrl ? `${proxyBaseUrl}/budget/new` : `/budget/new`;
const response = await fetch(url, {
method: "POST",
headers: {
Authorization: `Bearer ${accessToken}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
...formValues, // Include formValues in the request body
}),
});
if (!response.ok) {
const errorData = await response.text();
message.error("Failed to create key: " + errorData, 10);
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 keyCreateCall = async (
accessToken: string,
userID: string,
@ -1421,6 +1497,71 @@ export const serviceHealthCheck = async (
}
};
export const getBudgetList = async (accessToken: String) => {
/**
* Get all configurable params for setting a budget
*/
try {
let url = proxyBaseUrl ? `${proxyBaseUrl}/budget/list` : `/budget/list`;
//message.info("Requesting model data");
const response = await fetch(url, {
method: "GET",
headers: {
Authorization: `Bearer ${accessToken}`,
"Content-Type": "application/json",
},
});
if (!response.ok) {
const errorData = await response.text();
message.error(errorData, 10);
throw new Error("Network response was not ok");
}
const data = await response.json();
//message.info("Received model 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 get callbacks:", error);
throw error;
}
};
export const getBudgetSettings = async (accessToken: String) => {
/**
* Get all configurable params for setting a budget
*/
try {
let url = proxyBaseUrl
? `${proxyBaseUrl}/budget/settings`
: `/budget/settings`;
//message.info("Requesting model data");
const response = await fetch(url, {
method: "GET",
headers: {
Authorization: `Bearer ${accessToken}`,
"Content-Type": "application/json",
},
});
if (!response.ok) {
const errorData = await response.text();
message.error(errorData, 10);
throw new Error("Network response was not ok");
}
const data = await response.json();
//message.info("Received model 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 get callbacks:", error);
throw error;
}
};
export const getCallbacksCall = async (
accessToken: String,
userID: String,

View file

@ -82,8 +82,6 @@ const ViewUserDashboard: React.FC<ViewUserDashboardProps> = ({
if (accessToken && token && userRole && userID) {
fetchData();
}
}, [accessToken, token, userRole, userID, currentPage]);
if (!userData) {
@ -102,7 +100,7 @@ const ViewUserDashboard: React.FC<ViewUserDashboardProps> = ({
return (
<div className="flex justify-between items-center">
<div>
Showing Page {currentPage+1} of {totalPages}
Showing Page {currentPage + 1} of {totalPages}
</div>
<div className="flex">
<button
@ -129,17 +127,17 @@ const ViewUserDashboard: React.FC<ViewUserDashboardProps> = ({
return (
<div style={{ width: "100%" }}>
<Grid className="gap-2 p-2 h-[80vh] w-full mt-8">
<CreateUser userID={userID} accessToken={accessToken} teams={teams}/>
<CreateUser userID={userID} accessToken={accessToken} teams={teams} />
<Card className="w-full mx-auto flex-auto overflow-y-auto max-h-[80vh] mb-4">
<div className="mb-4 mt-1">
<Text>These are Users on LiteLLM that created API Keys. Automatically tracked by LiteLLM</Text>
<Text>
These are Users on LiteLLM that created API Keys. Automatically
tracked by LiteLLM
</Text>
</div>
<TabGroup>
<TabPanels>
<TabPanel>
<Table className="mt-5">
<TableHead>
<TableRow>
@ -162,27 +160,40 @@ const ViewUserDashboard: React.FC<ViewUserDashboardProps> = ({
? user.models
: "All Models"}
</TableCell>
<TableCell>{user.spend ? user.spend?.toFixed(2) : 0}</TableCell>
<TableCell>
{user.spend ? user.spend?.toFixed(2) : 0}
</TableCell>
<TableCell>
{user.max_budget ? user.max_budget : "Unlimited"}
</TableCell>
<TableCell>
<Grid numItems={2}>
{user && user.key_aliases
? user.key_aliases.filter((key: any) => key !== null).length > 0
? <Badge size={"xs"} color={"indigo"}>{user.key_aliases.filter((key: any) => key !== null).join(', ') }</Badge>
: <Badge size={"xs"} color={"gray"}>No Keys</Badge>
: <Badge size={"xs"} color={"gray"}>No Keys</Badge>}
{user && user.key_aliases ? (
user.key_aliases.filter(
(key: any) => key !== null
).length > 0 ? (
<Badge size={"xs"} color={"indigo"}>
{user.key_aliases
.filter((key: any) => key !== null)
.join(", ")}
</Badge>
) : (
<Badge size={"xs"} color={"gray"}>
No Keys
</Badge>
)
) : (
<Badge size={"xs"} color={"gray"}>
No Keys
</Badge>
)}
{/* <Text>{user.key_aliases.filter(key => key !== null).length} Keys</Text> */}
{/* <Icon icon={InformationCircleIcon} onClick= {() => {
setOpenDialogId(user.user_id)
setSelectedItem(user)
}}>View Keys</Icon> */}
</Grid>
</TableCell>
</TableRow>
))}
</TableBody>
@ -191,9 +202,7 @@ const ViewUserDashboard: React.FC<ViewUserDashboardProps> = ({
<TabPanel>
<div className="flex items-center">
<div className="flex-1"></div>
<div className="flex-1 flex justify-between items-center">
</div>
<div className="flex-1 flex justify-between items-center"></div>
</div>
{/* <Table className="max-h-[70vh] min-h-[500px]">
<TableHead>
@ -242,7 +251,6 @@ const ViewUserDashboard: React.FC<ViewUserDashboardProps> = ({
</DialogPanel>
</Dialog> */}
</div>
);
};