Add support for dynamic frontend panels
|
@ -1,37 +1,112 @@
|
||||||
"""Handle the frontend for Home Assistant."""
|
"""Handle the frontend for Home Assistant."""
|
||||||
|
import logging
|
||||||
import os
|
import os
|
||||||
|
|
||||||
|
from homeassistant.const import EVENT_HOMEASSISTANT_START
|
||||||
from homeassistant.components import api
|
from homeassistant.components import api
|
||||||
from homeassistant.components.http import HomeAssistantView
|
from homeassistant.components.http import HomeAssistantView
|
||||||
from . import version, mdi_version
|
from .version import FINGERPRINTS
|
||||||
|
|
||||||
DOMAIN = 'frontend'
|
DOMAIN = 'frontend'
|
||||||
DEPENDENCIES = ['api']
|
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 /<component_name> to index view.
|
||||||
|
|
||||||
|
|
||||||
def setup(hass, config):
|
def setup(hass, config):
|
||||||
"""Setup serving the frontend."""
|
"""Setup serving the frontend."""
|
||||||
hass.wsgi.register_view(IndexView)
|
|
||||||
hass.wsgi.register_view(BootstrapView)
|
hass.wsgi.register_view(BootstrapView)
|
||||||
|
|
||||||
www_static_path = os.path.join(os.path.dirname(__file__), 'www_static')
|
|
||||||
if hass.wsgi.development:
|
if hass.wsgi.development:
|
||||||
sw_path = "home-assistant-polymer/build/service_worker.js"
|
sw_path = "home-assistant-polymer/build/service_worker.js"
|
||||||
else:
|
else:
|
||||||
sw_path = "service_worker.js"
|
sw_path = "service_worker.js"
|
||||||
|
|
||||||
hass.wsgi.register_static_path(
|
hass.wsgi.register_static_path("/service_worker.js",
|
||||||
"/service_worker.js",
|
os.path.join(STATIC_PATH, sw_path), 0)
|
||||||
os.path.join(www_static_path, sw_path),
|
hass.wsgi.register_static_path("/robots.txt",
|
||||||
0
|
os.path.join(STATIC_PATH, "robots.txt"))
|
||||||
)
|
hass.wsgi.register_static_path("/static", STATIC_PATH)
|
||||||
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("/local", hass.config.path('www'))
|
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
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
@ -48,6 +123,7 @@ class BootstrapView(HomeAssistantView):
|
||||||
'states': self.hass.states.all(),
|
'states': self.hass.states.all(),
|
||||||
'events': api.events_json(self.hass),
|
'events': api.events_json(self.hass),
|
||||||
'services': api.services_json(self.hass),
|
'services': api.services_json(self.hass),
|
||||||
|
'panels': PANELS,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
@ -57,16 +133,15 @@ class IndexView(HomeAssistantView):
|
||||||
url = '/'
|
url = '/'
|
||||||
name = "frontend:index"
|
name = "frontend:index"
|
||||||
requires_auth = False
|
requires_auth = False
|
||||||
extra_urls = ['/logbook', '/history', '/map', '/devService', '/devState',
|
extra_urls = ['/states', '/states/<entity:entity_id>']
|
||||||
'/devEvent', '/devInfo', '/devTemplate',
|
|
||||||
'/states', '/states/<entity:entity_id>']
|
|
||||||
|
|
||||||
def __init__(self, hass):
|
def __init__(self, hass, extra_urls):
|
||||||
"""Initialize the frontend view."""
|
"""Initialize the frontend view."""
|
||||||
super().__init__(hass)
|
super().__init__(hass)
|
||||||
|
|
||||||
from jinja2 import FileSystemLoader, Environment
|
from jinja2 import FileSystemLoader, Environment
|
||||||
|
|
||||||
|
self.extra_urls = self.extra_urls + extra_urls
|
||||||
self.templates = Environment(
|
self.templates = Environment(
|
||||||
loader=FileSystemLoader(
|
loader=FileSystemLoader(
|
||||||
os.path.join(os.path.dirname(__file__), 'templates/')
|
os.path.join(os.path.dirname(__file__), 'templates/')
|
||||||
|
@ -76,32 +151,24 @@ class IndexView(HomeAssistantView):
|
||||||
def get(self, request, entity_id=None):
|
def get(self, request, entity_id=None):
|
||||||
"""Serve the index view."""
|
"""Serve the index view."""
|
||||||
if self.hass.wsgi.development:
|
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'
|
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:
|
else:
|
||||||
core_url = '/static/core-{}.js'.format(version.CORE)
|
core_url = '/static/core-{}.js'.format(
|
||||||
ui_url = '/static/frontend-{}.html'.format(version.UI)
|
FINGERPRINTS['core.js'])
|
||||||
map_url = '/static/partial-map-{}.html'.format(version.MAP)
|
ui_url = '/static/frontend-{}.html'.format(
|
||||||
dev_url = '/static/dev-tools-{}.html'.format(version.DEV)
|
FINGERPRINTS['frontend.html'])
|
||||||
|
|
||||||
# auto login if no password was set
|
# auto login if no password was set
|
||||||
if self.hass.config.api.api_password is None:
|
no_auth = 'false' if self.hass.config.api.api_password else 'true'
|
||||||
auth = 'true'
|
|
||||||
else:
|
|
||||||
auth = 'false'
|
|
||||||
|
|
||||||
icons_url = '/static/mdi-{}.html'.format(mdi_version.VERSION)
|
|
||||||
|
|
||||||
|
icons_url = '/static/mdi-{}.html'.format(FINGERPRINTS['mdi.html'])
|
||||||
template = self.templates.get_template('index.html')
|
template = self.templates.get_template('index.html')
|
||||||
|
|
||||||
# pylint is wrong
|
# pylint is wrong
|
||||||
# pylint: disable=no-member
|
# pylint: disable=no-member
|
||||||
resp = template.render(
|
resp = template.render(
|
||||||
core_url=core_url, ui_url=ui_url, map_url=map_url, auth=auth,
|
core_url=core_url, ui_url=ui_url, no_auth=no_auth,
|
||||||
dev_url=dev_url, icons_url=icons_url, icons=mdi_version.VERSION)
|
icons_url=icons_url, icons=FINGERPRINTS['mdi.html'])
|
||||||
|
|
||||||
return self.Response(resp, mimetype='text/html')
|
return self.Response(resp, mimetype='text/html')
|
||||||
|
|
|
@ -1,2 +0,0 @@
|
||||||
"""DO NOT MODIFY. Auto-generated by update_mdi script."""
|
|
||||||
VERSION = "758957b7ea989d6beca60e218ea7f7dd"
|
|
|
@ -5,14 +5,14 @@
|
||||||
<title>Home Assistant</title>
|
<title>Home Assistant</title>
|
||||||
|
|
||||||
<link rel='manifest' href='/static/manifest.json'>
|
<link rel='manifest' href='/static/manifest.json'>
|
||||||
<link rel='icon' href='/static/favicon.ico'>
|
<link rel='icon' href='/static/icons/favicon.ico'>
|
||||||
<link rel='apple-touch-icon' sizes='180x180'
|
<link rel='apple-touch-icon' sizes='180x180'
|
||||||
href='/static/favicon-apple-180x180.png'>
|
href='/static/icons/favicon-apple-180x180.png'>
|
||||||
<meta name='apple-mobile-web-app-capable' content='yes'>
|
<meta name='apple-mobile-web-app-capable' content='yes'>
|
||||||
<meta name="msapplication-square70x70logo" content="/static/tile-win-70x70.png"/>
|
<meta name="msapplication-square70x70logo" content="/static/icons/tile-win-70x70.png"/>
|
||||||
<meta name="msapplication-square150x150logo" content="/static/tile-win-150x150.png"/>
|
<meta name="msapplication-square150x150logo" content="/static/icons/tile-win-150x150.png"/>
|
||||||
<meta name="msapplication-wide310x150logo" content="/static/tile-win-310x150.png"/>
|
<meta name="msapplication-wide310x150logo" content="/static/icons/tile-win-310x150.png"/>
|
||||||
<meta name="msapplication-square310x310logo" content="/static/tile-win-310x310.png"/>
|
<meta name="msapplication-square310x310logo" content="/static/icons/tile-win-310x310.png"/>
|
||||||
<meta name="msapplication-TileColor" content="#3fbbf4ff"/>
|
<meta name="msapplication-TileColor" content="#3fbbf4ff"/>
|
||||||
<meta name='mobile-web-app-capable' content='yes'>
|
<meta name='mobile-web-app-capable' content='yes'>
|
||||||
<meta name='viewport' content='width=device-width, user-scalable=no'>
|
<meta name='viewport' content='width=device-width, user-scalable=no'>
|
||||||
|
@ -65,16 +65,12 @@
|
||||||
.getElementById('ha-init-skeleton')
|
.getElementById('ha-init-skeleton')
|
||||||
.classList.add('error');
|
.classList.add('error');
|
||||||
};
|
};
|
||||||
window.noAuth = {{ auth }};
|
window.noAuth = {{ no_auth }};
|
||||||
window.deferredLoading = {
|
|
||||||
map: '{{ map_url }}',
|
|
||||||
dev: '{{ dev_url }}',
|
|
||||||
};
|
|
||||||
</script>
|
</script>
|
||||||
</head>
|
</head>
|
||||||
<body fullbleed>
|
<body fullbleed>
|
||||||
<div id='ha-init-skeleton'>
|
<div id='ha-init-skeleton'>
|
||||||
<img src='/static/favicon-192x192.png' height='192'>
|
<img src='/static/icons/favicon-192x192.png' height='192'>
|
||||||
<paper-spinner active></paper-spinner>
|
<paper-spinner active></paper-spinner>
|
||||||
Home Assistant had trouble<br>connecting to the server.<br><br><a href='/'>TRY AGAIN</a>
|
Home Assistant had trouble<br>connecting to the server.<br><br><a href='/'>TRY AGAIN</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,5 +1,24 @@
|
||||||
|
<<<<<<< HEAD
|
||||||
"""DO NOT MODIFY. Auto-generated by build_frontend script."""
|
"""DO NOT MODIFY. Auto-generated by build_frontend script."""
|
||||||
CORE = "7d80cc0e4dea6bc20fa2889be0b3cd15"
|
CORE = "7d80cc0e4dea6bc20fa2889be0b3cd15"
|
||||||
UI = "805f8dda70419b26daabc8e8f625127f"
|
UI = "805f8dda70419b26daabc8e8f625127f"
|
||||||
MAP = "c922306de24140afd14f857f927bf8f0"
|
MAP = "c922306de24140afd14f857f927bf8f0"
|
||||||
DEV = "b7079ac3121b95b9856e5603a6d8a263"
|
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
|
||||||
|
|
Before Width: | Height: | Size: 93 KiB After Width: | Height: | Size: 93 KiB |
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 19 KiB |
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 44 KiB |
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 18 KiB |
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 10 KiB |
Before Width: | Height: | Size: 9.2 KiB After Width: | Height: | Size: 9.2 KiB |
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 22 KiB |
Before Width: | Height: | Size: 4.9 KiB After Width: | Height: | Size: 4.9 KiB |
|
@ -7,22 +7,22 @@
|
||||||
"background_color": "#FFFFFF",
|
"background_color": "#FFFFFF",
|
||||||
"icons": [
|
"icons": [
|
||||||
{
|
{
|
||||||
"src": "/static/favicon-192x192.png",
|
"src": "/static/icons/favicon-192x192.png",
|
||||||
"sizes": "192x192",
|
"sizes": "192x192",
|
||||||
"type": "image/png"
|
"type": "image/png"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"src": "/static/favicon-384x384.png",
|
"src": "/static/icons/favicon-384x384.png",
|
||||||
"sizes": "384x384",
|
"sizes": "384x384",
|
||||||
"type": "image/png"
|
"type": "image/png"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"src": "/static/favicon-512x512.png",
|
"src": "/static/icons/favicon-512x512.png",
|
||||||
"sizes": "512x512",
|
"sizes": "512x512",
|
||||||
"type": "image/png"
|
"type": "image/png"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"src": "/static/favicon-1024x1024.png",
|
"src": "/static/icons/favicon-1024x1024.png",
|
||||||
"sizes": "1024x1024",
|
"sizes": "1024x1024",
|
||||||
"type": "image/png"
|
"type": "image/png"
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,6 +11,7 @@ from itertools import groupby
|
||||||
|
|
||||||
import homeassistant.util.dt as dt_util
|
import homeassistant.util.dt as dt_util
|
||||||
from homeassistant.components import recorder, script
|
from homeassistant.components import recorder, script
|
||||||
|
from homeassistant.components.frontend import register_built_in_panel
|
||||||
from homeassistant.components.http import HomeAssistantView
|
from homeassistant.components.http import HomeAssistantView
|
||||||
|
|
||||||
DOMAIN = 'history'
|
DOMAIN = 'history'
|
||||||
|
@ -153,6 +154,7 @@ def setup(hass, config):
|
||||||
"""Setup the history hooks."""
|
"""Setup the history hooks."""
|
||||||
hass.wsgi.register_view(Last5StatesView)
|
hass.wsgi.register_view(Last5StatesView)
|
||||||
hass.wsgi.register_view(HistoryPeriodView)
|
hass.wsgi.register_view(HistoryPeriodView)
|
||||||
|
register_built_in_panel(hass, 'history', 'History', 'mdi:poll-box')
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
|
@ -14,6 +14,7 @@ import voluptuous as vol
|
||||||
import homeassistant.helpers.config_validation as cv
|
import homeassistant.helpers.config_validation as cv
|
||||||
import homeassistant.util.dt as dt_util
|
import homeassistant.util.dt as dt_util
|
||||||
from homeassistant.components import recorder, sun
|
from homeassistant.components import recorder, sun
|
||||||
|
from homeassistant.components.frontend import register_built_in_panel
|
||||||
from homeassistant.components.http import HomeAssistantView
|
from homeassistant.components.http import HomeAssistantView
|
||||||
from homeassistant.const import (EVENT_HOMEASSISTANT_START,
|
from homeassistant.const import (EVENT_HOMEASSISTANT_START,
|
||||||
EVENT_HOMEASSISTANT_STOP, EVENT_STATE_CHANGED,
|
EVENT_HOMEASSISTANT_STOP, EVENT_STATE_CHANGED,
|
||||||
|
@ -24,7 +25,7 @@ from homeassistant.helpers import template
|
||||||
from homeassistant.helpers.entity import split_entity_id
|
from homeassistant.helpers.entity import split_entity_id
|
||||||
|
|
||||||
DOMAIN = "logbook"
|
DOMAIN = "logbook"
|
||||||
DEPENDENCIES = ['recorder', 'http']
|
DEPENDENCIES = ['recorder', 'frontend']
|
||||||
|
|
||||||
URL_LOGBOOK = re.compile(r'/api/logbook(?:/(?P<date>\d{4}-\d{1,2}-\d{1,2})|)')
|
URL_LOGBOOK = re.compile(r'/api/logbook(?:/(?P<date>\d{4}-\d{1,2}-\d{1,2})|)')
|
||||||
|
|
||||||
|
@ -75,6 +76,9 @@ def setup(hass, config):
|
||||||
|
|
||||||
hass.wsgi.register_view(LogbookView)
|
hass.wsgi.register_view(LogbookView)
|
||||||
|
|
||||||
|
register_built_in_panel(hass, 'logbook', 'Logbook',
|
||||||
|
'mdi:format-list-bulleted-type')
|
||||||
|
|
||||||
hass.services.register(DOMAIN, 'log', log_message,
|
hass.services.register(DOMAIN, 'log', log_message,
|
||||||
schema=LOG_MESSAGE_SCHEMA)
|
schema=LOG_MESSAGE_SCHEMA)
|
||||||
return True
|
return True
|
||||||
|
|
|
@ -2,36 +2,21 @@
|
||||||
|
|
||||||
cd "$(dirname "$0")/.."
|
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
|
npm run frontend_prod
|
||||||
|
|
||||||
cp bower_components/webcomponentsjs/webcomponents-lite.min.js ..
|
cp bower_components/webcomponentsjs/webcomponents-lite.min.js ..
|
||||||
cp build/frontend.html ..
|
cp -r build/* ..
|
||||||
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
|
|
||||||
|
|
||||||
node script/sw-precache.js
|
node script/sw-precache.js
|
||||||
cp build/service_worker.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
|
# Generate the MD5 hash of the new frontend
|
||||||
cd ../..
|
cd ../../../..
|
||||||
echo '"""DO NOT MODIFY. Auto-generated by build_frontend script."""' > version.py
|
script/fingerprint_frontend.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
|
|
||||||
|
|
37
script/fingerprint_frontend.py
Executable file
|
@ -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()
|
|
@ -1,38 +1,24 @@
|
||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
"""Download the latest Polymer v1 iconset for materialdesignicons.com."""
|
"""Download the latest Polymer v1 iconset for materialdesignicons.com."""
|
||||||
import hashlib
|
|
||||||
import gzip
|
import gzip
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
import requests
|
import requests
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
|
from fingerprint_frontend import fingerprint
|
||||||
|
|
||||||
GETTING_STARTED_URL = ('https://raw.githubusercontent.com/Templarian/'
|
GETTING_STARTED_URL = ('https://raw.githubusercontent.com/Templarian/'
|
||||||
'MaterialDesign/master/site/getting-started.savvy')
|
'MaterialDesign/master/site/getting-started.savvy')
|
||||||
DOWNLOAD_LINK = re.compile(r'(/api/download/polymer/v1/([A-Z0-9-]{36}))')
|
DOWNLOAD_LINK = re.compile(r'(/api/download/polymer/v1/([A-Z0-9-]{36}))')
|
||||||
START_ICONSET = '<iron-iconset-svg'
|
START_ICONSET = '<iron-iconset-svg'
|
||||||
|
|
||||||
CUR_VERSION = re.compile(r'VERSION = "([A-Za-z0-9]{32})"')
|
|
||||||
|
|
||||||
OUTPUT_BASE = os.path.join('homeassistant', 'components', 'frontend')
|
OUTPUT_BASE = os.path.join('homeassistant', 'components', 'frontend')
|
||||||
VERSION_OUTPUT = os.path.join(OUTPUT_BASE, 'mdi_version.py')
|
|
||||||
ICONSET_OUTPUT = os.path.join(OUTPUT_BASE, 'www_static', 'mdi.html')
|
ICONSET_OUTPUT = os.path.join(OUTPUT_BASE, 'www_static', 'mdi.html')
|
||||||
ICONSET_OUTPUT_GZ = os.path.join(OUTPUT_BASE, 'www_static', 'mdi.html.gz')
|
ICONSET_OUTPUT_GZ = os.path.join(OUTPUT_BASE, 'www_static', 'mdi.html.gz')
|
||||||
|
|
||||||
|
|
||||||
def get_local_version():
|
|
||||||
"""Parse the local version."""
|
|
||||||
try:
|
|
||||||
with open(VERSION_OUTPUT) as inp:
|
|
||||||
for line in inp:
|
|
||||||
match = CUR_VERSION.search(line)
|
|
||||||
if match:
|
|
||||||
return match.group(1)
|
|
||||||
except FileNotFoundError:
|
|
||||||
return False
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
def get_remote_version():
|
def get_remote_version():
|
||||||
"""Get current version and download link."""
|
"""Get current version and download link."""
|
||||||
gs_page = requests.get(GETTING_STARTED_URL).text
|
gs_page = requests.get(GETTING_STARTED_URL).text
|
||||||
|
@ -43,10 +29,7 @@ def get_remote_version():
|
||||||
print("Unable to find download link")
|
print("Unable to find download link")
|
||||||
sys.exit()
|
sys.exit()
|
||||||
|
|
||||||
url = 'https://materialdesignicons.com' + mdi_download.group(1)
|
return 'https://materialdesignicons.com' + mdi_download.group(1)
|
||||||
version = mdi_download.group(2).replace('-', '')
|
|
||||||
|
|
||||||
return version, url
|
|
||||||
|
|
||||||
|
|
||||||
def clean_component(source):
|
def clean_component(source):
|
||||||
|
@ -54,7 +37,7 @@ def clean_component(source):
|
||||||
return source[source.index(START_ICONSET):]
|
return source[source.index(START_ICONSET):]
|
||||||
|
|
||||||
|
|
||||||
def write_component(version, source):
|
def write_component(source):
|
||||||
"""Write component."""
|
"""Write component."""
|
||||||
with open(ICONSET_OUTPUT, 'w') as outp:
|
with open(ICONSET_OUTPUT, 'w') as outp:
|
||||||
print('Writing icons to', ICONSET_OUTPUT)
|
print('Writing icons to', ICONSET_OUTPUT)
|
||||||
|
@ -64,12 +47,6 @@ def write_component(version, source):
|
||||||
print('Writing icons gz to', ICONSET_OUTPUT_GZ)
|
print('Writing icons gz to', ICONSET_OUTPUT_GZ)
|
||||||
outp.write(source.encode('utf-8'))
|
outp.write(source.encode('utf-8'))
|
||||||
|
|
||||||
with open(VERSION_OUTPUT, 'w') as outp:
|
|
||||||
print('Generating version file', VERSION_OUTPUT)
|
|
||||||
outp.write(
|
|
||||||
'"""DO NOT MODIFY. Auto-generated by update_mdi script."""\n')
|
|
||||||
outp.write('VERSION = "{}"\n'.format(version))
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
"""Main section of the script."""
|
"""Main section of the script."""
|
||||||
|
@ -79,19 +56,11 @@ def main():
|
||||||
|
|
||||||
print("materialdesignicons.com icon updater")
|
print("materialdesignicons.com icon updater")
|
||||||
|
|
||||||
local_version = get_local_version()
|
remote_url = get_remote_version()
|
||||||
|
|
||||||
# The remote version is not reliable.
|
|
||||||
_, remote_url = get_remote_version()
|
|
||||||
|
|
||||||
source = clean_component(requests.get(remote_url).text)
|
source = clean_component(requests.get(remote_url).text)
|
||||||
new_version = hashlib.md5(source.encode('utf-8')).hexdigest()
|
write_component(source)
|
||||||
|
fingerprint()
|
||||||
|
|
||||||
if local_version == new_version:
|
|
||||||
print('Already on the latest version.')
|
|
||||||
sys.exit()
|
|
||||||
|
|
||||||
write_component(new_version, source)
|
|
||||||
print('Updated to latest version')
|
print('Updated to latest version')
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
|
|
@ -7,7 +7,7 @@ import unittest
|
||||||
import requests
|
import requests
|
||||||
|
|
||||||
import homeassistant.bootstrap as bootstrap
|
import homeassistant.bootstrap as bootstrap
|
||||||
import homeassistant.components.http as http
|
from homeassistant.components import frontend, http
|
||||||
from homeassistant.const import HTTP_HEADER_HA_AUTH
|
from homeassistant.const import HTTP_HEADER_HA_AUTH
|
||||||
|
|
||||||
from tests.common import get_test_instance_port, get_test_home_assistant
|
from tests.common import get_test_instance_port, get_test_home_assistant
|
||||||
|
@ -48,6 +48,7 @@ def setUpModule(): # pylint: disable=invalid-name
|
||||||
def tearDownModule(): # pylint: disable=invalid-name
|
def tearDownModule(): # pylint: disable=invalid-name
|
||||||
"""Stop everything that was started."""
|
"""Stop everything that was started."""
|
||||||
hass.stop()
|
hass.stop()
|
||||||
|
frontend.PANELS = {}
|
||||||
|
|
||||||
|
|
||||||
class TestFrontend(unittest.TestCase):
|
class TestFrontend(unittest.TestCase):
|
||||||
|
|