mirror of
https://github.com/home-assistant/supervisor.git
synced 2025-07-24 09:36:31 +00:00
commit
cea6e7a9f2
1
.github/stale.yml
vendored
1
.github/stale.yml
vendored
@ -6,6 +6,7 @@ daysUntilClose: 7
|
||||
exemptLabels:
|
||||
- pinned
|
||||
- security
|
||||
- rfc
|
||||
# Label to use when marking an issue as stale
|
||||
staleLabel: stale
|
||||
# Comment to post when marking an issue as stale. Set to `false` to disable
|
||||
|
2
.github/workflows/sentry.yaml
vendored
2
.github/workflows/sentry.yaml
vendored
@ -12,7 +12,7 @@ jobs:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@v2
|
||||
- name: Sentry Release
|
||||
uses: getsentry/action-release@v1.0.0
|
||||
uses: getsentry/action-release@v1.0.1
|
||||
env:
|
||||
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
|
||||
SENTRY_ORG: ${{ secrets.SENTRY_ORG }}
|
||||
|
@ -1,6 +1,6 @@
|
||||
repos:
|
||||
- repo: https://github.com/psf/black
|
||||
rev: 19.10b0
|
||||
rev: 20.8b1
|
||||
hooks:
|
||||
- id: black
|
||||
args:
|
||||
|
65
API.md
65
API.md
@ -442,6 +442,65 @@ Proxy to Home Assistant Core websocket.
|
||||
}
|
||||
```
|
||||
|
||||
### Network
|
||||
|
||||
Network operations over the API
|
||||
|
||||
#### GET `/network/info`
|
||||
|
||||
Get network information
|
||||
|
||||
```json
|
||||
{
|
||||
"interfaces": {
|
||||
"enp0s31f6": {
|
||||
"ip_address": "192.168.2.148/24",
|
||||
"gateway": "192.168.2.1",
|
||||
"id": "Wired connection 1",
|
||||
"type": "802-3-ethernet",
|
||||
"nameservers": ["192.168.2.1"],
|
||||
"method": "static",
|
||||
"primary": true
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### GET `/network/{interface}/info`
|
||||
|
||||
Get information for a single interface
|
||||
|
||||
```json
|
||||
{
|
||||
"ip_address": "192.168.2.148/24",
|
||||
"gateway": "192.168.2.1",
|
||||
"id": "Wired connection 1",
|
||||
"type": "802-3-ethernet",
|
||||
"nameservers": ["192.168.2.1"],
|
||||
"method": "dhcp",
|
||||
"primary": true
|
||||
}
|
||||
```
|
||||
|
||||
#### POST `/network/{interface}/update`
|
||||
|
||||
Update information for a single interface
|
||||
|
||||
**Options:**
|
||||
|
||||
| Option | Description |
|
||||
| --------- | ---------------------------------------------------------------------- |
|
||||
| `address` | The new IP address for the interface in the X.X.X.X/XX format |
|
||||
| `dns` | Comma seperated list of DNS servers to use |
|
||||
| `gateway` | The gateway the interface should use |
|
||||
| `method` | Set if the interface should use DHCP or not, can be `dhcp` or `static` |
|
||||
|
||||
_All options are optional._
|
||||
|
||||
**NB!: If you change the `address` or `gateway` you may need to reconnect to the new address**
|
||||
|
||||
The result will be a updated object.
|
||||
|
||||
### RESTful for API add-ons
|
||||
|
||||
If an add-on will call itself, you can use `/addons/self/...`.
|
||||
@ -550,7 +609,8 @@ Get all available add-ons.
|
||||
"ingress_entry": "null|/api/hassio_ingress/slug",
|
||||
"ingress_url": "null|/api/hassio_ingress/slug/entry.html",
|
||||
"ingress_port": "null|int",
|
||||
"ingress_panel": "null|bool"
|
||||
"ingress_panel": "null|bool",
|
||||
"watchdog": "null|bool"
|
||||
}
|
||||
```
|
||||
|
||||
@ -570,7 +630,8 @@ Get all available add-ons.
|
||||
"options": {},
|
||||
"audio_output": "null|0,0",
|
||||
"audio_input": "null|0,0",
|
||||
"ingress_panel": "bool"
|
||||
"ingress_panel": "bool",
|
||||
"watchdog": "bool"
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -14,8 +14,7 @@ RUN \
|
||||
libffi \
|
||||
libpulse \
|
||||
musl \
|
||||
openssl \
|
||||
socat
|
||||
openssl
|
||||
|
||||
ARG BUILD_ARCH
|
||||
WORKDIR /usr/src
|
||||
|
@ -1 +1 @@
|
||||
Subproject commit 77b25f5132820c0596ccae82dd501ce67f101e72
|
||||
Subproject commit c1a4b27bc7fc68dfb4af6b382238c010340e7912
|
@ -1,12 +1,12 @@
|
||||
aiohttp==3.6.2
|
||||
async_timeout==3.0.1
|
||||
attrs==19.3.0
|
||||
attrs==20.1.0
|
||||
cchardet==2.1.6
|
||||
colorlog==4.2.1
|
||||
cpe==1.2.1
|
||||
cryptography==3.0
|
||||
debugpy==1.0.0rc1
|
||||
docker==4.3.0
|
||||
cryptography==3.1
|
||||
debugpy==1.0.0rc2
|
||||
docker==4.3.1
|
||||
gitpython==3.1.7
|
||||
jinja2==2.11.2
|
||||
packaging==20.4
|
||||
@ -14,6 +14,6 @@ pulsectl==20.5.1
|
||||
pytz==2020.1
|
||||
pyudev==0.22.0
|
||||
ruamel.yaml==0.15.100
|
||||
sentry-sdk==0.16.5
|
||||
sentry-sdk==0.17.0
|
||||
uvloop==0.14.0
|
||||
voluptuous==0.11.7
|
||||
|
@ -1,12 +1,13 @@
|
||||
black==19.10b0
|
||||
codecov==2.1.8
|
||||
black==20.8b1
|
||||
codecov==2.1.9
|
||||
coverage==5.2.1
|
||||
flake8-docstrings==1.5.0
|
||||
flake8==3.8.3
|
||||
pre-commit==2.6.0
|
||||
pydocstyle==5.0.2
|
||||
pylint==2.5.3
|
||||
pre-commit==2.7.1
|
||||
pydocstyle==5.1.0
|
||||
pylint==2.6.0
|
||||
pytest-aiohttp==0.3.0
|
||||
pytest-asyncio==0.12.0 # NB!: Versions over 0.12.0 breaks pytest-aiohttp (https://github.com/aio-libs/pytest-aiohttp/issues/16)
|
||||
pytest-cov==2.10.1
|
||||
pytest-timeout==1.4.2
|
||||
pytest==6.0.1
|
||||
|
8
setup.py
8
setup.py
@ -35,10 +35,18 @@ setup(
|
||||
"supervisor.docker",
|
||||
"supervisor.addons",
|
||||
"supervisor.api",
|
||||
"supervisor.dbus",
|
||||
"supervisor.discovery",
|
||||
"supervisor.discovery.services",
|
||||
"supervisor.services",
|
||||
"supervisor.services.modules",
|
||||
"supervisor.homeassistant",
|
||||
"supervisor.host",
|
||||
"supervisor.misc",
|
||||
"supervisor.utils",
|
||||
"supervisor.plugins",
|
||||
"supervisor.snapshots",
|
||||
"supervisor.store",
|
||||
],
|
||||
include_package_data=True,
|
||||
)
|
||||
|
@ -5,7 +5,7 @@ import logging
|
||||
import tarfile
|
||||
from typing import Dict, List, Optional, Union
|
||||
|
||||
from ..const import BOOT_AUTO, STATE_STARTED, AddonStartup
|
||||
from ..const import BOOT_AUTO, AddonStartup, AddonState
|
||||
from ..coresys import CoreSys, CoreSysAttributes
|
||||
from ..exceptions import (
|
||||
AddonsError,
|
||||
@ -108,7 +108,7 @@ class AddonManager(CoreSysAttributes):
|
||||
"""Shutdown addons."""
|
||||
tasks: List[Addon] = []
|
||||
for addon in self.installed:
|
||||
if await addon.state() != STATE_STARTED or addon.startup != stage:
|
||||
if addon.state != AddonState.STARTED or addon.startup != stage:
|
||||
continue
|
||||
tasks.append(addon)
|
||||
|
||||
@ -153,9 +153,9 @@ class AddonManager(CoreSysAttributes):
|
||||
|
||||
try:
|
||||
await addon.instance.install(store.version, store.image)
|
||||
except DockerAPIError:
|
||||
except DockerAPIError as err:
|
||||
self.data.uninstall(addon)
|
||||
raise AddonsError()
|
||||
raise AddonsError() from err
|
||||
else:
|
||||
self.local[slug] = addon
|
||||
|
||||
@ -174,8 +174,10 @@ class AddonManager(CoreSysAttributes):
|
||||
|
||||
try:
|
||||
await addon.instance.remove()
|
||||
except DockerAPIError:
|
||||
raise AddonsError()
|
||||
except DockerAPIError as err:
|
||||
raise AddonsError() from err
|
||||
else:
|
||||
addon.state = AddonState.UNKNOWN
|
||||
|
||||
await addon.remove_data()
|
||||
|
||||
@ -238,15 +240,15 @@ class AddonManager(CoreSysAttributes):
|
||||
raise AddonsNotSupportedError()
|
||||
|
||||
# Update instance
|
||||
last_state = await addon.state()
|
||||
last_state: AddonState = addon.state
|
||||
try:
|
||||
await addon.instance.update(store.version, store.image)
|
||||
|
||||
# Cleanup
|
||||
with suppress(DockerAPIError):
|
||||
await addon.instance.cleanup()
|
||||
except DockerAPIError:
|
||||
raise AddonsError()
|
||||
except DockerAPIError as err:
|
||||
raise AddonsError() from err
|
||||
else:
|
||||
self.data.update(store)
|
||||
_LOGGER.info("Add-on '%s' successfully updated", slug)
|
||||
@ -255,7 +257,7 @@ class AddonManager(CoreSysAttributes):
|
||||
await addon.install_apparmor()
|
||||
|
||||
# restore state
|
||||
if last_state == STATE_STARTED:
|
||||
if last_state == AddonState.STARTED:
|
||||
await addon.start()
|
||||
|
||||
async def rebuild(self, slug: str) -> None:
|
||||
@ -279,18 +281,18 @@ class AddonManager(CoreSysAttributes):
|
||||
raise AddonsNotSupportedError()
|
||||
|
||||
# remove docker container but not addon config
|
||||
last_state = await addon.state()
|
||||
last_state: AddonState = addon.state
|
||||
try:
|
||||
await addon.instance.remove()
|
||||
await addon.instance.install(addon.version)
|
||||
except DockerAPIError:
|
||||
raise AddonsError()
|
||||
except DockerAPIError as err:
|
||||
raise AddonsError() from err
|
||||
else:
|
||||
self.data.update(store)
|
||||
_LOGGER.info("Add-on '%s' successfully rebuilt", slug)
|
||||
|
||||
# restore state
|
||||
if last_state == STATE_STARTED:
|
||||
if last_state == AddonState.STARTED:
|
||||
await addon.start()
|
||||
|
||||
async def restore(self, slug: str, tar_file: tarfile.TarFile) -> None:
|
||||
|
@ -1,4 +1,5 @@
|
||||
"""Init file for Supervisor add-ons."""
|
||||
import asyncio
|
||||
from contextlib import suppress
|
||||
from copy import deepcopy
|
||||
from ipaddress import IPv4Address
|
||||
@ -11,6 +12,7 @@ import tarfile
|
||||
from tempfile import TemporaryDirectory
|
||||
from typing import Any, Awaitable, Dict, List, Optional
|
||||
|
||||
import aiohttp
|
||||
import voluptuous as vol
|
||||
from voluptuous.humanize import humanize_error
|
||||
|
||||
@ -35,9 +37,9 @@ from ..const import (
|
||||
ATTR_USER,
|
||||
ATTR_UUID,
|
||||
ATTR_VERSION,
|
||||
ATTR_WATCHDOG,
|
||||
DNS_SUFFIX,
|
||||
STATE_STARTED,
|
||||
STATE_STOPPED,
|
||||
AddonState,
|
||||
)
|
||||
from ..coresys import CoreSys
|
||||
from ..docker.addon import DockerAddon
|
||||
@ -50,6 +52,7 @@ from ..exceptions import (
|
||||
HostAppArmorError,
|
||||
JsonFileError,
|
||||
)
|
||||
from ..utils import check_port
|
||||
from ..utils.apparmor import adjust_profile
|
||||
from ..utils.json import read_json_file, write_json_file
|
||||
from ..utils.tar import atomic_contents_add, secure_path
|
||||
@ -64,8 +67,15 @@ RE_WEBUI = re.compile(
|
||||
r":\/\/\[HOST\]:\[PORT:(?P<t_port>\d+)\](?P<s_suffix>.*)$"
|
||||
)
|
||||
|
||||
RE_WATCHDOG = re.compile(
|
||||
r"^(?:(?P<s_prefix>https?|tcp)|\[PROTO:(?P<t_proto>\w+)\])"
|
||||
r":\/\/\[HOST\]:\[PORT:(?P<t_port>\d+)\](?P<s_suffix>.*)$"
|
||||
)
|
||||
|
||||
RE_OLD_AUDIO = re.compile(r"\d+,\d+")
|
||||
|
||||
WATCHDOG_TIMEOUT = aiohttp.ClientTimeout(total=10)
|
||||
|
||||
|
||||
class Addon(AddonModel):
|
||||
"""Hold data for add-on inside Supervisor."""
|
||||
@ -74,12 +84,24 @@ class Addon(AddonModel):
|
||||
"""Initialize data holder."""
|
||||
super().__init__(coresys, slug)
|
||||
self.instance: DockerAddon = DockerAddon(coresys, self)
|
||||
self.state: AddonState = AddonState.UNKNOWN
|
||||
|
||||
@property
|
||||
def in_progress(self) -> bool:
|
||||
"""Return True if a task is in progress."""
|
||||
return self.instance.in_progress
|
||||
|
||||
async def load(self) -> None:
|
||||
"""Async initialize of object."""
|
||||
with suppress(DockerAPIError):
|
||||
await self.instance.attach(tag=self.version)
|
||||
|
||||
# Evaluate state
|
||||
if await self.instance.is_running():
|
||||
self.state = AddonState.STARTED
|
||||
else:
|
||||
self.state = AddonState.STOPPED
|
||||
|
||||
@property
|
||||
def ip_address(self) -> IPv4Address:
|
||||
"""Return IP of add-on instance."""
|
||||
@ -155,6 +177,16 @@ class Addon(AddonModel):
|
||||
"""Set auto update."""
|
||||
self.persist[ATTR_AUTO_UPDATE] = value
|
||||
|
||||
@property
|
||||
def watchdog(self) -> bool:
|
||||
"""Return True if watchdog is enable."""
|
||||
return self.persist[ATTR_WATCHDOG]
|
||||
|
||||
@watchdog.setter
|
||||
def watchdog(self, value: bool) -> None:
|
||||
"""Set watchdog enable/disable."""
|
||||
self.persist[ATTR_WATCHDOG] = value
|
||||
|
||||
@property
|
||||
def uuid(self) -> str:
|
||||
"""Return an API token for this add-on."""
|
||||
@ -230,8 +262,6 @@ class Addon(AddonModel):
|
||||
if not url:
|
||||
return None
|
||||
webui = RE_WEBUI.match(url)
|
||||
if not webui:
|
||||
return None
|
||||
|
||||
# extract arguments
|
||||
t_port = webui.group("t_port")
|
||||
@ -245,10 +275,6 @@ class Addon(AddonModel):
|
||||
else:
|
||||
port = self.ports.get(f"{t_port}/tcp", t_port)
|
||||
|
||||
# for interface config or port lists
|
||||
if isinstance(port, (tuple, list)):
|
||||
port = port[-1]
|
||||
|
||||
# lookup the correct protocol from config
|
||||
if t_proto:
|
||||
proto = "https" if self.options.get(t_proto) else "http"
|
||||
@ -353,13 +379,55 @@ class Addon(AddonModel):
|
||||
"""Save data of add-on."""
|
||||
self.sys_addons.data.save_data()
|
||||
|
||||
async def watchdog_application(self) -> bool:
|
||||
"""Return True if application is running."""
|
||||
url = super().watchdog
|
||||
if not url:
|
||||
return True
|
||||
application = RE_WATCHDOG.match(url)
|
||||
|
||||
# extract arguments
|
||||
t_port = application.group("t_port")
|
||||
t_proto = application.group("t_proto")
|
||||
s_prefix = application.group("s_prefix") or ""
|
||||
s_suffix = application.group("s_suffix") or ""
|
||||
|
||||
# search host port for this docker port
|
||||
if self.host_network:
|
||||
port = self.ports.get(f"{t_port}/tcp", t_port)
|
||||
else:
|
||||
port = t_port
|
||||
|
||||
# TCP monitoring
|
||||
if s_prefix == "tcp":
|
||||
return await self.sys_run_in_executor(check_port, self.ip_address, port)
|
||||
|
||||
# lookup the correct protocol from config
|
||||
if t_proto:
|
||||
proto = "https" if self.options.get(t_proto) else "http"
|
||||
else:
|
||||
proto = s_prefix
|
||||
|
||||
# Make HTTP request
|
||||
try:
|
||||
url = f"{proto}://{self.ip_address}:{port}{s_suffix}"
|
||||
async with self.sys_websession_ssl.get(
|
||||
url, timeout=WATCHDOG_TIMEOUT
|
||||
) as req:
|
||||
if req.status < 300:
|
||||
return True
|
||||
except (asyncio.TimeoutError, aiohttp.ClientError):
|
||||
pass
|
||||
|
||||
return False
|
||||
|
||||
async def write_options(self) -> None:
|
||||
"""Return True if add-on options is written to data."""
|
||||
schema = self.schema
|
||||
options = self.options
|
||||
|
||||
# Update secrets for validation
|
||||
await self.sys_secrets.reload()
|
||||
await self.sys_homeassistant.secrets.reload()
|
||||
|
||||
try:
|
||||
options = schema(options)
|
||||
@ -462,12 +530,6 @@ class Addon(AddonModel):
|
||||
return False
|
||||
return True
|
||||
|
||||
async def state(self) -> str:
|
||||
"""Return running state of add-on."""
|
||||
if await self.instance.is_running():
|
||||
return STATE_STARTED
|
||||
return STATE_STOPPED
|
||||
|
||||
async def start(self) -> None:
|
||||
"""Set options and start add-on."""
|
||||
if await self.instance.is_running():
|
||||
@ -488,15 +550,21 @@ class Addon(AddonModel):
|
||||
# Start Add-on
|
||||
try:
|
||||
await self.instance.run()
|
||||
except DockerAPIError:
|
||||
raise AddonsError()
|
||||
except DockerAPIError as err:
|
||||
self.state = AddonState.ERROR
|
||||
raise AddonsError() from err
|
||||
else:
|
||||
self.state = AddonState.STARTED
|
||||
|
||||
async def stop(self) -> None:
|
||||
"""Stop add-on."""
|
||||
try:
|
||||
return await self.instance.stop()
|
||||
except DockerAPIError:
|
||||
raise AddonsError()
|
||||
except DockerAPIError as err:
|
||||
self.state = AddonState.ERROR
|
||||
raise AddonsError() from err
|
||||
else:
|
||||
self.state = AddonState.STOPPED
|
||||
|
||||
async def restart(self) -> None:
|
||||
"""Restart add-on."""
|
||||
@ -511,12 +579,19 @@ class Addon(AddonModel):
|
||||
"""
|
||||
return self.instance.logs()
|
||||
|
||||
def is_running(self) -> Awaitable[bool]:
|
||||
"""Return True if Docker container is running.
|
||||
|
||||
Return a coroutine.
|
||||
"""
|
||||
return self.instance.is_running()
|
||||
|
||||
async def stats(self) -> DockerStats:
|
||||
"""Return stats of container."""
|
||||
try:
|
||||
return await self.instance.stats()
|
||||
except DockerAPIError:
|
||||
raise AddonsError()
|
||||
except DockerAPIError as err:
|
||||
raise AddonsError() from err
|
||||
|
||||
async def write_stdin(self, data) -> None:
|
||||
"""Write data to add-on stdin.
|
||||
@ -529,8 +604,8 @@ class Addon(AddonModel):
|
||||
|
||||
try:
|
||||
return await self.instance.write_stdin(data)
|
||||
except DockerAPIError:
|
||||
raise AddonsError()
|
||||
except DockerAPIError as err:
|
||||
raise AddonsError() from err
|
||||
|
||||
async def snapshot(self, tar_file: tarfile.TarFile) -> None:
|
||||
"""Snapshot state of an add-on."""
|
||||
@ -541,31 +616,31 @@ class Addon(AddonModel):
|
||||
if self.need_build:
|
||||
try:
|
||||
await self.instance.export_image(temp_path.joinpath("image.tar"))
|
||||
except DockerAPIError:
|
||||
raise AddonsError()
|
||||
except DockerAPIError as err:
|
||||
raise AddonsError() from err
|
||||
|
||||
data = {
|
||||
ATTR_USER: self.persist,
|
||||
ATTR_SYSTEM: self.data,
|
||||
ATTR_VERSION: self.version,
|
||||
ATTR_STATE: await self.state(),
|
||||
ATTR_STATE: self.state,
|
||||
}
|
||||
|
||||
# Store local configs/state
|
||||
try:
|
||||
write_json_file(temp_path.joinpath("addon.json"), data)
|
||||
except JsonFileError:
|
||||
except JsonFileError as err:
|
||||
_LOGGER.error("Can't save meta for %s", self.slug)
|
||||
raise AddonsError()
|
||||
raise AddonsError() from err
|
||||
|
||||
# Store AppArmor Profile
|
||||
if self.sys_host.apparmor.exists(self.slug):
|
||||
profile = temp_path.joinpath("apparmor.txt")
|
||||
try:
|
||||
self.sys_host.apparmor.backup_profile(self.slug, profile)
|
||||
except HostAppArmorError:
|
||||
except HostAppArmorError as err:
|
||||
_LOGGER.error("Can't backup AppArmor profile")
|
||||
raise AddonsError()
|
||||
raise AddonsError() from err
|
||||
|
||||
# write into tarfile
|
||||
def _write_tarfile():
|
||||
@ -588,7 +663,7 @@ class Addon(AddonModel):
|
||||
await self.sys_run_in_executor(_write_tarfile)
|
||||
except (tarfile.TarError, OSError) as err:
|
||||
_LOGGER.error("Can't write tarfile %s: %s", tar_file, err)
|
||||
raise AddonsError()
|
||||
raise AddonsError() from err
|
||||
|
||||
_LOGGER.info("Finish snapshot for addon %s", self.slug)
|
||||
|
||||
@ -605,13 +680,13 @@ class Addon(AddonModel):
|
||||
await self.sys_run_in_executor(_extract_tarfile)
|
||||
except tarfile.TarError as err:
|
||||
_LOGGER.error("Can't read tarfile %s: %s", tar_file, err)
|
||||
raise AddonsError()
|
||||
raise AddonsError() from err
|
||||
|
||||
# Read snapshot data
|
||||
try:
|
||||
data = read_json_file(Path(temp, "addon.json"))
|
||||
except JsonFileError:
|
||||
raise AddonsError()
|
||||
except JsonFileError as err:
|
||||
raise AddonsError() from err
|
||||
|
||||
# Validate
|
||||
try:
|
||||
@ -622,7 +697,7 @@ class Addon(AddonModel):
|
||||
self.slug,
|
||||
humanize_error(data, err),
|
||||
)
|
||||
raise AddonsError()
|
||||
raise AddonsError() from err
|
||||
|
||||
# If available
|
||||
if not self._available(data[ATTR_SYSTEM]):
|
||||
@ -669,21 +744,21 @@ class Addon(AddonModel):
|
||||
await self.sys_run_in_executor(_restore_data)
|
||||
except shutil.Error as err:
|
||||
_LOGGER.error("Can't restore origin data: %s", err)
|
||||
raise AddonsError()
|
||||
raise AddonsError() from err
|
||||
|
||||
# Restore AppArmor
|
||||
profile_file = Path(temp, "apparmor.txt")
|
||||
if profile_file.exists():
|
||||
try:
|
||||
await self.sys_host.apparmor.load_profile(self.slug, profile_file)
|
||||
except HostAppArmorError:
|
||||
except HostAppArmorError as err:
|
||||
_LOGGER.error(
|
||||
"Can't restore AppArmor profile for add-on %s", self.slug
|
||||
)
|
||||
raise AddonsError()
|
||||
raise AddonsError() from err
|
||||
|
||||
# Run add-on
|
||||
if data[ATTR_STATE] == STATE_STARTED:
|
||||
if data[ATTR_STATE] == AddonState.STARTED:
|
||||
return await self.start()
|
||||
|
||||
_LOGGER.info("Finish restore for add-on %s", self.slug)
|
||||
|
@ -61,11 +61,12 @@ from ..const import (
|
||||
ATTR_USB,
|
||||
ATTR_VERSION,
|
||||
ATTR_VIDEO,
|
||||
ATTR_WATCHDOG,
|
||||
ATTR_WEBUI,
|
||||
SECURITY_DEFAULT,
|
||||
SECURITY_DISABLE,
|
||||
SECURITY_PROFILE,
|
||||
AddonStages,
|
||||
AddonStage,
|
||||
AddonStartup,
|
||||
)
|
||||
from ..coresys import CoreSys, CoreSysAttributes
|
||||
@ -206,7 +207,7 @@ class AddonModel(CoreSysAttributes, ABC):
|
||||
return self.data[ATTR_ADVANCED]
|
||||
|
||||
@property
|
||||
def stage(self) -> AddonStages:
|
||||
def stage(self) -> AddonStage:
|
||||
"""Return stage mode of add-on."""
|
||||
return self.data[ATTR_STAGE]
|
||||
|
||||
@ -248,6 +249,11 @@ class AddonModel(CoreSysAttributes, ABC):
|
||||
"""Return URL to webui or None."""
|
||||
return self.data.get(ATTR_WEBUI)
|
||||
|
||||
@property
|
||||
def watchdog(self) -> Optional[str]:
|
||||
"""Return URL to for watchdog or None."""
|
||||
return self.data.get(ATTR_WATCHDOG)
|
||||
|
||||
@property
|
||||
def ingress_port(self) -> Optional[int]:
|
||||
"""Return Ingress port."""
|
||||
|
@ -80,22 +80,22 @@ from ..const import (
|
||||
ATTR_UUID,
|
||||
ATTR_VERSION,
|
||||
ATTR_VIDEO,
|
||||
ATTR_WATCHDOG,
|
||||
ATTR_WEBUI,
|
||||
BOOT_AUTO,
|
||||
BOOT_MANUAL,
|
||||
PRIVILEGED_ALL,
|
||||
ROLE_ALL,
|
||||
ROLE_DEFAULT,
|
||||
STATE_STARTED,
|
||||
STATE_STOPPED,
|
||||
AddonStages,
|
||||
AddonStage,
|
||||
AddonStartup,
|
||||
AddonState,
|
||||
)
|
||||
from ..coresys import CoreSys
|
||||
from ..discovery.validate import valid_discovery_service
|
||||
from ..validate import (
|
||||
DOCKER_PORTS,
|
||||
DOCKER_PORTS_DESCRIPTION,
|
||||
docker_ports,
|
||||
docker_ports_description,
|
||||
network_port,
|
||||
token,
|
||||
uuid_match,
|
||||
@ -196,9 +196,12 @@ SCHEMA_ADDON_CONFIG = vol.Schema(
|
||||
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,
|
||||
vol.Optional(ATTR_PORTS_DESCRIPTION): DOCKER_PORTS_DESCRIPTION,
|
||||
vol.Optional(ATTR_STAGE, default=AddonStage.STABLE): vol.Coerce(AddonStage),
|
||||
vol.Optional(ATTR_PORTS): docker_ports,
|
||||
vol.Optional(ATTR_PORTS_DESCRIPTION): docker_ports_description,
|
||||
vol.Optional(ATTR_WATCHDOG): vol.Match(
|
||||
r"^(?:https?|\[PROTO:\w+\]|tcp):\/\/\[HOST\]:\[PORT:\d+\].*$"
|
||||
),
|
||||
vol.Optional(ATTR_WEBUI): vol.Match(
|
||||
r"^(?:https?|\[PROTO:\w+\]):\/\/\[HOST\]:\[PORT:\d+\].*$"
|
||||
),
|
||||
@ -301,11 +304,12 @@ SCHEMA_ADDON_USER = vol.Schema(
|
||||
vol.Optional(ATTR_OPTIONS, default=dict): dict,
|
||||
vol.Optional(ATTR_AUTO_UPDATE, default=False): vol.Boolean(),
|
||||
vol.Optional(ATTR_BOOT): vol.In([BOOT_AUTO, BOOT_MANUAL]),
|
||||
vol.Optional(ATTR_NETWORK): DOCKER_PORTS,
|
||||
vol.Optional(ATTR_NETWORK): docker_ports,
|
||||
vol.Optional(ATTR_AUDIO_OUTPUT): vol.Maybe(vol.Coerce(str)),
|
||||
vol.Optional(ATTR_AUDIO_INPUT): vol.Maybe(vol.Coerce(str)),
|
||||
vol.Optional(ATTR_PROTECTED, default=True): vol.Boolean(),
|
||||
vol.Optional(ATTR_INGRESS_PANEL, default=False): vol.Boolean(),
|
||||
vol.Optional(ATTR_WATCHDOG, default=False): vol.Boolean(),
|
||||
},
|
||||
extra=vol.REMOVE_EXTRA,
|
||||
)
|
||||
@ -331,7 +335,7 @@ SCHEMA_ADDON_SNAPSHOT = vol.Schema(
|
||||
{
|
||||
vol.Required(ATTR_USER): SCHEMA_ADDON_USER,
|
||||
vol.Required(ATTR_SYSTEM): SCHEMA_ADDON_SYSTEM,
|
||||
vol.Required(ATTR_STATE): vol.In([STATE_STARTED, STATE_STOPPED]),
|
||||
vol.Required(ATTR_STATE): vol.Coerce(AddonState),
|
||||
vol.Required(ATTR_VERSION): vol.Coerce(str),
|
||||
},
|
||||
extra=vol.REMOVE_EXTRA,
|
||||
@ -364,7 +368,7 @@ def validate_options(coresys: CoreSys, raw_schema: Dict[str, Any]):
|
||||
# normal value
|
||||
options[key] = _single_validate(coresys, typ, value, key)
|
||||
except (IndexError, KeyError):
|
||||
raise vol.Invalid(f"Type error for {key}")
|
||||
raise vol.Invalid(f"Type error for {key}") from None
|
||||
|
||||
_check_missing_options(raw_schema, options, "root")
|
||||
return options
|
||||
@ -378,20 +382,20 @@ def _single_validate(coresys: CoreSys, typ: str, value: Any, key: str):
|
||||
"""Validate a single element."""
|
||||
# if required argument
|
||||
if value is None:
|
||||
raise vol.Invalid(f"Missing required option '{key}'")
|
||||
raise vol.Invalid(f"Missing required option '{key}'") from None
|
||||
|
||||
# Lookup secret
|
||||
if str(value).startswith("!secret "):
|
||||
secret: str = value.partition(" ")[2]
|
||||
value = coresys.secrets.get(secret)
|
||||
if value is None:
|
||||
raise vol.Invalid(f"Unknown secret {secret}")
|
||||
raise vol.Invalid(f"Unknown secret {secret}") from None
|
||||
|
||||
# parse extend data from type
|
||||
match = RE_SCHEMA_ELEMENT.match(typ)
|
||||
|
||||
if not match:
|
||||
raise vol.Invalid(f"Unknown type {typ}")
|
||||
raise vol.Invalid(f"Unknown type {typ}") from None
|
||||
|
||||
# prepare range
|
||||
range_args = {}
|
||||
@ -419,7 +423,7 @@ def _single_validate(coresys: CoreSys, typ: str, value: Any, key: str):
|
||||
elif typ.startswith(V_LIST):
|
||||
return vol.In(match.group("list").split("|"))(str(value))
|
||||
|
||||
raise vol.Invalid(f"Fatal error for {key} type {typ}")
|
||||
raise vol.Invalid(f"Fatal error for {key} type {typ}") from None
|
||||
|
||||
|
||||
def _nested_validate_list(coresys, typ, data_list, key):
|
||||
@ -428,7 +432,7 @@ def _nested_validate_list(coresys, typ, data_list, key):
|
||||
|
||||
# Make sure it is a list
|
||||
if not isinstance(data_list, list):
|
||||
raise vol.Invalid(f"Invalid list for {key}")
|
||||
raise vol.Invalid(f"Invalid list for {key}") from None
|
||||
|
||||
# Process list
|
||||
for element in data_list:
|
||||
@ -448,7 +452,7 @@ def _nested_validate_dict(coresys, typ, data_dict, key):
|
||||
|
||||
# Make sure it is a dict
|
||||
if not isinstance(data_dict, dict):
|
||||
raise vol.Invalid(f"Invalid dict for {key}")
|
||||
raise vol.Invalid(f"Invalid dict for {key}") from None
|
||||
|
||||
# Process dict
|
||||
for c_key, c_value in data_dict.items():
|
||||
@ -475,7 +479,7 @@ def _check_missing_options(origin, exists, root):
|
||||
for miss_opt in missing:
|
||||
if isinstance(origin[miss_opt], str) and origin[miss_opt].endswith("?"):
|
||||
continue
|
||||
raise vol.Invalid(f"Missing option {miss_opt} in {root}")
|
||||
raise vol.Invalid(f"Missing option {miss_opt} in {root}") from None
|
||||
|
||||
|
||||
def schema_ui_options(raw_schema: Dict[str, Any]) -> List[Dict[str, Any]]:
|
||||
|
@ -18,6 +18,7 @@ from .host import APIHost
|
||||
from .info import APIInfo
|
||||
from .ingress import APIIngress
|
||||
from .multicast import APIMulticast
|
||||
from .network import APINetwork
|
||||
from .os import APIOS
|
||||
from .proxy import APIProxy
|
||||
from .security import SecurityMiddleware
|
||||
@ -54,6 +55,7 @@ class RestAPI(CoreSysAttributes):
|
||||
self._register_os()
|
||||
self._register_cli()
|
||||
self._register_multicast()
|
||||
self._register_network()
|
||||
self._register_hardware()
|
||||
self._register_homeassistant()
|
||||
self._register_proxy()
|
||||
@ -89,6 +91,24 @@ class RestAPI(CoreSysAttributes):
|
||||
]
|
||||
)
|
||||
|
||||
def _register_network(self) -> None:
|
||||
"""Register network functions."""
|
||||
api_network = APINetwork()
|
||||
api_network.coresys = self.coresys
|
||||
|
||||
self.webapp.add_routes(
|
||||
[
|
||||
web.get("/network/info", api_network.info),
|
||||
web.get(
|
||||
"/network/interface/{interface}/info", api_network.interface_info
|
||||
),
|
||||
web.post(
|
||||
"/network/interface/{interface}/update",
|
||||
api_network.interface_update,
|
||||
),
|
||||
]
|
||||
)
|
||||
|
||||
def _register_os(self) -> None:
|
||||
"""Register OS functions."""
|
||||
api_os = APIOS()
|
||||
|
@ -85,6 +85,7 @@ from ..const import (
|
||||
ATTR_VERSION,
|
||||
ATTR_VERSION_LATEST,
|
||||
ATTR_VIDEO,
|
||||
ATTR_WATCHDOG,
|
||||
ATTR_WEBUI,
|
||||
BOOT_AUTO,
|
||||
BOOT_MANUAL,
|
||||
@ -92,12 +93,12 @@ from ..const import (
|
||||
CONTENT_TYPE_PNG,
|
||||
CONTENT_TYPE_TEXT,
|
||||
REQUEST_FROM,
|
||||
STATE_NONE,
|
||||
AddonState,
|
||||
)
|
||||
from ..coresys import CoreSysAttributes
|
||||
from ..docker.stats import DockerStats
|
||||
from ..exceptions import APIError
|
||||
from ..validate import DOCKER_PORTS
|
||||
from ..validate import docker_ports
|
||||
from .utils import api_process, api_process_raw, api_validate
|
||||
|
||||
_LOGGER: logging.Logger = logging.getLogger(__name__)
|
||||
@ -108,11 +109,12 @@ SCHEMA_VERSION = vol.Schema({vol.Optional(ATTR_VERSION): vol.Coerce(str)})
|
||||
SCHEMA_OPTIONS = vol.Schema(
|
||||
{
|
||||
vol.Optional(ATTR_BOOT): vol.In([BOOT_AUTO, BOOT_MANUAL]),
|
||||
vol.Optional(ATTR_NETWORK): vol.Maybe(DOCKER_PORTS),
|
||||
vol.Optional(ATTR_NETWORK): vol.Maybe(docker_ports),
|
||||
vol.Optional(ATTR_AUTO_UPDATE): vol.Boolean(),
|
||||
vol.Optional(ATTR_AUDIO_OUTPUT): vol.Maybe(vol.Coerce(str)),
|
||||
vol.Optional(ATTR_AUDIO_INPUT): vol.Maybe(vol.Coerce(str)),
|
||||
vol.Optional(ATTR_INGRESS_PANEL): vol.Boolean(),
|
||||
vol.Optional(ATTR_WATCHDOG): vol.Boolean(),
|
||||
}
|
||||
)
|
||||
|
||||
@ -213,7 +215,7 @@ class APIAddons(CoreSysAttributes):
|
||||
ATTR_MACHINE: addon.supported_machine,
|
||||
ATTR_HOMEASSISTANT: addon.homeassistant_version,
|
||||
ATTR_URL: addon.url,
|
||||
ATTR_STATE: STATE_NONE,
|
||||
ATTR_STATE: AddonState.UNKNOWN,
|
||||
ATTR_DETACHED: addon.is_detached,
|
||||
ATTR_AVAILABLE: addon.available,
|
||||
ATTR_BUILD: addon.need_build,
|
||||
@ -255,12 +257,13 @@ class APIAddons(CoreSysAttributes):
|
||||
ATTR_INGRESS_URL: None,
|
||||
ATTR_INGRESS_PORT: None,
|
||||
ATTR_INGRESS_PANEL: None,
|
||||
ATTR_WATCHDOG: None,
|
||||
}
|
||||
|
||||
if isinstance(addon, Addon) and addon.is_installed:
|
||||
data.update(
|
||||
{
|
||||
ATTR_STATE: await addon.state(),
|
||||
ATTR_STATE: addon.state,
|
||||
ATTR_WEBUI: addon.webui,
|
||||
ATTR_INGRESS_ENTRY: addon.ingress_entry,
|
||||
ATTR_INGRESS_URL: addon.ingress_url,
|
||||
@ -271,6 +274,7 @@ class APIAddons(CoreSysAttributes):
|
||||
ATTR_AUTO_UPDATE: addon.auto_update,
|
||||
ATTR_IP_ADDRESS: str(addon.ip_address),
|
||||
ATTR_VERSION: addon.version,
|
||||
ATTR_WATCHDOG: addon.watchdog,
|
||||
}
|
||||
)
|
||||
|
||||
@ -282,7 +286,7 @@ class APIAddons(CoreSysAttributes):
|
||||
addon = self._extract_addon_installed(request)
|
||||
|
||||
# Update secrets for validation
|
||||
await self.sys_secrets.reload()
|
||||
await self.sys_homeassistant.secrets.reload()
|
||||
|
||||
# Extend schema with add-on specific validation
|
||||
addon_schema = SCHEMA_OPTIONS.extend(
|
||||
@ -306,6 +310,8 @@ class APIAddons(CoreSysAttributes):
|
||||
if ATTR_INGRESS_PANEL in body:
|
||||
addon.ingress_panel = body[ATTR_INGRESS_PANEL]
|
||||
await self.sys_ingress.update_hass_panel(addon)
|
||||
if ATTR_WATCHDOG in body:
|
||||
addon.watchdog = body[ATTR_WATCHDOG]
|
||||
|
||||
addon.save_persist()
|
||||
|
||||
|
@ -117,7 +117,7 @@ class APIHomeAssistant(CoreSysAttributes):
|
||||
@api_process
|
||||
async def stats(self, request: web.Request) -> Dict[Any, str]:
|
||||
"""Return resource information."""
|
||||
stats = await self.sys_homeassistant.stats()
|
||||
stats = await self.sys_homeassistant.core.stats()
|
||||
if not stats:
|
||||
raise APIError("No stats available")
|
||||
|
||||
@ -138,36 +138,36 @@ class APIHomeAssistant(CoreSysAttributes):
|
||||
body = await api_validate(SCHEMA_VERSION, request)
|
||||
version = body.get(ATTR_VERSION, self.sys_homeassistant.latest_version)
|
||||
|
||||
await asyncio.shield(self.sys_homeassistant.update(version))
|
||||
await asyncio.shield(self.sys_homeassistant.core.update(version))
|
||||
|
||||
@api_process
|
||||
def stop(self, request: web.Request) -> Awaitable[None]:
|
||||
"""Stop Home Assistant."""
|
||||
return asyncio.shield(self.sys_homeassistant.stop())
|
||||
return asyncio.shield(self.sys_homeassistant.core.stop())
|
||||
|
||||
@api_process
|
||||
def start(self, request: web.Request) -> Awaitable[None]:
|
||||
"""Start Home Assistant."""
|
||||
return asyncio.shield(self.sys_homeassistant.start())
|
||||
return asyncio.shield(self.sys_homeassistant.core.start())
|
||||
|
||||
@api_process
|
||||
def restart(self, request: web.Request) -> Awaitable[None]:
|
||||
"""Restart Home Assistant."""
|
||||
return asyncio.shield(self.sys_homeassistant.restart())
|
||||
return asyncio.shield(self.sys_homeassistant.core.restart())
|
||||
|
||||
@api_process
|
||||
def rebuild(self, request: web.Request) -> Awaitable[None]:
|
||||
"""Rebuild Home Assistant."""
|
||||
return asyncio.shield(self.sys_homeassistant.rebuild())
|
||||
return asyncio.shield(self.sys_homeassistant.core.rebuild())
|
||||
|
||||
@api_process_raw(CONTENT_TYPE_BINARY)
|
||||
def logs(self, request: web.Request) -> Awaitable[bytes]:
|
||||
"""Return Home Assistant Docker logs."""
|
||||
return self.sys_homeassistant.logs()
|
||||
return self.sys_homeassistant.core.logs()
|
||||
|
||||
@api_process
|
||||
async def check(self, request: web.Request) -> None:
|
||||
"""Check configuration of Home Assistant."""
|
||||
result = await self.sys_homeassistant.check_config()
|
||||
result = await self.sys_homeassistant.core.check_config()
|
||||
if not result.valid:
|
||||
raise APIError(result.log)
|
||||
|
@ -1,6 +1,5 @@
|
||||
"""Init file for Supervisor host RESTful API."""
|
||||
import asyncio
|
||||
import logging
|
||||
from typing import Awaitable
|
||||
|
||||
from aiohttp import web
|
||||
@ -26,8 +25,6 @@ from ..const import (
|
||||
from ..coresys import CoreSysAttributes
|
||||
from .utils import api_process, api_process_raw, api_validate
|
||||
|
||||
_LOGGER: logging.Logger = logging.getLogger(__name__)
|
||||
|
||||
SERVICE = "service"
|
||||
|
||||
SCHEMA_OPTIONS = vol.Schema({vol.Optional(ATTR_HOSTNAME): vol.Coerce(str)})
|
||||
|
98
supervisor/api/network.py
Normal file
98
supervisor/api/network.py
Normal file
@ -0,0 +1,98 @@
|
||||
"""REST API for network."""
|
||||
import asyncio
|
||||
from typing import Any, Dict
|
||||
|
||||
from aiohttp import web
|
||||
import voluptuous as vol
|
||||
|
||||
from ..const import (
|
||||
ATTR_ADDRESS,
|
||||
ATTR_DNS,
|
||||
ATTR_GATEWAY,
|
||||
ATTR_ID,
|
||||
ATTR_INTERFACE,
|
||||
ATTR_INTERFACES,
|
||||
ATTR_IP_ADDRESS,
|
||||
ATTR_METHOD,
|
||||
ATTR_METHODS,
|
||||
ATTR_NAMESERVERS,
|
||||
ATTR_PRIMARY,
|
||||
ATTR_TYPE,
|
||||
)
|
||||
from ..coresys import CoreSysAttributes
|
||||
from ..dbus.const import InterfaceMethodSimple
|
||||
from ..dbus.network.interface import NetworkInterface
|
||||
from ..dbus.network.utils import int2ip
|
||||
from ..exceptions import APIError
|
||||
from .utils import api_process, api_validate
|
||||
|
||||
SCHEMA_UPDATE = vol.Schema(
|
||||
{
|
||||
vol.Optional(ATTR_ADDRESS): vol.Coerce(str),
|
||||
vol.Optional(ATTR_METHOD): vol.In(ATTR_METHODS),
|
||||
vol.Optional(ATTR_GATEWAY): vol.Coerce(str),
|
||||
vol.Optional(ATTR_DNS): [str],
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def interface_information(interface: NetworkInterface) -> dict:
|
||||
"""Return a dict with information of a interface to be used in th API."""
|
||||
return {
|
||||
ATTR_IP_ADDRESS: f"{interface.ip_address}/{interface.prefix}",
|
||||
ATTR_GATEWAY: interface.gateway,
|
||||
ATTR_ID: interface.id,
|
||||
ATTR_TYPE: interface.type,
|
||||
ATTR_NAMESERVERS: [int2ip(x) for x in interface.nameservers],
|
||||
ATTR_METHOD: InterfaceMethodSimple.DHCP
|
||||
if interface.method == "auto"
|
||||
else InterfaceMethodSimple.STATIC,
|
||||
ATTR_PRIMARY: interface.primary,
|
||||
}
|
||||
|
||||
|
||||
class APINetwork(CoreSysAttributes):
|
||||
"""Handle REST API for network."""
|
||||
|
||||
@api_process
|
||||
async def info(self, request: web.Request) -> Dict[str, Any]:
|
||||
"""Return network information."""
|
||||
interfaces = {}
|
||||
for interface in self.sys_host.network.interfaces:
|
||||
interfaces[
|
||||
self.sys_host.network.interfaces[interface].name
|
||||
] = interface_information(self.sys_host.network.interfaces[interface])
|
||||
|
||||
return {ATTR_INTERFACES: interfaces}
|
||||
|
||||
@api_process
|
||||
async def interface_info(self, request: web.Request) -> Dict[str, Any]:
|
||||
"""Return network information for a interface."""
|
||||
req_interface = request.match_info.get(ATTR_INTERFACE)
|
||||
for interface in self.sys_host.network.interfaces:
|
||||
if req_interface == self.sys_host.network.interfaces[interface].name:
|
||||
return interface_information(
|
||||
self.sys_host.network.interfaces[interface]
|
||||
)
|
||||
|
||||
return {}
|
||||
|
||||
@api_process
|
||||
async def interface_update(self, request: web.Request) -> Dict[str, Any]:
|
||||
"""Update the configuration of an interface."""
|
||||
req_interface = request.match_info.get(ATTR_INTERFACE)
|
||||
|
||||
if not self.sys_host.network.interfaces.get(req_interface):
|
||||
raise APIError(f"Interface {req_interface} does not exsist")
|
||||
|
||||
args = await api_validate(SCHEMA_UPDATE, request)
|
||||
if not args:
|
||||
raise APIError("You need to supply at least one option to update")
|
||||
|
||||
await asyncio.shield(
|
||||
self.sys_host.network.interfaces[req_interface].update_settings(**args)
|
||||
)
|
||||
|
||||
await asyncio.shield(self.sys_host.network.update())
|
||||
|
||||
return await asyncio.shield(self.interface_info(request))
|
@ -1,9 +1,9 @@
|
||||
|
||||
try {
|
||||
new Function("import('/api/hassio/app/frontend_latest/entrypoint.855567b9.js')")();
|
||||
new Function("import('/api/hassio/app/frontend_latest/entrypoint.cfec6eb5.js')")();
|
||||
} catch (err) {
|
||||
var el = document.createElement('script');
|
||||
el.src = '/api/hassio/app/frontend_es5/entrypoint.19035830.js';
|
||||
el.src = '/api/hassio/app/frontend_es5/entrypoint.9b944a05.js';
|
||||
document.body.appendChild(el);
|
||||
}
|
||||
|
Binary file not shown.
@ -0,0 +1,2 @@
|
||||
(self.webpackJsonp=self.webpackJsonp||[]).push([[1],{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(165),o=n.n(a),s=n(173),t=(n(174),n(175),n(10));o.a.commands.save=function(e){Object(t.a)(e.getWrapperElement(),"editor-save")};var c=o.a,i=s.a}}]);
|
||||
//# sourceMappingURL=chunk.0d1dbeb0d1afbd18f64e.js.map
|
Binary file not shown.
@ -0,0 +1 @@
|
||||
{"version":3,"file":"chunk.0d1dbeb0d1afbd18f64e.js","sources":["webpack:///chunk.0d1dbeb0d1afbd18f64e.js"],"mappings":"AAAA","sourceRoot":""}
|
File diff suppressed because one or more lines are too long
Binary file not shown.
@ -1 +0,0 @@
|
||||
{"version":3,"file":"chunk.1edd93e4d4c4749e55ab.js","sources":["webpack:///chunk.1edd93e4d4c4749e55ab.js"],"mappings":"AAAA","sourceRoot":""}
|
File diff suppressed because one or more lines are too long
@ -1,10 +1,3 @@
|
||||
/*!
|
||||
* The buffer module from node.js, for the browser.
|
||||
*
|
||||
* @author Feross Aboukhadijeh <feross@feross.org> <http://feross.org>
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
/**
|
||||
@license
|
||||
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
|
Binary file not shown.
@ -0,0 +1 @@
|
||||
{"version":3,"file":"chunk.1f998a3d7e32b7b3c45c.js","sources":["webpack:///chunk.1f998a3d7e32b7b3c45c.js"],"mappings":";AAAA","sourceRoot":""}
|
File diff suppressed because one or more lines are too long
Binary file not shown.
@ -0,0 +1 @@
|
||||
{"version":3,"file":"chunk.2dfe3739f6cdf06691c3.js","sources":["webpack:///chunk.2dfe3739f6cdf06691c3.js"],"mappings":"AAAA","sourceRoot":""}
|
File diff suppressed because one or more lines are too long
Binary file not shown.
@ -0,0 +1 @@
|
||||
{"version":3,"file":"chunk.2e9e45ecb0d870d27d88.js","sources":["webpack:///chunk.2e9e45ecb0d870d27d88.js"],"mappings":"AAAA","sourceRoot":""}
|
File diff suppressed because one or more lines are too long
Binary file not shown.
@ -0,0 +1 @@
|
||||
{"version":3,"file":"chunk.4a9b56271bdf6fea0f78.js","sources":["webpack:///chunk.4a9b56271bdf6fea0f78.js"],"mappings":"AAAA","sourceRoot":""}
|
@ -1,2 +0,0 @@
|
||||
(self.webpackJsonp=self.webpackJsonp||[]).push([[1],{168: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(127),o=n.n(a),s=n(164),t=(n(165),n(166),n(8));o.a.commands.save=function(e){Object(t.a)(e.getWrapperElement(),"editor-save")};var c=o.a,i=s.a}}]);
|
||||
//# sourceMappingURL=chunk.53ba85527e846b37953a.js.map
|
Binary file not shown.
@ -1 +0,0 @@
|
||||
{"version":3,"file":"chunk.53ba85527e846b37953a.js","sources":["webpack:///chunk.53ba85527e846b37953a.js"],"mappings":"AAAA","sourceRoot":""}
|
File diff suppressed because one or more lines are too long
Binary file not shown.
@ -0,0 +1 @@
|
||||
{"version":3,"file":"chunk.5bff531a8ac4ea5d0e8f.js","sources":["webpack:///chunk.5bff531a8ac4ea5d0e8f.js"],"mappings":"AAAA","sourceRoot":""}
|
File diff suppressed because one or more lines are too long
Binary file not shown.
@ -1 +0,0 @@
|
||||
{"version":3,"file":"chunk.67ebcbc4ba9d39e9c2b9.js","sources":["webpack:///chunk.67ebcbc4ba9d39e9c2b9.js"],"mappings":"AAAA","sourceRoot":""}
|
File diff suppressed because one or more lines are too long
Binary file not shown.
@ -1 +0,0 @@
|
||||
{"version":3,"file":"chunk.6abfd4fdbd7486588838.js","sources":["webpack:///chunk.6abfd4fdbd7486588838.js"],"mappings":"AAAA","sourceRoot":""}
|
File diff suppressed because one or more lines are too long
Binary file not shown.
@ -0,0 +1 @@
|
||||
{"version":3,"file":"chunk.7ef6bcb43647bc158e88.js","sources":["webpack:///chunk.7ef6bcb43647bc158e88.js"],"mappings":"AAAA","sourceRoot":""}
|
File diff suppressed because one or more lines are too long
Binary file not shown.
@ -1 +0,0 @@
|
||||
{"version":3,"file":"chunk.937f66fb08e0d4aa8818.js","sources":["webpack:///chunk.937f66fb08e0d4aa8818.js"],"mappings":"AAAA","sourceRoot":""}
|
File diff suppressed because one or more lines are too long
Binary file not shown.
@ -1 +0,0 @@
|
||||
{"version":3,"file":"chunk.9de6943cce77c49b4586.js","sources":["webpack:///chunk.9de6943cce77c49b4586.js"],"mappings":";AAAA","sourceRoot":""}
|
Binary file not shown.
@ -1 +0,0 @@
|
||||
{"version":3,"file":"chunk.adad578706ef56ae55ba.js","sources":["webpack:///chunk.adad578706ef56ae55ba.js"],"mappings":"AAAA","sourceRoot":""}
|
Binary file not shown.
@ -1 +0,0 @@
|
||||
{"version":3,"file":"chunk.d3a71177023db5bc7440.js","sources":["webpack:///chunk.d3a71177023db5bc7440.js"],"mappings":"AAAA","sourceRoot":""}
|
File diff suppressed because one or more lines are too long
Binary file not shown.
@ -1 +0,0 @@
|
||||
{"version":3,"file":"chunk.dacd9533a16ebaba94e9.js","sources":["webpack:///chunk.dacd9533a16ebaba94e9.js"],"mappings":"AAAA","sourceRoot":""}
|
File diff suppressed because one or more lines are too long
Binary file not shown.
@ -0,0 +1 @@
|
||||
{"version":3,"file":"chunk.e4f91d8c5f0772ea87e5.js","sources":["webpack:///chunk.e4f91d8c5f0772ea87e5.js"],"mappings":"AAAA","sourceRoot":""}
|
File diff suppressed because one or more lines are too long
Binary file not shown.
@ -0,0 +1 @@
|
||||
{"version":3,"file":"chunk.f60d6d63bed838a42b65.js","sources":["webpack:///chunk.f60d6d63bed838a42b65.js"],"mappings":"AAAA","sourceRoot":""}
|
File diff suppressed because one or more lines are too long
Binary file not shown.
@ -1 +0,0 @@
|
||||
{"version":3,"file":"entrypoint.19035830.js","sources":["webpack:///entrypoint.19035830.js"],"mappings":";AAAA","sourceRoot":""}
|
3
supervisor/api/panel/frontend_es5/entrypoint.9b944a05.js
Normal file
3
supervisor/api/panel/frontend_es5/entrypoint.9b944a05.js
Normal file
File diff suppressed because one or more lines are too long
@ -1,3 +1,10 @@
|
||||
/*!
|
||||
* The buffer module from node.js, for the browser.
|
||||
*
|
||||
* @author Feross Aboukhadijeh <http://feross.org>
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
/*! *****************************************************************************
|
||||
Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
Licensed under the Apache License, Version 2.0 (the "License"); you may not use
|
||||
@ -13,6 +20,24 @@ See the Apache Version 2.0 License for specific language governing permissions
|
||||
and limitations under the License.
|
||||
***************************************************************************** */
|
||||
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2020 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) 2017 The Polymer Project Authors. All rights reserved.
|
||||
@ -104,6 +129,23 @@ and limitations under the License.
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @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 2019 Google Inc.
|
BIN
supervisor/api/panel/frontend_es5/entrypoint.9b944a05.js.gz
Normal file
BIN
supervisor/api/panel/frontend_es5/entrypoint.9b944a05.js.gz
Normal file
Binary file not shown.
@ -0,0 +1 @@
|
||||
{"version":3,"file":"entrypoint.9b944a05.js","sources":["webpack:///entrypoint.9b944a05.js"],"mappings":";AAAA","sourceRoot":""}
|
@ -1,3 +1,3 @@
|
||||
{
|
||||
"entrypoint.js": "/api/hassio/app/frontend_es5/entrypoint.19035830.js"
|
||||
"entrypoint.js": "/api/hassio/app/frontend_es5/entrypoint.9b944a05.js"
|
||||
}
|
File diff suppressed because one or more lines are too long
Binary file not shown.
@ -1 +1 @@
|
||||
{"version":3,"file":"chunk.d7009039727fd352acb9.js","sources":["webpack:///chunk.d7009039727fd352acb9.js"],"mappings":"AAAA;;;AA4LA;AACA;;;AAGA;;;;;AAKA;;AAEA;AAEA;AACA;;;;;;;AAQA;;;;;AAKA;;AAEA;AAEA;AACA;;;;;;;AAQA;;;AAKA;;;;;;;;;;;;;;;;AAuBA;AAohBA;;AAEA;;AAEA;AACA;;AAIA;AAwIA;;;;AAIA;;AAEA;AACA;;;AAGA;;;;AAIA;AACA;;;;;;AAQA;;;;;;;;;;;;;;;;;;;;;;AA6BA;;;AAkMA;AACA;;;;;;;;AAQA;;AAGA;;;AAGA;;AAEA;AACA;;;;AAIA;;;;;;;AAQA;;;AAGA;;;AAvCA;;;;;;;;;;;;;;;AAkEA;;;AAwLA;AACA;;AAEA;;AAEA;AACA;;AAEA;AACA;;AAEA;AACA;;AAEA;;AApBA;;;;;;;;;;;AA4CA;;;AA6GA;;AAEA;;;;AARA;;;;;;;;;;;;AAiCA;;;;AA8HA;;;AAMA;AACA;;;AAGA;;AAEA;;AAKA;;AAEA;;AAEA;;AAIA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA6EA;AA6LA;;;;AAIA;AACA;AACA;AACA;;;AAGA;;;;;;;;AAQA;AACA;AACA;;;;AAIA;AACA;;;AAGA;;;AAGA;AACA;;;;;;;AAOA;;;;;;;;;AASA;;AAEA;AACA;;;;AAIA;;AAEA;;;;AAIA;;;AAGA;;;;AAIA;AACA;AACA;;;AAGA;;;;;;AAMA;;AAEA;AACA;;;;AAIA;;;AAGA;;AAEA;;AAEA;AACA;AAIA;;;;;;AAMA;;AAEA;AACA;;AAEA;AAKA;;AAEA;;;;AAIA;;AAEA;;;;;AAKA;;AAEA;AACA;;AAEA;;;;;AAKA;;AAEA;AACA;;AAEA;;;;;AAKA;;AAEA;AACA;;AAEA;;;AAGA;;AAEA;;AAEA;AACA;;AAEA;;;;;AAKA;;AAEA;AACA;;AAEA;;;;;AAKA;;AAEA;AACA;;AAEA;AACA;;;;;AAKA;;AAEA;AACA;;AAEA;;;;;AAKA;;AAEA;AACA;;AAEA;;;;;;AAMA;;;AAGA;;;AAGA;;;;AAIA;AACA;;;;AAIA;;;;AAIA;AACA;;;;AAIA;AACA;;;;AAIA;AACA;AACA;;;AAGA;;;;;AAKA;;AAEA;AACA;;;;;AAKA;;;;;;;AAOA;AACA;;;;AAIA;AACA;AACA;;;AAGA;AACA;;;AAGA;AACA;;;;;;AAMA;AACA;;;;AAIA;;AAEA;AACA;;;;;AAKA;;AAEA;;;;;;;;;;AAUA;AACA;AACA;;;AAGA;;;AAGA;;;;AAIA;;;AAGA;AACA;;;;AAIA;AACA;AACA;;;;;;AAMA;AACA;AACA;;;;;;;;AAQA;;;;AAIA;;;;AAIA;AAGA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAkIA;;;AA0UA;AACA;AACA;;;AARA;;;;;;AA4BA;AAoGA;;AAEA;;AAEA;AACA;AACA;;;AAGA;;;AAKA;;;;;;;;;AAgBA;;;AAmGA;AACA;;;AAPA;;;;;;AA2BA;;AAmQA;AACA;AACA;AACA;;AAEA;;AAEA;;AAEA;AACA;AACA;AACA;;;AAKA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAsCA","sourceRoot":""}
|
||||
{"version":3,"file":"chunk.169d772822215fb022c3.js","sources":["webpack:///chunk.169d772822215fb022c3.js"],"mappings":"AAAA;;;AA4LA;AACA;;;AAGA;;;;;AAKA;;AAEA;AAEA;AACA;;;;;;;AAQA;;;;;AAKA;;AAEA;AAEA;AACA;;;;;;;AAQA;;;AAKA;;;;;;;;;;;;;;;;AAuBA;AA6mBA;;AAEA;;AAEA;AACA;;AAIA;AAwIA;;;;AAIA;;AAEA;AACA;;;AAGA;;;;AAIA;AACA;;;;;;AAQA;;;;;;;;;;;;;;;;;;;;;;AA6BA;;;AAkMA;AACA;;;;;;;;AAQA;;AAGA;;;AAGA;;AAEA;AACA;;;;AAIA;;;;;;;AAQA;;;AAGA;;;AAvCA;;;;;;;;;;;;;;;AAkEA;;;AAwLA;AACA;;AAEA;;AAEA;AACA;;AAEA;AACA;;AAEA;AACA;;AAEA;;AApBA;;;;;;;;;;;AA4CA;;;AA6GA;;AAEA;;;;AARA;;;;;;;;;;;;AAiCA;;;;AA8HA;;;AAMA;AACA;;;AAGA;;AAEA;;AAKA;;AAEA;;AAEA;;AAIA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA6EA;AAiMA;;;;AAIA;AACA;AACA;AACA;;;AAGA;;;;;;;;AAQA;AACA;AACA;;;;AAIA;AACA;;;AAGA;;;AAGA;AACA;;;;;;;AAOA;;;;;;;;;AASA;;AAEA;AACA;;;;AAIA;;AAEA;;;;AAIA;;;AAGA;;;;AAIA;AACA;AACA;;;AAGA;;;;;;AAMA;;AAEA;AACA;;;;AAIA;;;AAGA;;AAEA;;AAEA;AACA;AAIA;;;;;;AAMA;;AAEA;AACA;;AAEA;AAKA;;AAEA;;;;AAIA;;AAEA;;;;;AAKA;;AAEA;AACA;;AAEA;;;;;AAKA;;AAEA;AACA;;AAEA;;;;;AAKA;;AAEA;AACA;;AAEA;;;AAGA;;AAEA;;AAEA;AACA;;AAEA;;;;;AAKA;;AAEA;AACA;;AAEA;;;;;AAKA;;AAEA;AACA;;AAEA;AACA;;;;;AAKA;;AAEA;AACA;;AAEA;;;;;AAKA;;AAEA;AACA;;AAEA;;;;;;AAMA;;;AAGA;;;AAGA;;AAEA;;;;;;;;AAQA;AACA;;;;;AAKA;AACA;;;;;;;;AAQA;AACA;;;;AAIA;AACA;AACA;;;;;;;;;AASA;AACA;;;;AAIA;AACA;AACA;;;;;AAKA;;;AAGA;AACA;AACA;;;;AAIA;AACA;AACA;;;;;;;;AAQA;AACA;;;;AAIA;;AAEA;AACA;;;AAGA;AACA;;;AAGA;AACA;;;;;;AAMA;AACA;;;;AAIA;;AAEA;AACA;;;;;AAKA;;AAEA;;;;;;;;;;AAUA;AACA;AACA;;;AAGA;;;AAGA;;;;AAIA;;;AAGA;AACA;;;;AAIA;AACA;AACA;;;;;;AAMA;AACA;AACA;;;;;;;;AAQA;;;;AAIA;;;;AAIA;AAGA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAwZA;;;AAoFA;AACA;AACA;;;AARA;;;;;;AA4BA;AAoGA;;AAEA;;AAEA;AACA;AACA;;;AAGA;;;AAKA;;;;;;;;;AAgBA;;;AAmGA;AACA;;;AAPA;;;;;;AA2BA;;AAmQA;AACA;AACA;AACA;;AAEA;;AAEA;;AAEA;AACA;AACA;AACA;;;AAKA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAsCA","sourceRoot":""}
|
File diff suppressed because one or more lines are too long
Binary file not shown.
@ -0,0 +1 @@
|
||||
{"version":3,"file":"chunk.19fdeb1dc19e9028c877.js","sources":["webpack:///chunk.19fdeb1dc19e9028c877.js"],"mappings":"AAAA;AA0OA;AACA;AACA;AANA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAgEA","sourceRoot":""}
|
Binary file not shown.
@ -1 +0,0 @@
|
||||
{"version":3,"file":"chunk.1dcdd03de4add9e1f326.js","sources":["webpack:///chunk.1dcdd03de4add9e1f326.js"],"mappings":"AAAA","sourceRoot":""}
|
File diff suppressed because one or more lines are too long
@ -1,10 +1,3 @@
|
||||
/*!
|
||||
* The buffer module from node.js, for the browser.
|
||||
*
|
||||
* @author Feross Aboukhadijeh <feross@feross.org> <http://feross.org>
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
/**
|
||||
@license
|
||||
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
|
Binary file not shown.
@ -0,0 +1 @@
|
||||
{"version":3,"file":"chunk.3071be0252919da82a50.js","sources":["webpack:///chunk.3071be0252919da82a50.js"],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAy7EA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAkfA","sourceRoot":""}
|
File diff suppressed because one or more lines are too long
Binary file not shown.
@ -0,0 +1 @@
|
||||
{"version":3,"file":"chunk.3455ded08421a656fb89.js","sources":["webpack:///chunk.3455ded08421a656fb89.js"],"mappings":"AAAA","sourceRoot":""}
|
File diff suppressed because one or more lines are too long
Binary file not shown.
@ -1 +0,0 @@
|
||||
{"version":3,"file":"chunk.46e2dce2dcad3c8f5df8.js","sources":["webpack:///chunk.46e2dce2dcad3c8f5df8.js"],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAoyFA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAgrMA","sourceRoot":""}
|
@ -1,2 +0,0 @@
|
||||
(self.webpackJsonp=self.webpackJsonp||[]).push([[1],{163:function(e,r,n){"use strict";n.r(r),n.d(r,"codeMirror",(function(){return c})),n.d(r,"codeMirrorCss",(function(){return i}));var s=n(124),o=n.n(s),t=n(159),a=(n(160),n(161),n(8));o.a.commands.save=e=>{Object(a.a)(e.getWrapperElement(),"editor-save")};const c=o.a,i=t.a}}]);
|
||||
//# sourceMappingURL=chunk.5066cbaab136d2561122.js.map
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user