mirror of
https://github.com/home-assistant/core.git
synced 2025-07-14 16:57:10 +00:00
Remove HTML support from frontend (#39799)
This commit is contained in:
parent
2a68952334
commit
c6a7350db1
@ -70,8 +70,6 @@ MANIFEST_JSON = {
|
|||||||
|
|
||||||
DATA_PANELS = "frontend_panels"
|
DATA_PANELS = "frontend_panels"
|
||||||
DATA_JS_VERSION = "frontend_js_version"
|
DATA_JS_VERSION = "frontend_js_version"
|
||||||
DATA_EXTRA_HTML_URL = "frontend_extra_html_url"
|
|
||||||
DATA_EXTRA_HTML_URL_ES5 = "frontend_extra_html_url_es5"
|
|
||||||
DATA_EXTRA_MODULE_URL = "frontend_extra_module_url"
|
DATA_EXTRA_MODULE_URL = "frontend_extra_module_url"
|
||||||
DATA_EXTRA_JS_URL_ES5 = "frontend_extra_js_url_es5"
|
DATA_EXTRA_JS_URL_ES5 = "frontend_extra_js_url_es5"
|
||||||
|
|
||||||
@ -91,29 +89,23 @@ _LOGGER = logging.getLogger(__name__)
|
|||||||
|
|
||||||
CONFIG_SCHEMA = vol.Schema(
|
CONFIG_SCHEMA = vol.Schema(
|
||||||
{
|
{
|
||||||
DOMAIN: vol.All(
|
DOMAIN: vol.Schema(
|
||||||
cv.deprecated(CONF_EXTRA_HTML_URL, invalidation_version="0.115"),
|
{
|
||||||
cv.deprecated(CONF_EXTRA_HTML_URL_ES5, invalidation_version="0.115"),
|
vol.Optional(CONF_FRONTEND_REPO): cv.isdir,
|
||||||
vol.Schema(
|
vol.Optional(CONF_THEMES): vol.Schema(
|
||||||
{
|
{cv.string: {cv.string: cv.string}}
|
||||||
vol.Optional(CONF_FRONTEND_REPO): cv.isdir,
|
),
|
||||||
vol.Optional(CONF_THEMES): vol.Schema(
|
vol.Optional(CONF_EXTRA_MODULE_URL): vol.All(
|
||||||
{cv.string: {cv.string: cv.string}}
|
cv.ensure_list, [cv.string]
|
||||||
),
|
),
|
||||||
vol.Optional(CONF_EXTRA_HTML_URL): vol.All(
|
vol.Optional(CONF_EXTRA_JS_URL_ES5): vol.All(
|
||||||
cv.ensure_list, [cv.string]
|
cv.ensure_list, [cv.string]
|
||||||
),
|
),
|
||||||
vol.Optional(CONF_EXTRA_MODULE_URL): vol.All(
|
# We no longer use these options.
|
||||||
cv.ensure_list, [cv.string]
|
vol.Optional(CONF_EXTRA_HTML_URL): cv.match_all,
|
||||||
),
|
vol.Optional(CONF_EXTRA_HTML_URL_ES5): cv.match_all,
|
||||||
vol.Optional(CONF_EXTRA_JS_URL_ES5): vol.All(
|
vol.Optional(CONF_JS_VERSION): cv.match_all,
|
||||||
cv.ensure_list, [cv.string]
|
},
|
||||||
),
|
|
||||||
# We no longer use these options.
|
|
||||||
vol.Optional(CONF_EXTRA_HTML_URL_ES5): cv.match_all,
|
|
||||||
vol.Optional(CONF_JS_VERSION): cv.match_all,
|
|
||||||
},
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
extra=vol.ALLOW_EXTRA,
|
extra=vol.ALLOW_EXTRA,
|
||||||
@ -220,17 +212,6 @@ def async_remove_panel(hass, frontend_url_path):
|
|||||||
hass.bus.async_fire(EVENT_PANELS_UPDATED)
|
hass.bus.async_fire(EVENT_PANELS_UPDATED)
|
||||||
|
|
||||||
|
|
||||||
@bind_hass
|
|
||||||
@callback
|
|
||||||
def add_extra_html_url(hass, url, es5=False):
|
|
||||||
"""Register extra html url to load."""
|
|
||||||
key = DATA_EXTRA_HTML_URL_ES5 if es5 else DATA_EXTRA_HTML_URL
|
|
||||||
url_set = hass.data.get(key)
|
|
||||||
if url_set is None:
|
|
||||||
url_set = hass.data[key] = set()
|
|
||||||
url_set.add(url)
|
|
||||||
|
|
||||||
|
|
||||||
def add_extra_js_url(hass, url, es5=False):
|
def add_extra_js_url(hass, url, es5=False):
|
||||||
"""Register extra js or module url to load."""
|
"""Register extra js or module url to load."""
|
||||||
key = DATA_EXTRA_JS_URL_ES5 if es5 else DATA_EXTRA_MODULE_URL
|
key = DATA_EXTRA_JS_URL_ES5 if es5 else DATA_EXTRA_MODULE_URL
|
||||||
@ -267,6 +248,13 @@ async def async_setup(hass, config):
|
|||||||
|
|
||||||
conf = config.get(DOMAIN, {})
|
conf = config.get(DOMAIN, {})
|
||||||
|
|
||||||
|
for key in (CONF_EXTRA_HTML_URL, CONF_EXTRA_HTML_URL_ES5, CONF_JS_VERSION):
|
||||||
|
if key in conf:
|
||||||
|
_LOGGER.error(
|
||||||
|
"Please remove %s from your frontend config. It is no longer supported",
|
||||||
|
key,
|
||||||
|
)
|
||||||
|
|
||||||
repo_path = conf.get(CONF_FRONTEND_REPO)
|
repo_path = conf.get(CONF_FRONTEND_REPO)
|
||||||
is_dev = repo_path is not None
|
is_dev = repo_path is not None
|
||||||
root_path = _frontend_root(repo_path)
|
root_path = _frontend_root(repo_path)
|
||||||
@ -315,12 +303,6 @@ async def async_setup(hass, config):
|
|||||||
sidebar_icon="hass:hammer",
|
sidebar_icon="hass:hammer",
|
||||||
)
|
)
|
||||||
|
|
||||||
if DATA_EXTRA_HTML_URL not in hass.data:
|
|
||||||
hass.data[DATA_EXTRA_HTML_URL] = set()
|
|
||||||
|
|
||||||
for url in conf.get(CONF_EXTRA_HTML_URL, []):
|
|
||||||
add_extra_html_url(hass, url, False)
|
|
||||||
|
|
||||||
if DATA_EXTRA_MODULE_URL not in hass.data:
|
if DATA_EXTRA_MODULE_URL not in hass.data:
|
||||||
hass.data[DATA_EXTRA_MODULE_URL] = set()
|
hass.data[DATA_EXTRA_MODULE_URL] = set()
|
||||||
|
|
||||||
@ -522,7 +504,6 @@ class IndexView(web_urldispatcher.AbstractResource):
|
|||||||
return web.Response(
|
return web.Response(
|
||||||
text=template.render(
|
text=template.render(
|
||||||
theme_color=MANIFEST_JSON["theme_color"],
|
theme_color=MANIFEST_JSON["theme_color"],
|
||||||
extra_urls=hass.data[DATA_EXTRA_HTML_URL],
|
|
||||||
extra_modules=hass.data[DATA_EXTRA_MODULE_URL],
|
extra_modules=hass.data[DATA_EXTRA_MODULE_URL],
|
||||||
extra_js_es5=hass.data[DATA_EXTRA_JS_URL_ES5],
|
extra_js_es5=hass.data[DATA_EXTRA_JS_URL_ES5],
|
||||||
),
|
),
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
"""Register a custom front end panel."""
|
"""Register a custom front end panel."""
|
||||||
import logging
|
import logging
|
||||||
import os
|
|
||||||
|
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
@ -15,7 +14,6 @@ CONF_SIDEBAR_TITLE = "sidebar_title"
|
|||||||
CONF_SIDEBAR_ICON = "sidebar_icon"
|
CONF_SIDEBAR_ICON = "sidebar_icon"
|
||||||
CONF_URL_PATH = "url_path"
|
CONF_URL_PATH = "url_path"
|
||||||
CONF_CONFIG = "config"
|
CONF_CONFIG = "config"
|
||||||
CONF_WEBCOMPONENT_PATH = "webcomponent_path"
|
|
||||||
CONF_JS_URL = "js_url"
|
CONF_JS_URL = "js_url"
|
||||||
CONF_MODULE_URL = "module_url"
|
CONF_MODULE_URL = "module_url"
|
||||||
CONF_EMBED_IFRAME = "embed_iframe"
|
CONF_EMBED_IFRAME = "embed_iframe"
|
||||||
@ -32,55 +30,34 @@ LEGACY_URL = "/api/panel_custom/{}"
|
|||||||
PANEL_DIR = "panels"
|
PANEL_DIR = "panels"
|
||||||
|
|
||||||
|
|
||||||
def url_validator(value):
|
|
||||||
"""Validate required urls are specified."""
|
|
||||||
has_js_url = CONF_JS_URL in value
|
|
||||||
has_html_url = CONF_WEBCOMPONENT_PATH in value
|
|
||||||
has_module_url = CONF_MODULE_URL in value
|
|
||||||
|
|
||||||
if has_html_url and (has_js_url or has_module_url):
|
|
||||||
raise vol.Invalid("You cannot specify other urls besides a webcomponent path")
|
|
||||||
|
|
||||||
return value
|
|
||||||
|
|
||||||
|
|
||||||
CONFIG_SCHEMA = vol.Schema(
|
CONFIG_SCHEMA = vol.Schema(
|
||||||
{
|
{
|
||||||
DOMAIN: vol.All(
|
DOMAIN: vol.All(
|
||||||
cv.ensure_list,
|
cv.ensure_list,
|
||||||
[
|
[
|
||||||
vol.All(
|
vol.Schema(
|
||||||
cv.deprecated(CONF_WEBCOMPONENT_PATH),
|
{
|
||||||
vol.Schema(
|
vol.Required(CONF_COMPONENT_NAME): cv.string,
|
||||||
{
|
vol.Optional(CONF_SIDEBAR_TITLE): cv.string,
|
||||||
vol.Required(CONF_COMPONENT_NAME): cv.string,
|
vol.Optional(CONF_SIDEBAR_ICON, default=DEFAULT_ICON): cv.icon,
|
||||||
vol.Optional(CONF_SIDEBAR_TITLE): cv.string,
|
vol.Optional(CONF_URL_PATH): cv.string,
|
||||||
vol.Optional(
|
vol.Optional(CONF_CONFIG): dict,
|
||||||
CONF_SIDEBAR_ICON, default=DEFAULT_ICON
|
vol.Optional(
|
||||||
): cv.icon,
|
CONF_JS_URL,
|
||||||
vol.Optional(CONF_URL_PATH): cv.string,
|
): cv.string,
|
||||||
vol.Optional(CONF_CONFIG): dict,
|
vol.Optional(
|
||||||
vol.Optional(
|
CONF_MODULE_URL,
|
||||||
CONF_WEBCOMPONENT_PATH,
|
): cv.string,
|
||||||
): cv.string,
|
vol.Optional(
|
||||||
vol.Optional(
|
CONF_EMBED_IFRAME, default=DEFAULT_EMBED_IFRAME
|
||||||
CONF_JS_URL,
|
): cv.boolean,
|
||||||
): cv.string,
|
vol.Optional(
|
||||||
vol.Optional(
|
CONF_TRUST_EXTERNAL_SCRIPT,
|
||||||
CONF_MODULE_URL,
|
default=DEFAULT_TRUST_EXTERNAL,
|
||||||
): cv.string,
|
): cv.boolean,
|
||||||
vol.Optional(
|
vol.Optional(CONF_REQUIRE_ADMIN, default=False): cv.boolean,
|
||||||
CONF_EMBED_IFRAME, default=DEFAULT_EMBED_IFRAME
|
}
|
||||||
): cv.boolean,
|
),
|
||||||
vol.Optional(
|
|
||||||
CONF_TRUST_EXTERNAL_SCRIPT,
|
|
||||||
default=DEFAULT_TRUST_EXTERNAL,
|
|
||||||
): cv.boolean,
|
|
||||||
vol.Optional(CONF_REQUIRE_ADMIN, default=False): cv.boolean,
|
|
||||||
}
|
|
||||||
),
|
|
||||||
url_validator,
|
|
||||||
)
|
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
@ -98,8 +75,6 @@ async def async_register_panel(
|
|||||||
# Title/icon for sidebar
|
# Title/icon for sidebar
|
||||||
sidebar_title=None,
|
sidebar_title=None,
|
||||||
sidebar_icon=None,
|
sidebar_icon=None,
|
||||||
# HTML source of your panel
|
|
||||||
html_url=None,
|
|
||||||
# JS source of your panel
|
# JS source of your panel
|
||||||
js_url=None,
|
js_url=None,
|
||||||
# JS module of your panel
|
# JS module of your panel
|
||||||
@ -114,16 +89,11 @@ async def async_register_panel(
|
|||||||
require_admin=False,
|
require_admin=False,
|
||||||
):
|
):
|
||||||
"""Register a new custom panel."""
|
"""Register a new custom panel."""
|
||||||
if js_url is None and html_url is None and module_url is None:
|
if js_url is None and module_url is None:
|
||||||
raise ValueError("Either js_url, module_url or html_url is required.")
|
raise ValueError("Either js_url, module_url or html_url is required.")
|
||||||
if html_url and (js_url or module_url):
|
|
||||||
raise ValueError("You cannot specify other paths with an HTML url")
|
|
||||||
if config is not None and not isinstance(config, dict):
|
if config is not None and not isinstance(config, dict):
|
||||||
raise ValueError("Config needs to be a dictionary.")
|
raise ValueError("Config needs to be a dictionary.")
|
||||||
|
|
||||||
if html_url:
|
|
||||||
_LOGGER.warning("HTML custom panels have been deprecated")
|
|
||||||
|
|
||||||
custom_panel_config = {
|
custom_panel_config = {
|
||||||
"name": webcomponent_name,
|
"name": webcomponent_name,
|
||||||
"embed_iframe": embed_iframe,
|
"embed_iframe": embed_iframe,
|
||||||
@ -136,9 +106,6 @@ async def async_register_panel(
|
|||||||
if module_url is not None:
|
if module_url is not None:
|
||||||
custom_panel_config["module_url"] = module_url
|
custom_panel_config["module_url"] = module_url
|
||||||
|
|
||||||
if html_url is not None:
|
|
||||||
custom_panel_config["html_url"] = html_url
|
|
||||||
|
|
||||||
if config is not None:
|
if config is not None:
|
||||||
# Make copy because we're mutating it
|
# Make copy because we're mutating it
|
||||||
config = dict(config)
|
config = dict(config)
|
||||||
@ -162,8 +129,6 @@ async def async_setup(hass, config):
|
|||||||
if DOMAIN not in config:
|
if DOMAIN not in config:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
seen = set()
|
|
||||||
|
|
||||||
for panel in config[DOMAIN]:
|
for panel in config[DOMAIN]:
|
||||||
name = panel[CONF_COMPONENT_NAME]
|
name = panel[CONF_COMPONENT_NAME]
|
||||||
|
|
||||||
@ -184,29 +149,6 @@ async def async_setup(hass, config):
|
|||||||
if CONF_MODULE_URL in panel:
|
if CONF_MODULE_URL in panel:
|
||||||
kwargs["module_url"] = panel[CONF_MODULE_URL]
|
kwargs["module_url"] = panel[CONF_MODULE_URL]
|
||||||
|
|
||||||
if CONF_MODULE_URL not in panel and CONF_JS_URL not in panel:
|
|
||||||
if name in seen:
|
|
||||||
_LOGGER.warning(
|
|
||||||
"Got HTML panel with duplicate name %s. Not registering", name
|
|
||||||
)
|
|
||||||
continue
|
|
||||||
|
|
||||||
seen.add(name)
|
|
||||||
panel_path = panel.get(CONF_WEBCOMPONENT_PATH)
|
|
||||||
|
|
||||||
if panel_path is None:
|
|
||||||
panel_path = hass.config.path(PANEL_DIR, f"{name}.html")
|
|
||||||
|
|
||||||
if not await hass.async_add_executor_job(os.path.isfile, panel_path):
|
|
||||||
_LOGGER.error(
|
|
||||||
"Unable to find webcomponent for %s: %s", name, panel_path
|
|
||||||
)
|
|
||||||
continue
|
|
||||||
|
|
||||||
url = LEGACY_URL.format(name)
|
|
||||||
hass.http.register_static_path(url, panel_path)
|
|
||||||
kwargs["html_url"] = url
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
await async_register_panel(hass, **kwargs)
|
await async_register_panel(hass, **kwargs)
|
||||||
except ValueError as err:
|
except ValueError as err:
|
||||||
|
@ -334,14 +334,6 @@ async def test_missing_themes(hass, hass_ws_client):
|
|||||||
assert msg["result"]["themes"] == {}
|
assert msg["result"]["themes"] == {}
|
||||||
|
|
||||||
|
|
||||||
async def test_extra_urls(mock_http_client_with_urls, mock_onboarded):
|
|
||||||
"""Test that extra urls are loaded."""
|
|
||||||
resp = await mock_http_client_with_urls.get("/lovelace?latest")
|
|
||||||
assert resp.status == 200
|
|
||||||
text = await resp.text()
|
|
||||||
assert text.find('href="https://domain.com/my_extra_url.html"') >= 0
|
|
||||||
|
|
||||||
|
|
||||||
async def test_get_panels(hass, hass_ws_client, mock_http_client):
|
async def test_get_panels(hass, hass_ws_client, mock_http_client):
|
||||||
"""Test get_panels command."""
|
"""Test get_panels command."""
|
||||||
events = async_capture_events(hass, EVENT_PANELS_UPDATED)
|
events = async_capture_events(hass, EVENT_PANELS_UPDATED)
|
||||||
|
@ -30,52 +30,6 @@ async def test_webcomponent_custom_path_not_found(hass):
|
|||||||
assert "nice_url" not in panels
|
assert "nice_url" not in panels
|
||||||
|
|
||||||
|
|
||||||
async def test_webcomponent_custom_path(hass, caplog):
|
|
||||||
"""Test if a web component is found in config panels dir."""
|
|
||||||
filename = "mock.file"
|
|
||||||
|
|
||||||
config = {
|
|
||||||
"panel_custom": [
|
|
||||||
{
|
|
||||||
"name": "todo-mvc",
|
|
||||||
"webcomponent_path": filename,
|
|
||||||
"sidebar_title": "Sidebar Title",
|
|
||||||
"sidebar_icon": "mdi:iconicon",
|
|
||||||
"url_path": "nice_url",
|
|
||||||
"config": {"hello": "world"},
|
|
||||||
},
|
|
||||||
{"name": "todo-mvc"},
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
with patch("os.path.isfile", Mock(return_value=True)):
|
|
||||||
with patch("os.access", Mock(return_value=True)):
|
|
||||||
result = await setup.async_setup_component(hass, "panel_custom", config)
|
|
||||||
assert result
|
|
||||||
|
|
||||||
panels = hass.data.get(frontend.DATA_PANELS, [])
|
|
||||||
|
|
||||||
assert panels
|
|
||||||
assert "nice_url" in panels
|
|
||||||
|
|
||||||
panel = panels["nice_url"]
|
|
||||||
|
|
||||||
assert panel.config == {
|
|
||||||
"hello": "world",
|
|
||||||
"_panel_custom": {
|
|
||||||
"html_url": "/api/panel_custom/todo-mvc",
|
|
||||||
"name": "todo-mvc",
|
|
||||||
"embed_iframe": False,
|
|
||||||
"trust_external": False,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
assert panel.frontend_url_path == "nice_url"
|
|
||||||
assert panel.sidebar_icon == "mdi:iconicon"
|
|
||||||
assert panel.sidebar_title == "Sidebar Title"
|
|
||||||
|
|
||||||
assert "Got HTML panel with duplicate name todo-mvc. Not registering" in caplog.text
|
|
||||||
|
|
||||||
|
|
||||||
async def test_js_webcomponent(hass):
|
async def test_js_webcomponent(hass):
|
||||||
"""Test if a web component is found in config panels dir."""
|
"""Test if a web component is found in config panels dir."""
|
||||||
config = {
|
config = {
|
||||||
@ -188,31 +142,6 @@ async def test_latest_and_es5_build(hass):
|
|||||||
assert panel.frontend_url_path == "nice_url"
|
assert panel.frontend_url_path == "nice_url"
|
||||||
|
|
||||||
|
|
||||||
async def test_url_option_conflict(hass):
|
|
||||||
"""Test config with multiple url options."""
|
|
||||||
to_try = [
|
|
||||||
{
|
|
||||||
"panel_custom": {
|
|
||||||
"name": "todo-mvc",
|
|
||||||
"webcomponent_path": "/local/bla.html",
|
|
||||||
"js_url": "/local/bla.js",
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"panel_custom": {
|
|
||||||
"name": "todo-mvc",
|
|
||||||
"webcomponent_path": "/local/bla.html",
|
|
||||||
"module_url": "/local/bla.js",
|
|
||||||
"js_url": "/local/bla.js",
|
|
||||||
}
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
for config in to_try:
|
|
||||||
result = await setup.async_setup_component(hass, "panel_custom", config)
|
|
||||||
assert not result
|
|
||||||
|
|
||||||
|
|
||||||
async def test_url_path_conflict(hass):
|
async def test_url_path_conflict(hass):
|
||||||
"""Test config with overlapping url path."""
|
"""Test config with overlapping url path."""
|
||||||
assert await setup.async_setup_component(
|
assert await setup.async_setup_component(
|
||||||
|
Loading…
x
Reference in New Issue
Block a user