mirror of
https://github.com/home-assistant/core.git
synced 2025-07-23 13:17:32 +00:00
Further cleanup frontend (#14805)
* Remove registering panels * Remove unused image * Lint
This commit is contained in:
parent
ad9621ebe5
commit
b3b4f7468d
@ -5,7 +5,6 @@ For more details about this component, please refer to the documentation at
|
|||||||
https://home-assistant.io/components/frontend/
|
https://home-assistant.io/components/frontend/
|
||||||
"""
|
"""
|
||||||
import asyncio
|
import asyncio
|
||||||
import hashlib
|
|
||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
@ -30,8 +29,6 @@ REQUIREMENTS = ['home-assistant-frontend==20180603.0']
|
|||||||
DOMAIN = 'frontend'
|
DOMAIN = 'frontend'
|
||||||
DEPENDENCIES = ['api', 'websocket_api', 'http', 'system_log']
|
DEPENDENCIES = ['api', 'websocket_api', 'http', 'system_log']
|
||||||
|
|
||||||
URL_PANEL_COMPONENT_FP = '/frontend/panels/{}-{}.html'
|
|
||||||
|
|
||||||
CONF_THEMES = 'themes'
|
CONF_THEMES = 'themes'
|
||||||
CONF_EXTRA_HTML_URL = 'extra_html_url'
|
CONF_EXTRA_HTML_URL = 'extra_html_url'
|
||||||
CONF_EXTRA_HTML_URL_ES5 = 'extra_html_url_es5'
|
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."""
|
"""Abstract class for panels."""
|
||||||
|
|
||||||
# Name of the webcomponent
|
# Name of the webcomponent
|
||||||
@ -113,44 +110,12 @@ class AbstractPanel:
|
|||||||
# Title to show in the sidebar (optional)
|
# Title to show in the sidebar (optional)
|
||||||
sidebar_title = None
|
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
|
# Url to show the panel in the frontend
|
||||||
frontend_url_path = None
|
frontend_url_path = None
|
||||||
|
|
||||||
# Config to pass to the webcomponent
|
# Config to pass to the webcomponent
|
||||||
config = None
|
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,
|
def __init__(self, component_name, sidebar_title, sidebar_icon,
|
||||||
frontend_url_path, config):
|
frontend_url_path, config):
|
||||||
"""Initialize a built-in panel."""
|
"""Initialize a built-in panel."""
|
||||||
@ -160,6 +125,16 @@ class BuiltInPanel(AbstractPanel):
|
|||||||
self.frontend_url_path = frontend_url_path or component_name
|
self.frontend_url_path = frontend_url_path or component_name
|
||||||
self.config = config
|
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):
|
def to_response(self, hass, request):
|
||||||
"""Panel as dictionary."""
|
"""Panel as dictionary."""
|
||||||
return {
|
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
|
@bind_hass
|
||||||
@asyncio.coroutine
|
async def async_register_built_in_panel(hass, component_name,
|
||||||
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)
|
|
||||||
|
|
||||||
|
|
||||||
@bind_hass
|
|
||||||
@asyncio.coroutine
|
|
||||||
def async_register_panel(hass, component_name, path, md5=None,
|
|
||||||
sidebar_title=None, sidebar_icon=None,
|
sidebar_title=None, sidebar_icon=None,
|
||||||
frontend_url_path=None, config=None):
|
frontend_url_path=None, config=None):
|
||||||
"""Register a panel for the frontend.
|
"""Register a built-in panel."""
|
||||||
|
panel = Panel(component_name, sidebar_title, sidebar_icon,
|
||||||
|
frontend_url_path, config)
|
||||||
|
|
||||||
component_name: name of the web component
|
panels = hass.data.get(DATA_PANELS)
|
||||||
path: path to the HTML of the web component
|
if panels is None:
|
||||||
(required unless url is provided)
|
panels = hass.data[DATA_PANELS] = {}
|
||||||
md5: the md5 hash of the web component (for versioning in URL, optional)
|
|
||||||
sidebar_title: title to show in the sidebar (optional)
|
if panel.frontend_url_path in panels:
|
||||||
sidebar_icon: icon to show next to title in sidebar (optional)
|
_LOGGER.warning("Overwriting component %s", panel.frontend_url_path)
|
||||||
url_path: name to use in the URL (defaults to component_name)
|
|
||||||
config: config to be passed into the web component
|
if DATA_FINALIZE_PANEL in hass.data:
|
||||||
"""
|
hass.data[DATA_FINALIZE_PANEL](panel)
|
||||||
panel = ExternalPanel(component_name, path, md5, sidebar_title,
|
|
||||||
sidebar_icon, frontend_url_path, config)
|
panels[panel.frontend_url_path] = panel
|
||||||
yield from panel.async_register(hass)
|
|
||||||
|
|
||||||
|
|
||||||
@bind_hass
|
@bind_hass
|
||||||
@ -278,11 +183,10 @@ def add_manifest_json_key(key, val):
|
|||||||
MANIFEST_JSON[key] = val
|
MANIFEST_JSON[key] = val
|
||||||
|
|
||||||
|
|
||||||
@asyncio.coroutine
|
async def async_setup(hass, config):
|
||||||
def async_setup(hass, config):
|
|
||||||
"""Set up the serving of the frontend."""
|
"""Set up the serving of the frontend."""
|
||||||
if list(hass.auth.async_auth_providers):
|
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',
|
'Home Assistant Frontend',
|
||||||
redirect_uris=['/'],
|
redirect_uris=['/'],
|
||||||
no_secret=True,
|
no_secret=True,
|
||||||
@ -331,24 +235,22 @@ def async_setup(hass, config):
|
|||||||
index_view = IndexView(repo_path, js_version, client)
|
index_view = IndexView(repo_path, js_version, client)
|
||||||
hass.http.register_view(index_view)
|
hass.http.register_view(index_view)
|
||||||
|
|
||||||
async def finalize_panel(panel):
|
@callback
|
||||||
|
def async_finalize_panel(panel):
|
||||||
"""Finalize setup of a 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)
|
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)
|
async_register_built_in_panel(hass, panel)
|
||||||
for panel in ('dev-event', 'dev-info', 'dev-service', 'dev-state',
|
for panel in ('dev-event', 'dev-info', 'dev-service', 'dev-state',
|
||||||
'dev-template', 'dev-mqtt', 'kiosk')], loop=hass.loop)
|
'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
|
# Finalize registration of panels that registered before frontend was setup
|
||||||
# This includes the built-in panels from line above.
|
# This includes the built-in panels from line above.
|
||||||
yield from asyncio.wait(
|
for panel in hass.data[DATA_PANELS].values():
|
||||||
[finalize_panel(panel) for panel in hass.data[DATA_PANELS].values()],
|
async_finalize_panel(panel)
|
||||||
loop=hass.loop)
|
|
||||||
|
|
||||||
if DATA_EXTRA_HTML_URL not in hass.data:
|
if DATA_EXTRA_HTML_URL not in hass.data:
|
||||||
hass.data[DATA_EXTRA_HTML_URL] = set()
|
hass.data[DATA_EXTRA_HTML_URL] = set()
|
||||||
@ -456,38 +358,23 @@ class IndexView(HomeAssistantView):
|
|||||||
|
|
||||||
return tpl
|
return tpl
|
||||||
|
|
||||||
@asyncio.coroutine
|
async def get(self, request, extra=None):
|
||||||
def get(self, request, extra=None):
|
|
||||||
"""Serve the index view."""
|
"""Serve the index view."""
|
||||||
hass = request.app['hass']
|
hass = request.app['hass']
|
||||||
latest = self.repo_path is not None or \
|
latest = self.repo_path is not None or \
|
||||||
_is_latest(self.js_option, request)
|
_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'
|
no_auth = '1'
|
||||||
if hass.config.api.api_password and not request[KEY_AUTHENTICATED]:
|
if hass.config.api.api_password and not request[KEY_AUTHENTICATED]:
|
||||||
# do not try to auto connect on load
|
# do not try to auto connect on load
|
||||||
no_auth = '0'
|
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
|
extra_key = DATA_EXTRA_HTML_URL if latest else DATA_EXTRA_HTML_URL_ES5
|
||||||
|
|
||||||
template_params = dict(
|
template_params = dict(
|
||||||
no_auth=no_auth,
|
no_auth=no_auth,
|
||||||
panel_url=panel_url,
|
|
||||||
panels=hass.data[DATA_PANELS],
|
|
||||||
theme_color=MANIFEST_JSON['theme_color'],
|
theme_color=MANIFEST_JSON['theme_color'],
|
||||||
extra_urls=hass.data[extra_key],
|
extra_urls=hass.data[extra_key],
|
||||||
)
|
)
|
||||||
@ -506,7 +393,7 @@ class ManifestJSONView(HomeAssistantView):
|
|||||||
url = '/manifest.json'
|
url = '/manifest.json'
|
||||||
name = 'manifestjson'
|
name = 'manifestjson'
|
||||||
|
|
||||||
@asyncio.coroutine
|
@callback
|
||||||
def get(self, request): # pylint: disable=no-self-use
|
def get(self, request): # pylint: disable=no-self-use
|
||||||
"""Return the manifest.json."""
|
"""Return the manifest.json."""
|
||||||
msg = json.dumps(MANIFEST_JSON, sort_keys=True)
|
msg = json.dumps(MANIFEST_JSON, sort_keys=True)
|
||||||
@ -537,23 +424,16 @@ class TranslationsView(HomeAssistantView):
|
|||||||
url = '/api/translations/{language}'
|
url = '/api/translations/{language}'
|
||||||
name = 'api:translations'
|
name = 'api:translations'
|
||||||
|
|
||||||
@asyncio.coroutine
|
async def get(self, request, language):
|
||||||
def get(self, request, language):
|
|
||||||
"""Return translations."""
|
"""Return translations."""
|
||||||
hass = request.app['hass']
|
hass = request.app['hass']
|
||||||
|
|
||||||
resources = yield from async_get_translations(hass, language)
|
resources = await async_get_translations(hass, language)
|
||||||
return self.json({
|
return self.json({
|
||||||
'resources': resources,
|
'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):
|
def _is_latest(js_option, request):
|
||||||
"""
|
"""
|
||||||
Return whether we should serve latest untranspiled code.
|
Return whether we should serve latest untranspiled code.
|
||||||
|
Binary file not shown.
Before Width: | Height: | Size: 7.6 KiB |
@ -8,7 +8,7 @@ import pytest
|
|||||||
from homeassistant.setup import async_setup_component
|
from homeassistant.setup import async_setup_component
|
||||||
from homeassistant.components.frontend import (
|
from homeassistant.components.frontend import (
|
||||||
DOMAIN, CONF_JS_VERSION, CONF_THEMES, CONF_EXTRA_HTML_URL,
|
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
|
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
|
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):
|
async def test_get_panels(hass, hass_ws_client):
|
||||||
"""Test get_panels command."""
|
"""Test get_panels command."""
|
||||||
await async_setup_component(hass, 'frontend')
|
await async_setup_component(hass, 'frontend')
|
||||||
|
Loading…
x
Reference in New Issue
Block a user