Merge pull request #2249 from BerriAI/litellm_ui_admin_viewer_fixes

build(ui): fix admin viewer issue
This commit is contained in:
Krish Dholakia 2024-02-29 13:14:26 -08:00 committed by GitHub
commit 81c9283796
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
18 changed files with 326 additions and 130 deletions

File diff suppressed because one or more lines are too long

View file

@ -1 +1 @@
<!DOCTYPE html><html id="__next_error__"><head><meta charSet="utf-8"/><meta name="viewport" content="width=device-width, initial-scale=1"/><link rel="preload" as="script" fetchPriority="low" href="/ui/_next/static/chunks/webpack-12184ee6a95c1363.js" crossorigin=""/><script src="/ui/_next/static/chunks/fd9d1056-a85b2c176012d8e5.js" async="" crossorigin=""></script><script src="/ui/_next/static/chunks/69-e1b183dda365ec86.js" async="" crossorigin=""></script><script src="/ui/_next/static/chunks/main-app-096338c8e1915716.js" async="" crossorigin=""></script><title>🚅 LiteLLM</title><meta name="description" content="LiteLLM Proxy Admin UI"/><link rel="icon" href="/ui/favicon.ico" type="image/x-icon" sizes="16x16"/><meta name="next-size-adjust"/><script src="/ui/_next/static/chunks/polyfills-c67a75d1b6f99dc8.js" crossorigin="" noModule=""></script></head><body><script src="/ui/_next/static/chunks/webpack-12184ee6a95c1363.js" crossorigin="" async=""></script><script>(self.__next_f=self.__next_f||[]).push([0]);self.__next_f.push([2,null])</script><script>self.__next_f.push([1,"1:HL[\"/ui/_next/static/media/c9a5bc6a7c948fb0-s.p.woff2\",\"font\",{\"crossOrigin\":\"\",\"type\":\"font/woff2\"}]\n2:HL[\"/ui/_next/static/css/a40ad0909dd7838e.css\",\"style\",{\"crossOrigin\":\"\"}]\n0:\"$L3\"\n"])</script><script>self.__next_f.push([1,"4:I[47690,[],\"\"]\n6:I[77831,[],\"\"]\n7:I[30280,[\"303\",\"static/chunks/303-d80f23087a9e6aec.js\",\"931\",\"static/chunks/app/page-8f65fc157f538dff.js\"],\"\"]\n8:I[5613,[],\"\"]\n9:I[31778,[],\"\"]\nb:I[48955,[],\"\"]\nc:[]\n"])</script><script>self.__next_f.push([1,"3:[[[\"$\",\"link\",\"0\",{\"rel\":\"stylesheet\",\"href\":\"/ui/_next/static/css/a40ad0909dd7838e.css\",\"precedence\":\"next\",\"crossOrigin\":\"\"}]],[\"$\",\"$L4\",null,{\"buildId\":\"kyOCJPBB9pyUfbMKCAXr-\",\"assetPrefix\":\"/ui\",\"initialCanonicalUrl\":\"/\",\"initialTree\":[\"\",{\"children\":[\"__PAGE__\",{}]},\"$undefined\",\"$undefined\",true],\"initialSeedData\":[\"\",{\"children\":[\"__PAGE__\",{},[\"$L5\",[\"$\",\"$L6\",null,{\"propsForComponent\":{\"params\":{}},\"Component\":\"$7\",\"isStaticGeneration\":true}],null]]},[null,[\"$\",\"html\",null,{\"lang\":\"en\",\"children\":[\"$\",\"body\",null,{\"className\":\"__className_c23dc8\",\"children\":[\"$\",\"$L8\",null,{\"parallelRouterKey\":\"children\",\"segmentPath\":[\"children\"],\"loading\":\"$undefined\",\"loadingStyles\":\"$undefined\",\"loadingScripts\":\"$undefined\",\"hasLoading\":false,\"error\":\"$undefined\",\"errorStyles\":\"$undefined\",\"errorScripts\":\"$undefined\",\"template\":[\"$\",\"$L9\",null,{}],\"templateStyles\":\"$undefined\",\"templateScripts\":\"$undefined\",\"notFound\":[[\"$\",\"title\",null,{\"children\":\"404: This page could not be found.\"}],[\"$\",\"div\",null,{\"style\":{\"fontFamily\":\"system-ui,\\\"Segoe UI\\\",Roboto,Helvetica,Arial,sans-serif,\\\"Apple Color Emoji\\\",\\\"Segoe UI Emoji\\\"\",\"height\":\"100vh\",\"textAlign\":\"center\",\"display\":\"flex\",\"flexDirection\":\"column\",\"alignItems\":\"center\",\"justifyContent\":\"center\"},\"children\":[\"$\",\"div\",null,{\"children\":[[\"$\",\"style\",null,{\"dangerouslySetInnerHTML\":{\"__html\":\"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}\"}}],[\"$\",\"h1\",null,{\"className\":\"next-error-h1\",\"style\":{\"display\":\"inline-block\",\"margin\":\"0 20px 0 0\",\"padding\":\"0 23px 0 0\",\"fontSize\":24,\"fontWeight\":500,\"verticalAlign\":\"top\",\"lineHeight\":\"49px\"},\"children\":\"404\"}],[\"$\",\"div\",null,{\"style\":{\"display\":\"inline-block\"},\"children\":[\"$\",\"h2\",null,{\"style\":{\"fontSize\":14,\"fontWeight\":400,\"lineHeight\":\"49px\",\"margin\":0},\"children\":\"This page could not be found.\"}]}]]}]}]],\"notFoundStyles\":[],\"styles\":null}]}]}],null]],\"initialHead\":[false,\"$La\"],\"globalErrorComponent\":\"$b\",\"missingSlots\":\"$Wc\"}]]\n"])</script><script>self.__next_f.push([1,"a:[[\"$\",\"meta\",\"0\",{\"name\":\"viewport\",\"content\":\"width=device-width, initial-scale=1\"}],[\"$\",\"meta\",\"1\",{\"charSet\":\"utf-8\"}],[\"$\",\"title\",\"2\",{\"children\":\"🚅 LiteLLM\"}],[\"$\",\"meta\",\"3\",{\"name\":\"description\",\"content\":\"LiteLLM Proxy Admin UI\"}],[\"$\",\"link\",\"4\",{\"rel\":\"icon\",\"href\":\"/ui/favicon.ico\",\"type\":\"image/x-icon\",\"sizes\":\"16x16\"}],[\"$\",\"meta\",\"5\",{\"name\":\"next-size-adjust\"}]]\n5:null\n"])</script><script>self.__next_f.push([1,""])</script></body></html> <!DOCTYPE html><html id="__next_error__"><head><meta charSet="utf-8"/><meta name="viewport" content="width=device-width, initial-scale=1"/><link rel="preload" as="script" fetchPriority="low" href="/ui/_next/static/chunks/webpack-12184ee6a95c1363.js" crossorigin=""/><script src="/ui/_next/static/chunks/fd9d1056-a85b2c176012d8e5.js" async="" crossorigin=""></script><script src="/ui/_next/static/chunks/69-e1b183dda365ec86.js" async="" crossorigin=""></script><script src="/ui/_next/static/chunks/main-app-096338c8e1915716.js" async="" crossorigin=""></script><title>🚅 LiteLLM</title><meta name="description" content="LiteLLM Proxy Admin UI"/><link rel="icon" href="/ui/favicon.ico" type="image/x-icon" sizes="16x16"/><meta name="next-size-adjust"/><script src="/ui/_next/static/chunks/polyfills-c67a75d1b6f99dc8.js" crossorigin="" noModule=""></script></head><body><script src="/ui/_next/static/chunks/webpack-12184ee6a95c1363.js" crossorigin="" async=""></script><script>(self.__next_f=self.__next_f||[]).push([0]);self.__next_f.push([2,null])</script><script>self.__next_f.push([1,"1:HL[\"/ui/_next/static/media/c9a5bc6a7c948fb0-s.p.woff2\",\"font\",{\"crossOrigin\":\"\",\"type\":\"font/woff2\"}]\n2:HL[\"/ui/_next/static/css/a40ad0909dd7838e.css\",\"style\",{\"crossOrigin\":\"\"}]\n0:\"$L3\"\n"])</script><script>self.__next_f.push([1,"4:I[47690,[],\"\"]\n6:I[77831,[],\"\"]\n7:I[30280,[\"303\",\"static/chunks/303-d80f23087a9e6aec.js\",\"931\",\"static/chunks/app/page-f9c7d1158cda8c5f.js\"],\"\"]\n8:I[5613,[],\"\"]\n9:I[31778,[],\"\"]\nb:I[48955,[],\"\"]\nc:[]\n"])</script><script>self.__next_f.push([1,"3:[[[\"$\",\"link\",\"0\",{\"rel\":\"stylesheet\",\"href\":\"/ui/_next/static/css/a40ad0909dd7838e.css\",\"precedence\":\"next\",\"crossOrigin\":\"\"}]],[\"$\",\"$L4\",null,{\"buildId\":\"Au9NvM222ys66B_S9303T\",\"assetPrefix\":\"/ui\",\"initialCanonicalUrl\":\"/\",\"initialTree\":[\"\",{\"children\":[\"__PAGE__\",{}]},\"$undefined\",\"$undefined\",true],\"initialSeedData\":[\"\",{\"children\":[\"__PAGE__\",{},[\"$L5\",[\"$\",\"$L6\",null,{\"propsForComponent\":{\"params\":{}},\"Component\":\"$7\",\"isStaticGeneration\":true}],null]]},[null,[\"$\",\"html\",null,{\"lang\":\"en\",\"children\":[\"$\",\"body\",null,{\"className\":\"__className_c23dc8\",\"children\":[\"$\",\"$L8\",null,{\"parallelRouterKey\":\"children\",\"segmentPath\":[\"children\"],\"loading\":\"$undefined\",\"loadingStyles\":\"$undefined\",\"loadingScripts\":\"$undefined\",\"hasLoading\":false,\"error\":\"$undefined\",\"errorStyles\":\"$undefined\",\"errorScripts\":\"$undefined\",\"template\":[\"$\",\"$L9\",null,{}],\"templateStyles\":\"$undefined\",\"templateScripts\":\"$undefined\",\"notFound\":[[\"$\",\"title\",null,{\"children\":\"404: This page could not be found.\"}],[\"$\",\"div\",null,{\"style\":{\"fontFamily\":\"system-ui,\\\"Segoe UI\\\",Roboto,Helvetica,Arial,sans-serif,\\\"Apple Color Emoji\\\",\\\"Segoe UI Emoji\\\"\",\"height\":\"100vh\",\"textAlign\":\"center\",\"display\":\"flex\",\"flexDirection\":\"column\",\"alignItems\":\"center\",\"justifyContent\":\"center\"},\"children\":[\"$\",\"div\",null,{\"children\":[[\"$\",\"style\",null,{\"dangerouslySetInnerHTML\":{\"__html\":\"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}\"}}],[\"$\",\"h1\",null,{\"className\":\"next-error-h1\",\"style\":{\"display\":\"inline-block\",\"margin\":\"0 20px 0 0\",\"padding\":\"0 23px 0 0\",\"fontSize\":24,\"fontWeight\":500,\"verticalAlign\":\"top\",\"lineHeight\":\"49px\"},\"children\":\"404\"}],[\"$\",\"div\",null,{\"style\":{\"display\":\"inline-block\"},\"children\":[\"$\",\"h2\",null,{\"style\":{\"fontSize\":14,\"fontWeight\":400,\"lineHeight\":\"49px\",\"margin\":0},\"children\":\"This page could not be found.\"}]}]]}]}]],\"notFoundStyles\":[],\"styles\":null}]}]}],null]],\"initialHead\":[false,\"$La\"],\"globalErrorComponent\":\"$b\",\"missingSlots\":\"$Wc\"}]]\n"])</script><script>self.__next_f.push([1,"a:[[\"$\",\"meta\",\"0\",{\"name\":\"viewport\",\"content\":\"width=device-width, initial-scale=1\"}],[\"$\",\"meta\",\"1\",{\"charSet\":\"utf-8\"}],[\"$\",\"title\",\"2\",{\"children\":\"🚅 LiteLLM\"}],[\"$\",\"meta\",\"3\",{\"name\":\"description\",\"content\":\"LiteLLM Proxy Admin UI\"}],[\"$\",\"link\",\"4\",{\"rel\":\"icon\",\"href\":\"/ui/favicon.ico\",\"type\":\"image/x-icon\",\"sizes\":\"16x16\"}],[\"$\",\"meta\",\"5\",{\"name\":\"next-size-adjust\"}]]\n5:null\n"])</script><script>self.__next_f.push([1,""])</script></body></html>

