diff --git a/homeassistant/components/frontend/__init__.py b/homeassistant/components/frontend/__init__.py index 2f6ac91f9c3..1f6cdc81a81 100644 --- a/homeassistant/components/frontend/__init__.py +++ b/homeassistant/components/frontend/__init__.py @@ -1,37 +1,112 @@ """Handle the frontend for Home Assistant.""" +import logging import os +from homeassistant.const import EVENT_HOMEASSISTANT_START from homeassistant.components import api from homeassistant.components.http import HomeAssistantView -from . import version, mdi_version +from .version import FINGERPRINTS DOMAIN = 'frontend' DEPENDENCIES = ['api'] +PANELS = {} +URL_PANEL_COMPONENT = '/frontend/panels/{}.html' +URL_PANEL_COMPONENT_FP = '/frontend/panels/{}-{}.html' +STATIC_PATH = os.path.join(os.path.dirname(__file__), 'www_static') +_LOGGER = logging.getLogger(__name__) + + +def register_built_in_panel(hass, component_name, title=None, icon=None, + url_name=None, config=None): + """Register a built-in panel.""" + + path = 'panels/ha-panel-{}.html'.format(component_name) + + register_panel(hass, component_name, os.path.join(STATIC_PATH, path), + FINGERPRINTS[path], title, icon, url_name, config) + + +def register_panel(hass, component_name, path, md5, title=None, icon=None, + url_name=None, config=None): + """Register a panel for the frontend. + + component_name: name of the web component + path: path to the HTML of the web component + md5: the md5 hash of the web component (for versioning) + title: title to show in the sidebar (optional) + icon: icon to show next to title in sidebar (optional) + url_name: name to use in the url (defaults to component_name) + config: config to be passed into the web component + + Warning: this API will probably change. Use at own risk. + """ + if url_name is None: + url_name = component_name + + if url_name in PANELS: + _LOGGER.warning('Overwriting component %s', url_name) + if not os.path.isfile(path): + _LOGGER.warning('Panel %s component does not exist: %s', + component_name, path) + + data = { + 'url_name': url_name, + 'component_name': component_name, + } + + if title: + data['title'] = title + if icon: + data['icon'] = icon + if config is not None: + data['config'] = config + + if hass.wsgi.development: + data['url'] = ('/static/home-assistant-polymer/panels/' + '{0}/ha-panel-{0}.html'.format(component_name)) + else: + url = URL_PANEL_COMPONENT.format(component_name) + fprinted_url = URL_PANEL_COMPONENT_FP.format(component_name, md5) + hass.wsgi.register_static_path(url, path) + data['url'] = fprinted_url + + PANELS[url_name] = data + + # TODO register / to index view. def setup(hass, config): """Setup serving the frontend.""" - hass.wsgi.register_view(IndexView) hass.wsgi.register_view(BootstrapView) - www_static_path = os.path.join(os.path.dirname(__file__), 'www_static') if hass.wsgi.development: sw_path = "home-assistant-polymer/build/service_worker.js" else: sw_path = "service_worker.js" - hass.wsgi.register_static_path( - "/service_worker.js", - os.path.join(www_static_path, sw_path), - 0 - ) - hass.wsgi.register_static_path( - "/robots.txt", - os.path.join(www_static_path, "robots.txt") - ) - hass.wsgi.register_static_path("/static", www_static_path) + hass.wsgi.register_static_path("/service_worker.js", + os.path.join(STATIC_PATH, sw_path), 0) + hass.wsgi.register_static_path("/robots.txt", + os.path.join(STATIC_PATH, "robots.txt")) + hass.wsgi.register_static_path("/static", STATIC_PATH) hass.wsgi.register_static_path("/local", hass.config.path('www')) + register_built_in_panel(hass, 'map', 'Map', 'mdi:account-location') + + for panel in ('dev-event', 'dev-info', 'dev-service', 'dev-state', + 'dev-template'): + register_built_in_panel(hass, panel) + + def register_frontend_index(event): + """Register the frontend index urls. + + Done when Home Assistant is started so that all panels are known. + """ + hass.wsgi.register_view(IndexView( + hass, ['/{}'.format(name) for name in PANELS])) + + hass.bus.listen_once(EVENT_HOMEASSISTANT_START, register_frontend_index) + return True @@ -48,6 +123,7 @@ class BootstrapView(HomeAssistantView): 'states': self.hass.states.all(), 'events': api.events_json(self.hass), 'services': api.services_json(self.hass), + 'panels': PANELS, }) @@ -57,16 +133,15 @@ class IndexView(HomeAssistantView): url = '/' name = "frontend:index" requires_auth = False - extra_urls = ['/logbook', '/history', '/map', '/devService', '/devState', - '/devEvent', '/devInfo', '/devTemplate', - '/states', '/states/'] + extra_urls = ['/states', '/states/'] - def __init__(self, hass): + def __init__(self, hass, extra_urls): """Initialize the frontend view.""" super().__init__(hass) from jinja2 import FileSystemLoader, Environment + self.extra_urls = self.extra_urls + extra_urls self.templates = Environment( loader=FileSystemLoader( os.path.join(os.path.dirname(__file__), 'templates/') @@ -76,32 +151,24 @@ class IndexView(HomeAssistantView): def get(self, request, entity_id=None): """Serve the index view.""" if self.hass.wsgi.development: - core_url = '/static/home-assistant-polymer/build/_core_compiled.js' + core_url = '/static/home-assistant-polymer/build/core.js' ui_url = '/static/home-assistant-polymer/src/home-assistant.html' - map_url = ('/static/home-assistant-polymer/src/layouts/' - 'partial-map.html') - dev_url = ('/static/home-assistant-polymer/src/entry-points/' - 'dev-tools.html') else: - core_url = '/static/core-{}.js'.format(version.CORE) - ui_url = '/static/frontend-{}.html'.format(version.UI) - map_url = '/static/partial-map-{}.html'.format(version.MAP) - dev_url = '/static/dev-tools-{}.html'.format(version.DEV) + core_url = '/static/core-{}.js'.format( + FINGERPRINTS['core.js']) + ui_url = '/static/frontend-{}.html'.format( + FINGERPRINTS['frontend.html']) # auto login if no password was set - if self.hass.config.api.api_password is None: - auth = 'true' - else: - auth = 'false' - - icons_url = '/static/mdi-{}.html'.format(mdi_version.VERSION) + no_auth = 'false' if self.hass.config.api.api_password else 'true' + icons_url = '/static/mdi-{}.html'.format(FINGERPRINTS['mdi.html']) template = self.templates.get_template('index.html') # pylint is wrong # pylint: disable=no-member resp = template.render( - core_url=core_url, ui_url=ui_url, map_url=map_url, auth=auth, - dev_url=dev_url, icons_url=icons_url, icons=mdi_version.VERSION) + core_url=core_url, ui_url=ui_url, no_auth=no_auth, + icons_url=icons_url, icons=FINGERPRINTS['mdi.html']) return self.Response(resp, mimetype='text/html') diff --git a/homeassistant/components/frontend/mdi_version.py b/homeassistant/components/frontend/mdi_version.py deleted file mode 100644 index baf3042931d..00000000000 --- a/homeassistant/components/frontend/mdi_version.py +++ /dev/null @@ -1,2 +0,0 @@ -"""DO NOT MODIFY. Auto-generated by update_mdi script.""" -VERSION = "758957b7ea989d6beca60e218ea7f7dd" diff --git a/homeassistant/components/frontend/templates/index.html b/homeassistant/components/frontend/templates/index.html index dddf826018a..2165e8a0d22 100644 --- a/homeassistant/components/frontend/templates/index.html +++ b/homeassistant/components/frontend/templates/index.html @@ -5,14 +5,14 @@ Home Assistant - + + href='/static/icons/favicon-apple-180x180.png'> - - - - + + + + @@ -65,16 +65,12 @@ .getElementById('ha-init-skeleton') .classList.add('error'); }; - window.noAuth = {{ auth }}; - window.deferredLoading = { - map: '{{ map_url }}', - dev: '{{ dev_url }}', - }; + window.noAuth = {{ no_auth }};
- + Home Assistant had trouble
connecting to the server.

