diff --git a/litellm/proxy/proxy_cli.py b/litellm/proxy/proxy_cli.py index 584ad98c1..6583dbed1 100644 --- a/litellm/proxy/proxy_cli.py +++ b/litellm/proxy/proxy_cli.py @@ -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() diff --git a/litellm/proxy/proxy_server.py b/litellm/proxy/proxy_server.py index 4a854ec76..0a17b8ecd 100644 --- a/litellm/proxy/proxy_server.py +++ b/litellm/proxy/proxy_server.py @@ -515,11 +515,15 @@ async def user_api_key_auth( ) if ( - (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 + ( + route.startswith("/key/") + or route.startswith("/user/") + or route.startswith("/model/") + ) + 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) diff --git a/litellm/utils.py b/litellm/utils.py index ed86f2fae..bbc4e651c 100644 --- a/litellm/utils.py +++ b/litellm/utils.py @@ -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 diff --git a/ui/litellm-dashboard/src/app/page.tsx b/ui/litellm-dashboard/src/app/page.tsx index ac8ce4ad5..ad5f35227 100644 --- a/ui/litellm-dashboard/src/app/page.tsx +++ b/ui/litellm-dashboard/src/app/page.tsx @@ -1,22 +1,13 @@ -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 ( -
- - - - - - - +
+ +
); }; -export default CreateKeyPage; \ No newline at end of file +export default CreateKeyPage; diff --git a/ui/litellm-dashboard/src/components/create_key_button.tsx b/ui/litellm-dashboard/src/components/create_key_button.tsx index 2dfc6b1d8..f785b0b80 100644 --- a/ui/litellm-dashboard/src/components/create_key_button.tsx +++ b/ui/litellm-dashboard/src/components/create_key_button.tsx @@ -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 = ({ + userID, + accessToken, + proxyBaseUrl, +}) => { const handleClick = () => { - console.log('Hello World'); + console.log("Hello World"); }; return ( - // - // Key Name - // - - // - + ); -} \ No newline at end of file +}; + +export default CreateKey; diff --git a/ui/litellm-dashboard/src/components/networking.tsx b/ui/litellm-dashboard/src/components/networking.tsx new file mode 100644 index 000000000..a8534b662 --- /dev/null +++ b/ui/litellm-dashboard/src/components/networking.tsx @@ -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; + } +}; diff --git a/ui/litellm-dashboard/src/components/user_dashboard.tsx b/ui/litellm-dashboard/src/components/user_dashboard.tsx new file mode 100644 index 000000000..c7acd3640 --- /dev/null +++ b/ui/litellm-dashboard/src/components/user_dashboard.tsx @@ -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 ( + + Login to create/delete keys + + ); + } + + return ( + + + + + + + ); +} diff --git a/ui/litellm-dashboard/src/components/view_key_table.tsx b/ui/litellm-dashboard/src/components/view_key_table.tsx index ecf5a4329..50ef6a534 100644 --- a/ui/litellm-dashboard/src/components/view_key_table.tsx +++ b/ui/litellm-dashboard/src/components/view_key_table.tsx @@ -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,69 +15,105 @@ 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() { - - return ( - - API Keys - - - - Alias - Secret Key - Spend - Status - - - - {data.map((item) => ( - - {item.key_alias} - - {item.key_name} - - - {item.spend} - - - - {item.expires} - - +const ViewKeyTable: React.FC = ({ + 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 ( + + API Keys +
+ + + Alias + Secret Key + Spend + Status - ))} - -
-
-)}; \ No newline at end of file + + + {data.map((item) => ( + + {item.key_alias} + + {item.key_name} + + + {item.spend} + + + + {item.expires} + + + + ))} + + + + ); +}; + +export default ViewKeyTable;