mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-08 22:21:35 +00:00
Allow adding favicon to ui theme config
This commit is contained in:
parent
57d9b9e591
commit
be9da4004c
2 changed files with 172 additions and 24 deletions
|
|
@ -22,13 +22,19 @@ class IPAddress(BaseModel):
|
|||
|
||||
class UIThemeConfig(BaseModel):
|
||||
"""Configuration for UI theme customization"""
|
||||
|
||||
|
||||
# Logo configuration
|
||||
logo_url: Optional[str] = Field(
|
||||
default=None,
|
||||
description="URL or path to custom logo image. Can be a local file path or HTTP/HTTPS URL"
|
||||
)
|
||||
|
||||
# Favicon configuration
|
||||
favicon_url: Optional[str] = Field(
|
||||
default=None,
|
||||
description="URL or path to custom favicon image. Can be a local file path or HTTP/HTTPS URL"
|
||||
)
|
||||
|
||||
|
||||
class SettingsResponse(BaseModel):
|
||||
"""Base response model for settings with values and schema information"""
|
||||
|
|
@ -648,7 +654,7 @@ async def get_ui_theme_settings():
|
|||
async def update_ui_theme_settings(theme_config: UIThemeConfig):
|
||||
"""
|
||||
Update UI theme configuration.
|
||||
Updates logo settings for the admin UI.
|
||||
Updates logo and favicon settings for the admin UI.
|
||||
"""
|
||||
from litellm.proxy.proxy_server import proxy_config, store_model_in_db
|
||||
import os
|
||||
|
|
@ -671,31 +677,66 @@ async def update_ui_theme_settings(theme_config: UIThemeConfig):
|
|||
if "environment_variables" not in config:
|
||||
config["environment_variables"] = {}
|
||||
|
||||
# Convert theme config to dict
|
||||
theme_data = theme_config.model_dump(exclude_none=True)
|
||||
|
||||
# Convert theme config to dict - use exclude_unset to only get explicitly provided fields
|
||||
# This allows us to distinguish between fields not provided vs fields set to None
|
||||
theme_data = theme_config.model_dump(exclude_unset=True)
|
||||
|
||||
# Store UI theme config in litellm_settings (where it's retrieved from)
|
||||
if "litellm_settings" not in config:
|
||||
config["litellm_settings"] = {}
|
||||
config["litellm_settings"]["ui_theme_config"] = theme_data
|
||||
|
||||
# Get existing ui_theme_config or create empty dict
|
||||
existing_theme_config = config["litellm_settings"].get("ui_theme_config", {})
|
||||
|
||||
# Merge new values with existing config (only update provided fields)
|
||||
merged_theme_config = existing_theme_config.copy()
|
||||
|
||||
# Update UI_LOGO_PATH environment variable if logo_url is provided
|
||||
# If logo_url is empty string, None, or null, remove the environment variable to use default
|
||||
logo_url = theme_data.get("logo_url")
|
||||
verbose_proxy_logger.debug(f"Updating logo_url: {logo_url}")
|
||||
# Handle explicit null values to remove fields (must check before updating)
|
||||
for key, value in theme_data.items():
|
||||
if value is None:
|
||||
# Remove the field if it exists
|
||||
merged_theme_config.pop(key, None)
|
||||
else:
|
||||
# Update with non-None values
|
||||
merged_theme_config[key] = value
|
||||
|
||||
config["litellm_settings"]["ui_theme_config"] = merged_theme_config
|
||||
|
||||
if logo_url and isinstance(logo_url, str) and logo_url.strip(): # Check if logo_url exists and is not empty/whitespace
|
||||
config["environment_variables"]["UI_LOGO_PATH"] = logo_url
|
||||
os.environ["UI_LOGO_PATH"] = logo_url
|
||||
verbose_proxy_logger.debug(f"Set UI_LOGO_PATH to: {logo_url}")
|
||||
else:
|
||||
# Remove the environment variable to restore default logo
|
||||
if "UI_LOGO_PATH" in config.get("environment_variables", {}):
|
||||
del config["environment_variables"]["UI_LOGO_PATH"]
|
||||
verbose_proxy_logger.debug("Removed UI_LOGO_PATH from config")
|
||||
if "UI_LOGO_PATH" in os.environ:
|
||||
del os.environ["UI_LOGO_PATH"]
|
||||
verbose_proxy_logger.debug("Removed UI_LOGO_PATH from environment")
|
||||
# Update UI_LOGO_PATH environment variable only if logo_url was explicitly provided in the request
|
||||
if "logo_url" in theme_data:
|
||||
logo_url = theme_data["logo_url"]
|
||||
verbose_proxy_logger.debug(f"Updating logo_url: {logo_url}")
|
||||
|
||||
if logo_url and isinstance(logo_url, str) and logo_url.strip(): # Check if logo_url is a valid non-empty string
|
||||
config["environment_variables"]["UI_LOGO_PATH"] = logo_url
|
||||
os.environ["UI_LOGO_PATH"] = logo_url
|
||||
verbose_proxy_logger.debug(f"Set UI_LOGO_PATH to: {logo_url}")
|
||||
else:
|
||||
# Remove the environment variable to restore default logo (when explicitly set to None/empty)
|
||||
if "UI_LOGO_PATH" in config.get("environment_variables", {}):
|
||||
del config["environment_variables"]["UI_LOGO_PATH"]
|
||||
verbose_proxy_logger.debug("Removed UI_LOGO_PATH from config")
|
||||
if "UI_LOGO_PATH" in os.environ:
|
||||
del os.environ["UI_LOGO_PATH"]
|
||||
verbose_proxy_logger.debug("Removed UI_LOGO_PATH from environment")
|
||||
|
||||
# Update UI_FAVICON_PATH environment variable only if favicon_url was explicitly provided in the request
|
||||
if "favicon_url" in theme_data:
|
||||
favicon_url = theme_data["favicon_url"]
|
||||
verbose_proxy_logger.debug(f"Updating favicon_url: {favicon_url}")
|
||||
|
||||
if favicon_url and isinstance(favicon_url, str) and favicon_url.strip(): # Check if favicon_url is a valid non-empty string
|
||||
config["environment_variables"]["UI_FAVICON_PATH"] = favicon_url
|
||||
os.environ["UI_FAVICON_PATH"] = favicon_url
|
||||
verbose_proxy_logger.debug(f"Set UI_FAVICON_PATH to: {favicon_url}")
|
||||
else:
|
||||
# Remove the environment variable to restore default favicon (when explicitly set to None/empty)
|
||||
if "UI_FAVICON_PATH" in config.get("environment_variables", {}):
|
||||
del config["environment_variables"]["UI_FAVICON_PATH"]
|
||||
verbose_proxy_logger.debug("Removed UI_FAVICON_PATH from config")
|
||||
if "UI_FAVICON_PATH" in os.environ:
|
||||
del os.environ["UI_FAVICON_PATH"]
|
||||
verbose_proxy_logger.debug("Removed UI_FAVICON_PATH from environment")
|
||||
|
||||
# Handle environment variable encryption if needed
|
||||
stored_config = config.copy()
|
||||
|
|
@ -709,9 +750,9 @@ async def update_ui_theme_settings(theme_config: UIThemeConfig):
|
|||
await proxy_config.save_config(new_config=stored_config)
|
||||
|
||||
return {
|
||||
"message": "Logo settings updated successfully.",
|
||||
"message": "UI theme settings updated successfully.",
|
||||
"status": "success",
|
||||
"theme_config": theme_data,
|
||||
"theme_config": merged_theme_config,
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -666,6 +666,113 @@ class TestProxySettingEndpoints:
|
|||
assert "UI_LOGO_PATH" in updated_config["environment_variables"]
|
||||
assert mock_proxy_config["save_call_count"]() == 1
|
||||
|
||||
def test_update_favicon_url_by_itself(self, mock_proxy_config, mock_auth, monkeypatch):
|
||||
"""Test updating favicon_url by itself should successfully save"""
|
||||
monkeypatch.setenv("LITELLM_SALT_KEY", "test_salt_key")
|
||||
monkeypatch.setattr("litellm.proxy.proxy_server.store_model_in_db", True)
|
||||
|
||||
new_theme = {"favicon_url": "https://example.com/favicon.ico"}
|
||||
|
||||
response = client.patch("/update/ui_theme_settings", json=new_theme)
|
||||
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
|
||||
assert data["status"] == "success"
|
||||
assert data["theme_config"]["favicon_url"] == "https://example.com/favicon.ico"
|
||||
|
||||
# Verify config was updated
|
||||
updated_config = mock_proxy_config["config"]
|
||||
assert "UI_FAVICON_PATH" in updated_config["environment_variables"]
|
||||
assert updated_config["environment_variables"]["UI_FAVICON_PATH"] == "https://example.com/favicon.ico"
|
||||
|
||||
# Verify favicon_url is stored in litellm_settings
|
||||
assert "litellm_settings" in updated_config
|
||||
assert "ui_theme_config" in updated_config["litellm_settings"]
|
||||
assert updated_config["litellm_settings"]["ui_theme_config"]["favicon_url"] == "https://example.com/favicon.ico"
|
||||
|
||||
assert mock_proxy_config["save_call_count"]() == 1
|
||||
|
||||
def test_update_favicon_url_with_existing_logo_url(self, mock_proxy_config, mock_auth, monkeypatch):
|
||||
"""Test updating favicon_url when logo_url exists should not modify logo_url"""
|
||||
monkeypatch.setenv("LITELLM_SALT_KEY", "test_salt_key")
|
||||
monkeypatch.setattr("litellm.proxy.proxy_server.store_model_in_db", True)
|
||||
|
||||
# First, set up an existing logo_url
|
||||
initial_config = mock_proxy_config["config"]
|
||||
if "litellm_settings" not in initial_config:
|
||||
initial_config["litellm_settings"] = {}
|
||||
if "ui_theme_config" not in initial_config["litellm_settings"]:
|
||||
initial_config["litellm_settings"]["ui_theme_config"] = {}
|
||||
|
||||
initial_config["litellm_settings"]["ui_theme_config"]["logo_url"] = "https://example.com/existing-logo.png"
|
||||
initial_config["environment_variables"]["UI_LOGO_PATH"] = "https://example.com/existing-logo.png"
|
||||
|
||||
# Now update only favicon_url
|
||||
new_theme = {"favicon_url": "https://example.com/new-favicon.ico"}
|
||||
|
||||
response = client.patch("/update/ui_theme_settings", json=new_theme)
|
||||
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
|
||||
assert data["status"] == "success"
|
||||
assert data["theme_config"]["favicon_url"] == "https://example.com/new-favicon.ico"
|
||||
# Verify logo_url is still present and unchanged
|
||||
assert data["theme_config"]["logo_url"] == "https://example.com/existing-logo.png"
|
||||
|
||||
# Verify both are in config
|
||||
updated_config = mock_proxy_config["config"]
|
||||
assert "UI_FAVICON_PATH" in updated_config["environment_variables"]
|
||||
assert updated_config["environment_variables"]["UI_FAVICON_PATH"] == "https://example.com/new-favicon.ico"
|
||||
assert "UI_LOGO_PATH" in updated_config["environment_variables"]
|
||||
assert updated_config["environment_variables"]["UI_LOGO_PATH"] == "https://example.com/existing-logo.png"
|
||||
|
||||
# Verify both are in litellm_settings
|
||||
assert updated_config["litellm_settings"]["ui_theme_config"]["favicon_url"] == "https://example.com/new-favicon.ico"
|
||||
assert updated_config["litellm_settings"]["ui_theme_config"]["logo_url"] == "https://example.com/existing-logo.png"
|
||||
|
||||
def test_remove_favicon_url_with_existing_logo_url(self, mock_proxy_config, mock_auth, monkeypatch):
|
||||
"""Test removing favicon_url should not modify logo_url"""
|
||||
monkeypatch.setenv("LITELLM_SALT_KEY", "test_salt_key")
|
||||
monkeypatch.setattr("litellm.proxy.proxy_server.store_model_in_db", True)
|
||||
|
||||
# First, set up both logo_url and favicon_url
|
||||
initial_config = mock_proxy_config["config"]
|
||||
if "litellm_settings" not in initial_config:
|
||||
initial_config["litellm_settings"] = {}
|
||||
if "ui_theme_config" not in initial_config["litellm_settings"]:
|
||||
initial_config["litellm_settings"]["ui_theme_config"] = {}
|
||||
|
||||
initial_config["litellm_settings"]["ui_theme_config"]["logo_url"] = "https://example.com/existing-logo.png"
|
||||
initial_config["litellm_settings"]["ui_theme_config"]["favicon_url"] = "https://example.com/existing-favicon.ico"
|
||||
initial_config["environment_variables"]["UI_LOGO_PATH"] = "https://example.com/existing-logo.png"
|
||||
initial_config["environment_variables"]["UI_FAVICON_PATH"] = "https://example.com/existing-favicon.ico"
|
||||
|
||||
# Now remove favicon_url by setting it to null
|
||||
new_theme = {"favicon_url": None}
|
||||
|
||||
response = client.patch("/update/ui_theme_settings", json=new_theme)
|
||||
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
|
||||
assert data["status"] == "success"
|
||||
# Verify favicon_url is removed
|
||||
assert "favicon_url" not in data["theme_config"]
|
||||
# Verify logo_url is still present and unchanged
|
||||
assert data["theme_config"]["logo_url"] == "https://example.com/existing-logo.png"
|
||||
|
||||
# Verify favicon_url is removed from config but logo_url remains
|
||||
updated_config = mock_proxy_config["config"]
|
||||
assert "UI_FAVICON_PATH" not in updated_config.get("environment_variables", {})
|
||||
assert "UI_LOGO_PATH" in updated_config["environment_variables"]
|
||||
assert updated_config["environment_variables"]["UI_LOGO_PATH"] == "https://example.com/existing-logo.png"
|
||||
|
||||
# Verify favicon_url is removed from litellm_settings but logo_url remains
|
||||
assert "favicon_url" not in updated_config["litellm_settings"]["ui_theme_config"]
|
||||
assert updated_config["litellm_settings"]["ui_theme_config"]["logo_url"] == "https://example.com/existing-logo.png"
|
||||
|
||||
def test_get_ui_settings(self, mock_auth, monkeypatch):
|
||||
"""Test retrieving UI settings with allowlist sanitization"""
|
||||
from unittest.mock import AsyncMock, MagicMock
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue