Ingress panel support (#1047)

* Ingress Panel support

* Fix lists

* Allow to set the value

* fix panels

* Update ha realtime

* Fix url

* Fix update
This commit is contained in:
Pascal Vizeli 2019-04-23 11:18:04 +02:00 committed by GitHub
parent e10fe16f21
commit decf254e5f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 121 additions and 7 deletions

23
API.md
View File

@ -516,7 +516,8 @@ Get all available addons.
"ingress": "bool", "ingress": "bool",
"ingress_entry": "null|/api/hassio_ingress/slug", "ingress_entry": "null|/api/hassio_ingress/slug",
"ingress_url": "null|/api/hassio_ingress/slug/entry.html", "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": {}, "options": {},
"audio_output": "null|0,0", "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}` - VIEW `/ingress/{token}`
Ingress WebUI for this Add-on. The addon need support HASS Auth! Ingress WebUI for this Add-on. The addon need support HASS Auth!

View File

@ -47,6 +47,10 @@ from ..const import (
ATTR_INGRESS_ENTRY, ATTR_INGRESS_ENTRY,
ATTR_INGRESS_PORT, ATTR_INGRESS_PORT,
ATTR_INGRESS_TOKEN, ATTR_INGRESS_TOKEN,
ATTR_INGRESS_PANEL,
ATTR_INGRESS_PANEL_ADMIN,
ATTR_INGRESS_PANEL_ICON,
ATTR_INGRESS_PANEL_TITLE,
ATTR_KERNEL_MODULES, ATTR_KERNEL_MODULES,
ATTR_LEGACY, ATTR_LEGACY,
ATTR_LOCATON, ATTR_LOCATON,
@ -449,6 +453,21 @@ class Addon(CoreSysAttributes):
return self.sys_ingress.get_dynamic_port(self.slug) return self.sys_ingress.get_dynamic_port(self.slug)
return port 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 @property
def host_network(self): def host_network(self):
"""Return True if add-on run on host network.""" """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 True if the add-on access support ingress."""
return self._mesh[ATTR_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 @property
def with_gpio(self): def with_gpio(self):
"""Return True if the add-on access to GPIO interface.""" """Return True if the add-on access to GPIO interface."""

View File

@ -41,6 +41,10 @@ from ..const import (
ATTR_INGRESS_ENTRY, ATTR_INGRESS_ENTRY,
ATTR_INGRESS_PORT, ATTR_INGRESS_PORT,
ATTR_INGRESS_TOKEN, ATTR_INGRESS_TOKEN,
ATTR_INGRESS_PANEL,
ATTR_INGRESS_PANEL_ADMIN,
ATTR_INGRESS_PANEL_ICON,
ATTR_INGRESS_PANEL_TITLE,
ATTR_KERNEL_MODULES, ATTR_KERNEL_MODULES,
ATTR_LEGACY, ATTR_LEGACY,
ATTR_LOCATON, ATTR_LOCATON,
@ -159,6 +163,9 @@ SCHEMA_ADDON_CONFIG = vol.Schema({
vol.Optional(ATTR_INGRESS, default=False): vol.Boolean(), 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_PORT, default=8099): vol.Any(NETWORK_PORT, vol.Equal(0)),
vol.Optional(ATTR_INGRESS_ENTRY): vol.Coerce(str), 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_HOMEASSISTANT): vol.Maybe(vol.Coerce(str)),
vol.Optional(ATTR_HOST_NETWORK, default=False): vol.Boolean(), vol.Optional(ATTR_HOST_NETWORK, default=False): vol.Boolean(),
vol.Optional(ATTR_HOST_PID, 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_OUTPUT): ALSA_DEVICE,
vol.Optional(ATTR_AUDIO_INPUT): ALSA_DEVICE, vol.Optional(ATTR_AUDIO_INPUT): ALSA_DEVICE,
vol.Optional(ATTR_PROTECTED, default=True): vol.Boolean(), vol.Optional(ATTR_PROTECTED, default=True): vol.Boolean(),
vol.Optional(ATTR_INGRESS_PANEL, default=False): vol.Boolean(),
}, extra=vol.REMOVE_EXTRA) }, extra=vol.REMOVE_EXTRA)

View File

@ -195,6 +195,7 @@ class RestAPI(CoreSysAttributes):
self.webapp.add_routes([ self.webapp.add_routes([
web.post('/ingress/session', api_ingress.create_session), web.post('/ingress/session', api_ingress.create_session),
web.get('/ingress/panels', api_ingress.panels),
web.view('/ingress/{token}/{path:.*}', api_ingress.handler), web.view('/ingress/{token}/{path:.*}', api_ingress.handler),
]) ])

View File