View file

@ -1,7 +1,7 @@
2:I[77831,[],""] 2:I[77831,[],""]
3:I[30280,["303","static/chunks/303-d80f23087a9e6aec.js","931","static/chunks/app/page-8f65fc157f538dff.js"],""] 3:I[30280,["303","static/chunks/303-d80f23087a9e6aec.js","931","static/chunks/app/page-f9c7d1158cda8c5f.js"],""]
4:I[5613,[],""] 4:I[5613,[],""]
5:I[31778,[],""] 5:I[31778,[],""]
0:["kyOCJPBB9pyUfbMKCAXr-",[[["",{"children":["__PAGE__",{}]},"$undefined","$undefined",true],["",{"children":["__PAGE__",{},["$L1",["$","$L2",null,{"propsForComponent":{"params":{}},"Component":"$3","isStaticGeneration":true}],null]]},[null,["$","html",null,{"lang":"en","children":["$","body",null,{"className":"__className_c23dc8","children":["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children"],"loading":"$undefined","loadingStyles":"$undefined","loadingScripts":"$undefined","hasLoading":false,"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","textAlign":"center","display":"flex","flexDirection":"column","alignItems":"center","justifyContent":"center"},"children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"},"children":"404"}],["$","div",null,{"style":{"display":"inline-block"},"children":["$","h2",null,{"style":{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0},"children":"This page could not be found."}]}]]}]}]],"notFoundStyles":[],"styles":null}]}]}],null]],[[["$","link","0",{"rel":"stylesheet","href":"/ui/_next/static/css/a40ad0909dd7838e.css","precedence":"next","crossOrigin":""}]],"$L6"]]]] 0:["Au9NvM222ys66B_S9303T",[[["",{"children":["__PAGE__",{}]},"$undefined","$undefined",true],["",{"children":["__PAGE__",{},["$L1",["$","$L2",null,{"propsForComponent":{"params":{}},"Component":"$3","isStaticGeneration":true}],null]]},[null,["$","html",null,{"lang":"en","children":["$","body",null,{"className":"__className_c23dc8","children":["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children"],"loading":"$undefined","loadingStyles":"$undefined","loadingScripts":"$undefined","hasLoading":false,"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","textAlign":"center","display":"flex","flexDirection":"column","alignItems":"center","justifyContent":"center"},"children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"},"children":"404"}],["$","div",null,{"style":{"display":"inline-block"},"children":["$","h2",null,{"style":{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0},"children":"This page could not be found."}]}]]}]}]],"notFoundStyles":[],"styles":null}]}]}],null]],[[["$","link","0",{"rel":"stylesheet","href":"/ui/_next/static/css/a40ad0909dd7838e.css","precedence":"next","crossOrigin":""}]],"$L6"]]]]
6:[["$","meta","0",{"name":"viewport","content":"width=device-width, initial-scale=1"}],["$","meta","1",{"charSet":"utf-8"}],["$","title","2",{"children":"🚅 LiteLLM"}],["$","meta","3",{"name":"description","content":"LiteLLM Proxy Admin UI"}],["$","link","4",{"rel":"icon","href":"/ui/favicon.ico","type":"image/x-icon","sizes":"16x16"}],["$","meta","5",{"name":"next-size-adjust"}]] 6:[["$","meta","0",{"name":"viewport","content":"width=device-width, initial-scale=1"}],["$","meta","1",{"charSet":"utf-8"}],["$","title","2",{"children":"🚅 LiteLLM"}],["$","meta","3",{"name":"description","content":"LiteLLM Proxy Admin UI"}],["$","link","4",{"rel":"icon","href":"/ui/favicon.ico","type":"image/x-icon","sizes":"16x16"}],["$","meta","5",{"name":"next-size-adjust"}]]
1:null 1:null

