102 lines
3.7 KiB
Python
102 lines
3.7 KiB
Python
"""WebSocket commands for saved filter views (v2.24).
|
|
|
|
``views/list`` returns the shared named views (read — any user applies them);
|
|
``views/save`` upserts one and ``views/delete`` removes one (write — creating or
|
|
deleting a shared view is a content change). Views live on the global config
|
|
entry's options; see ``helpers/saved_views`` for the shape + sanitiser.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from typing import Any
|
|
|
|
import voluptuous as vol
|
|
from homeassistant.components import websocket_api
|
|
from homeassistant.core import HomeAssistant
|
|
|
|
from ..const import CONF_SAVED_FILTER_VIEWS, DOMAIN, MAX_ID_LENGTH, MAX_VIEW_NAME_LENGTH
|
|
from ..helpers.permissions import require_write
|
|
from ..helpers.saved_views import list_saved_views, remove_view, sanitize_view, upsert_view
|
|
from . import _get_global_entry
|
|
|
|
|
|
def _persist(hass: HomeAssistant, views: list[dict[str, Any]]) -> None:
|
|
"""Write the views list back to the global entry's options."""
|
|
global_entry = _get_global_entry(hass)
|
|
if global_entry is None:
|
|
raise LookupError("global_entry_missing")
|
|
options = dict(global_entry.options or global_entry.data)
|
|
options[CONF_SAVED_FILTER_VIEWS] = views
|
|
hass.config_entries.async_update_entry(global_entry, options=options)
|
|
|
|
|
|
@websocket_api.websocket_command({vol.Required("type"): f"{DOMAIN}/views/list"})
|
|
@websocket_api.async_response
|
|
async def ws_list_saved_views(
|
|
hass: HomeAssistant,
|
|
connection: websocket_api.ActiveConnection,
|
|
msg: dict[str, Any],
|
|
) -> None:
|
|
"""Return every shared saved filter view."""
|
|
connection.send_result(msg["id"], {"views": list_saved_views(hass)})
|
|
|
|
|
|
@websocket_api.websocket_command(
|
|
{
|
|
vol.Required("type"): f"{DOMAIN}/views/save",
|
|
# Omit view_id to create; include it to update in place. (Not "id" — that
|
|
# key is the WebSocket message id the framework owns.)
|
|
vol.Optional("view_id"): vol.All(str, vol.Length(max=MAX_ID_LENGTH)),
|
|
vol.Required("name"): vol.All(str, vol.Length(min=1, max=MAX_VIEW_NAME_LENGTH)),
|
|
vol.Optional("filters"): dict,
|
|
}
|
|
)
|
|
@require_write
|
|
@websocket_api.async_response
|
|
async def ws_save_saved_view(
|
|
hass: HomeAssistant,
|
|
connection: websocket_api.ActiveConnection,
|
|
msg: dict[str, Any],
|
|
) -> None:
|
|
"""Create or update a shared saved view, then return the full list."""
|
|
clean = sanitize_view(
|
|
{"id": msg.get("view_id"), "name": msg["name"], "filters": msg.get("filters", {})},
|
|
view_id=msg.get("view_id"),
|
|
)
|
|
if clean is None:
|
|
connection.send_error(msg["id"], "invalid_view", "A view needs a non-empty name")
|
|
return
|
|
try:
|
|
views, saved_id = upsert_view(list_saved_views(hass), clean)
|
|
_persist(hass, views)
|
|
except ValueError:
|
|
connection.send_error(msg["id"], "too_many_views", "The saved-views limit has been reached")
|
|
return
|
|
except LookupError:
|
|
connection.send_error(msg["id"], "not_found", "Global entry not found")
|
|
return
|
|
connection.send_result(msg["id"], {"views": views, "saved_id": saved_id})
|
|
|
|
|
|
@websocket_api.websocket_command(
|
|
{
|
|
vol.Required("type"): f"{DOMAIN}/views/delete",
|
|
vol.Required("view_id"): vol.All(str, vol.Length(min=1, max=MAX_ID_LENGTH)),
|
|
}
|
|
)
|
|
@require_write
|
|
@websocket_api.async_response
|
|
async def ws_delete_saved_view(
|
|
hass: HomeAssistant,
|
|
connection: websocket_api.ActiveConnection,
|
|
msg: dict[str, Any],
|
|
) -> None:
|
|
"""Delete a shared saved view by id, then return the remaining list."""
|
|
views = remove_view(list_saved_views(hass), msg["view_id"])
|
|
try:
|
|
_persist(hass, views)
|
|
except LookupError:
|
|
connection.send_error(msg["id"], "not_found", "Global entry not found")
|
|
return
|
|
connection.send_result(msg["id"], {"views": views})
|