@ -46,6 +46,7 @@ from ..const import (
ATTR_INGRESS_ENTRY, ATTR_INGRESS_ENTRY,
ATTR_INGRESS_PORT, ATTR_INGRESS_PORT,
ATTR_INGRESS_URL, ATTR_INGRESS_URL,
ATTR_INGRESS_PANEL,
ATTR_INSTALLED, ATTR_INSTALLED,
ATTR_IP_ADDRESS, ATTR_IP_ADDRESS,
ATTR_KERNEL_MODULES, ATTR_KERNEL_MODULES,
@ -100,6 +101,7 @@ SCHEMA_OPTIONS = vol.Schema({
vol.Optional(ATTR_AUTO_UPDATE): vol.Boolean(), vol.Optional(ATTR_AUTO_UPDATE): vol.Boolean(),
vol.Optional(ATTR_AUDIO_OUTPUT): ALSA_DEVICE, vol.Optional(ATTR_AUDIO_OUTPUT): ALSA_DEVICE,
vol.Optional(ATTR_AUDIO_INPUT): ALSA_DEVICE, vol.Optional(ATTR_AUDIO_INPUT): ALSA_DEVICE,
vol.Optional(ATTR_INGRESS_PANEL): vol.Boolean(),
}) })
# pylint: disable=no-value-for-parameter # pylint: disable=no-value-for-parameter
@ -227,6 +229,7 @@ class APIAddons(CoreSysAttributes):
ATTR_INGRESS_ENTRY: addon.ingress_entry, ATTR_INGRESS_ENTRY: addon.ingress_entry,
ATTR_INGRESS_URL: addon.ingress_url, ATTR_INGRESS_URL: addon.ingress_url,
ATTR_INGRESS_PORT: addon.ingress_port, ATTR_INGRESS_PORT: addon.ingress_port,
ATTR_INGRESS_PANEL: addon.ingress_panel,
} }
@api_process @api_process
@ -251,6 +254,9 @@ class APIAddons(CoreSysAttributes):
addon.audio_input = body[ATTR_AUDIO_INPUT] addon.audio_input = body[ATTR_AUDIO_INPUT]
if ATTR_AUDIO_OUTPUT in body: if ATTR_AUDIO_OUTPUT in body:
addon.audio_output = body[ATTR_AUDIO_OUTPUT] 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() addon.save_data()

View File

@ -14,7 +14,17 @@ from aiohttp.web_exceptions import (
from multidict import CIMultiDict, istr from multidict import CIMultiDict, istr
from ..addons.addon import Addon 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 ..coresys import CoreSysAttributes
from .utils import api_process from .utils import api_process
@ -45,6 +55,20 @@ class APIIngress(CoreSysAttributes):
"""Create URL to container.""" """Create URL to container."""
return f"http://{addon.ip_address}:{addon.ingress_port}/{path}" 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 @api_process
async def create_session(self, request: web.Request) -> Dict[str, Any]: async def create_session(self, request: web.Request) -> Dict[str, Any]:
"""Create a new session.""" """Create a new session."""

View File

@ -198,8 +198,16 @@ ATTR_INGRESS_PORT = "ingress_port"
ATTR_INGRESS_ENTRY = "ingress_entry" ATTR_INGRESS_ENTRY = "ingress_entry"
ATTR_INGRESS_TOKEN = "ingress_token" ATTR_INGRESS_TOKEN = "ingress_token"
ATTR_INGRESS_URL = "ingress_url" 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_IP_ADDRESS = "ip_address"
ATTR_SESSION = "session" ATTR_SESSION = "session"
ATTR_ADMIN = "admin"
ATTR_PANELS = "panels"
PROVIDE_SERVICE = "provide" PROVIDE_SERVICE = "provide"
NEED_SERVICE = "need" NEED_SERVICE = "need"

View File

@ -3,7 +3,7 @@ from datetime import timedelta
import logging import logging
import random import random
import secrets import secrets
from typing import Dict, Optional from typing import Dict, List, Optional
from .addons.addon import Addon from .addons.addon import Addon
from .const import ATTR_PORTS, ATTR_SESSION, FILE_HASSIO_INGRESS from .const import ATTR_PORTS, ATTR_SESSION, FILE_HASSIO_INGRESS
@ -40,6 +40,16 @@ class Ingress(JsonConfig, CoreSysAttributes):
"""Return list of dynamic ports.""" """Return list of dynamic ports."""
return self._data[ATTR_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: async def load(self) -> None:
"""Update internal data.""" """Update internal data."""
self._update_token_list() self._update_token_list()
@ -77,9 +87,7 @@ class Ingress(JsonConfig, CoreSysAttributes):
self.tokens.clear() self.tokens.clear()
# Read all ingress token and build a map # Read all ingress token and build a map
for addon in self.sys_addons.list_installed: for addon in self.addons:
if not addon.with_ingress:
continue
self.tokens[addon.ingress_token] = addon.slug self.tokens[addon.ingress_token] = addon.slug
def create_session(self) -> str: def create_session(self) -> str:
@ -118,3 +126,12 @@ class Ingress(JsonConfig, CoreSysAttributes):
self.ports[addon_slug] = port self.ports[addon_slug] = port
self.save_data() self.save_data()
return port 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)