1 """Lovelace dashboard support."""
3 from __future__
import annotations
5 from abc
import ABC, abstractmethod
8 from pathlib
import Path
10 from typing
import Any
12 import voluptuous
as vol
24 CONF_ALLOW_SINGLE_WORD,
28 EVENT_LOVELACE_UPDATED,
32 STORAGE_DASHBOARD_CREATE_FIELDS,
33 STORAGE_DASHBOARD_UPDATE_FIELDS,
37 CONFIG_STORAGE_KEY_DEFAULT = DOMAIN
38 CONFIG_STORAGE_KEY =
"lovelace.{}"
39 CONFIG_STORAGE_VERSION = 1
40 DASHBOARDS_STORAGE_KEY = f
"{DOMAIN}_dashboards"
41 DASHBOARDS_STORAGE_VERSION = 1
42 _LOGGER = logging.getLogger(__name__)
46 """Base class for Lovelace config."""
49 self, hass: HomeAssistant, url_path: str |
None, config: dict[str, Any] |
None
51 """Initialize Lovelace config."""
54 self.
configconfig: dict[str, Any] |
None = {**config, CONF_URL_PATH: url_path}
60 """Return url path."""
61 return self.
configconfig[CONF_URL_PATH]
if self.
configconfig
else None
66 """Return mode of the lovelace config."""
70 """Return the config info."""
73 async
def async_load(self, force: bool) -> dict[str, Any]:
86 """Fire config updated event."""
87 self.
hasshass.bus.async_fire(EVENT_LOVELACE_UPDATED, {
"url_path": self.
url_pathurl_path})
91 """Class to handle Storage based Lovelace config."""
93 def __init__(self, hass: HomeAssistant, config: dict[str, Any] |
None) ->
None:
94 """Initialize Lovelace config based on storage helper."""
96 url_path: str |
None =
None
97 storage_key = CONFIG_STORAGE_KEY_DEFAULT
99 url_path = config[CONF_URL_PATH]
100 storage_key = CONFIG_STORAGE_KEY.format(config[
"id"])
102 super().
__init__(hass, url_path, config)
104 self.
_store_store = storage.Store[dict[str, Any]](
105 hass, CONFIG_STORAGE_VERSION, storage_key
107 self.
_data_data: dict[str, Any] |
None =
None
108 self.
_json_config_json_config: json_fragment |
None =
None
112 """Return mode of the lovelace config."""
116 """Return the Lovelace storage info."""
117 data = self.
_data_data
or await self.
_load_load()
118 if data[
"config"]
is None:
119 return {
"mode":
"auto-gen"}
124 if self.
hasshass.config.recovery_mode:
127 data = self.
_data_data
or await self.
_load_load()
128 if (config := data[
"config"])
is None:
134 """Return JSON representation of the config."""
135 if self.
hasshass.config.recovery_mode:
137 if self.
_data_data
is None:
138 await self.
_load_load()
143 if self.
hasshass.config.recovery_mode:
146 if self.
_data_data
is None:
147 await self.
_load_load()
148 self.
_data_data[
"config"] = config
155 if self.
hasshass.config.recovery_mode:
163 async
def _load(self) -> dict[str, Any]:
164 """Load the config."""
166 self.
_data_data = data
if data
else {
"config":
None}
167 return self.
_data_data
171 """Build JSON representation of the config."""
172 if self.
_data_data
is None or self.
_data_data[
"config"]
is None:
179 """Class to handle YAML-based Lovelace config."""
182 self, hass: HomeAssistant, url_path: str |
None, config: dict[str, Any] |
None
184 """Initialize the YAML config."""
185 super().
__init__(hass, url_path, config)
187 self.
pathpath = hass.config.path(
188 config[CONF_FILENAME]
if config
else LOVELACE_CONFIG_FILE
190 self.
_cache_cache: tuple[dict[str, Any], float, json_fragment] |
None =
None
194 """Return mode of the lovelace config."""
198 """Return the YAML storage mode."""
201 except ConfigNotFound:
204 "error": f
"{self.path} not found",
215 """Return JSON representation of the config."""
221 ) -> tuple[dict[str, Any], json_fragment]:
222 """Load the config or return a cached version."""
223 is_updated, config, json = await self.
hasshass.async_add_executor_job(
230 def _load_config(self, force: bool) -> tuple[bool, dict[str, Any], json_fragment]:
231 """Load the actual config."""
233 if not force
and self.
_cache_cache
is not None:
234 config, last_update, json = self.
_cache_cache
235 modtime = os.path.getmtime(self.
pathpath)
236 if config
and last_update > modtime:
237 return False, config, json
239 is_updated = self.
_cache_cache
is not None
243 self.
pathpath, Secrets(Path(self.
hasshass.config.config_dir))
245 except FileNotFoundError:
246 raise ConfigNotFound
from None
249 self.
_cache_cache = (config, time.time(), json)
250 return is_updated, config, json
254 """Generate info about the config."""
257 "views": len(config.get(
"views", [])),
262 """Collection of dashboards."""
264 CREATE_SCHEMA = vol.Schema(STORAGE_DASHBOARD_CREATE_FIELDS)
265 UPDATE_SCHEMA = vol.Schema(STORAGE_DASHBOARD_UPDATE_FIELDS)
268 """Initialize the dashboards collection."""
270 storage.Store(hass, DASHBOARDS_STORAGE_VERSION, DASHBOARDS_STORAGE_KEY),
274 """Validate the config is valid."""
275 url_path = data[CONF_URL_PATH]
277 allow_single_word = data.pop(CONF_ALLOW_SINGLE_WORD,
False)
279 if not allow_single_word
and "-" not in url_path:
280 raise vol.Invalid(
"Url path needs to contain a hyphen (-)")
282 if url_path
in self.hass.data[DATA_PANELS]:
283 raise vol.Invalid(
"Panel url path needs to be unique")
289 """Suggest an ID based on the config."""
290 return info[CONF_URL_PATH]
293 """Return a new updated data object."""
295 updated = {**item, **update_data}
297 if CONF_ICON
in updated
and updated[CONF_ICON]
is None:
298 updated.pop(CONF_ICON)
304 """Class to expose storage collection management over websocket."""
313 """Send Lovelace UI resources over WebSocket connection."""
314 connection.send_result(
318 for dashboard
in hass.data[DOMAIN][
"dashboards"].values()
None ws_list_item(self, HomeAssistant hass, websocket_api.ActiveConnection connection, dict[str, Any] msg)
str _get_suggested_id(self, dict info)
dict _process_create_data(self, dict data)
dict _update_data(self, dict item, dict update_data)
dict[str, Any] async_load(self, bool force)
def async_save(self, config)
None _config_updated(self)
None __init__(self, HomeAssistant hass, str|None url_path, dict[str, Any]|None config)
json_fragment async_json(self, bool force)
def async_save(self, config)
dict[str, Any] async_load(self, bool force)
dict[str, Any] _load(self)
None __init__(self, HomeAssistant hass, dict[str, Any]|None config)
json_fragment _async_build_json(self)
tuple[bool, dict[str, Any], json_fragment] _load_config(self, bool force)
json_fragment async_json(self, bool force)
tuple[dict[str, Any], json_fragment] _async_load_or_cached(self, bool force)
None __init__(self, HomeAssistant hass, str|None url_path, dict[str, Any]|None config)
dict[str, Any] async_load(self, bool force)
def _config_info(mode, config)
None async_remove(HomeAssistant hass, str intent_type)
dict load_yaml_dict(str|os.PathLike[str] fname, Secrets|None secrets=None)