2018-09-25 06:39:35 +00:00
|
|
|
"""Lovelace UI."""
|
2018-10-17 14:31:06 +00:00
|
|
|
import logging
|
|
|
|
import uuid
|
2018-10-31 12:49:54 +00:00
|
|
|
from functools import wraps
|
2018-10-22 12:45:13 +00:00
|
|
|
from typing import Dict, List, Union
|
|
|
|
|
2018-09-25 06:39:35 +00:00
|
|
|
import voluptuous as vol
|
|
|
|
|
2018-10-31 12:49:54 +00:00
|
|
|
import homeassistant.util.ruamel_yaml as yaml
|
2018-09-25 06:39:35 +00:00
|
|
|
from homeassistant.components import websocket_api
|
|
|
|
from homeassistant.exceptions import HomeAssistantError
|
|
|
|
|
2018-10-17 14:31:06 +00:00
|
|
|
_LOGGER = logging.getLogger(__name__)
|
2018-09-25 06:39:35 +00:00
|
|
|
DOMAIN = 'lovelace'
|
|
|
|
|
2018-10-22 12:45:13 +00:00
|
|
|
LOVELACE_CONFIG_FILE = 'ui-lovelace.yaml'
|
|
|
|
JSON_TYPE = Union[List, Dict, str] # pylint: disable=invalid-name
|
|
|
|
|
2018-10-23 19:48:35 +00:00
|
|
|
FORMAT_YAML = 'yaml'
|
|
|
|
FORMAT_JSON = 'json'
|
|
|
|
|
2018-09-25 06:39:35 +00:00
|
|
|
OLD_WS_TYPE_GET_LOVELACE_UI = 'frontend/lovelace_config'
|
|
|
|
WS_TYPE_GET_LOVELACE_UI = 'lovelace/config'
|
2018-10-23 19:48:35 +00:00
|
|
|
|
2018-10-27 21:51:40 +00:00
|
|
|
WS_TYPE_MIGRATE_CONFIG = 'lovelace/config/migrate'
|
2018-10-22 12:45:13 +00:00
|
|
|
WS_TYPE_GET_CARD = 'lovelace/config/card/get'
|
2018-10-23 19:48:35 +00:00
|
|
|
WS_TYPE_UPDATE_CARD = 'lovelace/config/card/update'
|
|
|
|
WS_TYPE_ADD_CARD = 'lovelace/config/card/add'
|
2018-10-26 10:56:14 +00:00
|
|
|
WS_TYPE_MOVE_CARD = 'lovelace/config/card/move'
|
2018-10-26 15:29:33 +00:00
|
|
|
WS_TYPE_DELETE_CARD = 'lovelace/config/card/delete'
|
2018-10-17 14:31:06 +00:00
|
|
|
|
2018-09-25 06:39:35 +00:00
|
|
|
SCHEMA_GET_LOVELACE_UI = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend({
|
|
|
|
vol.Required('type'): vol.Any(WS_TYPE_GET_LOVELACE_UI,
|
|
|
|
OLD_WS_TYPE_GET_LOVELACE_UI),
|
|
|
|
})
|
|
|
|
|
2018-10-27 21:51:40 +00:00
|
|
|
SCHEMA_MIGRATE_CONFIG = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend({
|
|
|
|
vol.Required('type'): WS_TYPE_MIGRATE_CONFIG,
|
|
|
|
})
|
|
|
|
|
2018-10-22 12:45:13 +00:00
|
|
|
SCHEMA_GET_CARD = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend({
|
|
|
|
vol.Required('type'): WS_TYPE_GET_CARD,
|
|
|
|
vol.Required('card_id'): str,
|
2018-10-23 19:48:35 +00:00
|
|
|
vol.Optional('format', default=FORMAT_YAML): vol.Any(FORMAT_JSON,
|
|
|
|
FORMAT_YAML),
|
2018-10-22 12:45:13 +00:00
|
|
|
})
|
|
|
|
|
2018-10-23 19:48:35 +00:00
|
|
|
SCHEMA_UPDATE_CARD = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend({
|
|
|
|
vol.Required('type'): WS_TYPE_UPDATE_CARD,
|
2018-10-22 12:45:13 +00:00
|
|
|
vol.Required('card_id'): str,
|
|
|
|
vol.Required('card_config'): vol.Any(str, Dict),
|
2018-10-23 19:48:35 +00:00
|
|
|
vol.Optional('format', default=FORMAT_YAML): vol.Any(FORMAT_JSON,
|
|
|
|
FORMAT_YAML),
|
|
|
|
})
|
|
|
|
|
|
|
|
SCHEMA_ADD_CARD = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend({
|
|
|
|
vol.Required('type'): WS_TYPE_ADD_CARD,
|
|
|
|
vol.Required('view_id'): str,
|
|
|
|
vol.Required('card_config'): vol.Any(str, Dict),
|
|
|
|
vol.Optional('position'): int,
|
|
|
|
vol.Optional('format', default=FORMAT_YAML): vol.Any(FORMAT_JSON,
|
|
|
|
FORMAT_YAML),
|
2018-10-22 12:45:13 +00:00
|
|
|
})
|
2018-10-17 14:31:06 +00:00
|
|
|
|
2018-10-26 10:56:14 +00:00
|
|
|
SCHEMA_MOVE_CARD = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend({
|
|
|
|
vol.Required('type'): WS_TYPE_MOVE_CARD,
|
|
|
|
vol.Required('card_id'): str,
|
|
|
|
vol.Optional('new_position'): int,
|
|
|
|
vol.Optional('new_view_id'): str,
|
|
|
|
})
|
|
|
|
|
2018-10-26 15:29:33 +00:00
|
|
|
SCHEMA_DELETE_CARD = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend({
|
|
|
|
vol.Required('type'): WS_TYPE_DELETE_CARD,
|
|
|
|
vol.Required('card_id'): str,
|
|
|
|
})
|
|
|
|
|
2018-10-17 14:31:06 +00:00
|
|
|
|
2018-10-22 12:45:13 +00:00
|
|
|
class CardNotFoundError(HomeAssistantError):
|
|
|
|
"""Card not found in data."""
|
|
|
|
|
|
|
|
|
2018-10-23 19:48:35 +00:00
|
|
|
class ViewNotFoundError(HomeAssistantError):
|
|
|
|
"""View not found in data."""
|
|
|
|
|
|
|
|
|
2018-10-26 10:56:14 +00:00
|
|
|
class DuplicateIdError(HomeAssistantError):
|
|
|
|
"""Duplicate ID's."""
|
|
|
|
|
|
|
|
|
2018-10-17 14:31:06 +00:00
|
|
|
def load_config(fname: str) -> JSON_TYPE:
|
2018-10-27 21:51:40 +00:00
|
|
|
"""Load a YAML file."""
|
2018-10-31 12:49:54 +00:00
|
|
|
return yaml.load_yaml(fname, False)
|
2018-10-27 21:51:40 +00:00
|
|
|
|
|
|
|
|
2018-10-31 12:49:54 +00:00
|
|
|
def migrate_config(fname: str) -> None:
|
|
|
|
"""Add id to views and cards if not present and check duplicates."""
|
|
|
|
config = yaml.load_yaml(fname, True)
|
2018-10-17 14:31:06 +00:00
|
|
|
updated = False
|
2018-10-26 10:56:14 +00:00
|
|
|
seen_card_ids = set()
|
|
|
|
seen_view_ids = set()
|
2018-10-22 12:45:13 +00:00
|
|
|
index = 0
|
2018-10-17 14:31:06 +00:00
|
|
|
for view in config.get('views', []):
|
2018-10-31 12:49:54 +00:00
|
|
|
view_id = str(view.get('id', ''))
|
|
|
|
if not view_id:
|
2018-10-22 12:45:13 +00:00
|
|
|
updated = True
|
|
|
|
view.insert(0, 'id', index,
|
|
|
|
comment="Automatically created id")
|
2018-10-26 10:56:14 +00:00
|
|
|
else:
|
|
|
|
if view_id in seen_view_ids:
|
|
|
|
raise DuplicateIdError(
|
|
|
|
'ID `{}` has multiple occurances in views'.format(view_id))
|
|
|
|
seen_view_ids.add(view_id)
|
2018-10-17 14:31:06 +00:00
|
|
|
for card in view.get('cards', []):
|
2018-10-31 12:49:54 +00:00
|
|
|
card_id = str(card.get('id', ''))
|
|
|
|
if not card_id:
|
2018-10-17 14:31:06 +00:00
|
|
|
updated = True
|
2018-10-22 12:45:13 +00:00
|
|
|
card.insert(0, 'id', uuid.uuid4().hex,
|
|
|
|
comment="Automatically created id")
|
2018-10-26 10:56:14 +00:00
|
|
|
else:
|
|
|
|
if card_id in seen_card_ids:
|
|
|
|
raise DuplicateIdError(
|
|
|
|
'ID `{}` has multiple occurances in cards'
|
|
|
|
.format(card_id))
|
|
|
|
seen_card_ids.add(card_id)
|
2018-10-22 12:45:13 +00:00
|
|
|
index += 1
|
2018-10-17 14:31:06 +00:00
|
|
|
if updated:
|
2018-10-31 12:49:54 +00:00
|
|
|
yaml.save_yaml(fname, config)
|
2018-10-22 12:45:13 +00:00
|
|
|
|
|
|
|
|
2018-10-23 19:48:35 +00:00
|
|
|
def get_card(fname: str, card_id: str, data_format: str = FORMAT_YAML)\
|
|
|
|
-> JSON_TYPE:
|
2018-10-22 12:45:13 +00:00
|
|
|
"""Load a specific card config for id."""
|
2018-10-31 12:49:54 +00:00
|
|
|
round_trip = data_format == FORMAT_YAML
|
|
|
|
|
|
|
|
config = yaml.load_yaml(fname, round_trip)
|
|
|
|
|
2018-10-22 12:45:13 +00:00
|
|
|
for view in config.get('views', []):
|
|
|
|
for card in view.get('cards', []):
|
2018-10-31 12:49:54 +00:00
|
|
|
if str(card.get('id', '')) != card_id:
|
2018-10-23 19:48:35 +00:00
|
|
|
continue
|
|
|
|
if data_format == FORMAT_YAML:
|
2018-10-31 12:49:54 +00:00
|
|
|
return yaml.object_to_yaml(card)
|
2018-10-23 19:48:35 +00:00
|
|
|
return card
|
2018-10-22 12:45:13 +00:00
|
|
|
|
|
|
|
raise CardNotFoundError(
|
|
|
|
"Card with ID: {} was not found in {}.".format(card_id, fname))
|
|
|
|
|
|
|
|
|
2018-10-23 19:48:35 +00:00
|
|
|
def update_card(fname: str, card_id: str, card_config: str,
|
2018-10-31 12:49:54 +00:00
|
|
|
data_format: str = FORMAT_YAML) -> None:
|
2018-10-22 12:45:13 +00:00
|
|
|
"""Save a specific card config for id."""
|
2018-10-31 12:49:54 +00:00
|
|
|
config = yaml.load_yaml(fname, True)
|
2018-10-22 12:45:13 +00:00
|
|
|
for view in config.get('views', []):
|
|
|
|
for card in view.get('cards', []):
|
2018-10-31 12:49:54 +00:00
|
|
|
if str(card.get('id', '')) != card_id:
|
2018-10-23 19:48:35 +00:00
|
|
|
continue
|
|
|
|
if data_format == FORMAT_YAML:
|
2018-10-31 12:49:54 +00:00
|
|
|
card_config = yaml.yaml_to_object(card_config)
|
2018-10-23 19:48:35 +00:00
|
|
|
card.update(card_config)
|
2018-10-31 12:49:54 +00:00
|
|
|
yaml.save_yaml(fname, config)
|
2018-10-23 19:48:35 +00:00
|
|
|
return
|
2018-10-22 12:45:13 +00:00
|
|
|
|
|
|
|
raise CardNotFoundError(
|
|
|
|
"Card with ID: {} was not found in {}.".format(card_id, fname))
|
|
|
|
|
|
|
|
|
2018-10-23 19:48:35 +00:00
|
|
|
def add_card(fname: str, view_id: str, card_config: str,
|
2018-10-31 12:49:54 +00:00
|
|
|
position: int = None, data_format: str = FORMAT_YAML) -> None:
|
2018-10-23 19:48:35 +00:00
|
|
|
"""Add a card to a view."""
|
2018-10-31 12:49:54 +00:00
|
|
|
config = yaml.load_yaml(fname, True)
|
2018-10-23 19:48:35 +00:00
|
|
|
for view in config.get('views', []):
|
2018-10-31 12:49:54 +00:00
|
|
|
if str(view.get('id', '')) != view_id:
|
2018-10-23 19:48:35 +00:00
|
|
|
continue
|
|
|
|
cards = view.get('cards', [])
|
|
|
|
if data_format == FORMAT_YAML:
|
2018-10-31 12:49:54 +00:00
|
|
|
card_config = yaml.yaml_to_object(card_config)
|
2018-10-23 19:48:35 +00:00
|
|
|
if position is None:
|
|
|
|
cards.append(card_config)
|
|
|
|
else:
|
|
|
|
cards.insert(position, card_config)
|
2018-10-31 12:49:54 +00:00
|
|
|
yaml.save_yaml(fname, config)
|
2018-10-23 19:48:35 +00:00
|
|
|
return
|
|
|
|
|
|
|
|
raise ViewNotFoundError(
|
|
|
|
"View with ID: {} was not found in {}.".format(view_id, fname))
|
|
|
|
|
|
|
|
|
2018-10-31 12:49:54 +00:00
|
|
|
def move_card(fname: str, card_id: str, position: int = None) -> None:
|
2018-10-26 10:56:14 +00:00
|
|
|
"""Move a card to a different position."""
|
|
|
|
if position is None:
|
|
|
|
raise HomeAssistantError('Position is required if view is not\
|
|
|
|
specified.')
|
2018-10-31 12:49:54 +00:00
|
|
|
config = yaml.load_yaml(fname, True)
|
2018-10-26 10:56:14 +00:00
|
|
|
for view in config.get('views', []):
|
|
|
|
for card in view.get('cards', []):
|
2018-10-31 12:49:54 +00:00
|
|
|
if str(card.get('id', '')) != card_id:
|
2018-10-26 10:56:14 +00:00
|
|
|
continue
|
|
|
|
cards = view.get('cards')
|
|
|
|
cards.insert(position, cards.pop(cards.index(card)))
|
2018-10-31 12:49:54 +00:00
|
|
|
yaml.save_yaml(fname, config)
|
2018-10-26 10:56:14 +00:00
|
|
|
return
|
|
|
|
|
|
|
|
raise CardNotFoundError(
|
|
|
|
"Card with ID: {} was not found in {}.".format(card_id, fname))
|
|
|
|
|
|
|
|
|
|
|
|
def move_card_view(fname: str, card_id: str, view_id: str,
|
2018-10-31 12:49:54 +00:00
|
|
|
position: int = None) -> None:
|
2018-10-26 10:56:14 +00:00
|
|
|
"""Move a card to a different view."""
|
2018-10-31 12:49:54 +00:00
|
|
|
config = yaml.load_yaml(fname, True)
|
2018-10-26 10:56:14 +00:00
|
|
|
for view in config.get('views', []):
|
2018-10-31 12:49:54 +00:00
|
|
|
if str(view.get('id', '')) == view_id:
|
2018-10-26 10:56:14 +00:00
|
|
|
destination = view.get('cards')
|
|
|
|
for card in view.get('cards'):
|
2018-10-31 12:49:54 +00:00
|
|
|
if str(card.get('id', '')) != card_id:
|
2018-10-26 10:56:14 +00:00
|
|
|
continue
|
|
|
|
origin = view.get('cards')
|
|
|
|
card_to_move = card
|
|
|
|
|
|
|
|
if 'destination' not in locals():
|
|
|
|
raise ViewNotFoundError(
|
|
|
|
"View with ID: {} was not found in {}.".format(view_id, fname))
|
|
|
|
if 'card_to_move' not in locals():
|
|
|
|
raise CardNotFoundError(
|
|
|
|
"Card with ID: {} was not found in {}.".format(card_id, fname))
|
|
|
|
|
|
|
|
origin.pop(origin.index(card_to_move))
|
|
|
|
|
|
|
|
if position is None:
|
|
|
|
destination.append(card_to_move)
|
|
|
|
else:
|
|
|
|
destination.insert(position, card_to_move)
|
|
|
|
|
2018-10-31 12:49:54 +00:00
|
|
|
yaml.save_yaml(fname, config)
|
2018-10-26 10:56:14 +00:00
|
|
|
|
|
|
|
|
2018-10-31 12:49:54 +00:00
|
|
|
def delete_card(fname: str, card_id: str, position: int = None) -> None:
|
2018-10-26 15:29:33 +00:00
|
|
|
"""Delete a card from view."""
|
2018-10-31 12:49:54 +00:00
|
|
|
config = yaml.load_yaml(fname, True)
|
2018-10-26 15:29:33 +00:00
|
|
|
for view in config.get('views', []):
|
|
|
|
for card in view.get('cards', []):
|
2018-10-31 12:49:54 +00:00
|
|
|
if str(card.get('id', '')) != card_id:
|
2018-10-26 15:29:33 +00:00
|
|
|
continue
|
|
|
|
cards = view.get('cards')
|
|
|
|
cards.pop(cards.index(card))
|
2018-10-31 12:49:54 +00:00
|
|
|
yaml.save_yaml(fname, config)
|
2018-10-26 15:29:33 +00:00
|
|
|
return
|
|
|
|
|
|
|
|
raise CardNotFoundError(
|
|
|
|
"Card with ID: {} was not found in {}.".format(card_id, fname))
|
|
|
|
|
|
|
|
|
2018-09-25 06:39:35 +00:00
|
|
|
async def async_setup(hass, config):
|
|
|
|
"""Set up the Lovelace commands."""
|
|
|
|
# Backwards compat. Added in 0.80. Remove after 0.85
|
|
|
|
hass.components.websocket_api.async_register_command(
|
|
|
|
OLD_WS_TYPE_GET_LOVELACE_UI, websocket_lovelace_config,
|
|
|
|
SCHEMA_GET_LOVELACE_UI)
|
|
|
|
|
2018-10-27 21:51:40 +00:00
|
|
|
hass.components.websocket_api.async_register_command(
|
|
|
|
WS_TYPE_MIGRATE_CONFIG, websocket_lovelace_migrate_config,
|
|
|
|
SCHEMA_MIGRATE_CONFIG)
|
|
|
|
|
2018-09-25 06:39:35 +00:00
|
|
|
hass.components.websocket_api.async_register_command(
|
|
|
|
WS_TYPE_GET_LOVELACE_UI, websocket_lovelace_config,
|
|
|
|
SCHEMA_GET_LOVELACE_UI)
|
|
|
|
|
2018-10-22 12:45:13 +00:00
|
|
|
hass.components.websocket_api.async_register_command(
|
|
|
|
WS_TYPE_GET_CARD, websocket_lovelace_get_card,
|
|
|
|
SCHEMA_GET_CARD)
|
|
|
|
|
|
|
|
hass.components.websocket_api.async_register_command(
|
2018-10-23 19:48:35 +00:00
|
|
|
WS_TYPE_UPDATE_CARD, websocket_lovelace_update_card,
|
|
|
|
SCHEMA_UPDATE_CARD)
|
|
|
|
|
|
|
|
hass.components.websocket_api.async_register_command(
|
|
|
|
WS_TYPE_ADD_CARD, websocket_lovelace_add_card,
|
|
|
|
SCHEMA_ADD_CARD)
|
2018-10-22 12:45:13 +00:00
|
|
|
|
2018-10-26 10:56:14 +00:00
|
|
|
hass.components.websocket_api.async_register_command(
|
|
|
|
WS_TYPE_MOVE_CARD, websocket_lovelace_move_card,
|
|
|
|
SCHEMA_MOVE_CARD)
|
|
|
|
|
2018-10-26 15:29:33 +00:00
|
|
|
hass.components.websocket_api.async_register_command(
|
|
|
|
WS_TYPE_DELETE_CARD, websocket_lovelace_delete_card,
|
|
|
|
SCHEMA_DELETE_CARD)
|
|
|
|
|
2018-09-25 06:39:35 +00:00
|
|
|
return True
|
|
|
|
|
|
|
|
|
2018-10-31 12:49:54 +00:00
|
|
|
def handle_yaml_errors(func):
|
|
|
|
"""Handle error with websocket calls."""
|
|
|
|
@wraps(func)
|
|
|
|
async def send_with_error_handling(hass, connection, msg):
|
|
|
|
error = None
|
|
|
|
try:
|
|
|
|
result = await func(hass, connection, msg)
|
|
|
|
message = websocket_api.result_message(
|
|
|
|
msg['id'], result
|
|
|
|
)
|
|
|
|
except FileNotFoundError:
|
|
|
|
error = ('file_not_found',
|
|
|
|
'Could not find ui-lovelace.yaml in your config dir.')
|
|
|
|
except yaml.UnsupportedYamlError as err:
|
|
|
|
error = 'unsupported_error', str(err)
|
|
|
|
except yaml.WriteError as err:
|
|
|
|
error = 'write_error', str(err)
|
|
|
|
except CardNotFoundError as err:
|
|
|
|
error = 'card_not_found', str(err)
|
|
|
|
except ViewNotFoundError as err:
|
|
|
|
error = 'view_not_found', str(err)
|
|
|
|
except HomeAssistantError as err:
|
|
|
|
error = 'error', str(err)
|
|
|
|
|
|
|
|
if error is not None:
|
|
|
|
message = websocket_api.error_message(msg['id'], *error)
|
|
|
|
|
|
|
|
connection.send_message(message)
|
|
|
|
|
|
|
|
return send_with_error_handling
|
|
|
|
|
|
|
|
|
2018-09-25 06:39:35 +00:00
|
|
|
@websocket_api.async_response
|
2018-10-31 12:49:54 +00:00
|
|
|
@handle_yaml_errors
|
2018-09-25 06:39:35 +00:00
|
|
|
async def websocket_lovelace_config(hass, connection, msg):
|
|
|
|
"""Send lovelace UI config over websocket config."""
|
2018-10-31 12:49:54 +00:00
|
|
|
return await hass.async_add_executor_job(
|
|
|
|
load_config, hass.config.path(LOVELACE_CONFIG_FILE))
|
2018-10-27 21:51:40 +00:00
|
|
|
|
|
|
|
|
|
|
|
@websocket_api.async_response
|
2018-10-31 12:49:54 +00:00
|
|
|
@handle_yaml_errors
|
2018-10-27 21:51:40 +00:00
|
|
|
async def websocket_lovelace_migrate_config(hass, connection, msg):
|
|
|
|
"""Migrate lovelace UI config."""
|
2018-10-31 12:49:54 +00:00
|
|
|
return await hass.async_add_executor_job(
|
|
|
|
migrate_config, hass.config.path(LOVELACE_CONFIG_FILE))
|
2018-10-22 12:45:13 +00:00
|
|
|
|
|
|
|
|
|
|
|
@websocket_api.async_response
|
2018-10-31 12:49:54 +00:00
|
|
|
@handle_yaml_errors
|
2018-10-22 12:45:13 +00:00
|
|
|
async def websocket_lovelace_get_card(hass, connection, msg):
|
|
|
|
"""Send lovelace card config over websocket config."""
|
2018-10-31 12:49:54 +00:00
|
|
|
return await hass.async_add_executor_job(
|
|
|
|
get_card, hass.config.path(LOVELACE_CONFIG_FILE), msg['card_id'],
|
|
|
|
msg.get('format', FORMAT_YAML))
|
2018-10-22 12:45:13 +00:00
|
|
|
|
|
|
|
|
|
|
|
@websocket_api.async_response
|
2018-10-31 12:49:54 +00:00
|
|
|
@handle_yaml_errors
|
2018-10-23 19:48:35 +00:00
|
|
|
async def websocket_lovelace_update_card(hass, connection, msg):
|
2018-10-22 12:45:13 +00:00
|
|
|
"""Receive lovelace card config over websocket and save."""
|
2018-10-31 12:49:54 +00:00
|
|
|
return await hass.async_add_executor_job(
|
|
|
|
update_card, hass.config.path(LOVELACE_CONFIG_FILE),
|
|
|
|
msg['card_id'], msg['card_config'], msg.get('format', FORMAT_YAML))
|
2018-10-23 19:48:35 +00:00
|
|
|
|
|
|
|
|
|
|
|
@websocket_api.async_response
|
2018-10-31 12:49:54 +00:00
|
|
|
@handle_yaml_errors
|
2018-10-23 19:48:35 +00:00
|
|
|
async def websocket_lovelace_add_card(hass, connection, msg):
|
|
|
|
"""Add new card to view over websocket and save."""
|
2018-10-31 12:49:54 +00:00
|
|
|
return await hass.async_add_executor_job(
|
|
|
|
add_card, hass.config.path(LOVELACE_CONFIG_FILE),
|
|
|
|
msg['view_id'], msg['card_config'], msg.get('position'),
|
|
|
|
msg.get('format', FORMAT_YAML))
|
2018-10-26 10:56:14 +00:00
|
|
|
|
|
|
|
|
|
|
|
@websocket_api.async_response
|
2018-10-31 12:49:54 +00:00
|
|
|
@handle_yaml_errors
|
2018-10-26 10:56:14 +00:00
|
|
|
async def websocket_lovelace_move_card(hass, connection, msg):
|
|
|
|
"""Move card to different position over websocket and save."""
|
2018-10-31 12:49:54 +00:00
|
|
|
if 'new_view_id' in msg:
|
|
|
|
return await hass.async_add_executor_job(
|
|
|
|
move_card_view, hass.config.path(LOVELACE_CONFIG_FILE),
|
|
|
|
msg['card_id'], msg['new_view_id'], msg.get('new_position'))
|
|
|
|
|
|
|
|
return await hass.async_add_executor_job(
|
|
|
|
move_card, hass.config.path(LOVELACE_CONFIG_FILE),
|
|
|
|
msg['card_id'], msg.get('new_position'))
|
2018-10-26 15:29:33 +00:00
|
|
|
|
|
|
|
|
|
|
|
@websocket_api.async_response
|
2018-10-31 12:49:54 +00:00
|
|
|
@handle_yaml_errors
|
2018-10-26 15:29:33 +00:00
|
|
|
async def websocket_lovelace_delete_card(hass, connection, msg):
|
|
|
|
"""Delete card from lovelace over websocket and save."""
|
2018-10-31 12:49:54 +00:00
|
|
|
return await hass.async_add_executor_job(
|
|
|
|
delete_card, hass.config.path(LOVELACE_CONFIG_FILE),
|
|
|
|
msg['card_id'])
|