View file

@ -783,6 +783,9 @@ async def user_api_key_auth(
"/v2/key/info", "/v2/key/info",
"/models", "/models",
"/v1/models", "/v1/models",
"/global/spend/logs",
"/global/spend/keys",
"/global/spend/models",
] ]
# check if the current route startswith any of the allowed routes # check if the current route startswith any of the allowed routes
if ( if (
@ -4469,31 +4472,42 @@ async def user_update(data: UpdateUserRequest):
non_default_values[k] = v non_default_values[k] = v
## ADD USER, IF NEW ## ## ADD USER, IF NEW ##
if data.user_id is not None and len(data.user_id) == 0: verbose_proxy_logger.debug(f"/user/update: Received data = {data}")
if data.user_id is not None and len(data.user_id) > 0:
non_default_values["user_id"] = data.user_id # type: ignore non_default_values["user_id"] = data.user_id # type: ignore
await prisma_client.update_data( verbose_proxy_logger.debug(f"In update user, user_id condition block.")
response = await prisma_client.update_data(
user_id=data.user_id, user_id=data.user_id,
data=non_default_values, data=non_default_values,
table_name="user", table_name="user",
) )
verbose_proxy_logger.debug(
f"received response from updating prisma client. response={response}"
)
elif data.user_email is not None: elif data.user_email is not None:
non_default_values["user_id"] = str(uuid.uuid4()) non_default_values["user_id"] = str(uuid.uuid4())
non_default_values["user_email"] = data.user_email non_default_values["user_email"] = data.user_email
## user email is not unique acc. to prisma schema -> future improvement ## user email is not unique acc. to prisma schema -> future improvement
### for now: check if it exists in db, if not - insert it ### for now: check if it exists in db, if not - insert it
existing_user_row = await prisma_client.get_data( existing_user_rows = await prisma_client.get_data(
key_val={"user_email": data.user_email}, key_val={"user_email": data.user_email},
table_name="user", table_name="user",
query_type="find_all", query_type="find_all",
) )
if existing_user_row is None or ( if existing_user_rows is None or (
isinstance(existing_user_row, list) and len(existing_user_row) == 0 isinstance(existing_user_rows, list) and len(existing_user_rows) == 0
): ):
await prisma_client.insert_data( response = await prisma_client.insert_data(
data=non_default_values, table_name="user" data=non_default_values, table_name="user"
) )
elif isinstance(existing_user_rows, list) and len(existing_user_rows) > 0:
return non_default_values for existing_user in existing_user_rows:
response = await prisma_client.update_data(
user_id=existing_user.user_id,
data=non_default_values,
table_name="user",
)
return response
# update based on remaining passed in values # update based on remaining passed in values
except Exception as e: except Exception as e:
traceback.print_exc() traceback.print_exc()

View file

@ -542,6 +542,34 @@ class PrismaClient:
print("MonthlyGlobalSpend Created!") # noqa print("MonthlyGlobalSpend Created!") # noqa
try:
await self.db.query_raw("""SELECT 1 FROM "Last30dKeysBySpend" LIMIT 1""")
print("Last30dKeysBySpend Exists!") # noqa
except Exception as e:
sql_query = """
CREATE OR REPLACE VIEW "Last30dKeysBySpend" AS
SELECT
L."api_key",
V."key_alias",
V."key_name",
SUM(L."spend") AS total_spend
FROM
"LiteLLM_SpendLogs" L
LEFT JOIN
"LiteLLM_VerificationToken" V
ON
L."api_key" = V."token"
WHERE
L."startTime" >= (CURRENT_DATE - INTERVAL '30 days')
GROUP BY
L."api_key", V."key_alias", V."key_name"
ORDER BY
total_spend DESC;
"""
await self.db.execute_raw(query=sql_query)
print("Last30dKeysBySpend Created!") # noqa
return return
@backoff.on_exception( @backoff.on_exception(
@ -1034,7 +1062,7 @@ class PrismaClient:
+ f"DB User Table - update succeeded {update_user_row}" + f"DB User Table - update succeeded {update_user_row}"
+ "\033[0m" + "\033[0m"
) )
return {"user_id": user_id, "data": db_data} return {"user_id": user_id, "data": update_user_row}
elif ( elif (
team_id is not None team_id is not None
or (table_name is not None and table_name == "team") or (table_name is not None and table_name == "team")

File diff suppressed because one or more lines are too long

View file

@ -1 +1 @@
<!DOCTYPE html><html id="__next_error__"><head><meta charSet="utf-8"/><meta name="viewport" content="width=device-width, initial-scale=1"/><link rel="preload" as="script" fetchPriority="low" href="/ui/_next/static/chunks/webpack-12184ee6a95c1363.js" crossorigin=""/><script src="/ui/_next/static/chunks/fd9d1056-a85b2c176012d8e5.js" async="" crossorigin=""></script><script src="/ui/_next/static/chunks/69-e1b183dda365ec86.js" async="" crossorigin=""></script><script src="/ui/_next/static/chunks/main-app-096338c8e1915716.js" async="" crossorigin=""></script><title>🚅 LiteLLM</title><meta name="description" content="LiteLLM Proxy Admin UI"/><link rel="icon" href="/ui/favicon.ico" type="image/x-icon" sizes="16x16"/><meta name="next-size-adjust"/><script src="/ui/_next/static/chunks/polyfills-c67a75d1b6f99dc8.js" crossorigin="" noModule=""></script></head><body><script src="/ui/_next/static/chunks/webpack-12184ee6a95c1363.js" crossorigin="" async=""></script><script>(self.__next_f=self.__next_f||[]).push([0]);self.__next_f.push([2,null])</script><script>self.__next_f.push([1,"1:HL[\"/ui/_next/static/media/c9a5bc6a7c948fb0-s.p.woff2\",\"font\",{\"crossOrigin\":\"\",\"type\":\"font/woff2\"}]\n2:HL[\"/ui/_next/static/css/a40ad0909dd7838e.css\",\"style\",{\"crossOrigin\":\"\"}]\n0:\"$L3\"\n"])</script><script>self.__next_f.push([1,"4:I[47690,[],\"\"]\n6:I[77831,[],\"\"]\n7:I[30280,[\"303\",\"static/chunks/303-d80f23087a9e6aec.js\",\"931\",\"static/chunks/app/page-8f65fc157f538dff.js\"],\"\"]\n8:I[5613,[],\"\"]\n9:I[31778,[],\"\"]\nb:I[48955,[],\"\"]\nc:[]\n"])</script><script>self.__next_f.push([1,"3:[[[\"$\",\"link\",\"0\",{\"rel\":\"stylesheet\",\"href\":\"/ui/_next/static/css/a40ad0909dd7838e.css\",\"precedence\":\"next\",\"crossOrigin\":\"\"}]],[\"$\",\"$L4\",null,{\"buildId\":\"kyOCJPBB9pyUfbMKCAXr-\",\"assetPrefix\":\"/ui\",\"initialCanonicalUrl\":\"/\",\"initialTree\":[\"\",{\"children\":[\"__PAGE__\",{}]},\"$undefined\",\"$undefined\",true],\"initialSeedData\":[\"\",{\"children\":[\"__PAGE__\",{},[\"$L5\",[\"$\",\"$L6\",null,{\"propsForComponent\":{\"params\":{}},\"Component\":\"$7\",\"isStaticGeneration\":true}],null]]},[null,[\"$\",\"html\",null,{\"lang\":\"en\",\"children\":[\"$\",\"body\",null,{\"className\":\"__className_c23dc8\",\"children\":[\"$\",\"$L8\",null,{\"parallelRouterKey\":\"children\",\"segmentPath\":[\"children\"],\"loading\":\"$undefined\",\"loadingStyles\":\"$undefined\",\"loadingScripts\":\"$undefined\",\"hasLoading\":false,\"error\":\"$undefined\",\"errorStyles\":\"$undefined\",\"errorScripts\":\"$undefined\",\"template\":[\"$\",\"$L9\",null,{}],\"templateStyles\":\"$undefined\",\"templateScripts\":\"$undefined\",\"notFound\":[[\"$\",\"title\",null,{\"children\":\"404: This page could not be found.\"}],[\"$\",\"div\",null,{\"style\":{\"fontFamily\":\"system-ui,\\\"Segoe UI\\\",Roboto,Helvetica,Arial,sans-serif,\\\"Apple Color Emoji\\\",\\\"Segoe UI Emoji\\\"\",\"height\":\"100vh\",\"textAlign\":\"center\",\"display\":\"flex\",\"flexDirection\":\"column\",\"alignItems\":\"center\",\"justifyContent\":\"center\"},\"children\":[\"$\",\"div\",null,{\"children\":[[\"$\",\"style\",null,{\"dangerouslySetInnerHTML\":{\"__html\":\"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}\"}}],[\"$\",\"h1\",null,{\"className\":\"next-error-h1\",\"style\":{\"display\":\"inline-block\",\"margin\":\"0 20px 0 0\",\"padding\":\"0 23px 0 0\",\"fontSize\":24,\"fontWeight\":500,\"verticalAlign\":\"top\",\"lineHeight\":\"49px\"},\"children\":\"404\"}],[\"$\",\"div\",null,{\"style\":{\"display\":\"inline-block\"},\"children\":[\"$\",\"h2\",null,{\"style\":{\"fontSize\":14,\"fontWeight\":400,\"lineHeight\":\"49px\",\"margin\":0},\"children\":\"This page could not be found.\"}]}]]}]}]],\"notFoundStyles\":[],\"styles\":null}]}]}],null]],\"initialHead\":[false,\"$La\"],\"globalErrorComponent\":\"$b\",\"missingSlots\":\"$Wc\"}]]\n"])</script><script>self.__next_f.push([1,"a:[[\"$\",\"meta\",\"0\",{\"name\":\"viewport\",\"content\":\"width=device-width, initial-scale=1\"}],[\"$\",\"meta\",\"1\",{\"charSet\":\"utf-8\"}],[\"$\",\"title\",\"2\",{\"children\":\"🚅 LiteLLM\"}],[\"$\",\"meta\",\"3\",{\"name\":\"description\",\"content\":\"LiteLLM Proxy Admin UI\"}],[\"$\",\"link\",\"4\",{\"rel\":\"icon\",\"href\":\"/ui/favicon.ico\",\"type\":\"image/x-icon\",\"sizes\":\"16x16\"}],[\"$\",\"meta\",\"5\",{\"name\":\"next-size-adjust\"}]]\n5:null\n"])</script><script>self.__next_f.push([1,""])</script></body></html> <!DOCTYPE html><html id="__next_error__"><head><meta charSet="utf-8"/><meta name="viewport" content="width=device-width, initial-scale=1"/><link rel="preload" as="script" fetchPriority="low" href="/ui/_next/static/chunks/webpack-12184ee6a95c1363.js" crossorigin=""/><script src="/ui/_next/static/chunks/fd9d1056-a85b2c176012d8e5.js" async="" crossorigin=""></script><script src="/ui/_next/static/chunks/69-e1b183dda365ec86.js" async="" crossorigin=""></script><script src="/ui/_next/static/chunks/main-app-096338c8e1915716.js" async="" crossorigin=""></script><title>🚅 LiteLLM</title><meta name="description" content="LiteLLM Proxy Admin UI"/><link rel="icon" href="/ui/favicon.ico" type="image/x-icon" sizes="16x16"/><meta name="next-size-adjust"/><script src="/ui/_next/static/chunks/polyfills-c67a75d1b6f99dc8.js" crossorigin="" noModule=""></script></head><body><script src="/ui/_next/static/chunks/webpack-12184ee6a95c1363.js" crossorigin="" async=""></script><script>(self.__next_f=self.__next_f||[]).push([0]);self.__next_f.push([2,null])</script><script>self.__next_f.push([1,"1:HL[\"/ui/_next/static/media/c9a5bc6a7c948fb0-s.p.woff2\",\"font\",{\"crossOrigin\":\"\",\"type\":\"font/woff2\"}]\n2:HL[\"/ui/_next/static/css/a40ad0909dd7838e.css\",\"style\",{\"crossOrigin\":\"\"}]\n0:\"$L3\"\n"])</script><script>self.__next_f.push([1,"4:I[47690,[],\"\"]\n6:I[77831,[],\"\"]\n7:I[30280,[\"303\",\"static/chunks/303-d80f23087a9e6aec.js\",\"931\",\"static/chunks/app/page-f9c7d1158cda8c5f.js\"],\"\"]\n8:I[5613,[],\"\"]\n9:I[31778,[],\"\"]\nb:I[48955,[],\"\"]\nc:[]\n"])</script><script>self.__next_f.push([1,"3:[[[\"$\",\"link\",\"0\",{\"rel\":\"stylesheet\",\"href\":\"/ui/_next/static/css/a40ad0909dd7838e.css\",\"precedence\":\"next\",\"crossOrigin\":\"\"}]],[\"$\",\"$L4\",null,{\"buildId\":\"Au9NvM222ys66B_S9303T\",\"assetPrefix\":\"/ui\",\"initialCanonicalUrl\":\"/\",\"initialTree\":[\"\",{\"children\":[\"__PAGE__\",{}]},\"$undefined\",\"$undefined\",true],\"initialSeedData\":[\"\",{\"children\":[\"__PAGE__\",{},[\"$L5\",[\"$\",\"$L6\",null,{\"propsForComponent\":{\"params\":{}},\"Component\":\"$7\",\"isStaticGeneration\":true}],null]]},[null,[\"$\",\"html\",null,{\"lang\":\"en\",\"children\":[\"$\",\"body\",null,{\"className\":\"__className_c23dc8\",\"children\":[\"$\",\"$L8\",null,{\"parallelRouterKey\":\"children\",\"segmentPath\":[\"children\"],\"loading\":\"$undefined\",\"loadingStyles\":\"$undefined\",\"loadingScripts\":\"$undefined\",\"hasLoading\":false,\"error\":\"$undefined\",\"errorStyles\":\"$undefined\",\"errorScripts\":\"$undefined\",\"template\":[\"$\",\"$L9\",null,{}],\"templateStyles\":\"$undefined\",\"templateScripts\":\"$undefined\",\"notFound\":[[\"$\",\"title\",null,{\"children\":\"404: This page could not be found.\"}],[\"$\",\"div\",null,{\"style\":{\"fontFamily\":\"system-ui,\\\"Segoe UI\\\",Roboto,Helvetica,Arial,sans-serif,\\\"Apple Color Emoji\\\",\\\"Segoe UI Emoji\\\"\",\"height\":\"100vh\",\"textAlign\":\"center\",\"display\":\"flex\",\"flexDirection\":\"column\",\"alignItems\":\"center\",\"justifyContent\":\"center\"},\"children\":[\"$\",\"div\",null,{\"children\":[[\"$\",\"style\",null,{\"dangerouslySetInnerHTML\":{\"__html\":\"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}\"}}],[\"$\",\"h1\",null,{\"className\":\"next-error-h1\",\"style\":{\"display\":\"inline-block\",\"margin\":\"0 20px 0 0\",\"padding\":\"0 23px 0 0\",\"fontSize\":24,\"fontWeight\":500,\"verticalAlign\":\"top\",\"lineHeight\":\"49px\"},\"children\":\"404\"}],[\"$\",\"div\",null,{\"style\":{\"display\":\"inline-block\"},\"children\":[\"$\",\"h2\",null,{\"style\":{\"fontSize\":14,\"fontWeight\":400,\"lineHeight\":\"49px\",\"margin\":0},\"children\":\"This page could not be found.\"}]}]]}]}]],\"notFoundStyles\":[],\"styles\":null}]}]}],null]],\"initialHead\":[false,\"$La\"],\"globalErrorComponent\":\"$b\",\"missingSlots\":\"$Wc\"}]]\n"])</script><script>self.__next_f.push([1,"a:[[\"$\",\"meta\",\"0\",{\"name\":\"viewport\",\"content\":\"width=device-width, initial-scale=1\"}],[\"$\",\"meta\",\"1\",{\"charSet\":\"utf-8\"}],[\"$\",\"title\",\"2\",{\"children\":\"🚅 LiteLLM\"}],[\"$\",\"meta\",\"3\",{\"name\":\"description\",\"content\":\"LiteLLM Proxy Admin UI\"}],[\"$\",\"link\",\"4\",{\"rel\":\"icon\",\"href\":\"/ui/favicon.ico\",\"type\":\"image/x-icon\",\"sizes\":\"16x16\"}],[\"$\",\"meta\",\"5\",{\"name\":\"next-size-adjust\"}]]\n5:null\n"])</script><script>self.__next_f.push([1,""])</script></body></html>

View file

@ -1,7 +1,7 @@
2:I[77831,[],""] 2:I[77831,[],""]
3:I[30280,["303","static/chunks/303-d80f23087a9e6aec.js","931","static/chunks/app/page-8f65fc157f538dff.js"],""] 3:I[30280,["303","static/chunks/303-d80f23087a9e6aec.js","931","static/chunks/app/page-f9c7d1158cda8c5f.js"],""]
4:I[5613,[],""] 4:I[5613,[],""]
5:I[31778,[],""] 5:I[31778,[],""]
0:["kyOCJPBB9pyUfbMKCAXr-",[[["",{"children":["__PAGE__",{}]},"$undefined","$undefined",true],["",{"children":["__PAGE__",{},["$L1",["$","$L2",null,{"propsForComponent":{"params":{}},"Component":"$3","isStaticGeneration":true}],null]]},[null,["$","html",null,{"lang":"en","children":["$","body",null,{"className":"__className_c23dc8","children":["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children"],"loading":"$undefined","loadingStyles":"$undefined","loadingScripts":"$undefined","hasLoading":false,"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","textAlign":"center","display":"flex","flexDirection":"column","alignItems":"center","justifyContent":"center"},"children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"},"children":"404"}],["$","div",null,{"style":{"display":"inline-block"},"children":["$","h2",null,{"style":{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0},"children":"This page could not be found."}]}]]}]}]],"notFoundStyles":[],"styles":null}]}]}],null]],[[["$","link","0",{"rel":"stylesheet","href":"/ui/_next/static/css/a40ad0909dd7838e.css","precedence":"next","crossOrigin":""}]],"$L6"]]]] 0:["Au9NvM222ys66B_S9303T",[[["",{"children":["__PAGE__",{}]},"$undefined","$undefined",true],["",{"children":["__PAGE__",{},["$L1",["$","$L2",null,{"propsForComponent":{"params":{}},"Component":"$3","isStaticGeneration":true}],null]]},[null,["$","html",null,{"lang":"en","children":["$","body",null,{"className":"__className_c23dc8","children":["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children"],"loading":"$undefined","loadingStyles":"$undefined","loadingScripts":"$undefined","hasLoading":false,"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","textAlign":"center","display":"flex","flexDirection":"column","alignItems":"center","justifyContent":"center"},"children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"},"children":"404"}],["$","div",null,{"style":{"display":"inline-block"},"children":["$","h2",null,{"style":{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0},"children":"This page could not be found."}]}]]}]}]],"notFoundStyles":[],"styles":null}]}]}],null]],[[["$","link","0",{"rel":"stylesheet","href":"/ui/_next/static/css/a40ad0909dd7838e.css","precedence":"next","crossOrigin":""}]],"$L6"]]]]
6:[["$","meta","0",{"name":"viewport","content":"width=device-width, initial-scale=1"}],["$","meta","1",{"charSet":"utf-8"}],["$","title","2",{"children":"🚅 LiteLLM"}],["$","meta","3",{"name":"description","content":"LiteLLM Proxy Admin UI"}],["$","link","4",{"rel":"icon","href":"/ui/favicon.ico","type":"image/x-icon","sizes":"16x16"}],["$","meta","5",{"name":"next-size-adjust"}]] 6:[["$","meta","0",{"name":"viewport","content":"width=device-width, initial-scale=1"}],["$","meta","1",{"charSet":"utf-8"}],["$","title","2",{"children":"🚅 LiteLLM"}],["$","meta","3",{"name":"description","content":"LiteLLM Proxy Admin UI"}],["$","link","4",{"rel":"icon","href":"/ui/favicon.ico","type":"image/x-icon","sizes":"16x16"}],["$","meta","5",{"name":"next-size-adjust"}]]
1:null 1:null

