Skip to content

Commit

Permalink
Lovelace resource mgmt (#32224)
Browse files Browse the repository at this point in the history
* Add websockets commands for resource management

* Add LL resource management

Co-authored-by: Bram Kragten <[email protected]>
  • Loading branch information
balloob and bramkragten authored Feb 27, 2020
1 parent 92988d6 commit 483d822
Show file tree
Hide file tree
Showing 4 changed files with 112 additions and 7 deletions.
12 changes: 11 additions & 1 deletion homeassistant/components/lovelace/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from homeassistant.components import frontend
from homeassistant.const import CONF_FILENAME, CONF_ICON
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers import collection, config_validation as cv
from homeassistant.util import sanitize_filename, slugify

from . import dashboard, resources, websocket
Expand All @@ -17,7 +17,9 @@
LOVELACE_CONFIG_FILE,
MODE_STORAGE,
MODE_YAML,
RESOURCE_CREATE_FIELDS,
RESOURCE_SCHEMA,
RESOURCE_UPDATE_FIELDS,
)

_LOGGER = logging.getLogger(__name__)
Expand Down Expand Up @@ -111,6 +113,14 @@ async def async_setup(hass, config):

resource_collection = resources.ResourceStorageCollection(hass, default_config)

collection.StorageCollectionWebsocket(
resource_collection,
"lovelace/resources",
"resource",
RESOURCE_CREATE_FIELDS,
RESOURCE_UPDATE_FIELDS,
).async_setup(hass, create_list=False)

hass.components.websocket_api.async_register_command(
websocket.websocket_lovelace_config
)
Expand Down
16 changes: 15 additions & 1 deletion homeassistant/components/lovelace/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,27 @@
LOVELACE_CONFIG_FILE = "ui-lovelace.yaml"
CONF_RESOURCES = "resources"
CONF_URL_PATH = "url_path"
CONF_RESOURCE_TYPE_WS = "res_type"

RESOURCE_TYPES = ["js", "css", "module", "html"]

RESOURCE_FIELDS = {
CONF_TYPE: vol.In(["js", "css", "module", "html"]),
CONF_TYPE: vol.In(RESOURCE_TYPES),
CONF_URL: cv.string,
}

RESOURCE_SCHEMA = vol.Schema(RESOURCE_FIELDS)

RESOURCE_CREATE_FIELDS = {
vol.Required(CONF_RESOURCE_TYPE_WS): vol.In(RESOURCE_TYPES),
vol.Required(CONF_URL): cv.string,
}

RESOURCE_UPDATE_FIELDS = {
vol.Optional(CONF_RESOURCE_TYPE_WS): vol.In(RESOURCE_TYPES),
vol.Optional(CONF_URL): cv.string,
}


class ConfigNotFound(HomeAssistantError):
"""When no config available."""
30 changes: 25 additions & 5 deletions homeassistant/components/lovelace/resources.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,19 @@

import voluptuous as vol

from homeassistant.const import CONF_TYPE
from homeassistant.core import HomeAssistant, callback
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import collection, storage

from .const import CONF_RESOURCES, DOMAIN, RESOURCE_SCHEMA
from .const import (
CONF_RESOURCE_TYPE_WS,
CONF_RESOURCES,
DOMAIN,
RESOURCE_CREATE_FIELDS,
RESOURCE_SCHEMA,
RESOURCE_UPDATE_FIELDS,
)
from .dashboard import LovelaceConfig

RESOURCE_STORAGE_KEY = f"{DOMAIN}_resources"
Expand All @@ -36,6 +44,8 @@ class ResourceStorageCollection(collection.StorageCollection):
"""Collection to store resources."""

loaded = False
CREATE_SCHEMA = vol.Schema(RESOURCE_CREATE_FIELDS)
UPDATE_SCHEMA = vol.Schema(RESOURCE_UPDATE_FIELDS)

def __init__(self, hass: HomeAssistant, ll_config: LovelaceConfig):
"""Initialize the storage collection."""
Expand Down Expand Up @@ -84,13 +94,23 @@ async def _async_load_data(self) -> Optional[dict]:

async def _process_create_data(self, data: dict) -> dict:
"""Validate the config is valid."""
raise NotImplementedError
data = self.CREATE_SCHEMA(data)
data[CONF_TYPE] = data.pop(CONF_RESOURCE_TYPE_WS)
return data

@callback
def _get_suggested_id(self, info: dict) -> str:
"""Suggest an ID based on the config."""
raise NotImplementedError
"""Return unique ID."""
return uuid.uuid4().hex

async def _update_data(self, data: dict, update_data: dict) -> dict:
"""Return a new updated data object."""
raise NotImplementedError
if not self.loaded:
await self.async_load()
self.loaded = True

update_data = self.UPDATE_SCHEMA(update_data)
if CONF_RESOURCE_TYPE_WS in update_data:
update_data[CONF_TYPE] = update_data.pop(CONF_RESOURCE_TYPE_WS)

return {**data, **update_data}
61 changes: 61 additions & 0 deletions tests/components/lovelace/test_resources.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,67 @@ async def test_storage_resources_import(hass, hass_ws_client, hass_storage):
not in hass_storage[dashboard.CONFIG_STORAGE_KEY_DEFAULT]["data"]["config"]
)

# Add a resource
await client.send_json(
{
"id": 6,
"type": "lovelace/resources/create",
"res_type": "module",
"url": "/local/yo.js",
}
)
response = await client.receive_json()
assert response["success"]

await client.send_json({"id": 7, "type": "lovelace/resources"})
response = await client.receive_json()
assert response["success"]

last_item = response["result"][-1]
assert last_item["type"] == "module"
assert last_item["url"] == "/local/yo.js"

# Update a resource
first_item = response["result"][0]

await client.send_json(
{
"id": 8,
"type": "lovelace/resources/update",
"resource_id": first_item["id"],
"res_type": "css",
"url": "/local/updated.css",
}
)
response = await client.receive_json()
assert response["success"]

await client.send_json({"id": 9, "type": "lovelace/resources"})
response = await client.receive_json()
assert response["success"]

first_item = response["result"][0]
assert first_item["type"] == "css"
assert first_item["url"] == "/local/updated.css"

# Delete resources
await client.send_json(
{
"id": 10,
"type": "lovelace/resources/delete",
"resource_id": first_item["id"],
}
)
response = await client.receive_json()
assert response["success"]

await client.send_json({"id": 11, "type": "lovelace/resources"})
response = await client.receive_json()
assert response["success"]

assert len(response["result"]) == 2
assert first_item["id"] not in (item["id"] for item in response["result"])


async def test_storage_resources_import_invalid(hass, hass_ws_client, hass_storage):
"""Test importing resources from storage config."""
Expand Down

0 comments on commit 483d822

Please sign in to comment.