2019-02-14 05:35:12 +01:00
|
|
|
"""Support for the Lovelace UI."""
|
2018-11-03 10:24:02 +01:00
|
|
|
import logging
|
2020-02-25 11:18:21 -08:00
|
|
|
from typing import Any
|
2018-10-22 14:45:13 +02:00
|
|
|
|
2018-09-25 08:39:35 +02:00
|
|
|
import voluptuous as vol
|
|
|
|
|
2020-02-25 11:18:21 -08:00
|
|
|
from homeassistant.components import frontend
|
|
|
|
from homeassistant.const import CONF_FILENAME, CONF_ICON
|
2018-09-25 08:39:35 +02:00
|
|
|
from homeassistant.exceptions import HomeAssistantError
|
2020-02-25 11:18:21 -08:00
|
|
|
from homeassistant.helpers import config_validation as cv
|
|
|
|
from homeassistant.util import sanitize_filename, slugify
|
|
|
|
|
|
|
|
from . import dashboard, resources, websocket
|
|
|
|
from .const import (
|
|
|
|
CONF_RESOURCES,
|
|
|
|
DOMAIN,
|
|
|
|
LOVELACE_CONFIG_FILE,
|
|
|
|
MODE_STORAGE,
|
|
|
|
MODE_YAML,
|
|
|
|
RESOURCE_SCHEMA,
|
|
|
|
)
|
2018-09-25 08:39:35 +02:00
|
|
|
|
2018-10-17 16:31:06 +02:00
|
|
|
_LOGGER = logging.getLogger(__name__)
|
2018-11-03 10:24:02 +01:00
|
|
|
|
2019-07-31 12:25:30 -07:00
|
|
|
CONF_MODE = "mode"
|
2020-02-25 11:18:21 -08:00
|
|
|
|
|
|
|
CONF_DASHBOARDS = "dashboards"
|
|
|
|
CONF_SIDEBAR = "sidebar"
|
|
|
|
CONF_TITLE = "title"
|
|
|
|
CONF_REQUIRE_ADMIN = "require_admin"
|
|
|
|
|
|
|
|
DASHBOARD_BASE_SCHEMA = vol.Schema(
|
|
|
|
{
|
|
|
|
vol.Optional(CONF_REQUIRE_ADMIN, default=False): cv.boolean,
|
|
|
|
vol.Optional(CONF_SIDEBAR): {
|
|
|
|
vol.Required(CONF_ICON): cv.icon,
|
|
|
|
vol.Required(CONF_TITLE): cv.string,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
)
|
|
|
|
|
|
|
|
YAML_DASHBOARD_SCHEMA = DASHBOARD_BASE_SCHEMA.extend(
|
|
|
|
{
|
|
|
|
vol.Required(CONF_MODE): MODE_YAML,
|
|
|
|
vol.Required(CONF_FILENAME): vol.All(cv.string, sanitize_filename),
|
|
|
|
}
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
def url_slug(value: Any) -> str:
|
|
|
|
"""Validate value is a valid url slug."""
|
|
|
|
if value is None:
|
|
|
|
raise vol.Invalid("Slug should not be None")
|
|
|
|
str_value = str(value)
|
|
|
|
slg = slugify(str_value, separator="-")
|
|
|
|
if str_value == slg:
|
|
|
|
return str_value
|
|
|
|
raise vol.Invalid(f"invalid slug {value} (try {slg})")
|
|
|
|
|
2019-07-31 12:25:30 -07:00
|
|
|
|
|
|
|
CONFIG_SCHEMA = vol.Schema(
|
|
|
|
{
|
2020-02-25 11:18:21 -08:00
|
|
|
vol.Optional(DOMAIN, default={}): vol.Schema(
|
2019-07-31 12:25:30 -07:00
|
|
|
{
|
|
|
|
vol.Optional(CONF_MODE, default=MODE_STORAGE): vol.All(
|
|
|
|
vol.Lower, vol.In([MODE_YAML, MODE_STORAGE])
|
2020-02-25 11:18:21 -08:00
|
|
|
),
|
|
|
|
vol.Optional(CONF_DASHBOARDS): cv.schema_with_slug_keys(
|
|
|
|
YAML_DASHBOARD_SCHEMA, slug_validator=url_slug,
|
|
|
|
),
|
|
|
|
vol.Optional(CONF_RESOURCES): [RESOURCE_SCHEMA],
|
2019-07-31 12:25:30 -07:00
|
|
|
}
|
|
|
|
)
|
|
|
|
},
|
|
|
|
extra=vol.ALLOW_EXTRA,
|
|
|
|
)
|
2018-09-25 08:39:35 +02:00
|
|
|
|
2018-10-26 12:56:14 +02:00
|
|
|
|
2018-12-10 08:57:17 +01:00
|
|
|
async def async_setup(hass, config):
|
|
|
|
"""Set up the Lovelace commands."""
|
|
|
|
# Pass in default to `get` because defaults not set if loaded as dep
|
2020-02-25 11:18:21 -08:00
|
|
|
mode = config[DOMAIN][CONF_MODE]
|
|
|
|
yaml_resources = config[DOMAIN].get(CONF_RESOURCES)
|
2018-11-01 09:44:38 +01:00
|
|
|
|
2020-02-25 11:18:21 -08:00
|
|
|
frontend.async_register_built_in_panel(hass, DOMAIN, config={"mode": mode})
|
2018-10-17 16:31:06 +02:00
|
|
|
|
2018-12-10 08:57:17 +01:00
|
|
|
if mode == MODE_YAML:
|
2020-02-25 11:18:21 -08:00
|
|
|
default_config = dashboard.LovelaceYAML(hass, None, LOVELACE_CONFIG_FILE)
|
|
|
|
|
|
|
|
if yaml_resources is None:
|
|
|
|
try:
|
|
|
|
ll_conf = await default_config.async_load(False)
|
|
|
|
except HomeAssistantError:
|
|
|
|
pass
|
|
|
|
else:
|
|
|
|
if CONF_RESOURCES in ll_conf:
|
|
|
|
_LOGGER.warning(
|
|
|
|
"Resources need to be specified in your configuration.yaml. Please see the docs."
|
|
|
|
)
|
|
|
|
yaml_resources = ll_conf[CONF_RESOURCES]
|
|
|
|
|
|
|
|
resource_collection = resources.ResourceYAMLCollection(yaml_resources or [])
|
|
|
|
|
2018-10-26 12:56:14 +02:00
|
|
|
else:
|
2020-02-25 11:18:21 -08:00
|
|
|
default_config = dashboard.LovelaceStorage(hass, None)
|
2018-09-25 08:39:35 +02:00
|
|
|
|
2020-02-25 11:18:21 -08:00
|
|
|
if yaml_resources is not None:
|
|
|
|
_LOGGER.warning(
|
|
|
|
"Lovelace is running in storage mode. Define resources via user interface"
|
|
|
|
)
|
2020-01-07 22:59:04 +01:00
|
|
|
|
2020-02-25 11:18:21 -08:00
|
|
|
resource_collection = resources.ResourceStorageCollection(hass, default_config)
|
2018-11-23 22:56:58 +01:00
|
|
|
|
2018-09-25 08:39:35 +02:00
|
|
|
hass.components.websocket_api.async_register_command(
|
2020-02-25 11:18:21 -08:00
|
|
|
websocket.websocket_lovelace_config
|
|
|
|
)
|
|
|
|
hass.components.websocket_api.async_register_command(
|
|
|
|
websocket.websocket_lovelace_save_config
|
|
|
|
)
|
|
|
|
hass.components.websocket_api.async_register_command(
|
|
|
|
websocket.websocket_lovelace_delete_config
|
|
|
|
)
|
|
|
|
hass.components.websocket_api.async_register_command(
|
|
|
|
websocket.websocket_lovelace_resources
|
2019-07-31 12:25:30 -07:00
|
|
|
)
|
2018-09-25 08:39:35 +02:00
|
|
|
|
2019-07-31 12:25:30 -07:00
|
|
|
hass.components.system_health.async_register_info(DOMAIN, system_health_info)
|
2019-01-30 12:57:56 -08:00
|
|
|
|
2020-02-25 11:18:21 -08:00
|
|
|
hass.data[DOMAIN] = {
|
|
|
|
# We store a dictionary mapping url_path: config. None is the default.
|
|
|
|
"dashboards": {None: default_config},
|
|
|
|
"resources": resource_collection,
|
|
|
|
}
|
2018-12-10 08:57:17 +01:00
|
|
|
|
2020-02-25 11:18:21 -08:00
|
|
|
if hass.config.safe_mode or CONF_DASHBOARDS not in config[DOMAIN]:
|
|
|
|
return True
|
2018-09-25 08:39:35 +02:00
|
|
|
|
2020-02-25 11:18:21 -08:00
|
|
|
for url_path, dashboard_conf in config[DOMAIN][CONF_DASHBOARDS].items():
|
|
|
|
# For now always mode=yaml
|
|
|
|
config = dashboard.LovelaceYAML(hass, url_path, dashboard_conf[CONF_FILENAME])
|
|
|
|
hass.data[DOMAIN]["dashboards"][url_path] = config
|
2020-01-07 22:59:04 +01:00
|
|
|
|
2020-02-25 11:18:21 -08:00
|
|
|
kwargs = {
|
|
|
|
"hass": hass,
|
|
|
|
"component_name": DOMAIN,
|
|
|
|
"frontend_url_path": url_path,
|
|
|
|
"require_admin": dashboard_conf[CONF_REQUIRE_ADMIN],
|
|
|
|
"config": {"mode": dashboard_conf[CONF_MODE]},
|
|
|
|
}
|
2018-09-25 08:39:35 +02:00
|
|
|
|
2020-02-25 11:18:21 -08:00
|
|
|
if CONF_SIDEBAR in dashboard_conf:
|
|
|
|
kwargs["sidebar_title"] = dashboard_conf[CONF_SIDEBAR][CONF_TITLE]
|
|
|
|
kwargs["sidebar_icon"] = dashboard_conf[CONF_SIDEBAR][CONF_ICON]
|
2019-07-31 12:25:30 -07:00
|
|
|
|
2018-10-31 13:49:54 +01:00
|
|
|
try:
|
2020-02-25 11:18:21 -08:00
|
|
|
frontend.async_register_built_in_panel(**kwargs)
|
|
|
|
except ValueError:
|
|
|
|
_LOGGER.warning("Panel url path %s is not unique", url_path)
|
2018-10-22 14:45:13 +02:00
|
|
|
|
2020-02-25 11:18:21 -08:00
|
|
|
return True
|
2020-01-07 22:59:04 +01:00
|
|
|
|
|
|
|
|
2019-01-30 12:57:56 -08:00
|
|
|
async def system_health_info(hass):
|
|
|
|
"""Get info for the info page."""
|
2020-02-25 11:18:21 -08:00
|
|
|
return await hass.data[DOMAIN]["dashboards"][None].async_get_info()
|