diff --git a/litellm/proxy/common_utils/admin_ui_utils.py b/litellm/proxy/common_utils/admin_ui_utils.py index bb35ecd69..3389f723d 100644 --- a/litellm/proxy/common_utils/admin_ui_utils.py +++ b/litellm/proxy/common_utils/admin_ui_utils.py @@ -1,4 +1,5 @@ import os +import subprocess def show_missing_vars_in_env(): @@ -165,3 +166,67 @@ def missing_keys_form(missing_key_names: str): """ return missing_keys_html_form.format(missing_keys=missing_key_names) + + +def setup_admin_ui_on_server_root_path(): + """ + Helper util to setup Admin UI on Server root path + """ + from litellm._logging import verbose_proxy_logger + + server_root_path = os.getenv("SERVER_ROOT_PATH", "") + if server_root_path != "": + if os.getenv("PROXY_BASE_URL") is None: + os.environ["PROXY_BASE_URL"] = server_root_path + + # re-build admin UI on server root path + # Save the original directory + original_dir = os.getcwd() + + current_dir = ( + os.path.dirname(os.path.abspath(__file__)) + + "/../../../ui/litellm-dashboard/" + ) + build_ui_path = os.path.join(current_dir, "build_ui_custom_path.sh") + package_path = os.path.join(current_dir, "package.json") + + verbose_proxy_logger.debug( + f"Setting up Admin UI on {server_root_path}/ui ......." + ) # noqa + + try: + # Change the current working directory + os.chdir(current_dir) + + # Make the script executable + subprocess.run(["chmod", "+x", "build_ui_custom_path.sh"], check=True) + + # Run npm install + subprocess.run(["npm", "install"], check=True) + + # Run npm run build + subprocess.run(["npm", "run", "build"], check=True) + + # Run the custom build script with the argument + subprocess.run( + ["./build_ui_custom_path.sh", f"{server_root_path}/ui"], check=True + ) + + verbose_proxy_logger.debug("Admin UI setup completed successfully.") # noqa + + except subprocess.CalledProcessError as e: + verbose_proxy_logger.debug( + f"An error occurred during the Admin UI setup: {e}" + ) # noqa + + except Exception as e: + verbose_proxy_logger.debug(f"An unexpected error occurred: {e}") + + finally: + # Always return to the original directory, even if an error occurred + os.chdir(original_dir) + verbose_proxy_logger.debug( + f"Returned to original directory: {original_dir}" + ) # noqa + + pass diff --git a/litellm/proxy/proxy_server.py b/litellm/proxy/proxy_server.py index a9b49138b..260c728b5 100644 --- a/litellm/proxy/proxy_server.py +++ b/litellm/proxy/proxy_server.py @@ -138,6 +138,7 @@ from litellm.proxy.auth.user_api_key_auth import user_api_key_auth from litellm.proxy.caching_routes import router as caching_router from litellm.proxy.common_utils.admin_ui_utils import ( html_form, + setup_admin_ui_on_server_root_path, show_missing_vars_in_env, ) from litellm.proxy.common_utils.debug_utils import router as debugging_endpoints_router @@ -281,9 +282,12 @@ except Exception as e: except Exception as e: pass +server_root_path = os.getenv("SERVER_ROOT_PATH", "") +if server_root_path != "": + setup_admin_ui_on_server_root_path() _license_check = LicenseCheck() premium_user: bool = _license_check.is_premium() -ui_link = f"/ui/" +ui_link = f"{server_root_path}/ui/" ui_message = ( f"👉 [```LiteLLM Admin Panel on /ui```]({ui_link}). Create, Edit Keys with SSO" ) @@ -303,14 +307,13 @@ _description = ( else f"Proxy Server to call 100+ LLMs in the OpenAI format. {custom_swagger_message}\n\n{ui_message}" ) + app = FastAPI( docs_url=_docs_url, title=_title, description=_description, version=version, - root_path=os.environ.get( - "SERVER_ROOT_PATH", "" - ), # check if user passed root path, FastAPI defaults this value to "" + root_path=server_root_path, # check if user passed root path, FastAPI defaults this value to "" ) diff --git a/ui/litellm-dashboard/build_ui_custom_path.sh b/ui/litellm-dashboard/build_ui_custom_path.sh new file mode 100755 index 000000000..f947f87d3 --- /dev/null +++ b/ui/litellm-dashboard/build_ui_custom_path.sh @@ -0,0 +1,55 @@ +#!/bin/bash + +# Check if UI_BASE_PATH argument is provided +if [ -z "$1" ]; then + echo "Error: UI_BASE_PATH argument is required." + echo "Usage: $0 " + exit 1 +fi + +# Set UI_BASE_PATH from the first argument +UI_BASE_PATH="$1" + +# Check if nvm is not installed +if ! command -v nvm &> /dev/null; then + # Install nvm + curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.38.0/install.sh | bash + + # Source nvm script in the current session + export NVM_DIR="$HOME/.nvm" + [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" +fi + +# Use nvm to set the required Node.js version +nvm use v18.17.0 + +# Check if nvm use was successful +if [ $? -ne 0 ]; then + echo "Error: Failed to switch to Node.js v18.17.0. Deployment aborted." + exit 1 +fi + +# Run npm build with the environment variable +UI_BASE_PATH=$UI_BASE_PATH npm run build + +# Check if the build was successful +if [ $? -eq 0 ]; then + echo "Build successful. Copying files..." + + # echo current dir + echo + pwd + + # Specify the destination directory + destination_dir="../../litellm/proxy/_experimental/out" + + # Remove existing files in the destination directory + rm -rf "$destination_dir"/* + + # Copy the contents of the output directory to the specified destination + cp -r ./out/* "$destination_dir" + + echo "Deployment completed." +else + echo "Build failed. Deployment aborted." +fi \ No newline at end of file diff --git a/ui/litellm-dashboard/next.config.mjs b/ui/litellm-dashboard/next.config.mjs index e1f8aa083..6e2924677 100644 --- a/ui/litellm-dashboard/next.config.mjs +++ b/ui/litellm-dashboard/next.config.mjs @@ -1,7 +1,7 @@ /** @type {import('next').NextConfig} */ const nextConfig = { output: 'export', - basePath: '/ui', + basePath: process.env.UI_BASE_PATH || '/ui', }; nextConfig.experimental = {