Lovelace resource mgmt (#32224)
* Add websockets commands for resource management * Add LL resource management Co-authored-by: Bram Kragten <mail@bramkragten.nl>pull/32255/head
parent
92988d60a7
commit
483d822272
|
@ -7,7 +7,7 @@ import voluptuous as vol
|
||||||
from homeassistant.components import frontend
|
from homeassistant.components import frontend
|
||||||
from homeassistant.const import CONF_FILENAME, CONF_ICON
|
from homeassistant.const import CONF_FILENAME, CONF_ICON
|
||||||
from homeassistant.exceptions import HomeAssistantError
|
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 homeassistant.util import sanitize_filename, slugify
|
||||||
|
|
||||||
from . import dashboard, resources, websocket
|
from . import dashboard, resources, websocket
|
||||||
|
@ -17,7 +17,9 @@ from .const import (
|
||||||
LOVELACE_CONFIG_FILE,
|
LOVELACE_CONFIG_FILE,
|
||||||
MODE_STORAGE,
|
MODE_STORAGE,
|
||||||
MODE_YAML,
|
MODE_YAML,
|
||||||
|
RESOURCE_CREATE_FIELDS,
|
||||||
RESOURCE_SCHEMA,
|
RESOURCE_SCHEMA,
|
||||||
|
RESOURCE_UPDATE_FIELDS,
|
||||||
)
|
)
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
@ -111,6 +113,14 @@ async def async_setup(hass, config):
|
||||||
|
|
||||||
resource_collection = resources.ResourceStorageCollection(hass, default_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(
|
hass.components.websocket_api.async_register_command(
|
||||||
websocket.websocket_lovelace_config
|
websocket.websocket_lovelace_config
|
||||||
)
|
)
|
||||||
|
|
|
@ -14,13 +14,27 @@ MODE_STORAGE = "storage"
|
||||||
LOVELACE_CONFIG_FILE = "ui-lovelace.yaml"
|
LOVELACE_CONFIG_FILE = "ui-lovelace.yaml"
|
||||||
CONF_RESOURCES = "resources"
|
CONF_RESOURCES = "resources"
|
||||||
CONF_URL_PATH = "url_path"
|
CONF_URL_PATH = "url_path"
|
||||||
|
CONF_RESOURCE_TYPE_WS = "res_type"
|
||||||
|
|
||||||
|
RESOURCE_TYPES = ["js", "css", "module", "html"]
|
||||||
|
|
||||||
RESOURCE_FIELDS = {
|
RESOURCE_FIELDS = {
|
||||||
CONF_TYPE: vol.In(["js", "css", "module", "html"]),
|
CONF_TYPE: vol.In(RESOURCE_TYPES),
|
||||||
CONF_URL: cv.string,
|
CONF_URL: cv.string,
|
||||||
}
|
}
|
||||||
|
|
||||||
RESOURCE_SCHEMA = vol.Schema(RESOURCE_FIELDS)
|
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):
|
class ConfigNotFound(HomeAssistantError):
|
||||||
"""When no config available."""
|
"""When no config available."""
|
||||||
|
|
|
@ -5,11 +5,19 @@ import uuid
|
||||||
|
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
|
from homeassistant.const import CONF_TYPE
|
||||||
from homeassistant.core import HomeAssistant, callback
|
from homeassistant.core import HomeAssistant, callback
|
||||||
from homeassistant.exceptions import HomeAssistantError
|
from homeassistant.exceptions import HomeAssistantError
|
||||||
from homeassistant.helpers import collection, storage
|
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
|
from .dashboard import LovelaceConfig
|
||||||
|
|
||||||
RESOURCE_STORAGE_KEY = f"{DOMAIN}_resources"
|
RESOURCE_STORAGE_KEY = f"{DOMAIN}_resources"
|
||||||
|
@ -36,6 +44,8 @@ class ResourceStorageCollection(collection.StorageCollection):
|
||||||
"""Collection to store resources."""
|
"""Collection to store resources."""
|
||||||
|
|
||||||
loaded = False
|
loaded = False
|
||||||
|
CREATE_SCHEMA = vol.Schema(RESOURCE_CREATE_FIELDS)
|
||||||
|
UPDATE_SCHEMA = vol.Schema(RESOURCE_UPDATE_FIELDS)
|
||||||
|
|
||||||
def __init__(self, hass: HomeAssistant, ll_config: LovelaceConfig):
|
def __init__(self, hass: HomeAssistant, ll_config: LovelaceConfig):
|
||||||
"""Initialize the storage collection."""
|
"""Initialize the storage collection."""
|
||||||
|
@ -84,13 +94,23 @@ class ResourceStorageCollection(collection.StorageCollection):
|
||||||
|
|
||||||
async def _process_create_data(self, data: dict) -> dict:
|
async def _process_create_data(self, data: dict) -> dict:
|
||||||
"""Validate the config is valid."""
|
"""Validate the config is valid."""
|
||||||
raise NotImplementedError
|
data = self.CREATE_SCHEMA(data)
|
||||||
|
data[CONF_TYPE] = data.pop(CONF_RESOURCE_TYPE_WS)
|
||||||
|
return data
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def _get_suggested_id(self, info: dict) -> str:
|
def _get_suggested_id(self, info: dict) -> str:
|
||||||
"""Suggest an ID based on the config."""
|
"""Return unique ID."""
|
||||||
raise NotImplementedError
|
return uuid.uuid4().hex
|
||||||
|
|
||||||
async def _update_data(self, data: dict, update_data: dict) -> dict:
|
async def _update_data(self, data: dict, update_data: dict) -> dict:
|
||||||
"""Return a new updated data object."""
|
"""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}
|
||||||
|
|
|
@ -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"]
|
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):
|
async def test_storage_resources_import_invalid(hass, hass_ws_client, hass_storage):
|
||||||
"""Test importing resources from storage config."""
|
"""Test importing resources from storage config."""
|
||||||
|
|
Loading…
Reference in New Issue