diff --git a/homeassistant/components/frontend/__init__.py b/homeassistant/components/frontend/__init__.py index 5dad77f64ce..3f2f9ded22a 100644 --- a/homeassistant/components/frontend/__init__.py +++ b/homeassistant/components/frontend/__init__.py @@ -5,7 +5,6 @@ For more details about this component, please refer to the documentation at https://home-assistant.io/components/frontend/ """ import asyncio -import hashlib import json import logging import os @@ -30,8 +29,6 @@ REQUIREMENTS = ['home-assistant-frontend==20180603.0'] DOMAIN = 'frontend' DEPENDENCIES = ['api', 'websocket_api', 'http', 'system_log'] -URL_PANEL_COMPONENT_FP = '/frontend/panels/{}-{}.html' - CONF_THEMES = 'themes' CONF_EXTRA_HTML_URL = 'extra_html_url' CONF_EXTRA_HTML_URL_ES5 = 'extra_html_url_es5' @@ -101,7 +98,7 @@ SCHEMA_GET_PANELS = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend({ }) -class AbstractPanel: +class Panel: """Abstract class for panels.""" # Name of the webcomponent @@ -113,44 +110,12 @@ class AbstractPanel: # Title to show in the sidebar (optional) sidebar_title = None - # Url to the webcomponent (depending on JS version) - webcomponent_url_es5 = None - webcomponent_url_latest = None - # Url to show the panel in the frontend frontend_url_path = None # Config to pass to the webcomponent config = None - @asyncio.coroutine - def async_register(self, hass): - """Register panel with HASS.""" - panels = hass.data.get(DATA_PANELS) - if panels is None: - panels = hass.data[DATA_PANELS] = {} - - if self.frontend_url_path in panels: - _LOGGER.warning("Overwriting component %s", self.frontend_url_path) - - if DATA_FINALIZE_PANEL in hass.data: - yield from hass.data[DATA_FINALIZE_PANEL](self) - - panels[self.frontend_url_path] = self - - @callback - def async_register_index_routes(self, router, index_view): - """Register routes for panel to be served by index view.""" - router.add_route( - 'get', '/{}'.format(self.frontend_url_path), index_view.get) - router.add_route( - 'get', '/{}/{{extra:.+}}'.format(self.frontend_url_path), - index_view.get) - - -class BuiltInPanel(AbstractPanel): - """Panel that is part of hass_frontend.""" - def __init__(self, component_name, sidebar_title, sidebar_icon, frontend_url_path, config): """Initialize a built-in panel.""" @@ -160,6 +125,16 @@ class BuiltInPanel(AbstractPanel): self.frontend_url_path = frontend_url_path or component_name self.config = config + @callback + def async_register_index_routes(self, router, index_view): + """Register routes for panel to be served by index view.""" + router.add_route( + 'get', '/{}'.format(self.frontend_url_path), index_view.get) + router.add_route( + 'get', '/{}/{{extra:.+}}'.format(self.frontend_url_path), + index_view.get) + + @callback def to_response(self, hass, request): """Panel as dictionary.""" return { @@ -171,95 +146,25 @@ class BuiltInPanel(AbstractPanel): } -class ExternalPanel(AbstractPanel): - """Panel that is added by a custom component.""" - - REGISTERED_COMPONENTS = set() - - def __init__(self, component_name, path, md5, sidebar_title, sidebar_icon, - frontend_url_path, config): - """Initialize an external panel.""" - self.component_name = component_name - self.path = path - self.md5 = md5 - self.sidebar_title = sidebar_title - self.sidebar_icon = sidebar_icon - self.frontend_url_path = frontend_url_path or component_name - self.config = config - - @asyncio.coroutine - def async_finalize(self, hass, frontend_repository_path): - """Finalize this panel for usage. - - frontend_repository_path is set, will be prepended to path of built-in - components. - """ - try: - if self.md5 is None: - self.md5 = yield from hass.async_add_job( - _fingerprint, self.path) - except OSError: - _LOGGER.error('Cannot find or access %s at %s', - self.component_name, self.path) - hass.data[DATA_PANELS].pop(self.frontend_url_path) - return - - self.webcomponent_url_es5 = self.webcomponent_url_latest = \ - URL_PANEL_COMPONENT_FP.format(self.component_name, self.md5) - - if self.component_name not in self.REGISTERED_COMPONENTS: - hass.http.register_static_path( - self.webcomponent_url_latest, self.path, - # if path is None, we're in prod mode, so cache static assets - frontend_repository_path is None) - self.REGISTERED_COMPONENTS.add(self.component_name) - - def to_response(self, hass, request): - """Panel as dictionary.""" - result = { - 'component_name': self.component_name, - 'icon': self.sidebar_icon, - 'title': self.sidebar_title, - 'url_path': self.frontend_url_path, - 'config': self.config, - } - if _is_latest(hass.data[DATA_JS_VERSION], request): - result['url'] = self.webcomponent_url_latest - else: - result['url'] = self.webcomponent_url_es5 - return result - - @bind_hass -@asyncio.coroutine -def async_register_built_in_panel(hass, component_name, sidebar_title=None, - sidebar_icon=None, frontend_url_path=None, - config=None): +async def async_register_built_in_panel(hass, component_name, + sidebar_title=None, sidebar_icon=None, + frontend_url_path=None, config=None): """Register a built-in panel.""" - panel = BuiltInPanel(component_name, sidebar_title, sidebar_icon, - frontend_url_path, config) - yield from panel.async_register(hass) + panel = Panel(component_name, sidebar_title, sidebar_icon, + frontend_url_path, config) + panels = hass.data.get(DATA_PANELS) + if panels is None: + panels = hass.data[DATA_PANELS] = {} -@bind_hass -@asyncio.coroutine -def async_register_panel(hass, component_name, path, md5=None, - sidebar_title=None, sidebar_icon=None, - frontend_url_path=None, config=None): - """Register a panel for the frontend. + if panel.frontend_url_path in panels: + _LOGGER.warning("Overwriting component %s", panel.frontend_url_path) - component_name: name of the web component - path: path to the HTML of the web component - (required unless url is provided) - md5: the md5 hash of the web component (for versioning in URL, optional) - sidebar_title: title to show in the sidebar (optional) - sidebar_icon: icon to show next to title in sidebar (optional) - url_path: name to use in the URL (defaults to component_name) - config: config to be passed into the web component - """ - panel = ExternalPanel(component_name, path, md5, sidebar_title, - sidebar_icon, frontend_url_path, config) - yield from panel.async_register(hass) + if DATA_FINALIZE_PANEL in hass.data: + hass.data[DATA_FINALIZE_PANEL](panel) + + panels[panel.frontend_url_path] = panel @bind_hass @@ -278,11 +183,10 @@ def add_manifest_json_key(key, val): MANIFEST_JSON[key] = val -@asyncio.coroutine -def async_setup(hass, config): +async def async_setup(hass, config): """Set up the serving of the frontend.""" if list(hass.auth.async_auth_providers): - client = yield from hass.auth.async_create_client( + client = await hass.auth.async_create_client( 'Home Assistant Frontend', redirect_uris=['/'], no_secret=True, @@ -331,24 +235,22 @@ def async_setup(hass, config): index_view = IndexView(repo_path, js_version, client) hass.http.register_view(index_view) - async def finalize_panel(panel): + @callback + def async_finalize_panel(panel): """Finalize setup of a panel.""" - if hasattr(panel, 'async_finalize'): - await panel.async_finalize(hass, repo_path) panel.async_register_index_routes(hass.http.app.router, index_view) - yield from asyncio.wait([ + await asyncio.wait([ async_register_built_in_panel(hass, panel) for panel in ('dev-event', 'dev-info', 'dev-service', 'dev-state', 'dev-template', 'dev-mqtt', 'kiosk')], loop=hass.loop) - hass.data[DATA_FINALIZE_PANEL] = finalize_panel + hass.data[DATA_FINALIZE_PANEL] = async_finalize_panel # Finalize registration of panels that registered before frontend was setup # This includes the built-in panels from line above. - yield from asyncio.wait( - [finalize_panel(panel) for panel in hass.data[DATA_PANELS].values()], - loop=hass.loop) + for panel in hass.data[DATA_PANELS].values(): + async_finalize_panel(panel) if DATA_EXTRA_HTML_URL not in hass.data: hass.data[DATA_EXTRA_HTML_URL] = set() @@ -456,38 +358,23 @@ class IndexView(HomeAssistantView): return tpl - @asyncio.coroutine - def get(self, request, extra=None): + async def get(self, request, extra=None): """Serve the index view.""" hass = request.app['hass'] latest = self.repo_path is not None or \ _is_latest(self.js_option, request) - if request.path == '/': - panel = 'states' - else: - panel = request.path.split('/')[1] - - if panel == 'states': - panel_url = '' - elif latest: - panel_url = hass.data[DATA_PANELS][panel].webcomponent_url_latest - else: - panel_url = hass.data[DATA_PANELS][panel].webcomponent_url_es5 - no_auth = '1' if hass.config.api.api_password and not request[KEY_AUTHENTICATED]: # do not try to auto connect on load no_auth = '0' - template = yield from hass.async_add_job(self.get_template, latest) + template = await hass.async_add_job(self.get_template, latest) extra_key = DATA_EXTRA_HTML_URL if latest else DATA_EXTRA_HTML_URL_ES5 template_params = dict( no_auth=no_auth, - panel_url=panel_url, - panels=hass.data[DATA_PANELS], theme_color=MANIFEST_JSON['theme_color'], extra_urls=hass.data[extra_key], ) @@ -506,7 +393,7 @@ class ManifestJSONView(HomeAssistantView): url = '/manifest.json' name = 'manifestjson' - @asyncio.coroutine + @callback def get(self, request): # pylint: disable=no-self-use """Return the manifest.json.""" msg = json.dumps(MANIFEST_JSON, sort_keys=True) @@ -537,23 +424,16 @@ class TranslationsView(HomeAssistantView): url = '/api/translations/{language}' name = 'api:translations' - @asyncio.coroutine - def get(self, request, language): + async def get(self, request, language): """Return translations.""" hass = request.app['hass'] - resources = yield from async_get_translations(hass, language) + resources = await async_get_translations(hass, language) return self.json({ 'resources': resources, }) -def _fingerprint(path): - """Fingerprint a file.""" - with open(path) as fil: - return hashlib.md5(fil.read().encode('utf-8')).hexdigest() - - def _is_latest(js_option, request): """ Return whether we should serve latest untranspiled code. diff --git a/homeassistant/components/frontend/www_static/images/logo_tellduslive.png b/homeassistant/components/frontend/www_static/images/logo_tellduslive.png deleted file mode 100644 index 7ea78f8ef3a..00000000000 Binary files a/homeassistant/components/frontend/www_static/images/logo_tellduslive.png and /dev/null differ diff --git a/tests/components/test_frontend.py b/tests/components/test_frontend.py index 657497b868b..2f83d923e2b 100644 --- a/tests/components/test_frontend.py +++ b/tests/components/test_frontend.py @@ -8,7 +8,7 @@ import pytest from homeassistant.setup import async_setup_component from homeassistant.components.frontend import ( DOMAIN, CONF_JS_VERSION, CONF_THEMES, CONF_EXTRA_HTML_URL, - CONF_EXTRA_HTML_URL_ES5, DATA_PANELS) + CONF_EXTRA_HTML_URL_ES5) from homeassistant.components import websocket_api as wapi @@ -183,15 +183,6 @@ def test_extra_urls_es5(mock_http_client_with_urls): assert text.find('href="https://domain.com/my_extra_url_es5.html"') >= 0 -@asyncio.coroutine -def test_panel_without_path(hass): - """Test panel registration without file path.""" - yield from hass.components.frontend.async_register_panel( - 'test_component', 'nonexistant_file') - yield from async_setup_component(hass, 'frontend', {}) - assert 'test_component' not in hass.data[DATA_PANELS] - - async def test_get_panels(hass, hass_ws_client): """Test get_panels command.""" await async_setup_component(hass, 'frontend')