TRY AGAIN
diff --git a/homeassistant/components/frontend/version.py b/homeassistant/components/frontend/version.py index f94af466e85..a61befd97dd 100644 --- a/homeassistant/components/frontend/version.py +++ b/homeassistant/components/frontend/version.py @@ -1,5 +1,24 @@ +<<<<<<< HEAD """DO NOT MODIFY. Auto-generated by build_frontend script.""" CORE = "7d80cc0e4dea6bc20fa2889be0b3cd15" UI = "805f8dda70419b26daabc8e8f625127f" MAP = "c922306de24140afd14f857f927bf8f0" DEV = "b7079ac3121b95b9856e5603a6d8a263" +======= +"""DO NOT MODIFY. Auto-generated by script/fingerprint_frontend.""" + +FINGERPRINTS = { + "core.js": "4783ccdb2f15d3a63fcab9be411629b7", + "frontend.html": "6c50bcdd8c8b7d840bc2cdef02e9ee39", + "mdi.html": "a7fa9237b7da93951076b4fe26cb8cd2", + "panels/ha-panel-dev-event.html": "f1f47bf3f0e305f855a99dd1ee788045", + "panels/ha-panel-dev-info.html": "50a7817f60675feef3e4c9aa9a043fe1", + "panels/ha-panel-dev-service.html": "d507e0018faf73d58a1fdeb2a0368505", + "panels/ha-panel-dev-state.html": "6a4418826419f235fd9fcc5e952e858c", + "panels/ha-panel-dev-template.html": "cc8917fdad5a4fc81cc1d4104ea0d2dc", + "panels/ha-panel-history.html": "999ecb591df76d6a4aba1fe84e04baf1", + "panels/ha-panel-iframe.html": "f4aaaf31321cd8bfb57755c24af7fc31", + "panels/ha-panel-logbook.html": "6dde7050246875774ec9fce60df05442", + "panels/ha-panel-map.html": "d2cf412d52f43431307bbc2e216be9c9" +} +>>>>>>> Add support for dynamic frontend panels diff --git a/homeassistant/components/frontend/www_static/favicon-1024x1024.png b/homeassistant/components/frontend/www_static/icons/favicon-1024x1024.png similarity index 100% rename from homeassistant/components/frontend/www_static/favicon-1024x1024.png rename to homeassistant/components/frontend/www_static/icons/favicon-1024x1024.png diff --git a/homeassistant/components/frontend/www_static/favicon-192x192.png b/homeassistant/components/frontend/www_static/icons/favicon-192x192.png similarity index 100% rename from homeassistant/components/frontend/www_static/favicon-192x192.png rename to homeassistant/components/frontend/www_static/icons/favicon-192x192.png diff --git a/homeassistant/components/frontend/www_static/favicon-384x384.png b/homeassistant/components/frontend/www_static/icons/favicon-384x384.png similarity index 100% rename from homeassistant/components/frontend/www_static/favicon-384x384.png rename to homeassistant/components/frontend/www_static/icons/favicon-384x384.png diff --git a/homeassistant/components/frontend/www_static/favicon-512x512.png b/homeassistant/components/frontend/www_static/icons/favicon-512x512.png similarity index 100% rename from homeassistant/components/frontend/www_static/favicon-512x512.png rename to homeassistant/components/frontend/www_static/icons/favicon-512x512.png diff --git a/homeassistant/components/frontend/www_static/favicon-apple-180x180.png b/homeassistant/components/frontend/www_static/icons/favicon-apple-180x180.png similarity index 100% rename from homeassistant/components/frontend/www_static/favicon-apple-180x180.png rename to homeassistant/components/frontend/www_static/icons/favicon-apple-180x180.png diff --git a/homeassistant/components/frontend/www_static/favicon.ico b/homeassistant/components/frontend/www_static/icons/favicon.ico similarity index 100% rename from homeassistant/components/frontend/www_static/favicon.ico rename to homeassistant/components/frontend/www_static/icons/favicon.ico diff --git a/homeassistant/components/frontend/www_static/tile-win-150x150.png b/homeassistant/components/frontend/www_static/icons/tile-win-150x150.png similarity index 100% rename from homeassistant/components/frontend/www_static/tile-win-150x150.png rename to homeassistant/components/frontend/www_static/icons/tile-win-150x150.png diff --git a/homeassistant/components/frontend/www_static/tile-win-310x150.png b/homeassistant/components/frontend/www_static/icons/tile-win-310x150.png similarity index 100% rename from homeassistant/components/frontend/www_static/tile-win-310x150.png rename to homeassistant/components/frontend/www_static/icons/tile-win-310x150.png diff --git a/homeassistant/components/frontend/www_static/tile-win-310x310.png b/homeassistant/components/frontend/www_static/icons/tile-win-310x310.png similarity index 100% rename from homeassistant/components/frontend/www_static/tile-win-310x310.png rename to homeassistant/components/frontend/www_static/icons/tile-win-310x310.png diff --git a/homeassistant/components/frontend/www_static/tile-win-70x70.png b/homeassistant/components/frontend/www_static/icons/tile-win-70x70.png similarity index 100% rename from homeassistant/components/frontend/www_static/tile-win-70x70.png rename to homeassistant/components/frontend/www_static/icons/tile-win-70x70.png diff --git a/homeassistant/components/frontend/www_static/manifest.json b/homeassistant/components/frontend/www_static/manifest.json index aa09fb0e037..4cd13ad5470 100644 --- a/homeassistant/components/frontend/www_static/manifest.json +++ b/homeassistant/components/frontend/www_static/manifest.json @@ -7,22 +7,22 @@ "background_color": "#FFFFFF", "icons": [ { - "src": "/static/favicon-192x192.png", + "src": "/static/icons/favicon-192x192.png", "sizes": "192x192", "type": "image/png" }, { - "src": "/static/favicon-384x384.png", + "src": "/static/icons/favicon-384x384.png", "sizes": "384x384", "type": "image/png" }, { - "src": "/static/favicon-512x512.png", + "src": "/static/icons/favicon-512x512.png", "sizes": "512x512", "type": "image/png" }, { - "src": "/static/favicon-1024x1024.png", + "src": "/static/icons/favicon-1024x1024.png", "sizes": "1024x1024", "type": "image/png" } diff --git a/homeassistant/components/history.py b/homeassistant/components/history.py index dbd143888f2..759862b19b1 100644 --- a/homeassistant/components/history.py +++ b/homeassistant/components/history.py @@ -11,6 +11,7 @@ from itertools import groupby import homeassistant.util.dt as dt_util from homeassistant.components import recorder, script +from homeassistant.components.frontend import register_built_in_panel from homeassistant.components.http import HomeAssistantView DOMAIN = 'history' @@ -153,6 +154,7 @@ def setup(hass, config): """Setup the history hooks.""" hass.wsgi.register_view(Last5StatesView) hass.wsgi.register_view(HistoryPeriodView) + register_built_in_panel(hass, 'history', 'History', 'mdi:poll-box') return True diff --git a/homeassistant/components/logbook.py b/homeassistant/components/logbook.py index c6cec168aed..d36bae51260 100644 --- a/homeassistant/components/logbook.py +++ b/homeassistant/components/logbook.py @@ -14,6 +14,7 @@ import voluptuous as vol import homeassistant.helpers.config_validation as cv import homeassistant.util.dt as dt_util from homeassistant.components import recorder, sun +from homeassistant.components.frontend import register_built_in_panel from homeassistant.components.http import HomeAssistantView from homeassistant.const import (EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP, EVENT_STATE_CHANGED, @@ -24,7 +25,7 @@ from homeassistant.helpers import template from homeassistant.helpers.entity import split_entity_id DOMAIN = "logbook" -DEPENDENCIES = ['recorder', 'http'] +DEPENDENCIES = ['recorder', 'frontend'] URL_LOGBOOK = re.compile(r'/api/logbook(?:/(?P\d{4}-\d{1,2}-\d{1,2})|)') @@ -75,6 +76,9 @@ def setup(hass, config): hass.wsgi.register_view(LogbookView) + register_built_in_panel(hass, 'logbook', 'Logbook', + 'mdi:format-list-bulleted-type') + hass.services.register(DOMAIN, 'log', log_message, schema=LOG_MESSAGE_SCHEMA) return True diff --git a/script/build_frontend b/script/build_frontend index 7b9dad05e79..da484a943b0 100755 --- a/script/build_frontend +++ b/script/build_frontend @@ -2,36 +2,21 @@ cd "$(dirname "$0")/.." -cd homeassistant/components/frontend/www_static/home-assistant-polymer +cd homeassistant/components/frontend/www_static +rm -rf core.js* frontend.html* webcomponents-lite.min.js* panels +cd home-assistant-polymer +npm run clean npm run frontend_prod cp bower_components/webcomponentsjs/webcomponents-lite.min.js .. -cp build/frontend.html .. -gzip build/frontend.html -c -k -9 > ../frontend.html.gz -cp build/partial-map.html .. -gzip build/partial-map.html -c -k -9 > ../partial-map.html.gz -cp build/dev-tools.html .. -gzip build/dev-tools.html -c -k -9 > ../dev-tools.html.gz -cp build/_core_compiled.js ../core.js -gzip build/_core_compiled.js -c -k -9 > ../core.js.gz - +cp -r build/* .. node script/sw-precache.js cp build/service_worker.js .. -gzip build/service_worker.js -c -k -9 > ../service_worker.js.gz + +cd .. + +gzip -f -k -9 *.html *.js ./panels/*.html # Generate the MD5 hash of the new frontend -cd ../.. -echo '"""DO NOT MODIFY. Auto-generated by build_frontend script."""' > version.py -if [ $(command -v md5) ]; then - echo 'CORE = "'`md5 -q www_static/core.js`'"' >> version.py - echo 'UI = "'`md5 -q www_static/frontend.html`'"' >> version.py - echo 'MAP = "'`md5 -q www_static/partial-map.html`'"' >> version.py - echo 'DEV = "'`md5 -q www_static/dev-tools.html`'"' >> version.py -elif [ $(command -v md5sum) ]; then - echo 'CORE = "'`md5sum www_static/core.js | cut -c-32`'"' >> version.py - echo 'UI = "'`md5sum www_static/frontend.html | cut -c-32`'"' >> version.py - echo 'MAP = "'`md5sum www_static/partial-map.html | cut -c-32`'"' >> version.py - echo 'DEV = "'`md5sum www_static/dev-tools.html | cut -c-32`'"' >> version.py -else - echo 'Could not find an MD5 utility' -fi +cd ../../../.. +script/fingerprint_frontend.py diff --git a/script/fingerprint_frontend.py b/script/fingerprint_frontend.py new file mode 100755 index 00000000000..4dfcdd7d19d --- /dev/null +++ b/script/fingerprint_frontend.py @@ -0,0 +1,37 @@ +#!/usr/bin/env python3 + +from collections import OrderedDict +import glob +import hashlib +import json + +fingerprint_file = 'homeassistant/components/frontend/version.py' +base_dir = 'homeassistant/components/frontend/www_static/' + + +def fingerprint(): + """Fingerprint the frontend files.""" + files = (glob.glob(base_dir + '**/*.html') + + glob.glob(base_dir + '*.html') + + glob.glob(base_dir + 'core.js')) + + md5s = OrderedDict() + + for fil in sorted(files): + name = fil[len(base_dir):] + with open(fil) as fp: + md5 = hashlib.md5(fp.read().encode('utf-8')).hexdigest() + md5s[name] = md5 + + template = """\"\"\"DO NOT MODIFY. Auto-generated by script/fingerprint_frontend.\"\"\" + +FINGERPRINTS = {} +""" + + result = template.format(json.dumps(md5s, indent=4)) + + with open(fingerprint_file, 'w') as fp: + fp.write(result) + +if __name__ == '__main__': + fingerprint() diff --git a/script/update_mdi.py b/script/update_mdi.py index 96682a26bfa..135b2be2046 100755 --- a/script/update_mdi.py +++ b/script/update_mdi.py @@ -1,38 +1,24 @@ #!/usr/bin/env python3 + """Download the latest Polymer v1 iconset for materialdesignicons.com.""" -import hashlib import gzip import os import re import requests import sys +from fingerprint_frontend import fingerprint + GETTING_STARTED_URL = ('https://raw.githubusercontent.com/Templarian/' 'MaterialDesign/master/site/getting-started.savvy') DOWNLOAD_LINK = re.compile(r'(/api/download/polymer/v1/([A-Z0-9-]{36}))') START_ICONSET = '