Skip to content

Commit

Permalink
Add support for dynamic frontend panels
Browse files Browse the repository at this point in the history
  • Loading branch information
balloob committed Jul 17, 2016
1 parent 35a57e1 commit 22b4aeb
Show file tree
Hide file tree
Showing 21 changed files with 197 additions and 119 deletions.
135 changes: 101 additions & 34 deletions homeassistant/components/frontend/__init__.py
Original file line number Diff line number Diff line change
@@ -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 /<component_name> 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


Expand All @@ -48,6 +123,7 @@ def get(self, request):
'states': self.hass.states.all(),
'events': api.events_json(self.hass),
'services': api.services_json(self.hass),
'panels': PANELS,
})


Expand All @@ -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/<entity:entity_id>']
extra_urls = ['/states', '/states/<entity:entity_id>']

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/')
Expand All @@ -76,32 +151,24 @@ def __init__(self, hass):
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')
2 changes: 0 additions & 2 deletions homeassistant/components/frontend/mdi_version.py

This file was deleted.

20 changes: 8 additions & 12 deletions homeassistant/components/frontend/templates/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,14 @@
<title>Home Assistant</title>

<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'
href='/static/favicon-apple-180x180.png'>
href='/static/icons/favicon-apple-180x180.png'>
<meta name='apple-mobile-web-app-capable' content='yes'>
<meta name="msapplication-square70x70logo" content="/static/tile-win-70x70.png"/>
<meta name="msapplication-square150x150logo" content="/static/tile-win-150x150.png"/>
<meta name="msapplication-wide310x150logo" content="/static/tile-win-310x150.png"/>
<meta name="msapplication-square310x310logo" content="/static/tile-win-310x310.png"/>
<meta name="msapplication-square70x70logo" content="/static/icons/tile-win-70x70.png"/>
<meta name="msapplication-square150x150logo" content="/static/icons/tile-win-150x150.png"/>
<meta name="msapplication-wide310x150logo" content="/static/icons/tile-win-310x150.png"/>
<meta name="msapplication-square310x310logo" content="/static/icons/tile-win-310x310.png"/>
<meta name="msapplication-TileColor" content="#3fbbf4ff"/>
<meta name='mobile-web-app-capable' content='yes'>
<meta name='viewport' content='width=device-width, user-scalable=no'>
Expand Down Expand Up @@ -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 }};
</script>
</head>
<body fullbleed>
<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>
Home Assistant had trouble<br>connecting to the server.<br><br><a href='/'>TRY AGAIN</a>
</div>
Expand Down
19 changes: 19 additions & 0 deletions homeassistant/components/frontend/version.py
Original file line number Diff line number Diff line change
@@ -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
8 changes: 4 additions & 4 deletions homeassistant/components/frontend/www_static/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
Expand Down
2 changes: 2 additions & 0 deletions homeassistant/components/history.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

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'
Expand Down Expand Up @@ -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

Expand Down
6 changes: 5 additions & 1 deletion homeassistant/components/logbook.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
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,
Expand All @@ -24,7 +25,7 @@
from homeassistant.helpers.entity import split_entity_id

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})|)')

Expand Down Expand Up @@ -75,6 +76,9 @@ def log_message(service):

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
Expand Down
37 changes: 11 additions & 26 deletions script/build_frontend
Original file line number Diff line number Diff line change
Expand Up @@ -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
Loading

0 comments on commit 22b4aeb

Please sign in to comment.