View file

@ -11,8 +11,10 @@ import ChatUI from "@/components/chat_ui";
import Sidebar from "../components/leftnav"; import Sidebar from "../components/leftnav";
import Usage from "../components/usage"; import Usage from "../components/usage";
import { jwtDecode } from "jwt-decode"; import { jwtDecode } from "jwt-decode";
import { Typography } from "antd";
const CreateKeyPage = () => { const CreateKeyPage = () => {
const { Title, Paragraph } = Typography;
const [userRole, setUserRole] = useState(""); const [userRole, setUserRole] = useState("");
const [userEmail, setUserEmail] = useState<null | string>(null); const [userEmail, setUserEmail] = useState<null | string>(null);
const [teams, setTeams] = useState<null | any[]>(null); const [teams, setTeams] = useState<null | any[]>(null);
@ -41,6 +43,9 @@ const CreateKeyPage = () => {
const formattedUserRole = formatUserRole(decoded.user_role); const formattedUserRole = formatUserRole(decoded.user_role);
console.log("Decoded user_role:", formattedUserRole); console.log("Decoded user_role:", formattedUserRole);
setUserRole(formattedUserRole); setUserRole(formattedUserRole);
if (formattedUserRole == "Admin Viewer") {
setPage("usage");
}
} else { } else {
console.log("User role not defined"); console.log("User role not defined");
} }
@ -66,7 +71,8 @@ const CreateKeyPage = () => {
if (!userRole) { if (!userRole) {
return "Undefined Role"; return "Undefined Role";
} }
console.log(`Received user role: ${userRole}`); console.log(`Received user role: ${userRole.toLowerCase()}`);
console.log(`Received user role length: ${userRole.toLowerCase().length}`);
switch (userRole.toLowerCase()) { switch (userRole.toLowerCase()) {
case "app_owner": case "app_owner":
return "App Owner"; return "App Owner";

View file

@ -21,6 +21,7 @@ import {
import { modelAvailableCall } from "./networking"; import { modelAvailableCall } from "./networking";
import openai from "openai"; import openai from "openai";
import { Prism as SyntaxHighlighter } from "react-syntax-highlighter"; import { Prism as SyntaxHighlighter } from "react-syntax-highlighter";
import { Typography } from "antd";
interface ChatUIProps { interface ChatUIProps {
accessToken: string | null; accessToken: string | null;
@ -145,6 +146,16 @@ const ChatUI: React.FC<ChatUIProps> = ({
setInputMessage(""); setInputMessage("");
}; };
if (userRole && userRole == "Admin Viewer") {
const { Title, Paragraph } = Typography;
return (
<div>
<Title level={1}>Access Denied</Title>
<Paragraph>Ask your proxy admin for access to test models</Paragraph>
</div>
);
}
return ( return (
<div style={{ width: "100%", position: "relative" }}> <div style={{ width: "100%", position: "relative" }}>
<Grid className="gap-2 p-10 h-[75vh] w-full"> <Grid className="gap-2 p-10 h-[75vh] w-full">

View file

@ -16,6 +16,34 @@ const Sidebar: React.FC<SidebarProps> = ({
userRole, userRole,
defaultSelectedKey, defaultSelectedKey,
}) => { }) => {
if (userRole == "Admin Viewer") {
return (
<Layout style={{ minHeight: "100vh", maxWidth: "120px" }}>
<Sider width={120}>
<Menu
mode="inline"
defaultSelectedKeys={
defaultSelectedKey ? defaultSelectedKey : ["4"]
}
style={{ height: "100%", borderRight: 0 }}
>
<Menu.Item key="4" onClick={() => setPage("api-keys")}>
API Keys
</Menu.Item>
<Menu.Item key="2" onClick={() => setPage("models")}>
Models
</Menu.Item>
<Menu.Item key="3" onClick={() => setPage("llm-playground")}>
Chat UI
</Menu.Item>
<Menu.Item key="1" onClick={() => setPage("usage")}>
Usage
</Menu.Item>
</Menu>
</Sider>
</Layout>
);
}
return ( return (
<Layout style={{ minHeight: "100vh", maxWidth: "120px" }}> <Layout style={{ minHeight: "100vh", maxWidth: "120px" }}>
<Sider width={120}> <Sider width={120}>

View file

@ -1,8 +1,20 @@
import React, { useState, useEffect } from "react"; import React, { useState, useEffect } from "react";
import { Card, Title, Subtitle, Table, TableHead, TableRow, TableCell, TableBody, Metric, Grid } from "@tremor/react"; import {
Card,
Title,
Subtitle,
Table,
TableHead,
TableRow,
TableCell,
TableBody,
Metric,
Grid,
} from "@tremor/react";
import { modelInfoCall, userGetRequesedtModelsCall } from "./networking"; import { modelInfoCall, userGetRequesedtModelsCall } 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 { Typography } from "antd";
interface ModelDashboardProps { interface ModelDashboardProps {
accessToken: string | null; accessToken: string | null;
@ -20,7 +32,6 @@ const ModelDashboard: React.FC<ModelDashboardProps> = ({
const [modelData, setModelData] = useState<any>({ data: [] }); const [modelData, setModelData] = useState<any>({ data: [] });
const [pendingRequests, setPendingRequests] = useState<any[]>([]); const [pendingRequests, setPendingRequests] = useState<any[]>([]);
useEffect(() => { useEffect(() => {
if (!accessToken || !token || !userRole || !userID) { if (!accessToken || !token || !userRole || !userID) {
return; return;
@ -28,17 +39,20 @@ const ModelDashboard: React.FC<ModelDashboardProps> = ({
const fetchData = async () => { const fetchData = async () => {
try { try {
// Replace with your actual API call for model data // Replace with your actual API call for model data
const modelDataResponse = await modelInfoCall(accessToken, userID, userRole); const modelDataResponse = await modelInfoCall(
accessToken,
userID,
userRole
);
console.log("Model data response:", modelDataResponse.data); console.log("Model data response:", modelDataResponse.data);
setModelData(modelDataResponse); setModelData(modelDataResponse);
// if userRole is Admin, show the pending requests // if userRole is Admin, show the pending requests
if (userRole === "Admin" && accessToken) { if (userRole === "Admin" && accessToken) {
const user_requests = await userGetRequesedtModelsCall(accessToken); const user_requests = await userGetRequesedtModelsCall(accessToken);
console.log("Pending Requests:", pendingRequests); console.log("Pending Requests:", pendingRequests);
setPendingRequests(user_requests.requests || []); setPendingRequests(user_requests.requests || []);
} }
} catch (error) { } catch (error) {
console.error("There was an error fetching the model data", error); console.error("There was an error fetching the model data", error);
} }
@ -58,7 +72,7 @@ const ModelDashboard: React.FC<ModelDashboardProps> = ({
} }
let all_models_on_proxy: any[] = []; let all_models_on_proxy: any[] = [];
// loop through model data and edit each row // loop through model data and edit each row
for (let i = 0; i < modelData.data.length; i++) { for (let i = 0; i < modelData.data.length; i++) {
let curr_model = modelData.data[i]; let curr_model = modelData.data[i];
let litellm_model_name = curr_model?.litellm_params?.model; let litellm_model_name = curr_model?.litellm_params?.model;
@ -67,110 +81,155 @@ const ModelDashboard: React.FC<ModelDashboardProps> = ({
let defaultProvider = "openai"; let defaultProvider = "openai";
let provider = ""; let provider = "";
let input_cost = "Undefined" let input_cost = "Undefined";
let output_cost = "Undefined" let output_cost = "Undefined";
let max_tokens = "Undefined" let max_tokens = "Undefined";
// Check if litellm_model_name is null or undefined // Check if litellm_model_name is null or undefined
if (litellm_model_name) { if (litellm_model_name) {
// Split litellm_model_name based on "/" // Split litellm_model_name based on "/"
let splitModel = litellm_model_name.split("/"); let splitModel = litellm_model_name.split("/");
// Get the first element in the split // Get the first element in the split
let firstElement = splitModel[0]; let firstElement = splitModel[0];
// If there is only one element, default provider to openai // If there is only one element, default provider to openai
provider = splitModel.length === 1 ? defaultProvider : firstElement; provider = splitModel.length === 1 ? defaultProvider : firstElement;
} else { } else {
// litellm_model_name is null or undefined, default provider to openai // litellm_model_name is null or undefined, default provider to openai
provider = defaultProvider; provider = defaultProvider;
} }
if (model_info) { if (model_info) {
input_cost = model_info?.input_cost_per_token; input_cost = model_info?.input_cost_per_token;
output_cost = model_info?.output_cost_per_token; output_cost = model_info?.output_cost_per_token;
max_tokens = model_info?.max_tokens; max_tokens = model_info?.max_tokens;
} }
modelData.data[i].provider = provider modelData.data[i].provider = provider;
modelData.data[i].input_cost = input_cost modelData.data[i].input_cost = input_cost;
modelData.data[i].output_cost = output_cost modelData.data[i].output_cost = output_cost;
modelData.data[i].max_tokens = max_tokens modelData.data[i].max_tokens = max_tokens;
all_models_on_proxy.push(curr_model.model_name); all_models_on_proxy.push(curr_model.model_name);
console.log(modelData.data[i]); console.log(modelData.data[i]);
} }
// when users click request access show pop up to allow them to request access // when users click request access show pop up to allow them to request access
if (userRole && userRole == "Admin Viewer") {
const { Title, Paragraph } = Typography;
return (
<div>
<Title level={1}>Access Denied</Title>
<Paragraph>
Ask your proxy admin for access to view all models
</Paragraph>
</div>
);
}
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">
<Card> <Card>
<Table className="mt-5"> <Table className="mt-5">
<TableHead> <TableHead>
<TableRow> <TableRow>
<TableCell><Title>Model Name </Title></TableCell>
<TableCell><Title>Provider</Title></TableCell>
<TableCell><Title>Access</Title></TableCell>
<TableCell><Title>Input Price per token ($)</Title></TableCell>
<TableCell><Title>Output Price per token ($)</Title></TableCell>
<TableCell><Title>Max Tokens</Title></TableCell>
</TableRow>
</TableHead>
<TableBody>
{modelData.data.map((model: any) => (
<TableRow key={model.model_name}>
<TableCell><Title>{model.model_name}</Title></TableCell>
<TableCell>{model.provider}</TableCell>
<TableCell> <TableCell>
{model.user_access ? <Badge color={"green"}>Yes</Badge> : <RequestAccess userModels={all_models_on_proxy} accessToken={accessToken} userID={userID}></RequestAccess>} <Title>Model Name </Title>
</TableCell>
<TableCell>
<Title>Provider</Title>
</TableCell>
<TableCell>
<Title>Access</Title>
</TableCell>
<TableCell>
<Title>Input Price per token ($)</Title>
</TableCell>
<TableCell>
<Title>Output Price per token ($)</Title>
</TableCell>
<TableCell>
<Title>Max Tokens</Title>
</TableCell> </TableCell>
<TableCell>{model.input_cost}</TableCell>
<TableCell>{model.output_cost}</TableCell>
<TableCell>{model.max_tokens}</TableCell>
</TableRow> </TableRow>
))} </TableHead>
</TableBody> <TableBody>
</Table> {modelData.data.map((model: any) => (
</Card> <TableRow key={model.model_name}>
{ <TableCell>
userRole === "Admin" && pendingRequests && pendingRequests.length > 0 ? ( <Title>{model.model_name}</Title>
</TableCell>
<TableCell>{model.provider}</TableCell>
<TableCell>
{model.user_access ? (
<Badge color={"green"}>Yes</Badge>
) : (
<RequestAccess
userModels={all_models_on_proxy}
accessToken={accessToken}
userID={userID}
></RequestAccess>
)}
</TableCell>
<TableCell>{model.input_cost}</TableCell>
<TableCell>{model.output_cost}</TableCell>
<TableCell>{model.max_tokens}</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</Card>
{userRole === "Admin" &&
pendingRequests &&
pendingRequests.length > 0 ? (
<Card> <Card>
<Table> <Table>
<TableHead> <TableHead>
<Title>Pending Requests</Title> <Title>Pending Requests</Title>
<TableRow> <TableRow>
<TableCell><Title>User ID</Title></TableCell> <TableCell>
<TableCell><Title>Requested Models</Title></TableCell> <Title>User ID</Title>
<TableCell><Title>Justification</Title></TableCell> </TableCell>
<TableCell><Title>Justification</Title></TableCell> <TableCell>
<Title>Requested Models</Title>
</TableCell>
<TableCell>
<Title>Justification</Title>
</TableCell>
<TableCell>
<Title>Justification</Title>
</TableCell>
</TableRow> </TableRow>
</TableHead> </TableHead>
<TableBody> <TableBody>
{pendingRequests.map((request: any) => ( {pendingRequests.map((request: any) => (
<TableRow key={request.request_id}> <TableRow key={request.request_id}>
<TableCell><p>{request.user_id}</p></TableCell> <TableCell>
<TableCell><p>{request.models[0]}</p></TableCell> <p>{request.user_id}</p>
<TableCell><p>{request.justification}</p></TableCell> </TableCell>
<TableCell><p>{request.user_id}</p></TableCell> <TableCell>
<p>{request.models[0]}</p>
</TableCell>
<TableCell>
<p>{request.justification}</p>
</TableCell>
<TableCell>
<p>{request.user_id}</p>
</TableCell>
<Button>Approve</Button> <Button>Approve</Button>
<Button variant="secondary" className="ml-2">Deny</Button> <Button variant="secondary" className="ml-2">
Deny
</Button>
</TableRow> </TableRow>
))} ))}
</TableBody> </TableBody>
</Table> </Table>
</Card> </Card>
) : null ) : null}
}
</Grid> </Grid>
</div> </div>
); );

View file

@ -175,7 +175,7 @@ const UsagePage: React.FC<UsagePageProps> = ({
* If user is App Owner - use the normal spend logs call * If user is App Owner - use the normal spend logs call
*/ */
console.log(`user role: ${userRole}`); console.log(`user role: ${userRole}`);
if (userRole == "Admin") { if (userRole == "Admin" || userRole == "Admin Viewer") {
const overall_spend = await adminSpendLogsCall(accessToken); const overall_spend = await adminSpendLogsCall(accessToken);
setKeySpendData(overall_spend); setKeySpendData(overall_spend);
const top_keys = await adminTopKeysCall(accessToken); const top_keys = await adminTopKeysCall(accessToken);

View file

@ -8,7 +8,7 @@ import ViewUserSpend from "./view_user_spend";
import DashboardTeam from "./dashboard_default_team"; import DashboardTeam from "./dashboard_default_team";
import { useSearchParams, useRouter } from "next/navigation"; import { useSearchParams, useRouter } from "next/navigation";
import { jwtDecode } from "jwt-decode"; import { jwtDecode } from "jwt-decode";
import { Typography } from "antd";
const isLocal = process.env.NODE_ENV === "development"; const isLocal = process.env.NODE_ENV === "development";
console.log("isLocal:", isLocal); console.log("isLocal:", isLocal);
const proxyBaseUrl = isLocal ? "http://localhost:4000" : null; const proxyBaseUrl = isLocal ? "http://localhost:4000" : null;
@ -73,6 +73,10 @@ const UserDashboard: React.FC<UserDashboardProps> = ({
return "App Owner"; return "App Owner";
case "app_admin": case "app_admin":
return "Admin"; return "Admin";
case "proxy_admin":
return "Admin";
case "proxy_admin_viewer":
return "Admin Viewer";
case "app_user": case "app_user":
return "App User"; return "App User";
default: default:
@ -180,6 +184,16 @@ const UserDashboard: React.FC<UserDashboardProps> = ({
setUserRole("App Owner"); setUserRole("App Owner");
} }
if (userRole && userRole == "Admin Viewer") {
const { Title, Paragraph } = Typography;
return (
<div>
<Title level={1}>Access Denied</Title>
<Paragraph>Ask your proxy admin for access to create keys</Paragraph>
</div>
);
}
return ( return (
<div> <div>
<Grid numItems={1} className="gap-0 p-10 h-[75vh] w-full"> <Grid numItems={1} className="gap-0 p-10 h-[75vh] w-full">

View file

@ -1,7 +1,18 @@
import React, { useState, useEffect } from "react"; import React, { useState, useEffect } from "react";
import { Card, Title, Subtitle, Table, TableHead, TableRow, TableCell, TableBody, Metric, Grid } from "@tremor/react"; import {
Card,
Title,
Subtitle,
Table,
TableHead,
TableRow,
TableCell,
TableBody,
Metric,
Grid,
} from "@tremor/react";
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"; import CreateUser from "./create_user_button";
@ -21,7 +32,6 @@ const ViewUserDashboard: React.FC<ViewUserDashboardProps> = ({
const [userData, setuserData] = useState<null | any[]>(null); const [userData, setuserData] = useState<null | any[]>(null);
const [pendingRequests, setPendingRequests] = useState<any[]>([]); const [pendingRequests, setPendingRequests] = useState<any[]>([]);
useEffect(() => { useEffect(() => {
if (!accessToken || !token || !userRole || !userID) { if (!accessToken || !token || !userRole || !userID) {
return; return;
@ -29,10 +39,14 @@ const ViewUserDashboard: React.FC<ViewUserDashboardProps> = ({
const fetchData = async () => { const fetchData = async () => {
try { try {
// Replace with your actual API call for model data // Replace with your actual API call for model data
const userDataResponse = await userInfoCall(accessToken, null, userRole, true); const userDataResponse = await userInfoCall(
accessToken,
null,
userRole,
true
);
console.log("user data response:", userDataResponse); console.log("user data response:", userDataResponse);
setuserData(userDataResponse); setuserData(userDataResponse);
} catch (error) { } catch (error) {
console.error("There was an error fetching the model data", error); console.error("There was an error fetching the model data", error);
} }
@ -51,40 +65,62 @@ const ViewUserDashboard: React.FC<ViewUserDashboardProps> = ({
return <div>Loading...</div>; return <div>Loading...</div>;
} }
// when users click request access show pop up to allow them to request access
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 <CreateUser userID={userID} accessToken={accessToken} />
userID={userID} <Card>
accessToken={accessToken} <Table className="mt-5">
/> <TableHead>
<Card> <TableRow>
<Table className="mt-5"> <TableCell>
<TableHead> <Title>User ID </Title>
<TableRow> </TableCell>
<TableCell><Title>User ID </Title></TableCell> <TableCell>
<TableCell><Title>User Role</Title></TableCell> <Title>User Role</Title>
<TableCell><Title>User Models</Title></TableCell> </TableCell>
<TableCell><Title>User Spend ($ USD)</Title></TableCell> <TableCell>
<TableCell><Title>User Max Budget ($ USD)</Title></TableCell> <Title>User Models</Title>
</TableRow> </TableCell>
</TableHead> <TableCell>
<TableBody> <Title>User Spend ($ USD)</Title>
{userData.map((user: any) => ( </TableCell>
<TableRow key={user.user_id}> <TableCell>
<TableCell><Title>{user.user_id}</Title></TableCell> <Title>User Max Budget ($ USD)</Title>
<TableCell><Title>{user.user_role ? user.user_role : "app_user"}</Title></TableCell> </TableCell>
<TableCell><Title>{user.models && user.models.length > 0 ? user.models : "All Models"}</Title></TableCell>
<TableCell><Title>{user.spend ? user.spend : 0}</Title></TableCell>
<TableCell><Title>{user.max_budget ? user.max_budget : "Unlimited"}</Title></TableCell>
</TableRow> </TableRow>
))} </TableHead>
</TableBody> <TableBody>
</Table> {userData.map((user: any) => (
</Card> <TableRow key={user.user_id}>
<TableCell>
<Title>{user.user_id}</Title>
</TableCell>
<TableCell>
<Title>
{user.user_role ? user.user_role : "app_user"}
</Title>
</TableCell>
<TableCell>
<Title>
{user.models && user.models.length > 0
? user.models
: "All Models"}
</Title>
</TableCell>
<TableCell>
<Title>{user.spend ? user.spend : 0}</Title>
</TableCell>
<TableCell>
<Title>
{user.max_budget ? user.max_budget : "Unlimited"}
</Title>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</Card>
</Grid> </Grid>
</div> </div>
); );