diff --git a/.env.example b/.env.example index 3d4ed67dd..b8df7014c 100644 --- a/.env.example +++ b/.env.example @@ -10,3 +10,9 @@ LOG_LEVEL=INFO # Enable file logging (logs written to logs/bambutrack.log) LOG_TO_FILE=true + +# Home Assistant Integration (for HA Add-on deployments) +# When both HA_URL and HA_TOKEN are set, Home Assistant integration is automatically enabled +# and these values override any database settings (read-only in UI) +# HA_URL=http://supervisor/core +# HA_TOKEN=your-long-lived-access-token diff --git a/backend/app/api/routes/settings.py b/backend/app/api/routes/settings.py index 56cc5a83e..2c5fa5cfc 100644 --- a/backend/app/api/routes/settings.py +++ b/backend/app/api/routes/settings.py @@ -101,6 +101,10 @@ async def get_settings( else: settings_dict[setting.key] = setting.value + # Get Home Assistant settings (with environment variable overrides) + ha_settings = await get_homeassistant_settings(db) + settings_dict.update(ha_settings) + return AppSettings(**settings_dict) @@ -247,6 +251,43 @@ async def update_spoolman_settings( return await get_spoolman_settings(db) +async def get_homeassistant_settings(db: AsyncSession) -> dict: + """ + Get Home Assistant integration settings. + Environment variables (HA_URL, HA_TOKEN) take precedence over database settings. + """ + import os + + # Check environment variables first + ha_url_env = os.environ.get("HA_URL") + ha_token_env = os.environ.get("HA_TOKEN") + + # Fall back to database values + ha_url = ha_url_env or await get_setting(db, "ha_url") or "" + ha_token = ha_token_env or await get_setting(db, "ha_token") or "" + ha_enabled_db = await get_setting(db, "ha_enabled") or "false" + + # Track which settings come from environment + ha_url_from_env = bool(ha_url_env) + ha_token_from_env = bool(ha_token_env) + ha_env_managed = ha_url_from_env and ha_token_from_env + + # Auto-enable when both env vars are set, otherwise use database value + if ha_url_env and ha_token_env: + ha_enabled = True + else: + ha_enabled = ha_enabled_db.lower() == "true" + + return { + "ha_enabled": ha_enabled, + "ha_url": ha_url, + "ha_token": ha_token, + "ha_url_from_env": ha_url_from_env, + "ha_token_from_env": ha_token_from_env, + "ha_env_managed": ha_env_managed, + } + + @router.get("/backup") async def create_backup( db: AsyncSession = Depends(get_db), diff --git a/backend/app/api/routes/smart_plugs.py b/backend/app/api/routes/smart_plugs.py index c15352db9..096093419 100644 --- a/backend/app/api/routes/smart_plugs.py +++ b/backend/app/api/routes/smart_plugs.py @@ -354,8 +354,11 @@ async def list_ha_entities( Requires HA connection settings to be configured in Settings. """ - ha_url = await get_setting(db, "ha_url") or "" - ha_token = await get_setting(db, "ha_token") or "" + from backend.app.api.routes.settings import get_homeassistant_settings + + ha_settings = await get_homeassistant_settings(db) + ha_url = ha_settings["ha_url"] + ha_token = ha_settings["ha_token"] if not ha_url or not ha_token: raise HTTPException( @@ -376,8 +379,11 @@ async def list_ha_sensor_entities( Returns sensors with power/energy units (W, kW, kWh, Wh). Requires HA connection settings to be configured in Settings. """ - ha_url = await get_setting(db, "ha_url") or "" - ha_token = await get_setting(db, "ha_token") or "" + from backend.app.api.routes.settings import get_homeassistant_settings + + ha_settings = await get_homeassistant_settings(db) + ha_url = ha_settings["ha_url"] + ha_token = ha_settings["ha_token"] if not ha_url or not ha_token: raise HTTPException( @@ -546,9 +552,10 @@ async def _get_service_for_plug(plug: SmartPlug, db: AsyncSession): """ if plug.plug_type == "homeassistant": # Configure HA service with current settings - ha_url = await get_setting(db, "ha_url") or "" - ha_token = await get_setting(db, "ha_token") or "" - homeassistant_service.configure(ha_url, ha_token) + from backend.app.api.routes.settings import get_homeassistant_settings + + ha_settings = await get_homeassistant_settings(db) + homeassistant_service.configure(ha_settings["ha_url"], ha_settings["ha_token"]) return homeassistant_service return tasmota_service diff --git a/backend/app/main.py b/backend/app/main.py index ff50cd81e..48e001c0e 100644 --- a/backend/app/main.py +++ b/backend/app/main.py @@ -260,11 +260,10 @@ async def _get_plug_energy(plug, db) -> dict | None: For MQTT plugs, returns data from the subscription service. """ if plug.plug_type == "homeassistant": - from backend.app.api.routes.settings import get_setting + from backend.app.api.routes.settings import get_homeassistant_settings - ha_url = await get_setting(db, "ha_url") or "" - ha_token = await get_setting(db, "ha_token") or "" - homeassistant_service.configure(ha_url, ha_token) + ha_settings = await get_homeassistant_settings(db) + homeassistant_service.configure(ha_settings["ha_url"], ha_settings["ha_token"]) return await homeassistant_service.get_energy(plug) elif plug.plug_type == "mqtt": # MQTT plugs report "today" energy, not lifetime total diff --git a/backend/app/schemas/settings.py b/backend/app/schemas/settings.py index cc6a52190..0e692e4db 100644 --- a/backend/app/schemas/settings.py +++ b/backend/app/schemas/settings.py @@ -106,6 +106,13 @@ class AppSettings(BaseModel): ha_enabled: bool = Field(default=False, description="Enable Home Assistant integration for smart plug control") ha_url: str = Field(default="", description="Home Assistant URL (e.g., http://192.168.1.100:8123)") ha_token: str = Field(default="", description="Home Assistant Long-Lived Access Token") + ha_url_from_env: bool = Field(default=False, description="Whether HA URL is set via HA_URL environment variable") + ha_token_from_env: bool = Field( + default=False, description="Whether HA token is set via HA_TOKEN environment variable" + ) + ha_env_managed: bool = Field( + default=False, description="Whether HA integration is fully managed by environment variables" + ) # File Manager / Library settings library_archive_mode: str = Field( diff --git a/backend/app/services/smart_plug_manager.py b/backend/app/services/smart_plug_manager.py index 592259756..7a6eb9c9c 100644 --- a/backend/app/services/smart_plug_manager.py +++ b/backend/app/services/smart_plug_manager.py @@ -40,28 +40,20 @@ class SmartPlugManager: async def _configure_ha_service(self, db: AsyncSession | None = None): """Configure the HA service with URL and token from settings.""" - from backend.app.models.settings import Settings + from backend.app.api.routes.settings import get_homeassistant_settings try: if db: # Use provided session - result = await db.execute(select(Settings).where(Settings.key == "ha_url")) - ha_url_setting = result.scalar_one_or_none() - result = await db.execute(select(Settings).where(Settings.key == "ha_token")) - ha_token_setting = result.scalar_one_or_none() + ha_settings = await get_homeassistant_settings(db) else: # Create new session from backend.app.core.database import async_session async with async_session() as session: - result = await session.execute(select(Settings).where(Settings.key == "ha_url")) - ha_url_setting = result.scalar_one_or_none() - result = await session.execute(select(Settings).where(Settings.key == "ha_token")) - ha_token_setting = result.scalar_one_or_none() + ha_settings = await get_homeassistant_settings(session) - ha_url = ha_url_setting.value if ha_url_setting else "" - ha_token = ha_token_setting.value if ha_token_setting else "" - homeassistant_service.configure(ha_url, ha_token) + homeassistant_service.configure(ha_settings["ha_url"], ha_settings["ha_token"]) except Exception as e: logger.warning("Failed to configure HA service: %s", e) diff --git a/backend/tests/integration/test_settings_api.py b/backend/tests/integration/test_settings_api.py index 5fe595101..c2fec9fcb 100644 --- a/backend/tests/integration/test_settings_api.py +++ b/backend/tests/integration/test_settings_api.py @@ -3,6 +3,8 @@ Tests the full request/response cycle for /api/v1/settings/ endpoints. """ +import os + import pytest from httpx import AsyncClient @@ -393,6 +395,252 @@ class TestSettingsAPI: # Default is False as defined in schema assert isinstance(result["per_printer_mapping_expanded"], bool) + # ======================================================================== + # Home Assistant environment variable tests + # ======================================================================== + + @pytest.mark.asyncio + @pytest.mark.integration + async def test_ha_settings_default_no_env_vars(self, async_client: AsyncClient): + """Verify HA settings work without environment variables (default behavior).""" + # Ensure no env vars are set + os.environ.pop("HA_URL", None) + os.environ.pop("HA_TOKEN", None) + + response = await async_client.get("/api/v1/settings/") + result = response.json() + + assert response.status_code == 200 + assert "ha_enabled" in result + assert "ha_url" in result + assert "ha_token" in result + assert "ha_url_from_env" in result + assert "ha_token_from_env" in result + assert "ha_env_managed" in result + + # Default values without env vars + assert result["ha_url_from_env"] is False + assert result["ha_token_from_env"] is False + assert result["ha_env_managed"] is False + + @pytest.mark.asyncio + @pytest.mark.integration + async def test_ha_settings_with_both_env_vars(self, async_client: AsyncClient): + """Verify HA settings are overridden when both env vars are set.""" + # Set environment variables + os.environ["HA_URL"] = "http://supervisor/core" + os.environ["HA_TOKEN"] = "test-token-12345" + + try: + response = await async_client.get("/api/v1/settings/") + result = response.json() + + assert response.status_code == 200 + + # Verify env var values are used + assert result["ha_url"] == "http://supervisor/core" + assert result["ha_token"] == "test-token-12345" + + # Verify metadata fields + assert result["ha_url_from_env"] is True + assert result["ha_token_from_env"] is True + assert result["ha_env_managed"] is True + + # Verify auto-enable behavior + assert result["ha_enabled"] is True + + finally: + # Clean up + os.environ.pop("HA_URL", None) + os.environ.pop("HA_TOKEN", None) + + @pytest.mark.asyncio + @pytest.mark.integration + async def test_ha_settings_with_only_url_env_var(self, async_client: AsyncClient): + """Verify partial configuration when only HA_URL is set.""" + # Set only URL env var + os.environ["HA_URL"] = "http://supervisor/core" + os.environ.pop("HA_TOKEN", None) + + try: + response = await async_client.get("/api/v1/settings/") + result = response.json() + + assert response.status_code == 200 + + # Verify URL is from env, token is from database + assert result["ha_url"] == "http://supervisor/core" + assert result["ha_url_from_env"] is True + assert result["ha_token_from_env"] is False + assert result["ha_env_managed"] is False + + # No auto-enable with partial config + assert result["ha_enabled"] is False # Database default + + finally: + os.environ.pop("HA_URL", None) + + @pytest.mark.asyncio + @pytest.mark.integration + async def test_ha_settings_with_only_token_env_var(self, async_client: AsyncClient): + """Verify partial configuration when only HA_TOKEN is set.""" + # Set only token env var + os.environ.pop("HA_URL", None) + os.environ["HA_TOKEN"] = "test-token-12345" + + try: + response = await async_client.get("/api/v1/settings/") + result = response.json() + + assert response.status_code == 200 + + # Verify token is from env, URL is from database + assert result["ha_token"] == "test-token-12345" + assert result["ha_url_from_env"] is False + assert result["ha_token_from_env"] is True + assert result["ha_env_managed"] is False + + # No auto-enable with partial config + assert result["ha_enabled"] is False # Database default + + finally: + os.environ.pop("HA_TOKEN", None) + + @pytest.mark.asyncio + @pytest.mark.integration + async def test_ha_settings_env_vars_override_database(self, async_client: AsyncClient): + """Verify environment variables take precedence over database values.""" + # First, set database values + await async_client.put( + "/api/v1/settings/", + json={ + "ha_enabled": True, + "ha_url": "http://database-url:8123", + "ha_token": "database-token", + }, + ) + + # Verify database values are set + response = await async_client.get("/api/v1/settings/") + result = response.json() + assert result["ha_url"] == "http://database-url:8123" + assert result["ha_token"] == "database-token" + + # Now set environment variables + os.environ["HA_URL"] = "http://env-url/core" + os.environ["HA_TOKEN"] = "env-token-xyz" + + try: + response = await async_client.get("/api/v1/settings/") + result = response.json() + + # Verify env vars override database + assert result["ha_url"] == "http://env-url/core" + assert result["ha_token"] == "env-token-xyz" + assert result["ha_url_from_env"] is True + assert result["ha_token_from_env"] is True + assert result["ha_env_managed"] is True + assert result["ha_enabled"] is True + + finally: + os.environ.pop("HA_URL", None) + os.environ.pop("HA_TOKEN", None) + + # Verify database values are still there after removing env vars + response = await async_client.get("/api/v1/settings/") + result = response.json() + assert result["ha_url"] == "http://database-url:8123" + assert result["ha_token"] == "database-token" + assert result["ha_url_from_env"] is False + assert result["ha_token_from_env"] is False + + @pytest.mark.asyncio + @pytest.mark.integration + async def test_ha_settings_database_updates_accepted_but_ignored(self, async_client: AsyncClient): + """Verify database updates are accepted but have no effect when env vars are set.""" + # Set environment variables + os.environ["HA_URL"] = "http://supervisor/core" + os.environ["HA_TOKEN"] = "env-token" + + try: + # Attempt to update via API + response = await async_client.put( + "/api/v1/settings/", + json={ + "ha_url": "http://different-url:8123", + "ha_token": "different-token", + }, + ) + + # Update should succeed + assert response.status_code == 200 + + # But values should still be from env vars + result = response.json() + assert result["ha_url"] == "http://supervisor/core" + assert result["ha_token"] == "env-token" + assert result["ha_url_from_env"] is True + assert result["ha_token_from_env"] is True + + finally: + os.environ.pop("HA_URL", None) + os.environ.pop("HA_TOKEN", None) + + @pytest.mark.asyncio + @pytest.mark.integration + async def test_ha_settings_empty_env_vars_treated_as_not_set(self, async_client: AsyncClient): + """Verify empty environment variables are treated as not set.""" + # Set empty env vars + os.environ["HA_URL"] = "" + os.environ["HA_TOKEN"] = "" + + try: + response = await async_client.get("/api/v1/settings/") + result = response.json() + + # Empty env vars should be treated as not set + assert result["ha_url_from_env"] is False + assert result["ha_token_from_env"] is False + assert result["ha_env_managed"] is False + + finally: + os.environ.pop("HA_URL", None) + os.environ.pop("HA_TOKEN", None) + + @pytest.mark.asyncio + @pytest.mark.integration + async def test_ha_settings_can_be_updated_normally_without_env_vars(self, async_client: AsyncClient): + """Verify HA settings can be updated normally when env vars are not set.""" + # Ensure no env vars + os.environ.pop("HA_URL", None) + os.environ.pop("HA_TOKEN", None) + + # Update HA settings + response = await async_client.put( + "/api/v1/settings/", + json={ + "ha_enabled": True, + "ha_url": "http://192.168.1.100:8123", + "ha_token": "my-long-lived-token", + }, + ) + + assert response.status_code == 200 + result = response.json() + assert result["ha_enabled"] is True + assert result["ha_url"] == "http://192.168.1.100:8123" + assert result["ha_token"] == "my-long-lived-token" + assert result["ha_url_from_env"] is False + assert result["ha_token_from_env"] is False + assert result["ha_env_managed"] is False + + # Verify persistence + response = await async_client.get("/api/v1/settings/") + result = response.json() + assert result["ha_enabled"] is True + assert result["ha_url"] == "http://192.168.1.100:8123" + assert result["ha_token"] == "my-long-lived-token" + class TestSimplifiedBackupRestore: """Integration tests for the simplified backup/restore endpoints (ZIP-based). diff --git a/backend/tests/unit/test_homeassistant_settings.py b/backend/tests/unit/test_homeassistant_settings.py new file mode 100644 index 000000000..fe2916851 --- /dev/null +++ b/backend/tests/unit/test_homeassistant_settings.py @@ -0,0 +1,229 @@ +"""Unit tests for Home Assistant settings with environment variable support. + +Tests the get_homeassistant_settings() function in isolation. +""" + +import os +from unittest.mock import AsyncMock, patch + +import pytest +from sqlalchemy.ext.asyncio import AsyncSession + + +@pytest.mark.asyncio +@pytest.mark.unit +async def test_get_homeassistant_settings_no_env_vars(): + """Test get_homeassistant_settings with no environment variables.""" + from backend.app.api.routes.settings import get_homeassistant_settings + + # Mock database session + db = AsyncMock(spec=AsyncSession) + + # Mock get_setting to return database values + with patch("backend.app.api.routes.settings.get_setting") as mock_get_setting: + mock_get_setting.side_effect = lambda db, key: { + "ha_url": "http://db-url:8123", + "ha_token": "db-token", + "ha_enabled": "true", + }.get(key, "") + + # Ensure no env vars + with patch.dict(os.environ, {}, clear=False): + os.environ.pop("HA_URL", None) + os.environ.pop("HA_TOKEN", None) + + result = await get_homeassistant_settings(db) + + # Should use database values + assert result["ha_url"] == "http://db-url:8123" + assert result["ha_token"] == "db-token" + assert result["ha_enabled"] is True + assert result["ha_url_from_env"] is False + assert result["ha_token_from_env"] is False + assert result["ha_env_managed"] is False + + +@pytest.mark.asyncio +@pytest.mark.unit +async def test_get_homeassistant_settings_with_env_vars(): + """Test get_homeassistant_settings with environment variables set.""" + from backend.app.api.routes.settings import get_homeassistant_settings + + db = AsyncMock(spec=AsyncSession) + + with patch("backend.app.api.routes.settings.get_setting") as mock_get_setting: + mock_get_setting.side_effect = lambda db, key: { + "ha_url": "http://db-url:8123", + "ha_token": "db-token", + "ha_enabled": "false", + }.get(key, "") + + # Set environment variables + with patch.dict(os.environ, {"HA_URL": "http://supervisor/core", "HA_TOKEN": "env-token"}, clear=False): + result = await get_homeassistant_settings(db) + + # Should use environment values + assert result["ha_url"] == "http://supervisor/core" + assert result["ha_token"] == "env-token" + assert result["ha_enabled"] is True # Auto-enabled + assert result["ha_url_from_env"] is True + assert result["ha_token_from_env"] is True + assert result["ha_env_managed"] is True + + +@pytest.mark.asyncio +@pytest.mark.unit +async def test_get_homeassistant_settings_partial_env_url_only(): + """Test get_homeassistant_settings with only HA_URL set.""" + from backend.app.api.routes.settings import get_homeassistant_settings + + db = AsyncMock(spec=AsyncSession) + + with patch("backend.app.api.routes.settings.get_setting") as mock_get_setting: + mock_get_setting.side_effect = lambda db, key: { + "ha_url": "http://db-url:8123", + "ha_token": "db-token", + "ha_enabled": "false", + }.get(key, "") + + # Set only URL env var + with patch.dict(os.environ, {"HA_URL": "http://supervisor/core"}, clear=False): + os.environ.pop("HA_TOKEN", None) + + result = await get_homeassistant_settings(db) + + # URL from env, token from database + assert result["ha_url"] == "http://supervisor/core" + assert result["ha_token"] == "db-token" + assert result["ha_enabled"] is False # Not auto-enabled + assert result["ha_url_from_env"] is True + assert result["ha_token_from_env"] is False + assert result["ha_env_managed"] is False + + +@pytest.mark.asyncio +@pytest.mark.unit +async def test_get_homeassistant_settings_partial_env_token_only(): + """Test get_homeassistant_settings with only HA_TOKEN set.""" + from backend.app.api.routes.settings import get_homeassistant_settings + + db = AsyncMock(spec=AsyncSession) + + with patch("backend.app.api.routes.settings.get_setting") as mock_get_setting: + mock_get_setting.side_effect = lambda db, key: { + "ha_url": "http://db-url:8123", + "ha_token": "db-token", + "ha_enabled": "false", + }.get(key, "") + + # Set only token env var + with patch.dict(os.environ, {"HA_TOKEN": "env-token"}, clear=False): + os.environ.pop("HA_URL", None) + + result = await get_homeassistant_settings(db) + + # URL from database, token from env + assert result["ha_url"] == "http://db-url:8123" + assert result["ha_token"] == "env-token" + assert result["ha_enabled"] is False # Not auto-enabled + assert result["ha_url_from_env"] is False + assert result["ha_token_from_env"] is True + assert result["ha_env_managed"] is False + + +@pytest.mark.asyncio +@pytest.mark.unit +async def test_get_homeassistant_settings_empty_env_vars(): + """Test get_homeassistant_settings with empty environment variables.""" + from backend.app.api.routes.settings import get_homeassistant_settings + + db = AsyncMock(spec=AsyncSession) + + with patch("backend.app.api.routes.settings.get_setting") as mock_get_setting: + mock_get_setting.side_effect = lambda db, key: { + "ha_url": "http://db-url:8123", + "ha_token": "db-token", + "ha_enabled": "false", + }.get(key, "") + + # Set empty env vars + with patch.dict(os.environ, {"HA_URL": "", "HA_TOKEN": ""}, clear=False): + result = await get_homeassistant_settings(db) + + # Empty env vars treated as not set, should use database values + assert result["ha_url"] == "http://db-url:8123" + assert result["ha_token"] == "db-token" + assert result["ha_enabled"] is False + assert result["ha_url_from_env"] is False + assert result["ha_token_from_env"] is False + assert result["ha_env_managed"] is False + + +@pytest.mark.asyncio +@pytest.mark.unit +async def test_get_homeassistant_settings_auto_enable_logic(): + """Test auto-enable behavior with various configurations.""" + from backend.app.api.routes.settings import get_homeassistant_settings + + db = AsyncMock(spec=AsyncSession) + + with patch("backend.app.api.routes.settings.get_setting") as mock_get_setting: + # Database has ha_enabled=false + mock_get_setting.side_effect = lambda db, key: { + "ha_url": "", + "ha_token": "", + "ha_enabled": "false", + }.get(key, "") + + # Test 1: No env vars - use database enabled state + with patch.dict(os.environ, {}, clear=False): + os.environ.pop("HA_URL", None) + os.environ.pop("HA_TOKEN", None) + + result = await get_homeassistant_settings(db) + assert result["ha_enabled"] is False + + # Test 2: Both env vars set - auto-enable + with patch.dict(os.environ, {"HA_URL": "http://test", "HA_TOKEN": "token"}, clear=False): + result = await get_homeassistant_settings(db) + assert result["ha_enabled"] is True + + # Test 3: Only URL - use database enabled state + with patch.dict(os.environ, {"HA_URL": "http://test"}, clear=False): + os.environ.pop("HA_TOKEN", None) + + result = await get_homeassistant_settings(db) + assert result["ha_enabled"] is False + + # Test 4: Only token - use database enabled state + with patch.dict(os.environ, {"HA_TOKEN": "token"}, clear=False): + os.environ.pop("HA_URL", None) + + result = await get_homeassistant_settings(db) + assert result["ha_enabled"] is False + + +@pytest.mark.asyncio +@pytest.mark.unit +async def test_get_homeassistant_settings_env_vars_override_enabled_true(): + """Test that env vars auto-enable even when database has ha_enabled=true.""" + from backend.app.api.routes.settings import get_homeassistant_settings + + db = AsyncMock(spec=AsyncSession) + + with patch("backend.app.api.routes.settings.get_setting") as mock_get_setting: + # Database has ha_enabled=true + mock_get_setting.side_effect = lambda db, key: { + "ha_url": "http://db-url:8123", + "ha_token": "db-token", + "ha_enabled": "true", + }.get(key, "") + + # Both env vars set - should still be enabled + with patch.dict(os.environ, {"HA_URL": "http://supervisor/core", "HA_TOKEN": "env-token"}, clear=False): + result = await get_homeassistant_settings(db) + + assert result["ha_enabled"] is True # Auto-enabled by env vars + assert result["ha_url"] == "http://supervisor/core" + assert result["ha_token"] == "env-token" + assert result["ha_env_managed"] is True diff --git a/frontend/src/api/client.ts b/frontend/src/api/client.ts index 883d8a7aa..a39f947fb 100644 --- a/frontend/src/api/client.ts +++ b/frontend/src/api/client.ts @@ -751,6 +751,9 @@ export interface AppSettings { ha_enabled: boolean; ha_url: string; ha_token: string; + ha_url_from_env: boolean; + ha_token_from_env: boolean; + ha_env_managed: boolean; // File Manager / Library settings library_archive_mode: 'always' | 'never' | 'ask'; library_disk_warning_gb: number; diff --git a/frontend/src/pages/SettingsPage.tsx b/frontend/src/pages/SettingsPage.tsx index 6d7f7468b..4554277aa 100644 --- a/frontend/src/pages/SettingsPage.tsx +++ b/frontend/src/pages/SettingsPage.tsx @@ -1974,18 +1974,29 @@ export function SettingsPage() {
{t('settings.enableHomeAssistant')}
{t('settings.homeAssistantDescription')}
+ {localSettings.ha_env_managed && ( ++ Value set by HA_URL environment variable (read-only) +
+ )}- Create a token in HA: Profile → Long-Lived Access Tokens → Create Token -
++ Value set by HA_TOKEN environment variable (read-only) +
+ ) : ( ++ Create a token in HA: Profile → Long-Lived Access Tokens → Create Token +
+ )}