mirror of
https://github.com/home-assistant/supervisor.git
synced 2025-08-22 15:39:21 +00:00
Compare commits
21 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
e52af3bfb4 | ||
![]() |
0467b33cd5 | ||
![]() |
14167f6e13 | ||
![]() |
7a1aba6f81 | ||
![]() |
920f7f2ece | ||
![]() |
06fadbd70f | ||
![]() |
d4f486864f | ||
![]() |
d3a21303d9 | ||
![]() |
e1cbfdd84b | ||
![]() |
87170a4497 | ||
![]() |
ae6f8bd345 | ||
![]() |
b9496e0972 | ||
![]() |
c36a6dcd65 | ||
![]() |
19ca836b78 | ||
![]() |
8a6ea7ab50 | ||
![]() |
6721b8f265 | ||
![]() |
9393521f98 | ||
![]() |
398b24e0ab | ||
![]() |
374bcf8073 | ||
![]() |
7e3859e2f5 | ||
![]() |
490ec0d462 |
114
API.md
114
API.md
@@ -379,7 +379,9 @@ Trigger an udev reload
|
||||
"port": 8123,
|
||||
"ssl": "bool",
|
||||
"watchdog": "bool",
|
||||
"wait_boot": 600
|
||||
"wait_boot": 600,
|
||||
"audio_input": "null|profile",
|
||||
"audio_output": "null|profile"
|
||||
}
|
||||
```
|
||||
|
||||
@@ -413,7 +415,9 @@ Output is the raw Docker log.
|
||||
"ssl": "bool",
|
||||
"refresh_token": "",
|
||||
"watchdog": "bool",
|
||||
"wait_boot": 600
|
||||
"wait_boot": 600,
|
||||
"audio_input": "null|profile",
|
||||
"audio_output": "null|profile"
|
||||
}
|
||||
```
|
||||
|
||||
@@ -863,20 +867,73 @@ return:
|
||||
"version": "1",
|
||||
"latest_version": "2",
|
||||
"audio": {
|
||||
"card": [
|
||||
{
|
||||
"name": "...",
|
||||
"index": 1,
|
||||
"driver": "...",
|
||||
"profiles": [
|
||||
{
|
||||
"name": "...",
|
||||
"description": "...",
|
||||
"active": false
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"input": [
|
||||
{
|
||||
"name": "...",
|
||||
"index": 0,
|
||||
"description": "...",
|
||||
"volume": 0.3,
|
||||
"default": false
|
||||
"mute": false,
|
||||
"default": false,
|
||||
"card": "null|int",
|
||||
"applications": [
|
||||
{
|
||||
"name": "...",
|
||||
"index": 0,
|
||||
"stream_index": 0,
|
||||
"stream_type": "INPUT",
|
||||
"volume": 0.3,
|
||||
"mute": false,
|
||||
"addon": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"output": [
|
||||
{
|
||||
"name": "...",
|
||||
"index": 0,
|
||||
"description": "...",
|
||||
"volume": 0.3,
|
||||
"default": false
|
||||
"mute": false,
|
||||
"default": false,
|
||||
"card": "null|int",
|
||||
"applications": [
|
||||
{
|
||||
"name": "...",
|
||||
"index": 0,
|
||||
"stream_index": 0,
|
||||
"stream_type": "OUTPUT",
|
||||
"volume": 0.3,
|
||||
"mute": false,
|
||||
"addon": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"application": [
|
||||
{
|
||||
"name": "...",
|
||||
"index": 0,
|
||||
"stream_index": 0,
|
||||
"stream_type": "OUTPUT",
|
||||
"volume": 0.3,
|
||||
"mute": false,
|
||||
"addon": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -901,7 +958,7 @@ return:
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "...",
|
||||
"index": "...",
|
||||
"volume": 0.5
|
||||
}
|
||||
```
|
||||
@@ -910,11 +967,47 @@ return:
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "...",
|
||||
"index": "...",
|
||||
"volume": 0.5
|
||||
}
|
||||
```
|
||||
|
||||
- POST `/audio/volume/{output|input}/application`
|
||||
|
||||
```json
|
||||
{
|
||||
"index": "...",
|
||||
"volume": 0.5
|
||||
}
|
||||
```
|
||||
|
||||
- POST `/audio/mute/input`
|
||||
|
||||
```json
|
||||
{
|
||||
"index": "...",
|
||||
"active": false
|
||||
}
|
||||
```
|
||||
|
||||
- POST `/audio/mute/output`
|
||||
|
||||
```json
|
||||
{
|
||||
"index": "...",
|
||||
"active": false
|
||||
}
|
||||
```
|
||||
|
||||
- POST `/audio/mute/{output|input}/application`
|
||||
|
||||
```json
|
||||
{
|
||||
"index": "...",
|
||||
"active": false
|
||||
}
|
||||
```
|
||||
|
||||
- POST `/audio/default/input`
|
||||
|
||||
```json
|
||||
@@ -931,6 +1024,15 @@ return:
|
||||
}
|
||||
```
|
||||
|
||||
- POST `/audio/profile`
|
||||
|
||||
```json
|
||||
{
|
||||
"card": "...",
|
||||
"name": "..."
|
||||
}
|
||||
```
|
||||
|
||||
- GET `/audio/stats`
|
||||
|
||||
```json
|
||||
|
@@ -10,7 +10,7 @@ gitpython==3.1.0
|
||||
jinja2==2.11.1
|
||||
packaging==20.1
|
||||
ptvsd==4.3.2
|
||||
pulsectl==20.2.2
|
||||
pulsectl==20.2.4
|
||||
pytz==2019.3
|
||||
pyudev==0.22.0
|
||||
ruamel.yaml==0.15.100
|
||||
|
@@ -117,8 +117,11 @@ function init_dbus() {
|
||||
mkdir -p /var/lib/dbus
|
||||
cp -f /etc/machine-id /var/lib/dbus/machine-id
|
||||
|
||||
# run
|
||||
# cleanups
|
||||
mkdir -p /run/dbus
|
||||
rm -f /run/dbus/pid
|
||||
|
||||
# run
|
||||
dbus-daemon --system --print-address
|
||||
}
|
||||
|
||||
|
@@ -63,6 +63,8 @@ RE_WEBUI = re.compile(
|
||||
r":\/\/\[HOST\]:\[PORT:(?P<t_port>\d+)\](?P<s_suffix>.*)$"
|
||||
)
|
||||
|
||||
RE_OLD_AUDIO = re.compile(r"\d+,\d+")
|
||||
|
||||
|
||||
class Addon(AddonModel):
|
||||
"""Hold data for add-on inside Supervisor."""
|
||||
@@ -282,30 +284,36 @@ class Addon(AddonModel):
|
||||
"""Return a pulse profile for output or None."""
|
||||
if not self.with_audio:
|
||||
return None
|
||||
return self.persist.get(ATTR_AUDIO_OUTPUT)
|
||||
|
||||
# Fallback with old audio settings
|
||||
# Remove after 210
|
||||
output_data = self.persist.get(ATTR_AUDIO_OUTPUT)
|
||||
if output_data and RE_OLD_AUDIO.fullmatch(output_data):
|
||||
return None
|
||||
return output_data
|
||||
|
||||
@audio_output.setter
|
||||
def audio_output(self, value: Optional[str]):
|
||||
"""Set/reset audio output profile settings."""
|
||||
if value is None:
|
||||
self.persist.pop(ATTR_AUDIO_OUTPUT, None)
|
||||
else:
|
||||
self.persist[ATTR_AUDIO_OUTPUT] = value
|
||||
"""Set audio output profile settings."""
|
||||
self.persist[ATTR_AUDIO_OUTPUT] = value
|
||||
|
||||
@property
|
||||
def audio_input(self) -> Optional[str]:
|
||||
"""Return pulse profile for input or None."""
|
||||
if not self.with_audio:
|
||||
return None
|
||||
return self.persist.get(ATTR_AUDIO_INPUT)
|
||||
|
||||
# Fallback with old audio settings
|
||||
# Remove after 210
|
||||
input_data = self.persist.get(ATTR_AUDIO_INPUT)
|
||||
if input_data and RE_OLD_AUDIO.fullmatch(input_data):
|
||||
return None
|
||||
return input_data
|
||||
|
||||
@audio_input.setter
|
||||
def audio_input(self, value: Optional[str]):
|
||||
"""Set/reset audio input settings."""
|
||||
if value is None:
|
||||
self.persist.pop(ATTR_AUDIO_INPUT, None)
|
||||
else:
|
||||
self.persist[ATTR_AUDIO_INPUT] = value
|
||||
"""Set audio input settings."""
|
||||
self.persist[ATTR_AUDIO_INPUT] = value
|
||||
|
||||
@property
|
||||
def image(self):
|
||||
|
@@ -31,6 +31,7 @@ from ..const import (
|
||||
ATTR_HOST_PID,
|
||||
ATTR_IMAGE,
|
||||
ATTR_INGRESS,
|
||||
ATTR_INIT,
|
||||
ATTR_KERNEL_MODULES,
|
||||
ATTR_LEGACY,
|
||||
ATTR_LOCATON,
|
||||
@@ -344,6 +345,11 @@ class AddonModel(CoreSysAttributes):
|
||||
"""Return Exclude list for snapshot."""
|
||||
return self.data.get(ATTR_SNAPSHOT_EXCLUDE, [])
|
||||
|
||||
@property
|
||||
def default_init(self) -> bool:
|
||||
"""Return True if the add-on have no own init."""
|
||||
return self.data[ATTR_INIT]
|
||||
|
||||
@property
|
||||
def with_stdin(self) -> bool:
|
||||
"""Return True if the add-on access use stdin input."""
|
||||
|
@@ -44,6 +44,7 @@ from ..const import (
|
||||
ATTR_INGRESS_PANEL,
|
||||
ATTR_INGRESS_PORT,
|
||||
ATTR_INGRESS_TOKEN,
|
||||
ATTR_INIT,
|
||||
ATTR_KERNEL_MODULES,
|
||||
ATTR_LEGACY,
|
||||
ATTR_LOCATON,
|
||||
@@ -189,6 +190,7 @@ SCHEMA_ADDON_CONFIG = vol.Schema(
|
||||
vol.Optional(ATTR_URL): vol.Url(),
|
||||
vol.Required(ATTR_STARTUP): vol.All(_simple_startup, vol.In(STARTUP_ALL)),
|
||||
vol.Required(ATTR_BOOT): vol.In([BOOT_AUTO, BOOT_MANUAL]),
|
||||
vol.Optional(ATTR_INIT, default=True): vol.Boolean(),
|
||||
vol.Optional(ATTR_ADVANCED, default=False): vol.Boolean(),
|
||||
vol.Optional(ATTR_STAGE, default=AddonStages.STABLE): vol.Coerce(AddonStages),
|
||||
vol.Optional(ATTR_PORTS): DOCKER_PORTS,
|
||||
|
@@ -329,7 +329,11 @@ class RestAPI(CoreSysAttributes):
|
||||
web.post("/audio/update", api_audio.update),
|
||||
web.post("/audio/restart", api_audio.restart),
|
||||
web.post("/audio/reload", api_audio.reload),
|
||||
web.post("/audio/profile", api_audio.set_profile),
|
||||
web.post("/audio/volume/{source}/application", api_audio.set_volume),
|
||||
web.post("/audio/volume/{source}", api_audio.set_volume),
|
||||
web.post("/audio/mute/{source}/application", api_audio.set_mute),
|
||||
web.post("/audio/mute/{source}", api_audio.set_mute),
|
||||
web.post("/audio/default/{source}", api_audio.set_default),
|
||||
]
|
||||
)
|
||||
|
@@ -8,11 +8,15 @@ import attr
|
||||
import voluptuous as vol
|
||||
|
||||
from ..const import (
|
||||
ATTR_ACTIVE,
|
||||
ATTR_APPLICATION,
|
||||
ATTR_AUDIO,
|
||||
ATTR_BLK_READ,
|
||||
ATTR_BLK_WRITE,
|
||||
ATTR_CARD,
|
||||
ATTR_CPU_PERCENT,
|
||||
ATTR_HOST,
|
||||
ATTR_INDEX,
|
||||
ATTR_INPUT,
|
||||
ATTR_LATEST_VERSION,
|
||||
ATTR_MEMORY_LIMIT,
|
||||
@@ -28,7 +32,7 @@ from ..const import (
|
||||
)
|
||||
from ..coresys import CoreSysAttributes
|
||||
from ..exceptions import APIError
|
||||
from ..host.sound import SourceType
|
||||
from ..host.sound import StreamType
|
||||
from .utils import api_process, api_process_raw, api_validate
|
||||
|
||||
_LOGGER: logging.Logger = logging.getLogger(__name__)
|
||||
@@ -37,13 +41,25 @@ SCHEMA_VERSION = vol.Schema({vol.Optional(ATTR_VERSION): vol.Coerce(str)})
|
||||
|
||||
SCHEMA_VOLUME = vol.Schema(
|
||||
{
|
||||
vol.Required(ATTR_NAME): vol.Coerce(str),
|
||||
vol.Required(ATTR_INDEX): vol.Coerce(int),
|
||||
vol.Required(ATTR_VOLUME): vol.Coerce(float),
|
||||
}
|
||||
)
|
||||
|
||||
# pylint: disable=no-value-for-parameter
|
||||
SCHEMA_MUTE = vol.Schema(
|
||||
{
|
||||
vol.Required(ATTR_INDEX): vol.Coerce(int),
|
||||
vol.Required(ATTR_ACTIVE): vol.Boolean(),
|
||||
}
|
||||
)
|
||||
|
||||
SCHEMA_DEFAULT = vol.Schema({vol.Required(ATTR_NAME): vol.Coerce(str)})
|
||||
|
||||
SCHEMA_PROFILE = vol.Schema(
|
||||
{vol.Required(ATTR_CARD): vol.Coerce(str), vol.Required(ATTR_NAME): vol.Coerce(str)}
|
||||
)
|
||||
|
||||
|
||||
class APIAudio(CoreSysAttributes):
|
||||
"""Handle RESTful API for Audio functions."""
|
||||
@@ -56,13 +72,15 @@ class APIAudio(CoreSysAttributes):
|
||||
ATTR_LATEST_VERSION: self.sys_audio.latest_version,
|
||||
ATTR_HOST: str(self.sys_docker.network.audio),
|
||||
ATTR_AUDIO: {
|
||||
ATTR_CARD: [attr.asdict(card) for card in self.sys_host.sound.cards],
|
||||
ATTR_INPUT: [
|
||||
attr.asdict(profile)
|
||||
for profile in self.sys_host.sound.input_profiles
|
||||
attr.asdict(stream) for stream in self.sys_host.sound.inputs
|
||||
],
|
||||
ATTR_OUTPUT: [
|
||||
attr.asdict(profile)
|
||||
for profile in self.sys_host.sound.output_profiles
|
||||
attr.asdict(stream) for stream in self.sys_host.sound.outputs
|
||||
],
|
||||
ATTR_APPLICATION: [
|
||||
attr.asdict(stream) for stream in self.sys_host.sound.applications
|
||||
],
|
||||
},
|
||||
}
|
||||
@@ -110,18 +128,43 @@ class APIAudio(CoreSysAttributes):
|
||||
|
||||
@api_process
|
||||
async def set_volume(self, request: web.Request) -> None:
|
||||
"""Set Audio information."""
|
||||
source: SourceType = SourceType(request.match_info.get("source"))
|
||||
"""Set audio volume on stream."""
|
||||
source: StreamType = StreamType(request.match_info.get("source"))
|
||||
application: bool = request.path.endswith("application")
|
||||
body = await api_validate(SCHEMA_VOLUME, request)
|
||||
|
||||
await asyncio.shield(
|
||||
self.sys_host.sound.set_volume(source, body[ATTR_NAME], body[ATTR_VOLUME])
|
||||
self.sys_host.sound.set_volume(
|
||||
source, body[ATTR_INDEX], body[ATTR_VOLUME], application
|
||||
)
|
||||
)
|
||||
|
||||
@api_process
|
||||
async def set_mute(self, request: web.Request) -> None:
|
||||
"""Mute audio volume on stream."""
|
||||
source: StreamType = StreamType(request.match_info.get("source"))
|
||||
application: bool = request.path.endswith("application")
|
||||
body = await api_validate(SCHEMA_MUTE, request)
|
||||
|
||||
await asyncio.shield(
|
||||
self.sys_host.sound.set_mute(
|
||||
source, body[ATTR_INDEX], body[ATTR_ACTIVE], application
|
||||
)
|
||||
)
|
||||
|
||||
@api_process
|
||||
async def set_default(self, request: web.Request) -> None:
|
||||
"""Set Audio default sources."""
|
||||
source: SourceType = SourceType(request.match_info.get("source"))
|
||||
"""Set audio default stream."""
|
||||
source: StreamType = StreamType(request.match_info.get("source"))
|
||||
body = await api_validate(SCHEMA_DEFAULT, request)
|
||||
|
||||
await asyncio.shield(self.sys_host.sound.set_default(source, body[ATTR_NAME]))
|
||||
|
||||
@api_process
|
||||
async def set_profile(self, request: web.Request) -> None:
|
||||
"""Set audio default sources."""
|
||||
body = await api_validate(SCHEMA_DEFAULT, request)
|
||||
|
||||
await asyncio.shield(
|
||||
self.sys_host.sound.set_profile(body[ATTR_CARD], body[ATTR_NAME])
|
||||
)
|
||||
|
@@ -42,11 +42,11 @@ class APIHardware(CoreSysAttributes):
|
||||
ATTR_AUDIO: {
|
||||
ATTR_INPUT: {
|
||||
profile.name: profile.description
|
||||
for profile in self.sys_host.sound.input_profiles
|
||||
for profile in self.sys_host.sound.inputs
|
||||
},
|
||||
ATTR_OUTPUT: {
|
||||
profile.name: profile.description
|
||||
for profile in self.sys_host.sound.output_profiles
|
||||
for profile in self.sys_host.sound.outputs
|
||||
},
|
||||
}
|
||||
}
|
||||
|
@@ -1,24 +1,27 @@
|
||||
"""Init file for Supervisor Home Assistant RESTful API."""
|
||||
import asyncio
|
||||
import logging
|
||||
from typing import Coroutine, Dict, Any
|
||||
from typing import Any, Coroutine, Dict
|
||||
|
||||
import voluptuous as vol
|
||||
from aiohttp import web
|
||||
import voluptuous as vol
|
||||
|
||||
from ..const import (
|
||||
ATTR_ARCH,
|
||||
ATTR_AUDIO_INPUT,
|
||||
ATTR_AUDIO_OUTPUT,
|
||||
ATTR_BLK_READ,
|
||||
ATTR_BLK_WRITE,
|
||||
ATTR_BOOT,
|
||||
ATTR_CPU_PERCENT,
|
||||
ATTR_CUSTOM,
|
||||
ATTR_IMAGE,
|
||||
ATTR_IP_ADDRESS,
|
||||
ATTR_LAST_VERSION,
|
||||
ATTR_MACHINE,
|
||||
ATTR_MEMORY_LIMIT,
|
||||
ATTR_MEMORY_USAGE,
|
||||
ATTR_MEMORY_PERCENT,
|
||||
ATTR_MEMORY_USAGE,
|
||||
ATTR_NETWORK_RX,
|
||||
ATTR_NETWORK_TX,
|
||||
ATTR_PORT,
|
||||
@@ -27,7 +30,6 @@ from ..const import (
|
||||
ATTR_VERSION,
|
||||
ATTR_WAIT_BOOT,
|
||||
ATTR_WATCHDOG,
|
||||
ATTR_IP_ADDRESS,
|
||||
CONTENT_TYPE_BINARY,
|
||||
)
|
||||
from ..coresys import CoreSysAttributes
|
||||
@@ -48,6 +50,8 @@ SCHEMA_OPTIONS = vol.Schema(
|
||||
vol.Optional(ATTR_WATCHDOG): vol.Boolean(),
|
||||
vol.Optional(ATTR_WAIT_BOOT): vol.All(vol.Coerce(int), vol.Range(min=60)),
|
||||
vol.Optional(ATTR_REFRESH_TOKEN): vol.Maybe(vol.Coerce(str)),
|
||||
vol.Optional(ATTR_AUDIO_OUTPUT): vol.Maybe(vol.Coerce(str)),
|
||||
vol.Optional(ATTR_AUDIO_INPUT): vol.Maybe(vol.Coerce(str)),
|
||||
}
|
||||
)
|
||||
|
||||
@@ -73,6 +77,8 @@ class APIHomeAssistant(CoreSysAttributes):
|
||||
ATTR_SSL: self.sys_homeassistant.api_ssl,
|
||||
ATTR_WATCHDOG: self.sys_homeassistant.watchdog,
|
||||
ATTR_WAIT_BOOT: self.sys_homeassistant.wait_boot,
|
||||
ATTR_AUDIO_INPUT: self.sys_homeassistant.audio_input,
|
||||
ATTR_AUDIO_OUTPUT: self.sys_homeassistant.audio_output,
|
||||
}
|
||||
|
||||
@api_process
|
||||
@@ -102,6 +108,12 @@ class APIHomeAssistant(CoreSysAttributes):
|
||||
if ATTR_REFRESH_TOKEN in body:
|
||||
self.sys_homeassistant.refresh_token = body[ATTR_REFRESH_TOKEN]
|
||||
|
||||
if ATTR_AUDIO_INPUT in body:
|
||||
self.sys_homeassistant.audio_input = body[ATTR_AUDIO_INPUT]
|
||||
|
||||
if ATTR_AUDIO_OUTPUT in body:
|
||||
self.sys_homeassistant.audio_output = body[ATTR_AUDIO_OUTPUT]
|
||||
|
||||
self.sys_homeassistant.save_data()
|
||||
|
||||
@api_process
|
||||
|
2
supervisor/api/panel/a1ebfa0a88593a3b571c.worker.js
Normal file
2
supervisor/api/panel/a1ebfa0a88593a3b571c.worker.js
Normal file
File diff suppressed because one or more lines are too long
BIN
supervisor/api/panel/a1ebfa0a88593a3b571c.worker.js.gz
Normal file
BIN
supervisor/api/panel/a1ebfa0a88593a3b571c.worker.js.gz
Normal file
Binary file not shown.
1
supervisor/api/panel/a1ebfa0a88593a3b571c.worker.js.map
Normal file
1
supervisor/api/panel/a1ebfa0a88593a3b571c.worker.js.map
Normal file
File diff suppressed because one or more lines are too long
2
supervisor/api/panel/chunk.1a25d23325fed5a4d90b.js
Normal file
2
supervisor/api/panel/chunk.1a25d23325fed5a4d90b.js
Normal file
File diff suppressed because one or more lines are too long
BIN
supervisor/api/panel/chunk.1a25d23325fed5a4d90b.js.gz
Normal file
BIN
supervisor/api/panel/chunk.1a25d23325fed5a4d90b.js.gz
Normal file
Binary file not shown.
1
supervisor/api/panel/chunk.1a25d23325fed5a4d90b.js.map
Normal file
1
supervisor/api/panel/chunk.1a25d23325fed5a4d90b.js.map
Normal file
File diff suppressed because one or more lines are too long
2
supervisor/api/panel/chunk.26756b56961f7bf94974.js
Normal file
2
supervisor/api/panel/chunk.26756b56961f7bf94974.js
Normal file
@@ -0,0 +1,2 @@
|
||||
(self.webpackJsonp=self.webpackJsonp||[]).push([[2],{177:function(e,r,n){"use strict";n.r(r),n.d(r,"codeMirror",function(){return c}),n.d(r,"codeMirrorCss",function(){return i});var a=n(54),o=n.n(a),s=n(170),t=(n(171),n(172),n(11));o.a.commands.save=function(e){Object(t.a)(e.getWrapperElement(),"editor-save")};var c=o.a,i=s.a}}]);
|
||||
//# sourceMappingURL=chunk.26756b56961f7bf94974.js.map
|
BIN
supervisor/api/panel/chunk.26756b56961f7bf94974.js.gz
Normal file
BIN
supervisor/api/panel/chunk.26756b56961f7bf94974.js.gz
Normal file
Binary file not shown.
1
supervisor/api/panel/chunk.26756b56961f7bf94974.js.map
Normal file
1
supervisor/api/panel/chunk.26756b56961f7bf94974.js.map
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"sources":["webpack:///./src/resources/codemirror.ts"],"names":["__webpack_require__","r","__webpack_exports__","d","codeMirror","codeMirrorCss","codemirror__WEBPACK_IMPORTED_MODULE_0__","codemirror__WEBPACK_IMPORTED_MODULE_0___default","n","codemirror_lib_codemirror_css__WEBPACK_IMPORTED_MODULE_1__","_common_dom_fire_event__WEBPACK_IMPORTED_MODULE_4__","_CodeMirror","commands","save","cm","fireEvent","getWrapperElement","_codeMirrorCss"],"mappings":"sFAAAA,EAAAC,EAAAC,GAAAF,EAAAG,EAAAD,EAAA,+BAAAE,IAAAJ,EAAAG,EAAAD,EAAA,kCAAAG,IAAA,IAAAC,EAAAN,EAAA,IAAAO,EAAAP,EAAAQ,EAAAF,GAAAG,EAAAT,EAAA,KAAAU,GAAAV,EAAA,KAAAA,EAAA,KAAAA,EAAA,KAQAW,IAAYC,SAASC,KAAO,SAACC,GAC3BC,YAAUD,EAAGE,oBAAqB,gBAE7B,IAAMZ,EAAkBO,IAClBN,EAAqBY","file":"chunk.26756b56961f7bf94974.js","sourcesContent":["// @ts-ignore\nimport _CodeMirror, { Editor } from \"codemirror\";\n// @ts-ignore\nimport _codeMirrorCss from \"codemirror/lib/codemirror.css\";\nimport \"codemirror/mode/yaml/yaml\";\nimport \"codemirror/mode/jinja2/jinja2\";\nimport { fireEvent } from \"../common/dom/fire_event\";\n\n_CodeMirror.commands.save = (cm: Editor) => {\n fireEvent(cm.getWrapperElement(), \"editor-save\");\n};\nexport const codeMirror: any = _CodeMirror;\nexport const codeMirrorCss: any = _codeMirrorCss;\n"],"sourceRoot":""}
|
3
supervisor/api/panel/chunk.26be881fcb628958e718.js
Normal file
3
supervisor/api/panel/chunk.26be881fcb628958e718.js
Normal file
File diff suppressed because one or more lines are too long
10
supervisor/api/panel/chunk.26be881fcb628958e718.js.LICENSE
Normal file
10
supervisor/api/panel/chunk.26be881fcb628958e718.js.LICENSE
Normal file
@@ -0,0 +1,10 @@
|
||||
/**
|
||||
@license
|
||||
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
|
||||
This code may only be used under the BSD style license found at
|
||||
http://polymer.github.io/LICENSE.txt The complete set of authors may be found at
|
||||
http://polymer.github.io/AUTHORS.txt The complete set of contributors may be
|
||||
found at http://polymer.github.io/CONTRIBUTORS.txt Code distributed by Google as
|
||||
part of the polymer project is also subject to an additional IP rights grant
|
||||
found at http://polymer.github.io/PATENTS.txt
|
||||
*/
|
BIN
supervisor/api/panel/chunk.26be881fcb628958e718.js.gz
Normal file
BIN
supervisor/api/panel/chunk.26be881fcb628958e718.js.gz
Normal file
Binary file not shown.
1
supervisor/api/panel/chunk.26be881fcb628958e718.js.map
Normal file
1
supervisor/api/panel/chunk.26be881fcb628958e718.js.map
Normal file
File diff suppressed because one or more lines are too long
2
supervisor/api/panel/chunk.35929da61d769e57c884.js
Normal file
2
supervisor/api/panel/chunk.35929da61d769e57c884.js
Normal file
File diff suppressed because one or more lines are too long
BIN
supervisor/api/panel/chunk.35929da61d769e57c884.js.gz
Normal file
BIN
supervisor/api/panel/chunk.35929da61d769e57c884.js.gz
Normal file
Binary file not shown.
1
supervisor/api/panel/chunk.35929da61d769e57c884.js.map
Normal file
1
supervisor/api/panel/chunk.35929da61d769e57c884.js.map
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"sources":["webpack:///./hassio/src/ingress-view/hassio-ingress-view.ts"],"names":["customElement","HassioIngressView","property","this","_addon","html","_templateObject2","name","ingress_url","_templateObject","changedProps","_get","_getPrototypeOf","prototype","call","has","addon","route","path","substr","oldRoute","get","oldAddon","undefined","_fetchData","_callee","addonSlug","_ref","_ref2","regeneratorRuntime","wrap","_context","prev","next","Promise","all","fetchHassioAddonInfo","hass","Error","createHassioSession","sent","_slicedToArray","ingress","t0","console","error","alert","message","history","back","stop","css","_templateObject3","LitElement"],"mappings":"snSAmBCA,YAAc,0CACTC,smBACHC,kEACAA,mEACAA,4EAED,WACE,OAAKC,KAAKC,OAMHC,YAAPC,IAC0BH,KAAKC,OAAOG,KACpBJ,KAAKC,OAAOI,aAPrBH,YAAPI,0CAYJ,SAAkBC,GAGhB,GAFAC,EAAAC,EApBEX,EAoBFY,WAAA,eAAAV,MAAAW,KAAAX,KAAmBO,GAEdA,EAAaK,IAAI,SAAtB,CAIA,IAAMC,EAAQb,KAAKc,MAAMC,KAAKC,OAAO,GAE/BC,EAAWV,EAAaW,IAAI,SAC5BC,EAAWF,EAAWA,EAASF,KAAKC,OAAO,QAAKI,EAElDP,GAASA,IAAUM,GACrBnB,KAAKqB,WAAWR,0FAIpB,SAAAS,EAAyBC,GAAzB,IAAAC,EAAAC,EAAAZ,EAAA,OAAAa,mBAAAC,KAAA,SAAAC,GAAA,cAAAA,EAAAC,KAAAD,EAAAE,MAAA,cAAAF,EAAAC,KAAA,EAAAD,EAAAE,KAAA,EAE0BC,QAAQC,IAAI,CAChCC,YAAqBjC,KAAKkC,KAAMX,GAAhC,MAAiD,WAC/C,MAAM,IAAIY,MAAM,iCAElBC,YAAoBpC,KAAKkC,MAAzB,MAAqC,WACnC,MAAM,IAAIC,MAAM,2CAPxB,UAAAX,EAAAI,EAAAS,KAAAZ,EAAAa,EAAAd,EAAA,IAEWX,EAFXY,EAAA,IAWec,QAXf,CAAAX,EAAAE,KAAA,cAYY,IAAIK,MAAM,wCAZtB,OAeInC,KAAKC,OAASY,EAflBe,EAAAE,KAAA,iBAAAF,EAAAC,KAAA,GAAAD,EAAAY,GAAAZ,EAAA,SAkBIa,QAAQC,MAARd,EAAAY,IACAG,MAAMf,EAAAY,GAAII,SAAW,mCACrBC,QAAQC,OApBZ,yBAAAlB,EAAAmB,SAAAzB,EAAAtB,KAAA,yRAwBA,WACE,OAAOgD,YAAPC,UA7D4BC","file":"chunk.35929da61d769e57c884.js","sourcesContent":["import {\n LitElement,\n customElement,\n property,\n TemplateResult,\n html,\n PropertyValues,\n CSSResult,\n css,\n} from \"lit-element\";\nimport { HomeAssistant, Route } from \"../../../src/types\";\nimport { createHassioSession } from \"../../../src/data/hassio/supervisor\";\nimport {\n HassioAddonDetails,\n fetchHassioAddonInfo,\n} from \"../../../src/data/hassio/addon\";\nimport \"../../../src/layouts/hass-loading-screen\";\nimport \"../../../src/layouts/hass-subpage\";\n\n@customElement(\"hassio-ingress-view\")\nclass HassioIngressView extends LitElement {\n @property() public hass!: HomeAssistant;\n @property() public route!: Route;\n @property() private _addon?: HassioAddonDetails;\n\n protected render(): TemplateResult {\n if (!this._addon) {\n return html`\n <hass-loading-screen></hass-loading-screen>\n `;\n }\n\n return html`\n <hass-subpage .header=${this._addon.name} hassio>\n <iframe src=${this._addon.ingress_url}></iframe>\n </hass-subpage>\n `;\n }\n\n protected updated(changedProps: PropertyValues) {\n super.firstUpdated(changedProps);\n\n if (!changedProps.has(\"route\")) {\n return;\n }\n\n const addon = this.route.path.substr(1);\n\n const oldRoute = changedProps.get(\"route\") as this[\"route\"] | undefined;\n const oldAddon = oldRoute ? oldRoute.path.substr(1) : undefined;\n\n if (addon && addon !== oldAddon) {\n this._fetchData(addon);\n }\n }\n\n private async _fetchData(addonSlug: string) {\n try {\n const [addon] = await Promise.all([\n fetchHassioAddonInfo(this.hass, addonSlug).catch(() => {\n throw new Error(\"Failed to fetch add-on info\");\n }),\n createHassioSession(this.hass).catch(() => {\n throw new Error(\"Failed to create an ingress session\");\n }),\n ]);\n\n if (!addon.ingress) {\n throw new Error(\"This add-on does not support ingress\");\n }\n\n this._addon = addon;\n } catch (err) {\n // tslint:disable-next-line\n console.error(err);\n alert(err.message || \"Unknown error starting ingress.\");\n history.back();\n }\n }\n\n static get styles(): CSSResult {\n return css`\n iframe {\n display: block;\n width: 100%;\n height: 100%;\n border: 0;\n }\n paper-icon-button {\n color: var(--text-primary-color);\n }\n `;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n \"hassio-ingress-view\": HassioIngressView;\n }\n}\n"],"sourceRoot":""}
|
3
supervisor/api/panel/chunk.541d0b76b660d8646074.js
Normal file
3
supervisor/api/panel/chunk.541d0b76b660d8646074.js
Normal file
File diff suppressed because one or more lines are too long
189
supervisor/api/panel/chunk.541d0b76b660d8646074.js.LICENSE
Normal file
189
supervisor/api/panel/chunk.541d0b76b660d8646074.js.LICENSE
Normal file
@@ -0,0 +1,189 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright (c) 2017 The Polymer Project Authors. All rights reserved.
|
||||
* This code may only be used under the BSD style license found at
|
||||
* http://polymer.github.io/LICENSE.txt
|
||||
* The complete set of authors may be found at
|
||||
* http://polymer.github.io/AUTHORS.txt
|
||||
* The complete set of contributors may be found at
|
||||
* http://polymer.github.io/CONTRIBUTORS.txt
|
||||
* Code distributed by Google as part of the polymer project is also
|
||||
* subject to an additional IP rights grant found at
|
||||
* http://polymer.github.io/PATENTS.txt
|
||||
*/
|
||||
|
||||
/**
|
||||
@license
|
||||
Copyright (c) 2019 The Polymer Project Authors. All rights reserved.
|
||||
This code may only be used under the BSD style license found at
|
||||
http://polymer.github.io/LICENSE.txt The complete set of authors may be found at
|
||||
http://polymer.github.io/AUTHORS.txt The complete set of contributors may be
|
||||
found at http://polymer.github.io/CONTRIBUTORS.txt Code distributed by Google as
|
||||
part of the polymer project is also subject to an additional IP rights grant
|
||||
found at http://polymer.github.io/PATENTS.txt
|
||||
*/
|
||||
|
||||
/**
|
||||
@license
|
||||
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
|
||||
This code may only be used under the BSD style license found at
|
||||
http://polymer.github.io/LICENSE.txt The complete set of authors may be found at
|
||||
http://polymer.github.io/AUTHORS.txt The complete set of contributors may be
|
||||
found at http://polymer.github.io/CONTRIBUTORS.txt Code distributed by Google as
|
||||
part of the polymer project is also subject to an additional IP rights grant
|
||||
found at http://polymer.github.io/PATENTS.txt
|
||||
*/
|
||||
|
||||
/**
|
||||
@license
|
||||
Copyright 2018 Google Inc. All Rights Reserved.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
@license
|
||||
Copyright (c) 2016 The Polymer Project Authors. All rights reserved.
|
||||
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
||||
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
||||
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
||||
Code distributed by Google as part of the polymer project is also
|
||||
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
||||
*/
|
||||
|
||||
/**
|
||||
@license
|
||||
Copyright (c) 2017 The Polymer Project Authors. All rights reserved.
|
||||
This code may only be used under the BSD style license found at
|
||||
http://polymer.github.io/LICENSE.txt The complete set of authors may be found at
|
||||
http://polymer.github.io/AUTHORS.txt The complete set of contributors may be
|
||||
found at http://polymer.github.io/CONTRIBUTORS.txt Code distributed by Google as
|
||||
part of the polymer project is also subject to an additional IP rights grant
|
||||
found at http://polymer.github.io/PATENTS.txt
|
||||
*/
|
||||
|
||||
/*! *****************************************************************************
|
||||
Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
Licensed under the Apache License, Version 2.0 (the "License"); you may not use
|
||||
this file except in compliance with the License. You may obtain a copy of the
|
||||
License at http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
|
||||
WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
|
||||
MERCHANTABLITY OR NON-INFRINGEMENT.
|
||||
|
||||
See the Apache Version 2.0 License for specific language governing permissions
|
||||
and limitations under the License.
|
||||
***************************************************************************** */
|
||||
|
||||
/**
|
||||
* @license
|
||||
* Copyright (c) 2018 The Polymer Project Authors. All rights reserved.
|
||||
* This code may only be used under the BSD style license found at
|
||||
* http://polymer.github.io/LICENSE.txt
|
||||
* The complete set of authors may be found at
|
||||
* http://polymer.github.io/AUTHORS.txt
|
||||
* The complete set of contributors may be found at
|
||||
* http://polymer.github.io/CONTRIBUTORS.txt
|
||||
* Code distributed by Google as part of the polymer project is also
|
||||
* subject to an additional IP rights grant found at
|
||||
* http://polymer.github.io/PATENTS.txt
|
||||
*/
|
||||
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2019 Google Inc.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2016 Google Inc.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
/**
|
||||
@license
|
||||
Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
|
||||
This code may only be used under the BSD style license found at
|
||||
http://polymer.github.io/LICENSE.txt The complete set of authors may be found at
|
||||
http://polymer.github.io/AUTHORS.txt The complete set of contributors may be
|
||||
found at http://polymer.github.io/CONTRIBUTORS.txt Code distributed by Google as
|
||||
part of the polymer project is also subject to an additional IP rights grant
|
||||
found at http://polymer.github.io/PATENTS.txt
|
||||
*/
|
||||
|
||||
/**
|
||||
@license
|
||||
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
|
||||
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
||||
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
||||
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
||||
Code distributed by Google as part of the polymer project is also
|
||||
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
||||
*/
|
||||
|
||||
/**
|
||||
@license
|
||||
Copyright (c) 2016 The Polymer Project Authors. All rights reserved.
|
||||
This code may only be used under the BSD style license found at
|
||||
http://polymer.github.io/LICENSE.txt The complete set of authors may be found at
|
||||
http://polymer.github.io/AUTHORS.txt The complete set of contributors may be
|
||||
found at http://polymer.github.io/CONTRIBUTORS.txt Code distributed by Google as
|
||||
part of the polymer project is also subject to an additional IP rights grant
|
||||
found at http://polymer.github.io/PATENTS.txt
|
||||
*/
|
||||
|
||||
/*!
|
||||
* Fuse.js v3.4.4 - Lightweight fuzzy-search (http://fusejs.io)
|
||||
*
|
||||
* Copyright (c) 2012-2017 Kirollos Risk (http://kiro.me)
|
||||
* All Rights Reserved. Apache Software License 2.0
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*/
|
BIN
supervisor/api/panel/chunk.541d0b76b660d8646074.js.gz
Normal file
BIN
supervisor/api/panel/chunk.541d0b76b660d8646074.js.gz
Normal file
Binary file not shown.
1
supervisor/api/panel/chunk.541d0b76b660d8646074.js.map
Normal file
1
supervisor/api/panel/chunk.541d0b76b660d8646074.js.map
Normal file
File diff suppressed because one or more lines are too long
2
supervisor/api/panel/chunk.5e32280d595be3742226.js
Normal file
2
supervisor/api/panel/chunk.5e32280d595be3742226.js
Normal file
File diff suppressed because one or more lines are too long
BIN
supervisor/api/panel/chunk.5e32280d595be3742226.js.gz
Normal file
BIN
supervisor/api/panel/chunk.5e32280d595be3742226.js.gz
Normal file
Binary file not shown.
1
supervisor/api/panel/chunk.5e32280d595be3742226.js.map
Normal file
1
supervisor/api/panel/chunk.5e32280d595be3742226.js.map
Normal file
File diff suppressed because one or more lines are too long
2
supervisor/api/panel/chunk.70a435e100109291f210.js
Normal file
2
supervisor/api/panel/chunk.70a435e100109291f210.js
Normal file
File diff suppressed because one or more lines are too long
BIN
supervisor/api/panel/chunk.70a435e100109291f210.js.gz
Normal file
BIN
supervisor/api/panel/chunk.70a435e100109291f210.js.gz
Normal file
Binary file not shown.
1
supervisor/api/panel/chunk.70a435e100109291f210.js.map
Normal file
1
supervisor/api/panel/chunk.70a435e100109291f210.js.map
Normal file
File diff suppressed because one or more lines are too long
3
supervisor/api/panel/chunk.9339f70c8bfe2cbef5ad.js
Normal file
3
supervisor/api/panel/chunk.9339f70c8bfe2cbef5ad.js
Normal file
File diff suppressed because one or more lines are too long
20
supervisor/api/panel/chunk.9339f70c8bfe2cbef5ad.js.LICENSE
Normal file
20
supervisor/api/panel/chunk.9339f70c8bfe2cbef5ad.js.LICENSE
Normal file
@@ -0,0 +1,20 @@
|
||||
/**
|
||||
@license
|
||||
Copyright (c) 2017 The Polymer Project Authors. All rights reserved.
|
||||
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
||||
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
||||
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
||||
Code distributed by Google as part of the polymer project is also
|
||||
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
||||
*/
|
||||
|
||||
/**
|
||||
@license
|
||||
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
|
||||
This code may only be used under the BSD style license found at
|
||||
http://polymer.github.io/LICENSE.txt The complete set of authors may be found at
|
||||
http://polymer.github.io/AUTHORS.txt The complete set of contributors may be
|
||||
found at http://polymer.github.io/CONTRIBUTORS.txt Code distributed by Google as
|
||||
part of the polymer project is also subject to an additional IP rights grant
|
||||
found at http://polymer.github.io/PATENTS.txt
|
||||
*/
|
BIN
supervisor/api/panel/chunk.9339f70c8bfe2cbef5ad.js.gz
Normal file
BIN
supervisor/api/panel/chunk.9339f70c8bfe2cbef5ad.js.gz
Normal file
Binary file not shown.
1
supervisor/api/panel/chunk.9339f70c8bfe2cbef5ad.js.map
Normal file
1
supervisor/api/panel/chunk.9339f70c8bfe2cbef5ad.js.map
Normal file
File diff suppressed because one or more lines are too long
3
supervisor/api/panel/chunk.93a8a2e1dbccae0e07fa.js
Normal file
3
supervisor/api/panel/chunk.93a8a2e1dbccae0e07fa.js
Normal file
File diff suppressed because one or more lines are too long
28
supervisor/api/panel/chunk.93a8a2e1dbccae0e07fa.js.LICENSE
Normal file
28
supervisor/api/panel/chunk.93a8a2e1dbccae0e07fa.js.LICENSE
Normal file
@@ -0,0 +1,28 @@
|
||||
/**
|
||||
@license
|
||||
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
|
||||
This code may only be used under the BSD style license found at
|
||||
http://polymer.github.io/LICENSE.txt The complete set of authors may be found at
|
||||
http://polymer.github.io/AUTHORS.txt The complete set of contributors may be
|
||||
found at http://polymer.github.io/CONTRIBUTORS.txt Code distributed by Google as
|
||||
part of the polymer project is also subject to an additional IP rights grant
|
||||
found at http://polymer.github.io/PATENTS.txt
|
||||
*/
|
||||
|
||||
/*!
|
||||
* The buffer module from node.js, for the browser.
|
||||
*
|
||||
* @author Feross Aboukhadijeh <feross@feross.org> <http://feross.org>
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
/**
|
||||
@license
|
||||
Copyright (c) 2016 The Polymer Project Authors. All rights reserved.
|
||||
This code may only be used under the BSD style license found at
|
||||
http://polymer.github.io/LICENSE.txt The complete set of authors may be found at
|
||||
http://polymer.github.io/AUTHORS.txt The complete set of contributors may be
|
||||
found at http://polymer.github.io/CONTRIBUTORS.txt Code distributed by Google as
|
||||
part of the polymer project is also subject to an additional IP rights grant
|
||||
found at http://polymer.github.io/PATENTS.txt
|
||||
*/
|
BIN
supervisor/api/panel/chunk.93a8a2e1dbccae0e07fa.js.gz
Normal file
BIN
supervisor/api/panel/chunk.93a8a2e1dbccae0e07fa.js.gz
Normal file
Binary file not shown.
1
supervisor/api/panel/chunk.93a8a2e1dbccae0e07fa.js.map
Normal file
1
supervisor/api/panel/chunk.93a8a2e1dbccae0e07fa.js.map
Normal file
File diff suppressed because one or more lines are too long
2
supervisor/api/panel/chunk.a9cf4ae83af78188e158.js
Normal file
2
supervisor/api/panel/chunk.a9cf4ae83af78188e158.js
Normal file
File diff suppressed because one or more lines are too long
BIN
supervisor/api/panel/chunk.a9cf4ae83af78188e158.js.gz
Normal file
BIN
supervisor/api/panel/chunk.a9cf4ae83af78188e158.js.gz
Normal file
Binary file not shown.
1
supervisor/api/panel/chunk.a9cf4ae83af78188e158.js.map
Normal file
1
supervisor/api/panel/chunk.a9cf4ae83af78188e158.js.map
Normal file
File diff suppressed because one or more lines are too long
2
supervisor/api/panel/chunk.af76a4db9eb1e2862aae.js
Normal file
2
supervisor/api/panel/chunk.af76a4db9eb1e2862aae.js
Normal file
File diff suppressed because one or more lines are too long
BIN
supervisor/api/panel/chunk.af76a4db9eb1e2862aae.js.gz
Normal file
BIN
supervisor/api/panel/chunk.af76a4db9eb1e2862aae.js.gz
Normal file
Binary file not shown.
1
supervisor/api/panel/chunk.af76a4db9eb1e2862aae.js.map
Normal file
1
supervisor/api/panel/chunk.af76a4db9eb1e2862aae.js.map
Normal file
File diff suppressed because one or more lines are too long
3
supervisor/api/panel/chunk.b2a892416a728ca06e9a.js
Normal file
3
supervisor/api/panel/chunk.b2a892416a728ca06e9a.js
Normal file
File diff suppressed because one or more lines are too long
10
supervisor/api/panel/chunk.b2a892416a728ca06e9a.js.LICENSE
Normal file
10
supervisor/api/panel/chunk.b2a892416a728ca06e9a.js.LICENSE
Normal file
@@ -0,0 +1,10 @@
|
||||
/**
|
||||
@license
|
||||
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
|
||||
This code may only be used under the BSD style license found at
|
||||
http://polymer.github.io/LICENSE.txt The complete set of authors may be found at
|
||||
http://polymer.github.io/AUTHORS.txt The complete set of contributors may be
|
||||
found at http://polymer.github.io/CONTRIBUTORS.txt Code distributed by Google as
|
||||
part of the polymer project is also subject to an additional IP rights grant
|
||||
found at http://polymer.github.io/PATENTS.txt
|
||||
*/
|
BIN
supervisor/api/panel/chunk.b2a892416a728ca06e9a.js.gz
Normal file
BIN
supervisor/api/panel/chunk.b2a892416a728ca06e9a.js.gz
Normal file
Binary file not shown.
1
supervisor/api/panel/chunk.b2a892416a728ca06e9a.js.map
Normal file
1
supervisor/api/panel/chunk.b2a892416a728ca06e9a.js.map
Normal file
File diff suppressed because one or more lines are too long
3
supervisor/api/panel/chunk.b6e61f8340c32e6904ca.js
Normal file
3
supervisor/api/panel/chunk.b6e61f8340c32e6904ca.js
Normal file
File diff suppressed because one or more lines are too long
16
supervisor/api/panel/chunk.b6e61f8340c32e6904ca.js.LICENSE
Normal file
16
supervisor/api/panel/chunk.b6e61f8340c32e6904ca.js.LICENSE
Normal file
@@ -0,0 +1,16 @@
|
||||
/**
|
||||
@license
|
||||
Copyright 2018 Google Inc. All Rights Reserved.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
BIN
supervisor/api/panel/chunk.b6e61f8340c32e6904ca.js.gz
Normal file
BIN
supervisor/api/panel/chunk.b6e61f8340c32e6904ca.js.gz
Normal file
Binary file not shown.
1
supervisor/api/panel/chunk.b6e61f8340c32e6904ca.js.map
Normal file
1
supervisor/api/panel/chunk.b6e61f8340c32e6904ca.js.map
Normal file
File diff suppressed because one or more lines are too long
3
supervisor/api/panel/chunk.ea2041e4c67d4c05b0dd.js
Normal file
3
supervisor/api/panel/chunk.ea2041e4c67d4c05b0dd.js
Normal file
File diff suppressed because one or more lines are too long
10
supervisor/api/panel/chunk.ea2041e4c67d4c05b0dd.js.LICENSE
Normal file
10
supervisor/api/panel/chunk.ea2041e4c67d4c05b0dd.js.LICENSE
Normal file
@@ -0,0 +1,10 @@
|
||||
/**
|
||||
@license
|
||||
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
|
||||
This code may only be used under the BSD style license found at
|
||||
http://polymer.github.io/LICENSE.txt The complete set of authors may be found at
|
||||
http://polymer.github.io/AUTHORS.txt The complete set of contributors may be
|
||||
found at http://polymer.github.io/CONTRIBUTORS.txt Code distributed by Google as
|
||||
part of the polymer project is also subject to an additional IP rights grant
|
||||
found at http://polymer.github.io/PATENTS.txt
|
||||
*/
|
BIN
supervisor/api/panel/chunk.ea2041e4c67d4c05b0dd.js.gz
Normal file
BIN
supervisor/api/panel/chunk.ea2041e4c67d4c05b0dd.js.gz
Normal file
Binary file not shown.
1
supervisor/api/panel/chunk.ea2041e4c67d4c05b0dd.js.map
Normal file
1
supervisor/api/panel/chunk.ea2041e4c67d4c05b0dd.js.map
Normal file
File diff suppressed because one or more lines are too long
@@ -1,2 +1,2 @@
|
||||
!function(e){function n(n){for(var t,o,a=n[0],i=n[1],c=0,d=[];c<a.length;c++)o=a[c],Object.prototype.hasOwnProperty.call(r,o)&&r[o]&&d.push(r[o][0]),r[o]=0;for(t in i)Object.prototype.hasOwnProperty.call(i,t)&&(e[t]=i[t]);for(u&&u(n);d.length;)d.shift()()}var t={},r={6:0};function o(n){if(t[n])return t[n].exports;var r=t[n]={i:n,l:!1,exports:{}};return e[n].call(r.exports,r,r.exports,o),r.l=!0,r.exports}o.e=function(e){var n=[],t=r[e];if(0!==t)if(t)n.push(t[2]);else{var a=new Promise(function(n,o){t=r[e]=[n,o]});n.push(t[2]=a);var i,c=document.createElement("script");c.charset="utf-8",c.timeout=120,o.nc&&c.setAttribute("nonce",o.nc),c.src=function(e){return o.p+"chunk."+{0:"87b1d37fc9b8a6f7e2a6",1:"e46c606dd9100816af4e",2:"92a11ac1b80e0d7839d2",3:"429840c83fad61bc51a8",4:"715824f4764bdbe425b1",5:"9d371c8143226d4eaaee",7:"43e40fd69686ad51301d",8:"0b82745c7bdffe5c1404",9:"990ee58006b248f55d23",10:"4d45ee0a3d852768f97e",11:"b60200a57d6f63941b30",12:"b2dce600432c76a53d8c",13:"8527374a266cecf93aa9",14:"f49e500cf58ea310d452",15:"d4931d72592ad48ba2be"}[e]+".js"}(e);var u=new Error;i=function(n){c.onerror=c.onload=null,clearTimeout(d);var t=r[e];if(0!==t){if(t){var o=n&&("load"===n.type?"missing":n.type),a=n&&n.target&&n.target.src;u.message="Loading chunk "+e+" failed.\n("+o+": "+a+")",u.name="ChunkLoadError",u.type=o,u.request=a,t[1](u)}r[e]=void 0}};var d=setTimeout(function(){i({type:"timeout",target:c})},12e4);c.onerror=c.onload=i,document.head.appendChild(c)}return Promise.all(n)},o.m=e,o.c=t,o.d=function(e,n,t){o.o(e,n)||Object.defineProperty(e,n,{enumerable:!0,get:t})},o.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},o.t=function(e,n){if(1&n&&(e=o(e)),8&n)return e;if(4&n&&"object"==typeof e&&e&&e.__esModule)return e;var t=Object.create(null);if(o.r(t),Object.defineProperty(t,"default",{enumerable:!0,value:e}),2&n&&"string"!=typeof e)for(var r in e)o.d(t,r,function(n){return e[n]}.bind(null,r));return t},o.n=function(e){var n=e&&e.__esModule?function(){return e.default}:function(){return e};return o.d(n,"a",n),n},o.o=function(e,n){return Object.prototype.hasOwnProperty.call(e,n)},o.p="/api/hassio/app/",o.oe=function(e){throw console.error(e),e};var a=self.webpackJsonp=self.webpackJsonp||[],i=a.push.bind(a);a.push=n,a=a.slice();for(var c=0;c<a.length;c++)n(a[c]);var u=i;o(o.s=0)}([function(e,n,t){window.loadES5Adapter().then(function(){t.e(12).then(t.t.bind(null,1,7)),Promise.all([t.e(1),t.e(8)]).then(t.bind(null,3)),Promise.all([t.e(1),t.e(15),t.e(10)]).then(t.bind(null,2))});var r=document.createElement("style");r.innerHTML="\nbody {\n font-family: Roboto, sans-serif;\n -moz-osx-font-smoothing: grayscale;\n -webkit-font-smoothing: antialiased;\n font-weight: 400;\n margin: 0;\n padding: 0;\n height: 100vh;\n}\n",document.head.appendChild(r)}]);
|
||||
!function(e){function n(n){for(var t,o,a=n[0],i=n[1],c=0,f=[];c<a.length;c++)o=a[c],Object.prototype.hasOwnProperty.call(r,o)&&r[o]&&f.push(r[o][0]),r[o]=0;for(t in i)Object.prototype.hasOwnProperty.call(i,t)&&(e[t]=i[t]);for(u&&u(n);f.length;)f.shift()()}var t={},r={6:0};function o(n){if(t[n])return t[n].exports;var r=t[n]={i:n,l:!1,exports:{}};return e[n].call(r.exports,r,r.exports,o),r.l=!0,r.exports}o.e=function(e){var n=[],t=r[e];if(0!==t)if(t)n.push(t[2]);else{var a=new Promise(function(n,o){t=r[e]=[n,o]});n.push(t[2]=a);var i,c=document.createElement("script");c.charset="utf-8",c.timeout=120,o.nc&&c.setAttribute("nonce",o.nc),c.src=function(e){return o.p+"chunk."+{0:"b6e61f8340c32e6904ca",1:"9339f70c8bfe2cbef5ad",2:"26756b56961f7bf94974",3:"b2a892416a728ca06e9a",4:"26be881fcb628958e718",5:"ea2041e4c67d4c05b0dd",7:"1a25d23325fed5a4d90b",8:"af76a4db9eb1e2862aae",9:"35929da61d769e57c884",10:"5e32280d595be3742226",11:"a9cf4ae83af78188e158",12:"b2dce600432c76a53d8c",13:"70a435e100109291f210",14:"93a8a2e1dbccae0e07fa",15:"541d0b76b660d8646074"}[e]+".js"}(e);var u=new Error;i=function(n){c.onerror=c.onload=null,clearTimeout(f);var t=r[e];if(0!==t){if(t){var o=n&&("load"===n.type?"missing":n.type),a=n&&n.target&&n.target.src;u.message="Loading chunk "+e+" failed.\n("+o+": "+a+")",u.name="ChunkLoadError",u.type=o,u.request=a,t[1](u)}r[e]=void 0}};var f=setTimeout(function(){i({type:"timeout",target:c})},12e4);c.onerror=c.onload=i,document.head.appendChild(c)}return Promise.all(n)},o.m=e,o.c=t,o.d=function(e,n,t){o.o(e,n)||Object.defineProperty(e,n,{enumerable:!0,get:t})},o.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},o.t=function(e,n){if(1&n&&(e=o(e)),8&n)return e;if(4&n&&"object"==typeof e&&e&&e.__esModule)return e;var t=Object.create(null);if(o.r(t),Object.defineProperty(t,"default",{enumerable:!0,value:e}),2&n&&"string"!=typeof e)for(var r in e)o.d(t,r,function(n){return e[n]}.bind(null,r));return t},o.n=function(e){var n=e&&e.__esModule?function(){return e.default}:function(){return e};return o.d(n,"a",n),n},o.o=function(e,n){return Object.prototype.hasOwnProperty.call(e,n)},o.p="/api/hassio/app/",o.oe=function(e){throw console.error(e),e};var a=self.webpackJsonp=self.webpackJsonp||[],i=a.push.bind(a);a.push=n,a=a.slice();for(var c=0;c<a.length;c++)n(a[c]);var u=i;o(o.s=0)}([function(e,n,t){window.loadES5Adapter().then(function(){t.e(12).then(t.t.bind(null,1,7)),Promise.all([t.e(1),t.e(8)]).then(t.bind(null,3)),Promise.all([t.e(1),t.e(15),t.e(10)]).then(t.bind(null,2))});var r=document.createElement("style");r.innerHTML="\nbody {\n font-family: Roboto, sans-serif;\n -moz-osx-font-smoothing: grayscale;\n -webkit-font-smoothing: antialiased;\n font-weight: 400;\n margin: 0;\n padding: 0;\n height: 100vh;\n}\n",document.head.appendChild(r)}]);
|
||||
//# sourceMappingURL=entrypoint.js.map
|
Binary file not shown.
File diff suppressed because one or more lines are too long
@@ -1,43 +1,43 @@
|
||||
{
|
||||
"vendors~confirmation~hassio-addon-view.js": "/api/hassio/app/chunk.87b1d37fc9b8a6f7e2a6.js",
|
||||
"vendors~confirmation~hassio-addon-view.js.map": "/api/hassio/app/chunk.87b1d37fc9b8a6f7e2a6.js.map",
|
||||
"vendors~hassio-icons~hassio-main.js": "/api/hassio/app/chunk.e46c606dd9100816af4e.js",
|
||||
"vendors~hassio-icons~hassio-main.js.map": "/api/hassio/app/chunk.e46c606dd9100816af4e.js.map",
|
||||
"codemirror.js": "/api/hassio/app/chunk.92a11ac1b80e0d7839d2.js",
|
||||
"codemirror.js.map": "/api/hassio/app/chunk.92a11ac1b80e0d7839d2.js.map",
|
||||
"confirmation.js": "/api/hassio/app/chunk.429840c83fad61bc51a8.js",
|
||||
"confirmation.js.map": "/api/hassio/app/chunk.429840c83fad61bc51a8.js.map",
|
||||
"dialog-hassio-markdown.js": "/api/hassio/app/chunk.715824f4764bdbe425b1.js",
|
||||
"dialog-hassio-markdown.js.map": "/api/hassio/app/chunk.715824f4764bdbe425b1.js.map",
|
||||
"dialog-hassio-snapshot.js": "/api/hassio/app/chunk.9d371c8143226d4eaaee.js",
|
||||
"dialog-hassio-snapshot.js.map": "/api/hassio/app/chunk.9d371c8143226d4eaaee.js.map",
|
||||
"vendors~confirmation~hassio-addon-view.js": "/api/hassio/app/chunk.b6e61f8340c32e6904ca.js",
|
||||
"vendors~confirmation~hassio-addon-view.js.map": "/api/hassio/app/chunk.b6e61f8340c32e6904ca.js.map",
|
||||
"vendors~hassio-icons~hassio-main.js": "/api/hassio/app/chunk.9339f70c8bfe2cbef5ad.js",
|
||||
"vendors~hassio-icons~hassio-main.js.map": "/api/hassio/app/chunk.9339f70c8bfe2cbef5ad.js.map",
|
||||
"codemirror.js": "/api/hassio/app/chunk.26756b56961f7bf94974.js",
|
||||
"codemirror.js.map": "/api/hassio/app/chunk.26756b56961f7bf94974.js.map",
|
||||
"confirmation.js": "/api/hassio/app/chunk.b2a892416a728ca06e9a.js",
|
||||
"confirmation.js.map": "/api/hassio/app/chunk.b2a892416a728ca06e9a.js.map",
|
||||
"dialog-hassio-markdown.js": "/api/hassio/app/chunk.26be881fcb628958e718.js",
|
||||
"dialog-hassio-markdown.js.map": "/api/hassio/app/chunk.26be881fcb628958e718.js.map",
|
||||
"dialog-hassio-snapshot.js": "/api/hassio/app/chunk.ea2041e4c67d4c05b0dd.js",
|
||||
"dialog-hassio-snapshot.js.map": "/api/hassio/app/chunk.ea2041e4c67d4c05b0dd.js.map",
|
||||
"entrypoint.js": "/api/hassio/app/entrypoint.js",
|
||||
"entrypoint.js.map": "/api/hassio/app/entrypoint.js.map",
|
||||
"hassio-addon-view.js": "/api/hassio/app/chunk.43e40fd69686ad51301d.js",
|
||||
"hassio-addon-view.js.map": "/api/hassio/app/chunk.43e40fd69686ad51301d.js.map",
|
||||
"hassio-icons.js": "/api/hassio/app/chunk.0b82745c7bdffe5c1404.js",
|
||||
"hassio-icons.js.map": "/api/hassio/app/chunk.0b82745c7bdffe5c1404.js.map",
|
||||
"hassio-ingress-view.js": "/api/hassio/app/chunk.990ee58006b248f55d23.js",
|
||||
"hassio-ingress-view.js.map": "/api/hassio/app/chunk.990ee58006b248f55d23.js.map",
|
||||
"hassio-main.js": "/api/hassio/app/chunk.4d45ee0a3d852768f97e.js",
|
||||
"hassio-main.js.map": "/api/hassio/app/chunk.4d45ee0a3d852768f97e.js.map",
|
||||
"mdi-icons.js": "/api/hassio/app/chunk.b60200a57d6f63941b30.js",
|
||||
"mdi-icons.js.map": "/api/hassio/app/chunk.b60200a57d6f63941b30.js.map",
|
||||
"hassio-addon-view.js": "/api/hassio/app/chunk.1a25d23325fed5a4d90b.js",
|
||||
"hassio-addon-view.js.map": "/api/hassio/app/chunk.1a25d23325fed5a4d90b.js.map",
|
||||
"hassio-icons.js": "/api/hassio/app/chunk.af76a4db9eb1e2862aae.js",
|
||||
"hassio-icons.js.map": "/api/hassio/app/chunk.af76a4db9eb1e2862aae.js.map",
|
||||
"hassio-ingress-view.js": "/api/hassio/app/chunk.35929da61d769e57c884.js",
|
||||
"hassio-ingress-view.js.map": "/api/hassio/app/chunk.35929da61d769e57c884.js.map",
|
||||
"hassio-main.js": "/api/hassio/app/chunk.5e32280d595be3742226.js",
|
||||
"hassio-main.js.map": "/api/hassio/app/chunk.5e32280d595be3742226.js.map",
|
||||
"mdi-icons.js": "/api/hassio/app/chunk.a9cf4ae83af78188e158.js",
|
||||
"mdi-icons.js.map": "/api/hassio/app/chunk.a9cf4ae83af78188e158.js.map",
|
||||
"roboto.js": "/api/hassio/app/chunk.b2dce600432c76a53d8c.js",
|
||||
"roboto.js.map": "/api/hassio/app/chunk.b2dce600432c76a53d8c.js.map",
|
||||
"vendors~codemirror.js": "/api/hassio/app/chunk.8527374a266cecf93aa9.js",
|
||||
"vendors~codemirror.js.map": "/api/hassio/app/chunk.8527374a266cecf93aa9.js.map",
|
||||
"vendors~hassio-addon-view.js": "/api/hassio/app/chunk.f49e500cf58ea310d452.js",
|
||||
"vendors~hassio-addon-view.js.map": "/api/hassio/app/chunk.f49e500cf58ea310d452.js.map",
|
||||
"vendors~hassio-main.js": "/api/hassio/app/chunk.d4931d72592ad48ba2be.js",
|
||||
"vendors~hassio-main.js.map": "/api/hassio/app/chunk.d4931d72592ad48ba2be.js.map",
|
||||
"201359fd5a526afe13ef.worker.js": "/api/hassio/app/201359fd5a526afe13ef.worker.js",
|
||||
"201359fd5a526afe13ef.worker.js.map": "/api/hassio/app/201359fd5a526afe13ef.worker.js.map",
|
||||
"chunk.429840c83fad61bc51a8.js.LICENSE": "/api/hassio/app/chunk.429840c83fad61bc51a8.js.LICENSE",
|
||||
"chunk.715824f4764bdbe425b1.js.LICENSE": "/api/hassio/app/chunk.715824f4764bdbe425b1.js.LICENSE",
|
||||
"chunk.87b1d37fc9b8a6f7e2a6.js.LICENSE": "/api/hassio/app/chunk.87b1d37fc9b8a6f7e2a6.js.LICENSE",
|
||||
"chunk.9d371c8143226d4eaaee.js.LICENSE": "/api/hassio/app/chunk.9d371c8143226d4eaaee.js.LICENSE",
|
||||
"chunk.d4931d72592ad48ba2be.js.LICENSE": "/api/hassio/app/chunk.d4931d72592ad48ba2be.js.LICENSE",
|
||||
"chunk.e46c606dd9100816af4e.js.LICENSE": "/api/hassio/app/chunk.e46c606dd9100816af4e.js.LICENSE",
|
||||
"chunk.f49e500cf58ea310d452.js.LICENSE": "/api/hassio/app/chunk.f49e500cf58ea310d452.js.LICENSE"
|
||||
"vendors~codemirror.js": "/api/hassio/app/chunk.70a435e100109291f210.js",
|
||||
"vendors~codemirror.js.map": "/api/hassio/app/chunk.70a435e100109291f210.js.map",
|
||||
"vendors~hassio-addon-view.js": "/api/hassio/app/chunk.93a8a2e1dbccae0e07fa.js",
|
||||
"vendors~hassio-addon-view.js.map": "/api/hassio/app/chunk.93a8a2e1dbccae0e07fa.js.map",
|
||||
"vendors~hassio-main.js": "/api/hassio/app/chunk.541d0b76b660d8646074.js",
|
||||
"vendors~hassio-main.js.map": "/api/hassio/app/chunk.541d0b76b660d8646074.js.map",
|
||||
"a1ebfa0a88593a3b571c.worker.js": "/api/hassio/app/a1ebfa0a88593a3b571c.worker.js",
|
||||
"a1ebfa0a88593a3b571c.worker.js.map": "/api/hassio/app/a1ebfa0a88593a3b571c.worker.js.map",
|
||||
"chunk.26be881fcb628958e718.js.LICENSE": "/api/hassio/app/chunk.26be881fcb628958e718.js.LICENSE",
|
||||
"chunk.541d0b76b660d8646074.js.LICENSE": "/api/hassio/app/chunk.541d0b76b660d8646074.js.LICENSE",
|
||||
"chunk.9339f70c8bfe2cbef5ad.js.LICENSE": "/api/hassio/app/chunk.9339f70c8bfe2cbef5ad.js.LICENSE",
|
||||
"chunk.93a8a2e1dbccae0e07fa.js.LICENSE": "/api/hassio/app/chunk.93a8a2e1dbccae0e07fa.js.LICENSE",
|
||||
"chunk.b2a892416a728ca06e9a.js.LICENSE": "/api/hassio/app/chunk.b2a892416a728ca06e9a.js.LICENSE",
|
||||
"chunk.b6e61f8340c32e6904ca.js.LICENSE": "/api/hassio/app/chunk.b6e61f8340c32e6904ca.js.LICENSE",
|
||||
"chunk.ea2041e4c67d4c05b0dd.js.LICENSE": "/api/hassio/app/chunk.ea2041e4c67d4c05b0dd.js.LICENSE"
|
||||
}
|
@@ -3,6 +3,7 @@ import asyncio
|
||||
from contextlib import suppress
|
||||
import logging
|
||||
from pathlib import Path
|
||||
import shutil
|
||||
from typing import Awaitable, Optional
|
||||
|
||||
import jinja2
|
||||
@@ -18,6 +19,7 @@ from .validate import SCHEMA_AUDIO_CONFIG
|
||||
_LOGGER: logging.Logger = logging.getLogger(__name__)
|
||||
|
||||
PULSE_CLIENT_TMPL: Path = Path(__file__).parents[0].joinpath("data/pulse-client.tmpl")
|
||||
ASOUND_TMPL: Path = Path(__file__).parents[0].joinpath("data/asound.tmpl")
|
||||
|
||||
|
||||
class Audio(JsonConfig, CoreSysAttributes):
|
||||
@@ -31,10 +33,15 @@ class Audio(JsonConfig, CoreSysAttributes):
|
||||
self.client_template: Optional[jinja2.Template] = None
|
||||
|
||||
@property
|
||||
def path_extern_data(self) -> Path:
|
||||
"""Return path of pulse cookie file."""
|
||||
def path_extern_pulse(self) -> Path:
|
||||
"""Return path of pulse socket file."""
|
||||
return self.sys_config.path_extern_audio.joinpath("external")
|
||||
|
||||
@property
|
||||
def path_extern_asound(self) -> Path:
|
||||
"""Return path of default asound config file."""
|
||||
return self.sys_config.path_extern_audio.joinpath("asound")
|
||||
|
||||
@property
|
||||
def version(self) -> Optional[str]:
|
||||
"""Return current version of Audio."""
|
||||
@@ -81,9 +88,7 @@ class Audio(JsonConfig, CoreSysAttributes):
|
||||
|
||||
# Run PulseAudio
|
||||
with suppress(AudioError):
|
||||
if await self.instance.is_running():
|
||||
await self.restart()
|
||||
else:
|
||||
if not await self.instance.is_running():
|
||||
await self.start()
|
||||
|
||||
# Initialize Client Template
|
||||
@@ -92,6 +97,14 @@ class Audio(JsonConfig, CoreSysAttributes):
|
||||
except OSError as err:
|
||||
_LOGGER.error("Can't read pulse-client.tmpl: %s", err)
|
||||
|
||||
# Setup default asound config
|
||||
asound = self.sys_config.path_audio.joinpath("asound")
|
||||
if not asound.exists():
|
||||
try:
|
||||
shutil.copy(ASOUND_TMPL, asound)
|
||||
except OSError as err:
|
||||
_LOGGER.error("Can't create default asound: %s", err)
|
||||
|
||||
async def install(self) -> None:
|
||||
"""Install Audio."""
|
||||
_LOGGER.info("Setup Audio plugin")
|
||||
@@ -137,8 +150,12 @@ class Audio(JsonConfig, CoreSysAttributes):
|
||||
|
||||
async def restart(self) -> None:
|
||||
"""Restart Audio plugin."""
|
||||
with suppress(DockerAPIError):
|
||||
_LOGGER.info("Restart Audio plugin")
|
||||
try:
|
||||
await self.instance.restart()
|
||||
except DockerAPIError:
|
||||
_LOGGER.error("Can't start Audio plugin")
|
||||
raise AudioError() from None
|
||||
|
||||
async def start(self) -> None:
|
||||
"""Run CoreDNS."""
|
||||
|
@@ -21,6 +21,7 @@ from .dns import CoreDNS
|
||||
from .hassos import HassOS
|
||||
from .homeassistant import HomeAssistant
|
||||
from .host import HostManager
|
||||
from .hwmon import HwMonitor
|
||||
from .ingress import Ingress
|
||||
from .services import ServiceManager
|
||||
from .snapshots import SnapshotManager
|
||||
@@ -57,6 +58,7 @@ async def initialize_coresys():
|
||||
coresys.addons = AddonManager(coresys)
|
||||
coresys.snapshots = SnapshotManager(coresys)
|
||||
coresys.host = HostManager(coresys)
|
||||
coresys.hwmonitor = HwMonitor(coresys)
|
||||
coresys.ingress = Ingress(coresys)
|
||||
coresys.tasks = Tasks(coresys)
|
||||
coresys.services = ServiceManager(coresys)
|
||||
|
@@ -3,7 +3,7 @@ from enum import Enum
|
||||
from ipaddress import ip_network
|
||||
from pathlib import Path
|
||||
|
||||
SUPERVISOR_VERSION = "202"
|
||||
SUPERVISOR_VERSION = "205"
|
||||
|
||||
|
||||
URL_HASSIO_ADDONS = "https://github.com/home-assistant/hassio-addons"
|
||||
@@ -233,6 +233,11 @@ ATTR_STAGE = "stage"
|
||||
ATTR_CLI = "cli"
|
||||
ATTR_DEFAULT = "default"
|
||||
ATTR_VOLUME = "volume"
|
||||
ATTR_CARD = "card"
|
||||
ATTR_INDEX = "index"
|
||||
ATTR_ACTIVE = "active"
|
||||
ATTR_APPLICATION = "application"
|
||||
ATTR_INIT = "init"
|
||||
|
||||
PROVIDE_SERVICE = "provide"
|
||||
NEED_SERVICE = "need"
|
||||
|
@@ -142,13 +142,16 @@ class Core(CoreSysAttributes):
|
||||
if self.sys_homeassistant.version == "landingpage":
|
||||
self.sys_create_task(self.sys_homeassistant.install())
|
||||
|
||||
# Start observe the host Hardware
|
||||
await self.sys_hwmonitor.load()
|
||||
|
||||
# Upate Host/Deivce information
|
||||
self.sys_create_task(self.sys_host.reload())
|
||||
self.sys_create_task(self.sys_updater.reload())
|
||||
|
||||
_LOGGER.info("Supervisor is up and running")
|
||||
self.state = CoreStates.RUNNING
|
||||
|
||||
# On full host boot, relaod information
|
||||
self.sys_create_task(self.sys_host.reload())
|
||||
self.sys_create_task(self.sys_updater.reload())
|
||||
|
||||
async def stop(self):
|
||||
"""Stop a running orchestration."""
|
||||
# don't process scheduler anymore
|
||||
@@ -168,6 +171,7 @@ class Core(CoreSysAttributes):
|
||||
self.sys_websession_ssl.close(),
|
||||
self.sys_ingress.unload(),
|
||||
self.sys_dns.unload(),
|
||||
self.sys_hwmonitor.unload(),
|
||||
]
|
||||
)
|
||||
except asyncio.TimeoutError:
|
||||
|
@@ -22,6 +22,7 @@ if TYPE_CHECKING:
|
||||
from .discovery import Discovery
|
||||
from .dns import CoreDNS
|
||||
from .hassos import HassOS
|
||||
from .hwmon import HwMonitor
|
||||
from .homeassistant import HomeAssistant
|
||||
from .host import HostManager
|
||||
from .ingress import Ingress
|
||||
@@ -76,6 +77,7 @@ class CoreSys:
|
||||
self._secrets: Optional[SecretsManager] = None
|
||||
self._store: Optional[StoreManager] = None
|
||||
self._discovery: Optional[Discovery] = None
|
||||
self._hwmonitor: Optional[HwMonitor] = None
|
||||
|
||||
@property
|
||||
def machine(self) -> str:
|
||||
@@ -345,6 +347,18 @@ class CoreSys:
|
||||
raise RuntimeError("HostManager already set!")
|
||||
self._host = value
|
||||
|
||||
@property
|
||||
def hwmonitor(self) -> HwMonitor:
|
||||
"""Return HwMonitor object."""
|
||||
return self._hwmonitor
|
||||
|
||||
@hwmonitor.setter
|
||||
def hwmonitor(self, value: HwMonitor):
|
||||
"""Set a HwMonitor object."""
|
||||
if self._hwmonitor:
|
||||
raise RuntimeError("HwMonitor already set!")
|
||||
self._hwmonitor = value
|
||||
|
||||
@property
|
||||
def ingress(self) -> Ingress:
|
||||
"""Return Ingress object."""
|
||||
@@ -520,6 +534,11 @@ class CoreSysAttributes:
|
||||
"""Return HostManager object."""
|
||||
return self.coresys.host
|
||||
|
||||
@property
|
||||
def sys_hwmonitor(self) -> HwMonitor:
|
||||
"""Return HwMonitor object."""
|
||||
return self.coresys.hwmonitor
|
||||
|
||||
@property
|
||||
def sys_ingress(self) -> Ingress:
|
||||
"""Return Ingress object."""
|
||||
|
13
supervisor/data/asound.tmpl
Normal file
13
supervisor/data/asound.tmpl
Normal file
@@ -0,0 +1,13 @@
|
||||
# Default to PulseAudio
|
||||
|
||||
pcm.!default {
|
||||
type pulse
|
||||
hint {
|
||||
show on
|
||||
description "Default ALSA Output (Home Assistant PulseAudio Sound Server)"
|
||||
}
|
||||
}
|
||||
|
||||
ctl.!default {
|
||||
type pulse
|
||||
}
|
@@ -20,7 +20,7 @@
|
||||
{% if default_sink %}default-sink = {{ default_sink }}{% endif %}
|
||||
{% if default_source %}default-source = {{ default_source }}{% endif %}
|
||||
|
||||
default-server = unix://run/pulse.sock
|
||||
default-server = unix://run/audio/pulse.sock
|
||||
; default-dbus-server =
|
||||
|
||||
autospawn = no
|
||||
|
@@ -213,10 +213,10 @@ class DockerAddon(DockerInterface):
|
||||
@property
|
||||
def volumes(self) -> Dict[str, Dict[str, str]]:
|
||||
"""Generate volumes for mappings."""
|
||||
volumes = {str(self.addon.path_extern_data): {"bind": "/data", "mode": "rw"}}
|
||||
|
||||
addon_mapping = self.addon.map_volumes
|
||||
|
||||
volumes = {str(self.addon.path_extern_data): {"bind": "/data", "mode": "rw"}}
|
||||
|
||||
# setup config mappings
|
||||
if MAP_CONFIG in addon_mapping:
|
||||
volumes.update(
|
||||
@@ -298,7 +298,7 @@ class DockerAddon(DockerInterface):
|
||||
|
||||
# Host D-Bus system
|
||||
if self.addon.host_dbus:
|
||||
volumes.update({"/run/dbus": {"bind": "/run/dbus", "mode": "rw"}})
|
||||
volumes.update({"/run/dbus": {"bind": "/run/dbus", "mode": "ro"}})
|
||||
|
||||
# Configuration Audio
|
||||
if self.addon.with_audio:
|
||||
@@ -308,9 +308,13 @@ class DockerAddon(DockerInterface):
|
||||
"bind": "/etc/pulse/client.conf",
|
||||
"mode": "ro",
|
||||
},
|
||||
str(self.sys_audio.path_extern_data.joinpath("pulse.sock")): {
|
||||
"bind": "/run/pulse.sock",
|
||||
"mode": "rw",
|
||||
str(self.sys_audio.path_extern_pulse): {
|
||||
"bind": "/run/audio",
|
||||
"mode": "ro",
|
||||
},
|
||||
str(self.sys_audio.path_extern_asound): {
|
||||
"bind": "/etc/asound.conf",
|
||||
"mode": "ro",
|
||||
},
|
||||
}
|
||||
)
|
||||
@@ -340,7 +344,7 @@ class DockerAddon(DockerInterface):
|
||||
name=self.name,
|
||||
hostname=self.addon.hostname,
|
||||
detach=True,
|
||||
init=True,
|
||||
init=self.addon.default_init,
|
||||
privileged=self.full_access,
|
||||
ipc_mode=self.ipc,
|
||||
stdin_open=self.addon.with_stdin,
|
||||
|
@@ -41,6 +41,7 @@ class DockerAudio(DockerInterface, CoreSysAttributes):
|
||||
docker_container = self.sys_docker.run(
|
||||
self.image,
|
||||
version=self.sys_audio.version,
|
||||
init=False,
|
||||
ipv4=self.sys_docker.network.audio,
|
||||
name=self.name,
|
||||
hostname=self.name.replace("_", "-"),
|
||||
|
@@ -41,6 +41,7 @@ class DockerDNS(DockerInterface, CoreSysAttributes):
|
||||
docker_container = self.sys_docker.run(
|
||||
self.image,
|
||||
version=self.sys_dns.version,
|
||||
init=False,
|
||||
dns=False,
|
||||
ipv4=self.sys_docker.network.dns,
|
||||
name=self.name,
|
||||
|
@@ -2,7 +2,7 @@
|
||||
from contextlib import suppress
|
||||
from ipaddress import IPv4Address
|
||||
import logging
|
||||
from typing import Awaitable, Optional
|
||||
from typing import Awaitable, Dict, Optional
|
||||
|
||||
import docker
|
||||
|
||||
@@ -45,6 +45,46 @@ class DockerHomeAssistant(DockerInterface):
|
||||
"""Return IP address of this container."""
|
||||
return self.sys_docker.network.gateway
|
||||
|
||||
@property
|
||||
def volumes(self) -> Dict[str, Dict[str, str]]:
|
||||
"""Return Volumes for the mount."""
|
||||
volumes = {}
|
||||
|
||||
# Add folders
|
||||
volumes.update(
|
||||
{
|
||||
str(self.sys_config.path_extern_homeassistant): {
|
||||
"bind": "/config",
|
||||
"mode": "rw",
|
||||
},
|
||||
str(self.sys_config.path_extern_ssl): {"bind": "/ssl", "mode": "ro"},
|
||||
str(self.sys_config.path_extern_share): {
|
||||
"bind": "/share",
|
||||
"mode": "rw",
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
# Configuration Audio
|
||||
volumes.update(
|
||||
{
|
||||
str(self.sys_homeassistant.path_extern_pulse): {
|
||||
"bind": "/etc/pulse/client.conf",
|
||||
"mode": "ro",
|
||||
},
|
||||
str(self.sys_audio.path_extern_pulse): {
|
||||
"bind": "/run/audio",
|
||||
"mode": "ro",
|
||||
},
|
||||
str(self.sys_audio.path_extern_asound): {
|
||||
"bind": "/etc/asound.conf",
|
||||
"mode": "ro",
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
return volumes
|
||||
|
||||
def _run(self) -> None:
|
||||
"""Run Docker image.
|
||||
|
||||
@@ -65,8 +105,9 @@ class DockerHomeAssistant(DockerInterface):
|
||||
hostname=self.name,
|
||||
detach=True,
|
||||
privileged=True,
|
||||
init=True,
|
||||
init=False,
|
||||
network_mode="host",
|
||||
volumes=self.volumes,
|
||||
environment={
|
||||
"HASSIO": self.sys_docker.network.supervisor,
|
||||
"SUPERVISOR": self.sys_docker.network.supervisor,
|
||||
@@ -74,17 +115,6 @@ class DockerHomeAssistant(DockerInterface):
|
||||
ENV_TOKEN: self.sys_homeassistant.hassio_token,
|
||||
ENV_TOKEN_OLD: self.sys_homeassistant.hassio_token,
|
||||
},
|
||||
volumes={
|
||||
str(self.sys_config.path_extern_homeassistant): {
|
||||
"bind": "/config",
|
||||
"mode": "rw",
|
||||
},
|
||||
str(self.sys_config.path_extern_ssl): {"bind": "/ssl", "mode": "ro"},
|
||||
str(self.sys_config.path_extern_share): {
|
||||
"bind": "/share",
|
||||
"mode": "rw",
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
self._meta = docker_container.attrs
|
||||
@@ -101,21 +131,12 @@ class DockerHomeAssistant(DockerInterface):
|
||||
command=command,
|
||||
privileged=True,
|
||||
init=True,
|
||||
entrypoint=[],
|
||||
detach=True,
|
||||
stdout=True,
|
||||
stderr=True,
|
||||
volumes=self.volumes,
|
||||
environment={ENV_TIME: self.sys_timezone},
|
||||
volumes={
|
||||
str(self.sys_config.path_extern_homeassistant): {
|
||||
"bind": "/config",
|
||||
"mode": "rw",
|
||||
},
|
||||
str(self.sys_config.path_extern_ssl): {"bind": "/ssl", "mode": "ro"},
|
||||
str(self.sys_config.path_extern_share): {
|
||||
"bind": "/share",
|
||||
"mode": "ro",
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
def is_initialize(self) -> Awaitable[bool]:
|
||||
|
@@ -19,6 +19,8 @@ from packaging import version as pkg_version
|
||||
|
||||
from .const import (
|
||||
ATTR_ACCESS_TOKEN,
|
||||
ATTR_AUDIO_INPUT,
|
||||
ATTR_AUDIO_OUTPUT,
|
||||
ATTR_BOOT,
|
||||
ATTR_IMAGE,
|
||||
ATTR_LAST_VERSION,
|
||||
@@ -232,6 +234,36 @@ class HomeAssistant(JsonConfig, CoreSysAttributes):
|
||||
"""Set Home Assistant refresh_token."""
|
||||
self._data[ATTR_REFRESH_TOKEN] = value
|
||||
|
||||
@property
|
||||
def path_pulse(self):
|
||||
"""Return path to asound config."""
|
||||
return Path(self.sys_config.path_tmp, f"homeassistant_pulse")
|
||||
|
||||
@property
|
||||
def path_extern_pulse(self):
|
||||
"""Return path to asound config for Docker."""
|
||||
return Path(self.sys_config.path_extern_tmp, f"homeassistant_pulse")
|
||||
|
||||
@property
|
||||
def audio_output(self) -> Optional[str]:
|
||||
"""Return a pulse profile for output or None."""
|
||||
return self._data[ATTR_AUDIO_OUTPUT]
|
||||
|
||||
@audio_output.setter
|
||||
def audio_output(self, value: Optional[str]):
|
||||
"""Set audio output profile settings."""
|
||||
self._data[ATTR_AUDIO_OUTPUT] = value
|
||||
|
||||
@property
|
||||
def audio_input(self) -> Optional[str]:
|
||||
"""Return pulse profile for input or None."""
|
||||
return self._data[ATTR_AUDIO_INPUT]
|
||||
|
||||
@audio_input.setter
|
||||
def audio_input(self, value: Optional[str]):
|
||||
"""Set audio input settings."""
|
||||
self._data[ATTR_AUDIO_INPUT] = value
|
||||
|
||||
@process_lock
|
||||
async def install_landingpage(self) -> None:
|
||||
"""Install a landing page."""
|
||||
@@ -334,6 +366,9 @@ class HomeAssistant(JsonConfig, CoreSysAttributes):
|
||||
self._data[ATTR_ACCESS_TOKEN] = secrets.token_hex(56)
|
||||
self.save_data()
|
||||
|
||||
# Write audio settings
|
||||
self.write_pulse()
|
||||
|
||||
try:
|
||||
await self.instance.run()
|
||||
except DockerAPIError:
|
||||
@@ -602,3 +637,18 @@ class HomeAssistant(JsonConfig, CoreSysAttributes):
|
||||
await self.instance.install(self.version)
|
||||
except DockerAPIError:
|
||||
_LOGGER.error("Repairing of Home Assistant fails")
|
||||
|
||||
def write_pulse(self):
|
||||
"""Write asound config to file and return True on success."""
|
||||
pulse_config = self.sys_audio.pulse_client(
|
||||
input_profile=self.audio_input, output_profile=self.audio_output
|
||||
)
|
||||
|
||||
try:
|
||||
with self.path_pulse.open("w") as config_file:
|
||||
config_file.write(pulse_config)
|
||||
except OSError as err:
|
||||
_LOGGER.error("Home Assistant can't write pulse/client.config: %s", err)
|
||||
raise HomeAssistantError()
|
||||
|
||||
_LOGGER.debug("Home Assistant write pulse/client.config: %s", self.path_pulse)
|
||||
|
@@ -1,20 +1,22 @@
|
||||
"""Pulse host control."""
|
||||
from datetime import timedelta
|
||||
from enum import Enum
|
||||
import logging
|
||||
from typing import List
|
||||
from typing import List, Optional
|
||||
|
||||
import attr
|
||||
from pulsectl import Pulse, PulseError, PulseIndexError, PulseOperationFailed
|
||||
|
||||
from ..coresys import CoreSys, CoreSysAttributes
|
||||
from ..exceptions import PulseAudioError
|
||||
from ..utils import AsyncThrottle
|
||||
|
||||
_LOGGER: logging.Logger = logging.getLogger(__name__)
|
||||
|
||||
PULSE_NAME = "supervisor"
|
||||
|
||||
|
||||
class SourceType(str, Enum):
|
||||
class StreamType(str, Enum):
|
||||
"""INPUT/OUTPUT type of source."""
|
||||
|
||||
INPUT = "input"
|
||||
@@ -22,13 +24,49 @@ class SourceType(str, Enum):
|
||||
|
||||
|
||||
@attr.s(frozen=True)
|
||||
class AudioProfile:
|
||||
"""Represent a input/output profile."""
|
||||
class AudioApplication:
|
||||
"""Represent a application on the stream."""
|
||||
|
||||
name: str = attr.ib()
|
||||
index: int = attr.ib()
|
||||
stream_index: str = attr.ib()
|
||||
stream_type: StreamType = attr.ib()
|
||||
volume: float = attr.ib()
|
||||
mute: bool = attr.ib()
|
||||
addon: str = attr.ib()
|
||||
|
||||
|
||||
@attr.s(frozen=True)
|
||||
class AudioStream:
|
||||
"""Represent a input/output stream."""
|
||||
|
||||
name: str = attr.ib()
|
||||
index: int = attr.ib()
|
||||
description: str = attr.ib()
|
||||
volume: float = attr.ib()
|
||||
mute: bool = attr.ib()
|
||||
default: bool = attr.ib()
|
||||
card: Optional[int] = attr.ib()
|
||||
applications: List[AudioApplication] = attr.ib()
|
||||
|
||||
|
||||
@attr.s(frozen=True)
|
||||
class SoundProfile:
|
||||
"""Represent a Sound Card profile."""
|
||||
|
||||
name: str = attr.ib()
|
||||
description: str = attr.ib()
|
||||
volume: float = attr.ib()
|
||||
default: bool = attr.ib()
|
||||
active: bool = attr.ib()
|
||||
|
||||
|
||||
@attr.s(frozen=True)
|
||||
class SoundCard:
|
||||
"""Represent a Sound Card."""
|
||||
|
||||
name: str = attr.ib()
|
||||
index: int = attr.ib()
|
||||
driver: str = attr.ib()
|
||||
profiles: List[SoundProfile] = attr.ib()
|
||||
|
||||
|
||||
class SoundControl(CoreSysAttributes):
|
||||
@@ -37,95 +75,265 @@ class SoundControl(CoreSysAttributes):
|
||||
def __init__(self, coresys: CoreSys) -> None:
|
||||
"""Initialize PulseAudio sound control."""
|
||||
self.coresys: CoreSys = coresys
|
||||
self._input: List[AudioProfile] = []
|
||||
self._output: List[AudioProfile] = []
|
||||
self._cards: List[SoundCard] = []
|
||||
self._inputs: List[AudioStream] = []
|
||||
self._outputs: List[AudioStream] = []
|
||||
self._applications: List[AudioApplication] = []
|
||||
|
||||
@property
|
||||
def input_profiles(self) -> List[AudioProfile]:
|
||||
"""Return a list of available input profiles."""
|
||||
return self._input
|
||||
def cards(self) -> List[SoundCard]:
|
||||
"""Return a list of available sound cards and profiles."""
|
||||
return self._cards
|
||||
|
||||
@property
|
||||
def output_profiles(self) -> List[AudioProfile]:
|
||||
"""Return a list of available output profiles."""
|
||||
return self._output
|
||||
def inputs(self) -> List[AudioStream]:
|
||||
"""Return a list of available input streams."""
|
||||
return self._inputs
|
||||
|
||||
async def set_default(self, source: SourceType, name: str) -> None:
|
||||
"""Set a profile to default input/output."""
|
||||
try:
|
||||
with Pulse(PULSE_NAME) as pulse:
|
||||
if source == SourceType.OUTPUT:
|
||||
# Get source and set it as default
|
||||
source = pulse.get_source_by_name(name)
|
||||
pulse.source_default_set(source)
|
||||
else:
|
||||
# Get sink and set it as default
|
||||
sink = pulse.get_sink_by_name(name)
|
||||
pulse.sink_default_set(sink)
|
||||
except PulseIndexError:
|
||||
_LOGGER.error("Can't find %s profile %s", source, name)
|
||||
raise PulseAudioError() from None
|
||||
except PulseError as err:
|
||||
_LOGGER.error("Can't set %s as default: %s", name, err)
|
||||
raise PulseAudioError() from None
|
||||
@property
|
||||
def outputs(self) -> List[AudioStream]:
|
||||
"""Return a list of available output streams."""
|
||||
return self._outputs
|
||||
|
||||
# Reload data
|
||||
@property
|
||||
def applications(self) -> List[AudioApplication]:
|
||||
"""Return a list of available application streams."""
|
||||
return self._applications
|
||||
|
||||
async def set_default(self, stream_type: StreamType, name: str) -> None:
|
||||
"""Set a stream to default input/output."""
|
||||
|
||||
def _set_default():
|
||||
try:
|
||||
with Pulse(PULSE_NAME) as pulse:
|
||||
if stream_type == StreamType.INPUT:
|
||||
# Get source and set it as default
|
||||
source = pulse.get_source_by_name(name)
|
||||
pulse.source_default_set(source)
|
||||
else:
|
||||
# Get sink and set it as default
|
||||
sink = pulse.get_sink_by_name(name)
|
||||
pulse.sink_default_set(sink)
|
||||
|
||||
except PulseIndexError:
|
||||
_LOGGER.error("Can't find %s stream %s", source, name)
|
||||
raise PulseAudioError() from None
|
||||
except PulseError as err:
|
||||
_LOGGER.error("Can't set %s as stream: %s", name, err)
|
||||
raise PulseAudioError() from None
|
||||
|
||||
# Run and Reload data
|
||||
await self.sys_run_in_executor(_set_default)
|
||||
await self.update()
|
||||
|
||||
async def set_volume(self, source: SourceType, name: str, volume: float) -> None:
|
||||
async def set_volume(
|
||||
self, stream_type: StreamType, index: int, volume: float, application: bool
|
||||
) -> None:
|
||||
"""Set a stream to volume input/output/application."""
|
||||
|
||||
def _set_volume():
|
||||
try:
|
||||
with Pulse(PULSE_NAME) as pulse:
|
||||
if stream_type == StreamType.INPUT:
|
||||
if application:
|
||||
stream = pulse.source_output_info(index)
|
||||
else:
|
||||
stream = pulse.source_info(index)
|
||||
else:
|
||||
if application:
|
||||
stream = pulse.sink_input_info(index)
|
||||
else:
|
||||
stream = pulse.sink_info(index)
|
||||
|
||||
# Set volume
|
||||
pulse.volume_set_all_chans(stream, volume)
|
||||
except PulseIndexError:
|
||||
_LOGGER.error(
|
||||
"Can't find %s stream %d (App: %s)", stream_type, index, application
|
||||
)
|
||||
raise PulseAudioError() from None
|
||||
except PulseError as err:
|
||||
_LOGGER.error("Can't set %d volume: %s", index, err)
|
||||
raise PulseAudioError() from None
|
||||
|
||||
# Run and Reload data
|
||||
await self.sys_run_in_executor(_set_volume)
|
||||
await self.update()
|
||||
|
||||
async def set_mute(
|
||||
self, stream_type: StreamType, index: int, mute: bool, application: bool
|
||||
) -> None:
|
||||
"""Set a stream to mute input/output/application."""
|
||||
|
||||
def _set_mute():
|
||||
try:
|
||||
with Pulse(PULSE_NAME) as pulse:
|
||||
if stream_type == StreamType.INPUT:
|
||||
if application:
|
||||
stream = pulse.source_output_info(index)
|
||||
else:
|
||||
stream = pulse.source_info(index)
|
||||
else:
|
||||
if application:
|
||||
stream = pulse.sink_input_info(index)
|
||||
else:
|
||||
stream = pulse.sink_info(index)
|
||||
|
||||
# Mute stream
|
||||
pulse.mute(stream, mute)
|
||||
except PulseIndexError:
|
||||
_LOGGER.error(
|
||||
"Can't find %s stream %d (App: %s)", stream_type, index, application
|
||||
)
|
||||
raise PulseAudioError() from None
|
||||
except PulseError as err:
|
||||
_LOGGER.error("Can't set %d volume: %s", index, err)
|
||||
raise PulseAudioError() from None
|
||||
|
||||
# Run and Reload data
|
||||
await self.sys_run_in_executor(_set_mute)
|
||||
await self.update()
|
||||
|
||||
async def ativate_profile(self, card_name: str, profile_name: str) -> None:
|
||||
"""Set a profile to volume input/output."""
|
||||
try:
|
||||
with Pulse(PULSE_NAME) as pulse:
|
||||
if source == SourceType.OUTPUT:
|
||||
# Get source and set it as default
|
||||
source = pulse.get_source_by_name(name)
|
||||
else:
|
||||
# Get sink and set it as default
|
||||
source = pulse.get_sink_by_name(name)
|
||||
|
||||
pulse.volume_set_all_chans(source, volume)
|
||||
except PulseIndexError:
|
||||
_LOGGER.error("Can't find %s profile %s", source, name)
|
||||
raise PulseAudioError() from None
|
||||
except PulseError as err:
|
||||
_LOGGER.error("Can't set %s volume: %s", name, err)
|
||||
raise PulseAudioError() from None
|
||||
def _activate_profile():
|
||||
try:
|
||||
with Pulse(PULSE_NAME) as pulse:
|
||||
card = pulse.get_sink_by_name(card_name)
|
||||
pulse.card_profile_set(card, profile_name)
|
||||
|
||||
# Reload data
|
||||
except PulseIndexError:
|
||||
_LOGGER.error("Can't find %s profile %s", card_name, profile_name)
|
||||
raise PulseAudioError() from None
|
||||
except PulseError as err:
|
||||
_LOGGER.error(
|
||||
"Can't activate %s profile %s: %s", card_name, profile_name, err
|
||||
)
|
||||
raise PulseAudioError() from None
|
||||
|
||||
# Run and Reload data
|
||||
await self.sys_run_in_executor(_activate_profile)
|
||||
await self.update()
|
||||
|
||||
@AsyncThrottle(timedelta(seconds=10))
|
||||
async def update(self):
|
||||
"""Update properties over dbus."""
|
||||
_LOGGER.info("Update PulseAudio information")
|
||||
try:
|
||||
with Pulse(PULSE_NAME) as pulse:
|
||||
server = pulse.server_info()
|
||||
|
||||
# Update output
|
||||
self._output.clear()
|
||||
for sink in pulse.sink_list():
|
||||
self._output.append(
|
||||
AudioProfile(
|
||||
sink.name,
|
||||
sink.description,
|
||||
sink.volume.value_flat,
|
||||
sink.name == server.default_sink_name,
|
||||
)
|
||||
)
|
||||
def _update():
|
||||
try:
|
||||
with Pulse(PULSE_NAME) as pulse:
|
||||
server = pulse.server_info()
|
||||
|
||||
# Update input
|
||||
self._input.clear()
|
||||
for source in pulse.source_list():
|
||||
self._input.append(
|
||||
AudioProfile(
|
||||
source.name,
|
||||
source.description,
|
||||
source.volume.value_flat,
|
||||
source.name == server.default_source_name,
|
||||
# Update applications
|
||||
self._applications.clear()
|
||||
for application in pulse.sink_input_list():
|
||||
self._applications.append(
|
||||
AudioApplication(
|
||||
application.proplist.get(
|
||||
"application.name", application.name
|
||||
),
|
||||
application.index,
|
||||
application.sink,
|
||||
StreamType.OUTPUT,
|
||||
application.volume.value_flat,
|
||||
bool(application.mute),
|
||||
application.proplist.get(
|
||||
"application.process.machine_id", ""
|
||||
).replace("-", "_"),
|
||||
)
|
||||
)
|
||||
)
|
||||
except PulseOperationFailed as err:
|
||||
_LOGGER.error("Error while processing pulse update: %s", err)
|
||||
raise PulseAudioError() from None
|
||||
except PulseError as err:
|
||||
_LOGGER.debug("Can't update PulseAudio data: %s", err)
|
||||
for application in pulse.source_output_list():
|
||||
self._applications.append(
|
||||
AudioApplication(
|
||||
application.proplist.get(
|
||||
"application.name", application.name
|
||||
),
|
||||
application.index,
|
||||
application.source,
|
||||
StreamType.INPUT,
|
||||
application.volume.value_flat,
|
||||
bool(application.mute),
|
||||
application.proplist.get(
|
||||
"application.process.machine_id", ""
|
||||
).replace("-", "_"),
|
||||
)
|
||||
)
|
||||
|
||||
# Update output
|
||||
self._outputs.clear()
|
||||
for sink in pulse.sink_list():
|
||||
self._outputs.append(
|
||||
AudioStream(
|
||||
sink.name,
|
||||
sink.index,
|
||||
sink.description,
|
||||
sink.volume.value_flat,
|
||||
bool(sink.mute),
|
||||
sink.name == server.default_sink_name,
|
||||
sink.card if sink.card != 0xFFFFFFFF else None,
|
||||
[
|
||||
application
|
||||
for application in self._applications
|
||||
if application.stream_index == sink.index
|
||||
and application.stream_type == StreamType.OUTPUT
|
||||
],
|
||||
)
|
||||
)
|
||||
|
||||
# Update input
|
||||
self._inputs.clear()
|
||||
for source in pulse.source_list():
|
||||
# Filter monitor devices out because we did not use it now
|
||||
if source.name.endswith(".monitor"):
|
||||
continue
|
||||
self._inputs.append(
|
||||
AudioStream(
|
||||
source.name,
|
||||
source.index,
|
||||
source.description,
|
||||
source.volume.value_flat,
|
||||
bool(source.mute),
|
||||
source.name == server.default_source_name,
|
||||
source.card if source.card != 0xFFFFFFFF else None,
|
||||
[
|
||||
application
|
||||
for application in self._applications
|
||||
if application.stream_index == source.index
|
||||
and application.stream_type == StreamType.INPUT
|
||||
],
|
||||
)
|
||||
)
|
||||
|
||||
# Update Sound Card
|
||||
self._cards.clear()
|
||||
for card in pulse.card_list():
|
||||
sound_profiles: List[SoundProfile] = []
|
||||
|
||||
# Generate profiles
|
||||
for profile in card.profile_list:
|
||||
if not profile.available:
|
||||
continue
|
||||
sound_profiles.append(
|
||||
SoundProfile(
|
||||
profile.name,
|
||||
profile.description,
|
||||
profile.name == card.profile_active.name,
|
||||
)
|
||||
)
|
||||
|
||||
self._cards.append(
|
||||
SoundCard(
|
||||
card.name, card.index, card.driver, sound_profiles
|
||||
)
|
||||
)
|
||||
|
||||
except PulseOperationFailed as err:
|
||||
_LOGGER.error("Error while processing pulse update: %s", err)
|
||||
raise PulseAudioError() from None
|
||||
except PulseError as err:
|
||||
_LOGGER.debug("Can't update PulseAudio data: %s", err)
|
||||
|
||||
# Run update from pulse server
|
||||
await self.sys_run_in_executor(_update)
|
||||
|
57
supervisor/hwmon.py
Normal file
57
supervisor/hwmon.py
Normal file
@@ -0,0 +1,57 @@
|
||||
"""Supervisor Hardware monitor based on udev."""
|
||||
from datetime import timedelta
|
||||
import logging
|
||||
from pprint import pformat
|
||||
from typing import Optional
|
||||
|
||||
import pyudev
|
||||
|
||||
from .coresys import CoreSysAttributes, CoreSys
|
||||
from .utils import AsyncCallFilter
|
||||
|
||||
_LOGGER: logging.Logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class HwMonitor(CoreSysAttributes):
|
||||
"""Hardware monitor for supervisor."""
|
||||
|
||||
def __init__(self, coresys: CoreSys):
|
||||
"""Initialize Hardware Monitor object."""
|
||||
self.coresys: CoreSys = coresys
|
||||
self.context = pyudev.Context()
|
||||
self.monitor = pyudev.Monitor.from_netlink(self.context)
|
||||
self.observer: Optional[pyudev.MonitorObserver] = None
|
||||
|
||||
async def load(self) -> None:
|
||||
"""Start hardware monitor."""
|
||||
self.observer = pyudev.MonitorObserver(self.monitor, self._udev_events)
|
||||
self.observer.start()
|
||||
|
||||
_LOGGER.info("Start Supervisor hardware monitor")
|
||||
|
||||
async def unload(self) -> None:
|
||||
"""Shutdown sessions."""
|
||||
if self.observer is None:
|
||||
return
|
||||
self.observer.stop()
|
||||
_LOGGER.info("Stop Supervisor hardware monitor")
|
||||
|
||||
def _udev_events(self, action: str, device: pyudev.Device):
|
||||
"""Incomming events from udev.
|
||||
|
||||
This is inside a observe thread and need pass into our eventloop.
|
||||
"""
|
||||
_LOGGER.debug("Hardware monitor: %s - %s", action, pformat(device))
|
||||
self.sys_loop.call_soon_threadsafe(self._async_udev_events, action, device)
|
||||
|
||||
def _async_udev_events(self, action: str, device: pyudev.Device):
|
||||
"""Incomming events from udev into loop."""
|
||||
# Sound changes
|
||||
if device.subsystem == "sound":
|
||||
self._action_sound(device)
|
||||
|
||||
@AsyncCallFilter(timedelta(seconds=5))
|
||||
def _action_sound(self, device: pyudev.Device):
|
||||
"""Process sound actions."""
|
||||
_LOGGER.info("Detect changed audio hardware")
|
||||
self.sys_loop.call_later(5, self.sys_create_task, self.sys_host.sound.update())
|
@@ -12,7 +12,6 @@ import pyudev
|
||||
from ..const import ATTR_DEVICES, ATTR_NAME, ATTR_TYPE, CHAN_ID, CHAN_TYPE
|
||||
from ..exceptions import HardwareNotSupportedError
|
||||
|
||||
|
||||
_LOGGER: logging.Logger = logging.getLogger(__name__)
|
||||
|
||||
ASOUND_CARDS: Path = Path("/proc/asound/cards")
|
||||
@@ -133,7 +132,6 @@ class Hardware:
|
||||
def audio_devices(self) -> Dict[str, Any]:
|
||||
"""Return all available audio interfaces."""
|
||||
if not ASOUND_CARDS.exists():
|
||||
_LOGGER.info("No audio devices found")
|
||||
return {}
|
||||
|
||||
try:
|
||||
|
@@ -16,6 +16,8 @@ from voluptuous.humanize import humanize_error
|
||||
|
||||
from ..const import (
|
||||
ATTR_ADDONS,
|
||||
ATTR_AUDIO_INPUT,
|
||||
ATTR_AUDIO_OUTPUT,
|
||||
ATTR_BOOT,
|
||||
ATTR_CRYPTO,
|
||||
ATTR_DATE,
|
||||
@@ -443,6 +445,10 @@ class Snapshot(CoreSysAttributes):
|
||||
self.sys_homeassistant.refresh_token
|
||||
)
|
||||
|
||||
# Audio
|
||||
self.homeassistant[ATTR_AUDIO_INPUT] = self.sys_homeassistant.audio_input
|
||||
self.homeassistant[ATTR_AUDIO_OUTPUT] = self.sys_homeassistant.audio_output
|
||||
|
||||
def restore_homeassistant(self):
|
||||
"""Write all data to the Home Assistant object."""
|
||||
self.sys_homeassistant.watchdog = self.homeassistant[ATTR_WATCHDOG]
|
||||
@@ -463,6 +469,10 @@ class Snapshot(CoreSysAttributes):
|
||||
self.homeassistant[ATTR_REFRESH_TOKEN]
|
||||
)
|
||||
|
||||
# Audio
|
||||
self.sys_homeassistant.audio_input = self.homeassistant[ATTR_AUDIO_INPUT]
|
||||
self.sys_homeassistant.audio_output = self.homeassistant[ATTR_AUDIO_OUTPUT]
|
||||
|
||||
# save
|
||||
self.sys_homeassistant.save_data()
|
||||
|
||||
|
@@ -3,6 +3,8 @@ import voluptuous as vol
|
||||
|
||||
from ..const import (
|
||||
ATTR_ADDONS,
|
||||
ATTR_AUDIO_INPUT,
|
||||
ATTR_AUDIO_OUTPUT,
|
||||
ATTR_BOOT,
|
||||
ATTR_CRYPTO,
|
||||
ATTR_DATE,
|
||||
@@ -68,6 +70,12 @@ SCHEMA_SNAPSHOT = vol.Schema(
|
||||
vol.Optional(ATTR_WAIT_BOOT, default=600): vol.All(
|
||||
vol.Coerce(int), vol.Range(min=60)
|
||||
),
|
||||
vol.Optional(ATTR_AUDIO_OUTPUT, default=None): vol.Maybe(
|
||||
vol.Coerce(str)
|
||||
),
|
||||
vol.Optional(ATTR_AUDIO_INPUT, default=None): vol.Maybe(
|
||||
vol.Coerce(str)
|
||||
),
|
||||
},
|
||||
extra=vol.REMOVE_EXTRA,
|
||||
),
|
||||
|
@@ -36,7 +36,7 @@ def process_lock(method):
|
||||
class AsyncThrottle:
|
||||
"""
|
||||
Decorator that prevents a function from being called more than once every
|
||||
time period.
|
||||
time period with blocking.
|
||||
"""
|
||||
|
||||
def __init__(self, delta):
|
||||
@@ -64,6 +64,32 @@ class AsyncThrottle:
|
||||
return wrapper
|
||||
|
||||
|
||||
class AsyncCallFilter:
|
||||
"""
|
||||
Decorator that prevents a function from being called more than once every
|
||||
time period.
|
||||
"""
|
||||
|
||||
def __init__(self, delta):
|
||||
"""Initialize async throttle."""
|
||||
self.throttle_period = delta
|
||||
self.time_of_last_call = datetime.min
|
||||
|
||||
def __call__(self, method):
|
||||
"""Throttle function"""
|
||||
|
||||
async def wrapper(*args, **kwargs):
|
||||
"""Throttle function wrapper"""
|
||||
now = datetime.now()
|
||||
time_since_last_call = now - self.time_of_last_call
|
||||
|
||||
if time_since_last_call > self.throttle_period:
|
||||
self.time_of_last_call = now
|
||||
return await method(*args, **kwargs)
|
||||
|
||||
return wrapper
|
||||
|
||||
|
||||
def check_port(address: IPv4Address, port: int) -> bool:
|
||||
"""Check if port is mapped."""
|
||||
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
|
@@ -9,6 +9,8 @@ from .const import (
|
||||
ATTR_ACCESS_TOKEN,
|
||||
ATTR_ADDONS_CUSTOM_LIST,
|
||||
ATTR_AUDIO,
|
||||
ATTR_AUDIO_INPUT,
|
||||
ATTR_AUDIO_OUTPUT,
|
||||
ATTR_BOOT,
|
||||
ATTR_CHANNEL,
|
||||
ATTR_CLI,
|
||||
@@ -111,6 +113,8 @@ SCHEMA_HASS_CONFIG = vol.Schema(
|
||||
vol.Optional(ATTR_WAIT_BOOT, default=600): vol.All(
|
||||
vol.Coerce(int), vol.Range(min=60)
|
||||
),
|
||||
vol.Optional(ATTR_AUDIO_OUTPUT, default=None): vol.Maybe(vol.Coerce(str)),
|
||||
vol.Optional(ATTR_AUDIO_INPUT, default=None): vol.Maybe(vol.Coerce(str)),
|
||||
},
|
||||
extra=vol.REMOVE_EXTRA,
|
||||
)
|
||||
|
Reference in New Issue
Block a user