build(ui/litellm_dashboard_v_2): allow app owner to create keys and view their keys

This commit is contained in:
Krrish Dholakia 2024-01-27 13:48:35 -08:00
parent af8b35d556
commit dda115fcb7
8 changed files with 249 additions and 81 deletions

View file

@ -190,17 +190,27 @@ def run_server(
global feature_telemetry
args = locals()
if local:
from proxy_server import app, save_worker_config, usage_telemetry
from proxy_server import app, save_worker_config, usage_telemetry, ProxyConfig
else:
try:
from .proxy_server import app, save_worker_config, usage_telemetry
from .proxy_server import (
app,
save_worker_config,
usage_telemetry,
ProxyConfig,
)
except ImportError as e:
if "litellm[proxy]" in str(e):
# user is missing a proxy dependency, ask them to pip install litellm[proxy]
raise e
else:
# this is just a local/relative import error, user git cloned litellm
from proxy_server import app, save_worker_config, usage_telemetry
from proxy_server import (
app,
save_worker_config,
usage_telemetry,
ProxyConfig,
)
feature_telemetry = usage_telemetry
if version == True:
pkg_version = importlib.metadata.version("litellm")
@ -373,16 +383,16 @@ def run_server(
read from there and save it to os.env['DATABASE_URL']
"""
try:
import yaml
import yaml, asyncio
except:
raise ImportError(
"yaml needs to be imported. Run - `pip install 'litellm[proxy]'`"
)
if os.path.exists(config):
with open(config, "r") as config_file:
config = yaml.safe_load(config_file)
general_settings = config.get("general_settings", {})
proxy_config = ProxyConfig()
_, _, general_settings = asyncio.run(
proxy_config.load_config(router=None, config_file_path=config)
)
database_url = general_settings.get("database_url", None)
if database_url and database_url.startswith("os.environ/"):
original_dir = os.getcwd()

View file

@ -515,11 +515,15 @@ async def user_api_key_auth(
)
if (
(route.startswith("/key/") or route.startswith("/user/"))
(
route.startswith("/key/")
or route.startswith("/user/")
or route.startswith("/model/")
and not is_master_key_valid
and general_settings.get("allow_user_auth", False) != True
)
and (not is_master_key_valid)
and (not general_settings.get("allow_user_auth", False))
):
assert not general_settings.get("allow_user_auth", False)
if route == "/key/info":
# check if user can access this route
query_params = request.query_params
@ -546,7 +550,7 @@ async def user_api_key_auth(
pass
else:
raise Exception(
f"If master key is set, only master key can be used to generate, delete, update or get info for new keys/users"
f"only master key can be used to generate, delete, update or get info for new keys/users."
)
return UserAPIKeyAuth(api_key=api_key, **valid_token_dict)

View file

@ -2190,7 +2190,6 @@ def client(original_function):
result = original_function(*args, **kwargs)
end_time = datetime.datetime.now()
if "stream" in kwargs and kwargs["stream"] == True:
# TODO: Add to cache for streaming
if (
"complete_response" in kwargs
and kwargs["complete_response"] == True

View file

@ -1,20 +1,11 @@
import React from 'react';
import CreateKey from "../components/create_key_button"
import ViewKeyTable from "../components/view_key_table"
import Navbar from "../components/navbar"
import { Grid, Col } from "@tremor/react";
import React from "react";
import Navbar from "../components/navbar";
import UserDashboard from "../components/user_dashboard";
const CreateKeyPage = () => {
return (
<div className='h-screen'>
<Navbar/>
<Grid numItems={1} className="gap-0 p-10 h-[75vh]">
<Col numColSpan={1}>
<ViewKeyTable/>
<CreateKey/>
</Col>
</Grid>
<div className="flex min-h-screen flex-col items-center">
<Navbar />
<UserDashboard />
</div>
);
};

View file

@ -1,21 +1,40 @@
'use client';
"use client";
import React from 'react';
import { Button, TextInput } from '@tremor/react';
import React, { use } from "react";
import { Button, TextInput } from "@tremor/react";
import { Card, Metric, Text } from "@tremor/react";
import { createKeyCall } from "./networking";
// Define the props type
interface CreateKeyProps {
userID: string;
accessToken: string;
proxyBaseUrl: string;
}
export default function CreateKey() {
const CreateKey: React.FC<CreateKeyProps> = ({
userID,
accessToken,
proxyBaseUrl,
}) => {
const handleClick = () => {
console.log('Hello World');
console.log("Hello World");
};
return (
// <Card className="max-w-xs mx-auto" decoration="top" decorationColor="indigo">
// <Text className='mb-4'>Key Name</Text>
// <TextInput className='mb-4' placeholder="My test key"></TextInput>
// </Card>
<Button className="mx-auto" onClick={handleClick}>+ Create New Key</Button>
<Button
className="mx-auto"
onClick={() =>
createKeyCall(
(proxyBaseUrl = proxyBaseUrl),
(accessToken = accessToken),
(userID = userID)
)
}
>
+ Create New Key
</Button>
);
}
};
export default CreateKey;

View file

@ -0,0 +1,65 @@
/**
* Helper file for calls being made to proxy
*/
export const createKeyCall = async (
proxyBaseUrl: String,
accessToken: String,
userID: String
) => {
try {
const response = await fetch(`${proxyBaseUrl}/key/generate`, {
method: "POST",
headers: {
Authorization: `Bearer ${accessToken}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
team_id: "core-infra-4",
max_budget: 10,
user_id: userID,
}),
});
if (!response.ok) {
throw new Error("Network response was not ok");
}
const data = await response.json();
console.log(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);
}
};
export const userInfoCall = async (
proxyBaseUrl: String,
accessToken: String,
userID: String
) => {
try {
const response = await fetch(
`${proxyBaseUrl}/user/info?user_id=${userID}`,
{
method: "GET",
headers: {
Authorization: `Bearer ${accessToken}`,
"Content-Type": "application/json",
},
}
);
if (!response.ok) {
throw new Error("Network response was not ok");
}
const data = await response.json();
console.log(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;
}
};

View file

@ -0,0 +1,42 @@
"use client";
import React from "react";
import { Grid, Col, Card, Text } from "@tremor/react";
import CreateKey from "./create_key_button";
import ViewKeyTable from "./view_key_table";
import { useSearchParams } from "next/navigation";
export default function UserDashboard() {
const searchParams = useSearchParams();
const userID = searchParams.get("userID");
const accessToken = searchParams.get("accessToken");
const proxyBaseUrl = searchParams.get("proxyBaseUrl");
if (userID == null || accessToken == null || proxyBaseUrl == null) {
return (
<Card
className="max-w-xs mx-auto"
decoration="top"
decorationColor="indigo"
>
<Text>Login to create/delete keys</Text>
</Card>
);
}
return (
<Grid numItems={1} className="gap-0 p-10 h-[75vh]">
<Col numColSpan={1}>
<ViewKeyTable
userID={userID}
accessToken={accessToken}
proxyBaseUrl={proxyBaseUrl}
/>
<CreateKey
userID={userID}
accessToken={accessToken}
proxyBaseUrl={proxyBaseUrl}
/>
</Col>
</Grid>
);
}

View file

@ -1,4 +1,6 @@
'use client';
"use client";
import React, { useEffect, useState } from "react";
import { userInfoCall } from "./networking";
import { StatusOnlineIcon } from "@heroicons/react/outline";
import {
Badge,
@ -13,39 +15,72 @@ import {
Title,
} from "@tremor/react";
// Define the props type
interface ViewKeyTableProps {
userID: string;
accessToken: string;
proxyBaseUrl: string;
}
const data = [
{
key_alias: "my test key",
key_name: "sk-...hd74",
spend: 23.0,
expires: "active",
token: "23902dwojd90"
token: "23902dwojd90",
},
{
key_alias: "my test key",
key_name: "sk-...hd74",
spend: 23.0,
expires: "active",
token: "23902dwojd90"
token: "23902dwojd90",
},
{
key_alias: "my test key",
key_name: "sk-...hd74",
spend: 23.0,
expires: "active",
token: "23902dwojd90"
token: "23902dwojd90",
},
{
key_alias: "my test key",
key_name: "sk-...hd74",
spend: 23.0,
expires: "active",
token: "23902dwojd90"
token: "23902dwojd90",
},
];
export default function ViewKeyTable() {
const ViewKeyTable: React.FC<ViewKeyTableProps> = ({
userID,
accessToken,
proxyBaseUrl,
}) => {
const [data, setData] = useState(null); // State to store the data from the API
useEffect(() => {
const fetchData = async () => {
try {
const response = await userInfoCall(
(proxyBaseUrl = proxyBaseUrl),
(accessToken = accessToken),
(userID = userID)
);
setData(response["keys"]); // Update state with the fetched data
} catch (error) {
console.error("There was an error fetching the data", error);
// Optionally, update your UI to reflect the error state
}
};
fetchData(); // Call the async function to fetch data
}, []); // Empty dependency array
if (data == null) {
return;
}
return (
<Card className="flex-auto overflow-y-auto max-h-[50vh] mb-4">
<Title>API Keys</Title>
@ -78,4 +113,7 @@ export default function ViewKeyTable() {
</TableBody>
</Table>
</Card>
)};
);
};
export default ViewKeyTable;