From 1a9e27cdafcb6e9d4dbcba44715d74cce70177df Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Tue, 18 Jul 2023 11:35:44 +0200 Subject: [PATCH] Allow integrations to register custom config panels (#96245) --- homeassistant/components/frontend/__init__.py | 9 +++++ .../components/panel_custom/__init__.py | 3 ++ tests/components/hassio/test_init.py | 1 + tests/components/panel_custom/test_init.py | 36 ++++++++++++++++++- tests/components/panel_iframe/test_init.py | 4 +++ 5 files changed, 52 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/frontend/__init__.py b/homeassistant/components/frontend/__init__.py index 8c04e591968..59315e9f576 100644 --- a/homeassistant/components/frontend/__init__.py +++ b/homeassistant/components/frontend/__init__.py @@ -222,6 +222,9 @@ class Panel: # If the panel should only be visible to admins require_admin = False + # If the panel is a configuration panel for a integration + config_panel_domain: str | None = None + def __init__( self, component_name: str, @@ -230,6 +233,7 @@ class Panel: frontend_url_path: str | None, config: dict[str, Any] | None, require_admin: bool, + config_panel_domain: str | None, ) -> None: """Initialize a built-in panel.""" self.component_name = component_name @@ -238,6 +242,7 @@ class Panel: self.frontend_url_path = frontend_url_path or component_name self.config = config self.require_admin = require_admin + self.config_panel_domain = config_panel_domain @callback def to_response(self) -> PanelRespons: @@ -249,6 +254,7 @@ class Panel: "config": self.config, "url_path": self.frontend_url_path, "require_admin": self.require_admin, + "config_panel_domain": self.config_panel_domain, } @@ -264,6 +270,7 @@ def async_register_built_in_panel( require_admin: bool = False, *, update: bool = False, + config_panel_domain: str | None = None, ) -> None: """Register a built-in panel.""" panel = Panel( @@ -273,6 +280,7 @@ def async_register_built_in_panel( frontend_url_path, config, require_admin, + config_panel_domain, ) panels = hass.data.setdefault(DATA_PANELS, {}) @@ -720,3 +728,4 @@ class PanelRespons(TypedDict): config: dict[str, Any] | None url_path: str | None require_admin: bool + config_panel_domain: str | None diff --git a/homeassistant/components/panel_custom/__init__.py b/homeassistant/components/panel_custom/__init__.py index 493a738c1ea..4f084d5900a 100644 --- a/homeassistant/components/panel_custom/__init__.py +++ b/homeassistant/components/panel_custom/__init__.py @@ -92,6 +92,8 @@ async def async_register_panel( config: ConfigType | None = None, # If your panel should only be shown to admin users require_admin: bool = False, + # If your panel is used to configure an integration, needs the domain of the integration + config_panel_domain: str | None = None, ) -> None: """Register a new custom panel.""" if js_url is None and module_url is None: @@ -127,6 +129,7 @@ async def async_register_panel( frontend_url_path=frontend_url_path, config=config, require_admin=require_admin, + config_panel_domain=config_panel_domain, ) diff --git a/tests/components/hassio/test_init.py b/tests/components/hassio/test_init.py index 0dff261d864..b394d439654 100644 --- a/tests/components/hassio/test_init.py +++ b/tests/components/hassio/test_init.py @@ -265,6 +265,7 @@ async def test_setup_api_panel( "title": None, "url_path": "hassio", "require_admin": True, + "config_panel_domain": None, "config": { "_panel_custom": { "embed_iframe": True, diff --git a/tests/components/panel_custom/test_init.py b/tests/components/panel_custom/test_init.py index 81365273986..d84b4c812c7 100644 --- a/tests/components/panel_custom/test_init.py +++ b/tests/components/panel_custom/test_init.py @@ -2,7 +2,7 @@ from unittest.mock import Mock, patch from homeassistant import setup -from homeassistant.components import frontend +from homeassistant.components import frontend, panel_custom from homeassistant.core import HomeAssistant @@ -155,3 +155,37 @@ async def test_url_path_conflict(hass: HomeAssistant) -> None: ] }, ) + + +async def test_register_config_panel(hass: HomeAssistant) -> None: + """Test setting up a custom config panel for an integration.""" + result = await setup.async_setup_component(hass, "panel_custom", {}) + assert result + + # Register a custom panel + await panel_custom.async_register_panel( + hass=hass, + frontend_url_path="config_panel", + webcomponent_name="custom-frontend", + module_url="custom-frontend", + embed_iframe=True, + require_admin=True, + config_panel_domain="test", + ) + + panels = hass.data.get(frontend.DATA_PANELS, []) + assert panels + assert "config_panel" in panels + + panel = panels["config_panel"] + + assert panel.config == { + "_panel_custom": { + "module_url": "custom-frontend", + "name": "custom-frontend", + "embed_iframe": True, + "trust_external": False, + }, + } + assert panel.frontend_url_path == "config_panel" + assert panel.config_panel_domain == "test" diff --git a/tests/components/panel_iframe/test_init.py b/tests/components/panel_iframe/test_init.py index 79bc7e37ee3..bd8950163a9 100644 --- a/tests/components/panel_iframe/test_init.py +++ b/tests/components/panel_iframe/test_init.py @@ -54,6 +54,7 @@ async def test_correct_config(hass: HomeAssistant) -> None: assert panels.get("router").to_response() == { "component_name": "iframe", "config": {"url": "http://192.168.1.1"}, + "config_panel_domain": None, "icon": "mdi:network-wireless", "title": "Router", "url_path": "router", @@ -63,6 +64,7 @@ async def test_correct_config(hass: HomeAssistant) -> None: assert panels.get("weather").to_response() == { "component_name": "iframe", "config": {"url": "https://www.wunderground.com/us/ca/san-diego"}, + "config_panel_domain": None, "icon": "mdi:weather", "title": "Weather", "url_path": "weather", @@ -72,6 +74,7 @@ async def test_correct_config(hass: HomeAssistant) -> None: assert panels.get("api").to_response() == { "component_name": "iframe", "config": {"url": "/api"}, + "config_panel_domain": None, "icon": "mdi:weather", "title": "Api", "url_path": "api", @@ -81,6 +84,7 @@ async def test_correct_config(hass: HomeAssistant) -> None: assert panels.get("ftp").to_response() == { "component_name": "iframe", "config": {"url": "ftp://some/ftp"}, + "config_panel_domain": None, "icon": "mdi:weather", "title": "FTP", "url_path": "ftp",