mirror of
https://github.com/home-assistant/core.git
synced 2025-07-09 22:37:11 +00:00
Route themes and translations over websocket (#14828)
This commit is contained in:
parent
a6880c452f
commit
fa2e6ada26
@ -96,6 +96,15 @@ WS_TYPE_GET_PANELS = 'get_panels'
|
||||
SCHEMA_GET_PANELS = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend({
|
||||
vol.Required('type'): WS_TYPE_GET_PANELS,
|
||||
})
|
||||
WS_TYPE_GET_THEMES = 'frontend/get_themes'
|
||||
SCHEMA_GET_THEMES = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend({
|
||||
vol.Required('type'): WS_TYPE_GET_THEMES,
|
||||
})
|
||||
WS_TYPE_GET_TRANSLATIONS = 'frontend/get_translations'
|
||||
SCHEMA_GET_TRANSLATIONS = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend({
|
||||
vol.Required('type'): WS_TYPE_GET_TRANSLATIONS,
|
||||
vol.Required('language'): str,
|
||||
})
|
||||
|
||||
|
||||
class Panel:
|
||||
@ -195,7 +204,12 @@ async def async_setup(hass, config):
|
||||
client = None
|
||||
|
||||
hass.components.websocket_api.async_register_command(
|
||||
WS_TYPE_GET_PANELS, websocket_handle_get_panels, SCHEMA_GET_PANELS)
|
||||
WS_TYPE_GET_PANELS, websocket_get_panels, SCHEMA_GET_PANELS)
|
||||
hass.components.websocket_api.async_register_command(
|
||||
WS_TYPE_GET_THEMES, websocket_get_themes, SCHEMA_GET_THEMES)
|
||||
hass.components.websocket_api.async_register_command(
|
||||
WS_TYPE_GET_TRANSLATIONS, websocket_get_translations,
|
||||
SCHEMA_GET_TRANSLATIONS)
|
||||
hass.http.register_view(ManifestJSONView)
|
||||
|
||||
conf = config.get(DOMAIN, {})
|
||||
@ -262,16 +276,14 @@ async def async_setup(hass, config):
|
||||
for url in conf.get(CONF_EXTRA_HTML_URL_ES5, []):
|
||||
add_extra_html_url(hass, url, True)
|
||||
|
||||
async_setup_themes(hass, conf.get(CONF_THEMES))
|
||||
|
||||
hass.http.register_view(TranslationsView)
|
||||
_async_setup_themes(hass, conf.get(CONF_THEMES))
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def async_setup_themes(hass, themes):
|
||||
@callback
|
||||
def _async_setup_themes(hass, themes):
|
||||
"""Set up themes data and services."""
|
||||
hass.http.register_view(ThemesView)
|
||||
hass.data[DATA_DEFAULT_THEME] = DEFAULT_THEME
|
||||
if themes is None:
|
||||
hass.data[DATA_THEMES] = {}
|
||||
@ -400,40 +412,6 @@ class ManifestJSONView(HomeAssistantView):
|
||||
return web.Response(text=msg, content_type="application/manifest+json")
|
||||
|
||||
|
||||
class ThemesView(HomeAssistantView):
|
||||
"""View to return defined themes."""
|
||||
|
||||
requires_auth = False
|
||||
url = '/api/themes'
|
||||
name = 'api:themes'
|
||||
|
||||
@callback
|
||||
def get(self, request):
|
||||
"""Return themes."""
|
||||
hass = request.app['hass']
|
||||
|
||||
return self.json({
|
||||
'themes': hass.data[DATA_THEMES],
|
||||
'default_theme': hass.data[DATA_DEFAULT_THEME],
|
||||
})
|
||||
|
||||
|
||||
class TranslationsView(HomeAssistantView):
|
||||
"""View to return backend defined translations."""
|
||||
|
||||
url = '/api/translations/{language}'
|
||||
name = 'api:translations'
|
||||
|
||||
async def get(self, request, language):
|
||||
"""Return translations."""
|
||||
hass = request.app['hass']
|
||||
|
||||
resources = await async_get_translations(hass, language)
|
||||
return self.json({
|
||||
'resources': resources,
|
||||
})
|
||||
|
||||
|
||||
def _is_latest(js_option, request):
|
||||
"""
|
||||
Return whether we should serve latest untranspiled code.
|
||||
@ -467,7 +445,7 @@ def _is_latest(js_option, request):
|
||||
|
||||
|
||||
@callback
|
||||
def websocket_handle_get_panels(hass, connection, msg):
|
||||
def websocket_get_panels(hass, connection, msg):
|
||||
"""Handle get panels command.
|
||||
|
||||
Async friendly.
|
||||
@ -480,3 +458,33 @@ def websocket_handle_get_panels(hass, connection, msg):
|
||||
|
||||
connection.to_write.put_nowait(websocket_api.result_message(
|
||||
msg['id'], panels))
|
||||
|
||||
|
||||
@callback
|
||||
def websocket_get_themes(hass, connection, msg):
|
||||
"""Handle get themes command.
|
||||
|
||||
Async friendly.
|
||||
"""
|
||||
connection.to_write.put_nowait(websocket_api.result_message(msg['id'], {
|
||||
'themes': hass.data[DATA_THEMES],
|
||||
'default_theme': hass.data[DATA_DEFAULT_THEME],
|
||||
}))
|
||||
|
||||
|
||||
@callback
|
||||
def websocket_get_translations(hass, connection, msg):
|
||||
"""Handle get translations command.
|
||||
|
||||
Async friendly.
|
||||
"""
|
||||
async def send_translations():
|
||||
"""Send a camera still."""
|
||||
resources = await async_get_translations(hass, msg['language'])
|
||||
connection.send_message_outside(websocket_api.result_message(
|
||||
msg['id'], {
|
||||
'resources': resources,
|
||||
}
|
||||
))
|
||||
|
||||
hass.async_add_job(send_translations())
|
||||
|
@ -38,6 +38,7 @@ MAX_PENDING_MSG = 512
|
||||
ERR_ID_REUSE = 1
|
||||
ERR_INVALID_FORMAT = 2
|
||||
ERR_NOT_FOUND = 3
|
||||
ERR_UNKNOWN_COMMAND = 4
|
||||
|
||||
TYPE_AUTH = 'auth'
|
||||
TYPE_AUTH_INVALID = 'auth_invalid'
|
||||
@ -353,8 +354,11 @@ class ActiveConnection:
|
||||
'Identifier values have to increase.'))
|
||||
|
||||
elif msg['type'] not in handlers:
|
||||
# Unknown command
|
||||
break
|
||||
self.log_error(
|
||||
'Received invalid command: {}'.format(msg['type']))
|
||||
self.to_write.put_nowait(error_message(
|
||||
cur_id, ERR_UNKNOWN_COMMAND,
|
||||
'Unknown command.'))
|
||||
|
||||
else:
|
||||
handler, schema = handlers[msg['type']]
|
||||
|
@ -11,6 +11,19 @@ from homeassistant.components.frontend import (
|
||||
CONF_EXTRA_HTML_URL_ES5)
|
||||
from homeassistant.components import websocket_api as wapi
|
||||
|
||||
from tests.common import mock_coro
|
||||
|
||||
|
||||
CONFIG_THEMES = {
|
||||
DOMAIN: {
|
||||
CONF_THEMES: {
|
||||
'happy': {
|
||||
'primary-color': 'red'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_http_client(hass, aiohttp_client):
|
||||
@ -101,68 +114,109 @@ def test_states_routes(mock_http_client):
|
||||
assert resp.status == 200
|
||||
|
||||
|
||||
@asyncio.coroutine
|
||||
def test_themes_api(mock_http_client_with_themes):
|
||||
async def test_themes_api(hass, hass_ws_client):
|
||||
"""Test that /api/themes returns correct data."""
|
||||
resp = yield from mock_http_client_with_themes.get('/api/themes')
|
||||
json = yield from resp.json()
|
||||
assert json['default_theme'] == 'default'
|
||||
assert json['themes'] == {'happy': {'primary-color': 'red'}}
|
||||
assert await async_setup_component(hass, 'frontend', CONFIG_THEMES)
|
||||
client = await hass_ws_client(hass)
|
||||
|
||||
await client.send_json({
|
||||
'id': 5,
|
||||
'type': 'frontend/get_themes',
|
||||
})
|
||||
msg = await client.receive_json()
|
||||
|
||||
assert msg['result']['default_theme'] == 'default'
|
||||
assert msg['result']['themes'] == {'happy': {'primary-color': 'red'}}
|
||||
|
||||
|
||||
@asyncio.coroutine
|
||||
def test_themes_set_theme(hass, mock_http_client_with_themes):
|
||||
async def test_themes_set_theme(hass, hass_ws_client):
|
||||
"""Test frontend.set_theme service."""
|
||||
yield from hass.services.async_call(DOMAIN, 'set_theme', {'name': 'happy'})
|
||||
yield from hass.async_block_till_done()
|
||||
resp = yield from mock_http_client_with_themes.get('/api/themes')
|
||||
json = yield from resp.json()
|
||||
assert json['default_theme'] == 'happy'
|
||||
assert await async_setup_component(hass, 'frontend', CONFIG_THEMES)
|
||||
client = await hass_ws_client(hass)
|
||||
|
||||
yield from hass.services.async_call(
|
||||
DOMAIN, 'set_theme', {'name': 'default'})
|
||||
yield from hass.async_block_till_done()
|
||||
resp = yield from mock_http_client_with_themes.get('/api/themes')
|
||||
json = yield from resp.json()
|
||||
assert json['default_theme'] == 'default'
|
||||
await hass.services.async_call(
|
||||
DOMAIN, 'set_theme', {'name': 'happy'}, blocking=True)
|
||||
|
||||
await client.send_json({
|
||||
'id': 5,
|
||||
'type': 'frontend/get_themes',
|
||||
})
|
||||
msg = await client.receive_json()
|
||||
|
||||
assert msg['result']['default_theme'] == 'happy'
|
||||
|
||||
await hass.services.async_call(
|
||||
DOMAIN, 'set_theme', {'name': 'default'}, blocking=True)
|
||||
|
||||
await client.send_json({
|
||||
'id': 6,
|
||||
'type': 'frontend/get_themes',
|
||||
})
|
||||
msg = await client.receive_json()
|
||||
|
||||
assert msg['result']['default_theme'] == 'default'
|
||||
|
||||
|
||||
@asyncio.coroutine
|
||||
def test_themes_set_theme_wrong_name(hass, mock_http_client_with_themes):
|
||||
async def test_themes_set_theme_wrong_name(hass, hass_ws_client):
|
||||
"""Test frontend.set_theme service called with wrong name."""
|
||||
yield from hass.services.async_call(DOMAIN, 'set_theme', {'name': 'wrong'})
|
||||
yield from hass.async_block_till_done()
|
||||
resp = yield from mock_http_client_with_themes.get('/api/themes')
|
||||
json = yield from resp.json()
|
||||
assert json['default_theme'] == 'default'
|
||||
assert await async_setup_component(hass, 'frontend', CONFIG_THEMES)
|
||||
client = await hass_ws_client(hass)
|
||||
|
||||
await hass.services.async_call(
|
||||
DOMAIN, 'set_theme', {'name': 'wrong'}, blocking=True)
|
||||
|
||||
await client.send_json({
|
||||
'id': 5,
|
||||
'type': 'frontend/get_themes',
|
||||
})
|
||||
|
||||
msg = await client.receive_json()
|
||||
|
||||
assert msg['result']['default_theme'] == 'default'
|
||||
|
||||
|
||||
@asyncio.coroutine
|
||||
def test_themes_reload_themes(hass, mock_http_client_with_themes):
|
||||
async def test_themes_reload_themes(hass, hass_ws_client):
|
||||
"""Test frontend.reload_themes service."""
|
||||
assert await async_setup_component(hass, 'frontend', CONFIG_THEMES)
|
||||
client = await hass_ws_client(hass)
|
||||
|
||||
with patch('homeassistant.components.frontend.load_yaml_config_file',
|
||||
return_value={DOMAIN: {
|
||||
CONF_THEMES: {
|
||||
'sad': {'primary-color': 'blue'}
|
||||
}}}):
|
||||
yield from hass.services.async_call(DOMAIN, 'set_theme',
|
||||
{'name': 'happy'})
|
||||
yield from hass.services.async_call(DOMAIN, 'reload_themes')
|
||||
yield from hass.async_block_till_done()
|
||||
resp = yield from mock_http_client_with_themes.get('/api/themes')
|
||||
json = yield from resp.json()
|
||||
assert json['themes'] == {'sad': {'primary-color': 'blue'}}
|
||||
assert json['default_theme'] == 'default'
|
||||
await hass.services.async_call(
|
||||
DOMAIN, 'set_theme', {'name': 'happy'}, blocking=True)
|
||||
await hass.services.async_call(DOMAIN, 'reload_themes', blocking=True)
|
||||
|
||||
await client.send_json({
|
||||
'id': 5,
|
||||
'type': 'frontend/get_themes',
|
||||
})
|
||||
|
||||
msg = await client.receive_json()
|
||||
|
||||
assert msg['result']['themes'] == {'sad': {'primary-color': 'blue'}}
|
||||
assert msg['result']['default_theme'] == 'default'
|
||||
|
||||
|
||||
@asyncio.coroutine
|
||||
def test_missing_themes(mock_http_client):
|
||||
async def test_missing_themes(hass, hass_ws_client):
|
||||
"""Test that themes API works when themes are not defined."""
|
||||
resp = yield from mock_http_client.get('/api/themes')
|
||||
assert resp.status == 200
|
||||
json = yield from resp.json()
|
||||
assert json['default_theme'] == 'default'
|
||||
assert json['themes'] == {}
|
||||
await async_setup_component(hass, 'frontend')
|
||||
|
||||
client = await hass_ws_client(hass)
|
||||
await client.send_json({
|
||||
'id': 5,
|
||||
'type': 'frontend/get_themes',
|
||||
})
|
||||
|
||||
msg = await client.receive_json()
|
||||
|
||||
assert msg['id'] == 5
|
||||
assert msg['type'] == wapi.TYPE_RESULT
|
||||
assert msg['success']
|
||||
assert msg['result']['default_theme'] == 'default'
|
||||
assert msg['result']['themes'] == {}
|
||||
|
||||
|
||||
@asyncio.coroutine
|
||||
@ -204,3 +258,23 @@ async def test_get_panels(hass, hass_ws_client):
|
||||
assert msg['result']['map']['url_path'] == 'map'
|
||||
assert msg['result']['map']['icon'] == 'mdi:account-location'
|
||||
assert msg['result']['map']['title'] == 'Map'
|
||||
|
||||
|
||||
async def test_get_translations(hass, hass_ws_client):
|
||||
"""Test get_translations command."""
|
||||
await async_setup_component(hass, 'frontend')
|
||||
client = await hass_ws_client(hass)
|
||||
|
||||
with patch('homeassistant.components.frontend.async_get_translations',
|
||||
side_effect=lambda hass, lang: mock_coro({'lang': lang})):
|
||||
await client.send_json({
|
||||
'id': 5,
|
||||
'type': 'frontend/get_translations',
|
||||
'language': 'nl',
|
||||
})
|
||||
msg = await client.receive_json()
|
||||
|
||||
assert msg['id'] == 5
|
||||
assert msg['type'] == wapi.TYPE_RESULT
|
||||
assert msg['success']
|
||||
assert msg['result'] == {'resources': {'lang': 'nl'}}
|
||||
|
@ -311,8 +311,9 @@ def test_unknown_command(websocket_client):
|
||||
'type': 'unknown_command',
|
||||
})
|
||||
|
||||
msg = yield from websocket_client.receive()
|
||||
assert msg.type == WSMsgType.close
|
||||
msg = yield from websocket_client.receive_json()
|
||||
assert not msg['success']
|
||||
assert msg['error']['code'] == wapi.ERR_UNKNOWN_COMMAND
|
||||
|
||||
|
||||
async def test_auth_with_token(hass, aiohttp_client, hass_access_token):
|
||||
|
Loading…
x
Reference in New Issue
Block a user