From decf254e5f5260d8acbd7632aa7f302f0bb0424a Mon Sep 17 00:00:00 2001 From: Pascal Vizeli Date: Tue, 23 Apr 2019 11:18:04 +0200 Subject: [PATCH] Ingress panel support (#1047) * Ingress Panel support * Fix lists * Allow to set the value * fix panels * Update ha realtime * Fix url * Fix update --- API.md | 23 +++++++++++++++++++++-- hassio/addons/addon.py | 31 +++++++++++++++++++++++++++++++ hassio/addons/validate.py | 8 ++++++++ hassio/api/__init__.py | 1 + hassio/api/addons.py | 6 ++++++ hassio/api/ingress.py | 26 +++++++++++++++++++++++++- hassio/const.py | 8 ++++++++ hassio/ingress.py | 25 +++++++++++++++++++++---- 8 files changed, 121 insertions(+), 7 deletions(-) diff --git a/API.md b/API.md index 0d4679e42..030599729 100644 --- a/API.md +++ b/API.md @@ -516,7 +516,8 @@ Get all available addons. "ingress": "bool", "ingress_entry": "null|/api/hassio_ingress/slug", "ingress_url": "null|/api/hassio_ingress/slug/entry.html", - "ingress_port": "null|int" + "ingress_port": "null|int", + "ingress_panel": "null|bool" } ``` @@ -537,7 +538,8 @@ Get all available addons. }, "options": {}, "audio_output": "null|0,0", - "audio_input": "null|0,0" + "audio_input": "null|0,0", + "ingress_panel": "bool" } ``` @@ -602,6 +604,23 @@ Create a new Session for access to ingress service. } ``` +- GET `/ingress/panels` + +Return a list of enabled panels. + +```json +{ + "panels": { + "addon_slug": { + "enable": "boolean", + "icon": "mdi:...", + "title": "title", + "admin": "boolean" + } + } +} +``` + - VIEW `/ingress/{token}` Ingress WebUI for this Add-on. The addon need support HASS Auth! diff --git a/hassio/addons/addon.py b/hassio/addons/addon.py index c6980a24e..659de8fc6 100644 --- a/hassio/addons/addon.py +++ b/hassio/addons/addon.py @@ -47,6 +47,10 @@ from ..const import ( ATTR_INGRESS_ENTRY, ATTR_INGRESS_PORT, ATTR_INGRESS_TOKEN, + ATTR_INGRESS_PANEL, + ATTR_INGRESS_PANEL_ADMIN, + ATTR_INGRESS_PANEL_ICON, + ATTR_INGRESS_PANEL_TITLE, ATTR_KERNEL_MODULES, ATTR_LEGACY, ATTR_LOCATON, @@ -449,6 +453,21 @@ class Addon(CoreSysAttributes): return self.sys_ingress.get_dynamic_port(self.slug) return port + @property + def ingress_icon(self) -> str: + """Return panel icon for Ingress frame.""" + return self._mesh[ATTR_INGRESS_PANEL_ICON] + + @property + def ingress_title(self) -> str: + """Return panel icon for Ingress frame.""" + return self._mesh.get(ATTR_INGRESS_PANEL_TITLE, self.name) + + @property + def ingress_admin(self) -> str: + """Return panel icon for Ingress frame.""" + return self._mesh[ATTR_INGRESS_PANEL_ADMIN] + @property def host_network(self): """Return True if add-on run on host network.""" @@ -538,6 +557,18 @@ class Addon(CoreSysAttributes): """Return True if the add-on access support ingress.""" return self._mesh[ATTR_INGRESS] + @property + def ingress_panel(self) -> Optional[bool]: + """Return True if the add-on access support ingress.""" + if self.is_installed: + return self._data.user[self._id][ATTR_INGRESS_PANEL] + return None + + @ingress_panel.setter + def ingress_panel(self, value: bool): + """Return True if the add-on access support ingress.""" + self._data.user[self._id][ATTR_INGRESS_PANEL] = value + @property def with_gpio(self): """Return True if the add-on access to GPIO interface.""" diff --git a/hassio/addons/validate.py b/hassio/addons/validate.py index 1bc5ca216..a618961a8 100644 --- a/hassio/addons/validate.py +++ b/hassio/addons/validate.py @@ -41,6 +41,10 @@ from ..const import ( ATTR_INGRESS_ENTRY, ATTR_INGRESS_PORT, ATTR_INGRESS_TOKEN, + ATTR_INGRESS_PANEL, + ATTR_INGRESS_PANEL_ADMIN, + ATTR_INGRESS_PANEL_ICON, + ATTR_INGRESS_PANEL_TITLE, ATTR_KERNEL_MODULES, ATTR_LEGACY, ATTR_LOCATON, @@ -159,6 +163,9 @@ SCHEMA_ADDON_CONFIG = vol.Schema({ vol.Optional(ATTR_INGRESS, default=False): vol.Boolean(), vol.Optional(ATTR_INGRESS_PORT, default=8099): vol.Any(NETWORK_PORT, vol.Equal(0)), vol.Optional(ATTR_INGRESS_ENTRY): vol.Coerce(str), + vol.Optional(ATTR_INGRESS_PANEL_ICON, default="mdi:puzzle"): vol.Coerce(str), + vol.Optional(ATTR_INGRESS_PANEL_TITLE): vol.Coerce(str), + vol.Optional(ATTR_INGRESS_PANEL_ADMIN, default=True): vol.Boolean(), vol.Optional(ATTR_HOMEASSISTANT): vol.Maybe(vol.Coerce(str)), vol.Optional(ATTR_HOST_NETWORK, default=False): vol.Boolean(), vol.Optional(ATTR_HOST_PID, default=False): vol.Boolean(), @@ -239,6 +246,7 @@ SCHEMA_ADDON_USER = vol.Schema({ vol.Optional(ATTR_AUDIO_OUTPUT): ALSA_DEVICE, vol.Optional(ATTR_AUDIO_INPUT): ALSA_DEVICE, vol.Optional(ATTR_PROTECTED, default=True): vol.Boolean(), + vol.Optional(ATTR_INGRESS_PANEL, default=False): vol.Boolean(), }, extra=vol.REMOVE_EXTRA) diff --git a/hassio/api/__init__.py b/hassio/api/__init__.py index 865a3cb54..32f453bec 100644 --- a/hassio/api/__init__.py +++ b/hassio/api/__init__.py @@ -195,6 +195,7 @@ class RestAPI(CoreSysAttributes): self.webapp.add_routes([ web.post('/ingress/session', api_ingress.create_session), + web.get('/ingress/panels', api_ingress.panels), web.view('/ingress/{token}/{path:.*}', api_ingress.handler), ]) diff --git a/hassio/api/addons.py b/hassio/api/addons.py index 455195f08..13475539f 100644 --- a/hassio/api/addons.py +++ b/hassio/api/addons.py @@ -46,6 +46,7 @@ from ..const import ( ATTR_INGRESS_ENTRY, ATTR_INGRESS_PORT, ATTR_INGRESS_URL, + ATTR_INGRESS_PANEL, ATTR_INSTALLED, ATTR_IP_ADDRESS, ATTR_KERNEL_MODULES, @@ -100,6 +101,7 @@ SCHEMA_OPTIONS = vol.Schema({ vol.Optional(ATTR_AUTO_UPDATE): vol.Boolean(), vol.Optional(ATTR_AUDIO_OUTPUT): ALSA_DEVICE, vol.Optional(ATTR_AUDIO_INPUT): ALSA_DEVICE, + vol.Optional(ATTR_INGRESS_PANEL): vol.Boolean(), }) # pylint: disable=no-value-for-parameter @@ -227,6 +229,7 @@ class APIAddons(CoreSysAttributes): ATTR_INGRESS_ENTRY: addon.ingress_entry, ATTR_INGRESS_URL: addon.ingress_url, ATTR_INGRESS_PORT: addon.ingress_port, + ATTR_INGRESS_PANEL: addon.ingress_panel, } @api_process @@ -251,6 +254,9 @@ class APIAddons(CoreSysAttributes): addon.audio_input = body[ATTR_AUDIO_INPUT] if ATTR_AUDIO_OUTPUT in body: addon.audio_output = body[ATTR_AUDIO_OUTPUT] + if ATTR_INGRESS_PANEL in body: + addon.ingress_panel = body[ATTR_INGRESS_PANEL] + await self.sys_ingress.update_hass_panel(addon) addon.save_data() diff --git a/hassio/api/ingress.py b/hassio/api/ingress.py index 438fa81d9..ac9ae289a 100644 --- a/hassio/api/ingress.py +++ b/hassio/api/ingress.py @@ -14,7 +14,17 @@ from aiohttp.web_exceptions import ( from multidict import CIMultiDict, istr from ..addons.addon import Addon -from ..const import ATTR_SESSION, HEADER_TOKEN, REQUEST_FROM, COOKIE_INGRESS +from ..const import ( + ATTR_ADMIN, + ATTR_ICON, + ATTR_SESSION, + ATTR_TITLE, + ATTR_PANELS, + ATTR_ENABLE, + COOKIE_INGRESS, + HEADER_TOKEN, + REQUEST_FROM, +) from ..coresys import CoreSysAttributes from .utils import api_process @@ -45,6 +55,20 @@ class APIIngress(CoreSysAttributes): """Create URL to container.""" return f"http://{addon.ip_address}:{addon.ingress_port}/{path}" + @api_process + async def panels(self, request: web.Request) -> Dict[str, Any]: + """Create a list of panel data.""" + addons = {} + for addon in self.sys_ingress.addons: + addons[addon.slug] = { + ATTR_TITLE: addon.ingress_title, + ATTR_ICON: addon.ingress_icon, + ATTR_ADMIN: addon.ingress_admin, + ATTR_ENABLE: addon.ingress_panel, + } + + return {ATTR_PANELS: addons} + @api_process async def create_session(self, request: web.Request) -> Dict[str, Any]: """Create a new session.""" diff --git a/hassio/const.py b/hassio/const.py index bef00bb51..b322d99f4 100644 --- a/hassio/const.py +++ b/hassio/const.py @@ -198,8 +198,16 @@ ATTR_INGRESS_PORT = "ingress_port" ATTR_INGRESS_ENTRY = "ingress_entry" ATTR_INGRESS_TOKEN = "ingress_token" ATTR_INGRESS_URL = "ingress_url" +ATTR_INGRESS_PANEL = "ingress_panel" +ATTR_INGRESS_PANEL_ICON = "ingress_panel_icon" +ATTR_INGRESS_PANEL_TITLE = "ingress_panel_title" +ATTR_INGRESS_PANEL_ADMIN = "ingress_panel_admin" +ATTR_TITLE = "title" +ATTR_ENABLE = "enable" ATTR_IP_ADDRESS = "ip_address" ATTR_SESSION = "session" +ATTR_ADMIN = "admin" +ATTR_PANELS = "panels" PROVIDE_SERVICE = "provide" NEED_SERVICE = "need" diff --git a/hassio/ingress.py b/hassio/ingress.py index 28b7c12e4..ef44f4b6f 100644 --- a/hassio/ingress.py +++ b/hassio/ingress.py @@ -3,7 +3,7 @@ from datetime import timedelta import logging import random import secrets -from typing import Dict, Optional +from typing import Dict, List, Optional from .addons.addon import Addon from .const import ATTR_PORTS, ATTR_SESSION, FILE_HASSIO_INGRESS @@ -40,6 +40,16 @@ class Ingress(JsonConfig, CoreSysAttributes): """Return list of dynamic ports.""" return self._data[ATTR_PORTS] + @property + def addons(self) -> List[Addon]: + """Return list of ingress Add-ons.""" + addons = [] + for addon in self.sys_addons.list_installed: + if not addon.with_ingress: + continue + addons.append(addon) + return addons + async def load(self) -> None: """Update internal data.""" self._update_token_list() @@ -77,9 +87,7 @@ class Ingress(JsonConfig, CoreSysAttributes): self.tokens.clear() # Read all ingress token and build a map - for addon in self.sys_addons.list_installed: - if not addon.with_ingress: - continue + for addon in self.addons: self.tokens[addon.ingress_token] = addon.slug def create_session(self) -> str: @@ -118,3 +126,12 @@ class Ingress(JsonConfig, CoreSysAttributes): self.ports[addon_slug] = port self.save_data() return port + + async def update_hass_panel(self, addon: Addon): + """Return True if Home Assistant up and running.""" + method = "post" if addon.ingress_panel else "delete" + async with self.sys_homeassistant.make_request(method, f"api/hassio_push/panel/{addon.slug}") as resp: + if resp.status in (200, 201): + _LOGGER.info("Update Ingress as panel for %s", addon.slug) + else: + _LOGGER.warning("Fails Ingress panel for %s with %i", addon.slug, resp.status)