mirror of
https://github.com/home-assistant/supervisor.git
synced 2025-08-19 05:59:21 +00:00
Compare commits
75 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
4beaf571c2 | ||
![]() |
58a948447e | ||
![]() |
32af7ef28b | ||
![]() |
208fb549b7 | ||
![]() |
ab704c11cf | ||
![]() |
966b962ccf | ||
![]() |
4ea3695982 | ||
![]() |
b2abe37d72 | ||
![]() |
9bf8d15b01 | ||
![]() |
70acbffc23 | ||
![]() |
c9b1eb751e | ||
![]() |
ad8d850ed7 | ||
![]() |
1b0eb9397d | ||
![]() |
8572f8c4e5 | ||
![]() |
66565dde87 | ||
![]() |
d54c23952f | ||
![]() |
62b364ea29 | ||
![]() |
e6f00144f2 | ||
![]() |
8894984c12 | ||
![]() |
49fbdedf6b | ||
![]() |
0899c16895 | ||
![]() |
0747a7e4b2 | ||
![]() |
0123d7935d | ||
![]() |
7a1009446b | ||
![]() |
034606cd0f | ||
![]() |
ddc30cfd7d | ||
![]() |
f10fccaff8 | ||
![]() |
31001280c8 | ||
![]() |
9638775944 | ||
![]() |
fbec0befde | ||
![]() |
81e7fac848 | ||
![]() |
97599b3e70 | ||
![]() |
c94b23a3fd | ||
![]() |
9758980ae0 | ||
![]() |
6ab3fbaab3 | ||
![]() |
4933ff83df | ||
![]() |
71e12ecb2b | ||
![]() |
36687530e0 | ||
![]() |
e7b5864c03 | ||
![]() |
9497f85db9 | ||
![]() |
419f603571 | ||
![]() |
f4f1fc524d | ||
![]() |
6d9f44a900 | ||
![]() |
aeb9b26d44 | ||
![]() |
631f78f468 | ||
![]() |
13cedb308e | ||
![]() |
82c183e1a8 | ||
![]() |
25cf1e7394 | ||
![]() |
91509a4205 | ||
![]() |
d93ebd15a2 | ||
![]() |
85e7f817e6 | ||
![]() |
772cadb435 | ||
![]() |
853aeef583 | ||
![]() |
cd07bde307 | ||
![]() |
3057df3181 | ||
![]() |
fe785622ec | ||
![]() |
2b6829a786 | ||
![]() |
7c6c982414 | ||
![]() |
07eeb2eaf2 | ||
![]() |
223f5b7bb1 | ||
![]() |
8a9657c452 | ||
![]() |
564e9811d0 | ||
![]() |
b944b52b21 | ||
![]() |
24aecdddf3 | ||
![]() |
7bbfb60039 | ||
![]() |
ece40008c7 | ||
![]() |
0177b38ded | ||
![]() |
16f2f63081 | ||
![]() |
5f376c2a27 | ||
![]() |
90a6f109ee | ||
![]() |
e6fd0ef5dc | ||
![]() |
d46ab56901 | ||
![]() |
3b1ad5c0cd | ||
![]() |
de8a241e72 | ||
![]() |
a4a0b43d91 |
@@ -1,6 +1,8 @@
|
||||
FROM mcr.microsoft.com/vscode/devcontainers/python:0-3.9
|
||||
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
ENV \
|
||||
DEBIAN_FRONTEND=noninteractive \
|
||||
VCN_VERSION=0.9.8
|
||||
|
||||
SHELL ["/bin/bash", "-c"]
|
||||
|
||||
@@ -48,8 +50,10 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
jq \
|
||||
dbus \
|
||||
network-manager \
|
||||
apparmor-utils \
|
||||
libpulse0 \
|
||||
&& bash <(curl https://getvcn.codenotary.com -L) \
|
||||
&& curl -Lo /bin/vcn https://github.com/codenotary/vcn/releases/download/${VCN_VERSION}/vcn-${VCN_VERSION}-linux-amd64-static \
|
||||
&& chmod a+x /bin/vcn \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Install Python dependencies from requirements.txt if it exists
|
||||
|
@@ -5,7 +5,7 @@
|
||||
"appPort": "9123:8123",
|
||||
"postCreateCommand": "pre-commit install",
|
||||
"runArgs": ["-e", "GIT_EDITOR=code --wait", "--privileged"],
|
||||
"containerEnv": {"NVM_DIR":"/usr/local/share/nvm"},
|
||||
"containerEnv": { "NVM_DIR": "/usr/local/share/nvm" },
|
||||
"extensions": [
|
||||
"ms-python.python",
|
||||
"ms-python.vscode-pylance",
|
||||
@@ -22,7 +22,7 @@
|
||||
"python.linting.pylintEnabled": true,
|
||||
"python.linting.enabled": true,
|
||||
"python.formatting.provider": "black",
|
||||
"python.formatting.blackArgs": ["--target-version", "py38"],
|
||||
"python.formatting.blackArgs": ["--target-version", "py39"],
|
||||
"python.formatting.blackPath": "/usr/local/bin/black",
|
||||
"python.linting.banditPath": "/usr/local/bin/bandit",
|
||||
"python.linting.flake8Path": "/usr/local/bin/flake8",
|
||||
|
12
.github/workflows/builder.yml
vendored
12
.github/workflows/builder.yml
vendored
@@ -35,7 +35,7 @@ on:
|
||||
env:
|
||||
BUILD_NAME: supervisor
|
||||
BUILD_TYPE: supervisor
|
||||
WHEELS_TAG: 3.9-alpine3.13
|
||||
WHEELS_TAG: 3.9-alpine3.14
|
||||
|
||||
jobs:
|
||||
init:
|
||||
@@ -109,14 +109,14 @@ jobs:
|
||||
|
||||
- name: Login to DockerHub
|
||||
if: needs.init.outputs.publish == 'true'
|
||||
uses: docker/login-action@v1.9.0
|
||||
uses: docker/login-action@v1.10.0
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
if: needs.init.outputs.publish == 'true'
|
||||
uses: docker/login-action@v1.9.0
|
||||
uses: docker/login-action@v1.10.0
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ secrets.GIT_USER }}
|
||||
@@ -127,7 +127,7 @@ jobs:
|
||||
run: echo "BUILD_ARGS=--test" >> $GITHUB_ENV
|
||||
|
||||
- name: Build supervisor
|
||||
uses: home-assistant/builder@2021.06.2
|
||||
uses: home-assistant/builder@2021.07.0
|
||||
with:
|
||||
args: |
|
||||
$BUILD_ARGS \
|
||||
@@ -198,7 +198,7 @@ jobs:
|
||||
|
||||
- name: Build the Supervisor
|
||||
if: needs.init.outputs.publish != 'true'
|
||||
uses: home-assistant/builder@2021.06.2
|
||||
uses: home-assistant/builder@2021.07.0
|
||||
with:
|
||||
args: |
|
||||
--test \
|
||||
@@ -278,7 +278,7 @@ jobs:
|
||||
if: needs.init.outputs.publish == 'true'
|
||||
run: |
|
||||
echo "Enable Content-Trust"
|
||||
test=$(docker exec hassio_cli ha supervisor options --content-trust=true --no-progress --raw-json | jq -r '.result')
|
||||
test=$(docker exec hassio_cli ha security options --content-trust=true --no-progress --raw-json | jq -r '.result')
|
||||
if [ "$test" != "ok" ];then
|
||||
docker logs hassio_supervisor
|
||||
exit 1
|
||||
|
13
.github/workflows/ci.yaml
vendored
13
.github/workflows/ci.yaml
vendored
@@ -10,6 +10,7 @@ on:
|
||||
env:
|
||||
DEFAULT_PYTHON: 3.9
|
||||
PRE_COMMIT_HOME: ~/.cache/pre-commit
|
||||
DEFAULT_VCN: v0.9.8
|
||||
|
||||
jobs:
|
||||
# Separate job to pre-populate the base dependency cache
|
||||
@@ -353,10 +354,10 @@ jobs:
|
||||
id: python
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
- name: Install CodeNotary
|
||||
shell: bash
|
||||
run: |
|
||||
bash <(curl https://getvcn.codenotary.com -L)
|
||||
- name: Install VCN tools
|
||||
uses: home-assistant/actions/helpers/vcn@master
|
||||
with:
|
||||
vnc_version: ${{ env.DEFAULT_VCN }}
|
||||
- name: Restore Python virtual environment
|
||||
id: cache-venv
|
||||
uses: actions/cache@v2.1.6
|
||||
@@ -394,7 +395,7 @@ jobs:
|
||||
-o console_output_style=count \
|
||||
tests
|
||||
- name: Upload coverage artifact
|
||||
uses: actions/upload-artifact@v2.2.3
|
||||
uses: actions/upload-artifact@v2.2.4
|
||||
with:
|
||||
name: coverage-${{ matrix.python-version }}
|
||||
path: .coverage
|
||||
@@ -432,4 +433,4 @@ jobs:
|
||||
coverage report
|
||||
coverage xml
|
||||
- name: Upload coverage to Codecov
|
||||
uses: codecov/codecov-action@v1.5.2
|
||||
uses: codecov/codecov-action@v2.0.3
|
||||
|
2
.github/workflows/lock.yml
vendored
2
.github/workflows/lock.yml
vendored
@@ -9,7 +9,7 @@ jobs:
|
||||
lock:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: dessant/lock-threads@v2.0.3
|
||||
- uses: dessant/lock-threads@v2.1.2
|
||||
with:
|
||||
github-token: ${{ github.token }}
|
||||
issue-lock-inactive-days: "30"
|
||||
|
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.3.4
|
||||
- name: Sentry Release
|
||||
uses: getsentry/action-release@v1.1.5
|
||||
uses: getsentry/action-release@v1.1.6
|
||||
env:
|
||||
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
|
||||
SENTRY_ORG: ${{ secrets.SENTRY_ORG }}
|
||||
|
2
.github/workflows/stale.yml
vendored
2
.github/workflows/stale.yml
vendored
@@ -9,7 +9,7 @@ jobs:
|
||||
stale:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/stale@v3.0.19
|
||||
- uses: actions/stale@v4
|
||||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
days-before-stale: 60
|
||||
|
@@ -1,6 +1,6 @@
|
||||
repos:
|
||||
- repo: https://github.com/psf/black
|
||||
rev: 21.6b0
|
||||
rev: 21.8b0
|
||||
hooks:
|
||||
- id: black
|
||||
args:
|
||||
|
9
.vscode/tasks.json
vendored
9
.vscode/tasks.json
vendored
@@ -32,7 +32,7 @@
|
||||
{
|
||||
"label": "Update Supervisor Panel",
|
||||
"type": "shell",
|
||||
"command": "./scripts/update-frontend.sh",
|
||||
"command": "LOKALISE_TOKEN='${input:localiseToken}' ./scripts/update-frontend.sh",
|
||||
"group": {
|
||||
"kind": "build",
|
||||
"isDefault": true
|
||||
@@ -86,5 +86,12 @@
|
||||
},
|
||||
"problemMatcher": []
|
||||
}
|
||||
],
|
||||
"inputs": [
|
||||
{
|
||||
"id": "localiseToken",
|
||||
"type": "promptString",
|
||||
"description": "Paste your lokalise token to download frontend translations"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@@ -29,9 +29,6 @@ RUN \
|
||||
https://github.com/codenotary/vcn \
|
||||
&& cd vcn \
|
||||
\
|
||||
# Fix: https://github.com/codenotary/vcn/issues/131
|
||||
&& go get github.com/codenotary/immudb@4cf9e2ae06ac2e6ec98a60364c3de3eab5524757 \
|
||||
\
|
||||
&& if [ "${BUILD_ARCH}" = "armhf" ]; then \
|
||||
GOARM=6 GOARCH=arm go build -o vcn -ldflags="-s -w" ./cmd/vcn; \
|
||||
elif [ "${BUILD_ARCH}" = "armv7" ]; then \
|
||||
|
12
build.json
12
build.json
@@ -2,14 +2,14 @@
|
||||
"image": "homeassistant/{arch}-hassio-supervisor",
|
||||
"shadow_repository": "ghcr.io/home-assistant",
|
||||
"build_from": {
|
||||
"aarch64": "ghcr.io/home-assistant/aarch64-base-python:3.9-alpine3.13",
|
||||
"armhf": "ghcr.io/home-assistant/armhf-base-python:3.9-alpine3.13",
|
||||
"armv7": "ghcr.io/home-assistant/armv7-base-python:3.9-alpine3.13",
|
||||
"amd64": "ghcr.io/home-assistant/amd64-base-python:3.9-alpine3.13",
|
||||
"i386": "ghcr.io/home-assistant/i386-base-python:3.9-alpine3.13"
|
||||
"aarch64": "ghcr.io/home-assistant/aarch64-base-python:3.9-alpine3.14",
|
||||
"armhf": "ghcr.io/home-assistant/armhf-base-python:3.9-alpine3.14",
|
||||
"armv7": "ghcr.io/home-assistant/armv7-base-python:3.9-alpine3.14",
|
||||
"amd64": "ghcr.io/home-assistant/amd64-base-python:3.9-alpine3.14",
|
||||
"i386": "ghcr.io/home-assistant/i386-base-python:3.9-alpine3.14"
|
||||
},
|
||||
"args": {
|
||||
"VCN_VERSION": "0.9.4"
|
||||
"VCN_VERSION": "0.9.8"
|
||||
},
|
||||
"labels": {
|
||||
"io.hass.type": "supervisor",
|
||||
|
Submodule home-assistant-polymer updated: a45b8ca8e7...d5a161769c
3
pylintrc
3
pylintrc
@@ -4,6 +4,9 @@ jobs=2
|
||||
|
||||
good-names=id,i,j,k,ex,Run,_,fp,T
|
||||
|
||||
extension-pkg-whitelist=
|
||||
ciso8601
|
||||
|
||||
# Reasons disabled:
|
||||
# format - handled by black
|
||||
# locally-disabled - it spams too much
|
||||
|
@@ -2,19 +2,19 @@ aiohttp==3.7.4.post0
|
||||
async_timeout==3.0.1
|
||||
atomicwrites==1.4.0
|
||||
attrs==21.2.0
|
||||
awesomeversion==21.6.0
|
||||
brotli==1.0.9
|
||||
awesomeversion==21.8.1
|
||||
brotlipy==0.7.0
|
||||
cchardet==2.1.7
|
||||
ciso8601==2.1.3
|
||||
colorlog==5.0.1
|
||||
ciso8601==2.2.0
|
||||
colorlog==6.4.1
|
||||
cpe==1.2.1
|
||||
cryptography==3.4.6
|
||||
debugpy==1.3.0
|
||||
debugpy==1.4.1
|
||||
docker==5.0.0
|
||||
gitpython==3.1.17
|
||||
gitpython==3.1.18
|
||||
jinja2==3.0.1
|
||||
pulsectl==21.5.18
|
||||
pyudev==0.22.0
|
||||
ruamel.yaml==0.15.100
|
||||
sentry-sdk==1.1.0
|
||||
sentry-sdk==1.3.1
|
||||
voluptuous==0.12.1
|
||||
|
@@ -1,14 +1,14 @@
|
||||
black==21.6b0
|
||||
codecov==2.1.11
|
||||
black==21.8b0
|
||||
codecov==2.1.12
|
||||
coverage==5.5
|
||||
flake8-docstrings==1.6.0
|
||||
flake8==3.9.2
|
||||
pre-commit==2.13.0
|
||||
pre-commit==2.15.0
|
||||
pydocstyle==6.1.1
|
||||
pylint==2.8.3
|
||||
pylint==2.10.2
|
||||
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.12.1
|
||||
pytest-timeout==1.4.2
|
||||
pytest==6.2.4
|
||||
pyupgrade==2.19.4
|
||||
pytest==6.2.5
|
||||
pyupgrade==2.25.0
|
||||
|
@@ -15,7 +15,7 @@ function start_docker() {
|
||||
starttime="$(date +%s)"
|
||||
endtime="$(date +%s)"
|
||||
until docker info >/dev/null 2>&1; do
|
||||
if [ $((endtime - starttime)) -le $DOCKER_TIMEOUT ]; then
|
||||
if [[ $((endtime - starttime)) -le $DOCKER_TIMEOUT ]]; then
|
||||
sleep 1
|
||||
endtime=$(date +%s)
|
||||
else
|
||||
@@ -38,7 +38,7 @@ function stop_docker() {
|
||||
# Now wait for it to die
|
||||
kill "$DOCKER_PID"
|
||||
while kill -0 "$DOCKER_PID" 2> /dev/null; do
|
||||
if [ $((endtime - starttime)) -le $DOCKER_TIMEOUT ]; then
|
||||
if [[ $((endtime - starttime)) -le $DOCKER_TIMEOUT ]]; then
|
||||
sleep 1
|
||||
endtime=$(date +%s)
|
||||
else
|
||||
|
@@ -88,6 +88,21 @@ function init_udev() {
|
||||
udevadm trigger && udevadm settle
|
||||
}
|
||||
|
||||
function init_os-agent() {
|
||||
if pgrep os-agent; then
|
||||
echo "os-agent is running"
|
||||
return 0
|
||||
fi
|
||||
|
||||
if [ ! -f /usr/sbin/os-agent ]; then
|
||||
curl -Lo /usr/sbin/os-agent https://github.com/home-assistant/os-agent/releases/latest/download/os-agent-debian-amd64.bin
|
||||
curl -Lo /etc/dbus-1/system.d/io.hass.conf https://raw.githubusercontent.com/home-assistant/os-agent/main/contrib/io.hass.conf
|
||||
chmod a+x /usr/sbin/os-agent
|
||||
fi
|
||||
|
||||
/usr/sbin/os-agent &
|
||||
}
|
||||
|
||||
echo "Run Supervisor"
|
||||
|
||||
start_docker
|
||||
@@ -99,6 +114,7 @@ if [ "$( docker container inspect -f '{{.State.Status}}' hassio_supervisor )" ==
|
||||
docker rm -f hassio_supervisor
|
||||
init_dbus
|
||||
init_udev
|
||||
init_os-agent
|
||||
cleanup_lastboot
|
||||
run_supervisor
|
||||
stop_docker
|
||||
@@ -111,6 +127,7 @@ else
|
||||
cleanup_docker
|
||||
init_dbus
|
||||
init_udev
|
||||
init_os-agent
|
||||
run_supervisor
|
||||
stop_docker
|
||||
fi
|
||||
fi
|
||||
|
@@ -1,4 +1,6 @@
|
||||
#!/bin/bash
|
||||
source "${BASH_SOURCE[0]%/*}/common.sh"
|
||||
|
||||
set -e
|
||||
|
||||
# Update frontend
|
||||
@@ -9,6 +11,10 @@ cd home-assistant-polymer
|
||||
nvm install
|
||||
script/bootstrap
|
||||
|
||||
# Download translations
|
||||
start_docker
|
||||
./script/translations_download
|
||||
|
||||
# build frontend
|
||||
cd hassio
|
||||
./script/build_hassio
|
||||
@@ -16,3 +22,9 @@ cd hassio
|
||||
# Copy frontend
|
||||
rm -rf ../../supervisor/api/panel/*
|
||||
cp -rf build/* ../../supervisor/api/panel/
|
||||
|
||||
# Reset frontend git
|
||||
cd ..
|
||||
git reset --hard HEAD
|
||||
|
||||
stop_docker
|
2
setup.py
2
setup.py
@@ -33,6 +33,7 @@ setup(
|
||||
packages=[
|
||||
"supervisor.addons",
|
||||
"supervisor.api",
|
||||
"supervisor.backups",
|
||||
"supervisor.dbus.network",
|
||||
"supervisor.dbus.payloads",
|
||||
"supervisor.dbus",
|
||||
@@ -50,7 +51,6 @@ setup(
|
||||
"supervisor.resolution",
|
||||
"supervisor.services.modules",
|
||||
"supervisor.services",
|
||||
"supervisor.snapshots",
|
||||
"supervisor.store",
|
||||
"supervisor.utils",
|
||||
"supervisor",
|
||||
|
@@ -65,10 +65,11 @@ 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
|
||||
from .const import AddonBackupMode
|
||||
from .model import AddonModel, Data
|
||||
from .options import AddonOptions
|
||||
from .utils import remove_data
|
||||
from .validate import SCHEMA_ADDON_SNAPSHOT
|
||||
from .validate import SCHEMA_ADDON_BACKUP
|
||||
|
||||
_LOGGER: logging.Logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -678,23 +679,25 @@ class Addon(AddonModel):
|
||||
except DockerError as err:
|
||||
raise AddonsError() from err
|
||||
|
||||
async def _snapshot_command(self, command: str) -> None:
|
||||
async def _backup_command(self, command: str) -> None:
|
||||
try:
|
||||
command_return = await self.instance.run_inside(command)
|
||||
if command_return.exit_code != 0:
|
||||
_LOGGER.error(
|
||||
"Pre-/Post-Snapshot command returned error code: %s",
|
||||
"Pre-/Post backup command returned error code: %s",
|
||||
command_return.exit_code,
|
||||
)
|
||||
raise AddonsError()
|
||||
except DockerError as err:
|
||||
_LOGGER.error(
|
||||
"Failed running pre-/post-snapshot command %s: %s", command, err
|
||||
"Failed running pre-/post backup command %s: %s", command, err
|
||||
)
|
||||
raise AddonsError() from err
|
||||
|
||||
async def snapshot(self, tar_file: tarfile.TarFile) -> None:
|
||||
"""Snapshot state of an add-on."""
|
||||
async def backup(self, tar_file: tarfile.TarFile) -> None:
|
||||
"""Backup state of an add-on."""
|
||||
is_running = await self.is_running()
|
||||
|
||||
with TemporaryDirectory(dir=self.sys_config.path_tmp) as temp:
|
||||
temp_path = Path(temp)
|
||||
|
||||
@@ -731,42 +734,56 @@ class Addon(AddonModel):
|
||||
# write into tarfile
|
||||
def _write_tarfile():
|
||||
"""Write tar inside loop."""
|
||||
with tar_file as snapshot:
|
||||
# Snapshot system
|
||||
with tar_file as backup:
|
||||
# Backup system
|
||||
|
||||
snapshot.add(temp, arcname=".")
|
||||
backup.add(temp, arcname=".")
|
||||
|
||||
# Snapshot data
|
||||
# Backup data
|
||||
atomic_contents_add(
|
||||
snapshot,
|
||||
backup,
|
||||
self.path_data,
|
||||
excludes=self.snapshot_exclude,
|
||||
excludes=self.backup_exclude,
|
||||
arcname="data",
|
||||
)
|
||||
|
||||
if self.snapshot_pre is not None:
|
||||
await self._snapshot_command(self.snapshot_pre)
|
||||
if (
|
||||
is_running
|
||||
and self.backup_mode == AddonBackupMode.HOT
|
||||
and self.backup_pre is not None
|
||||
):
|
||||
await self._backup_command(self.backup_pre)
|
||||
elif is_running and self.backup_mode == AddonBackupMode.COLD:
|
||||
_LOGGER.info("Shutdown add-on %s for cold backup", self.slug)
|
||||
await self.instance.stop()
|
||||
|
||||
try:
|
||||
_LOGGER.info("Building snapshot for add-on %s", self.slug)
|
||||
_LOGGER.info("Building backup for add-on %s", self.slug)
|
||||
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() from err
|
||||
finally:
|
||||
if self.snapshot_post is not None:
|
||||
await self._snapshot_command(self.snapshot_post)
|
||||
if (
|
||||
is_running
|
||||
and self.backup_mode == AddonBackupMode.HOT
|
||||
and self.backup_post is not None
|
||||
):
|
||||
await self._backup_command(self.backup_post)
|
||||
elif is_running and self.backup_mode is AddonBackupMode.COLD:
|
||||
_LOGGER.info("Starting add-on %s again", self.slug)
|
||||
await self.start()
|
||||
|
||||
_LOGGER.info("Finish snapshot for addon %s", self.slug)
|
||||
_LOGGER.info("Finish backup for addon %s", self.slug)
|
||||
|
||||
async def restore(self, tar_file: tarfile.TarFile) -> None:
|
||||
"""Restore state of an add-on."""
|
||||
with TemporaryDirectory(dir=self.sys_config.path_tmp) as temp:
|
||||
# extract snapshot
|
||||
# extract backup
|
||||
def _extract_tarfile():
|
||||
"""Extract tar snapshot."""
|
||||
with tar_file as snapshot:
|
||||
snapshot.extractall(path=Path(temp), members=secure_path(snapshot))
|
||||
"""Extract tar backup."""
|
||||
with tar_file as backup:
|
||||
backup.extractall(path=Path(temp), members=secure_path(backup))
|
||||
|
||||
try:
|
||||
await self.sys_run_in_executor(_extract_tarfile)
|
||||
@@ -774,7 +791,7 @@ class Addon(AddonModel):
|
||||
_LOGGER.error("Can't read tarfile %s: %s", tar_file, err)
|
||||
raise AddonsError() from err
|
||||
|
||||
# Read snapshot data
|
||||
# Read backup data
|
||||
try:
|
||||
data = read_json_file(Path(temp, "addon.json"))
|
||||
except ConfigurationFileError as err:
|
||||
@@ -782,10 +799,10 @@ class Addon(AddonModel):
|
||||
|
||||
# Validate
|
||||
try:
|
||||
data = SCHEMA_ADDON_SNAPSHOT(data)
|
||||
data = SCHEMA_ADDON_BACKUP(data)
|
||||
except vol.Invalid as err:
|
||||
_LOGGER.error(
|
||||
"Can't validate %s, snapshot data: %s",
|
||||
"Can't validate %s, backup data: %s",
|
||||
self.slug,
|
||||
humanize_error(data, err),
|
||||
)
|
||||
|
@@ -48,7 +48,7 @@ class AddonBuild(FileConfiguration, CoreSysAttributes):
|
||||
def base_image(self) -> str:
|
||||
"""Return base image for this add-on."""
|
||||
if not self._data[ATTR_BUILD_FROM]:
|
||||
return f"homeassistant/{self.sys_arch.default}-base:latest"
|
||||
return f"ghcr.io/home-assistant/{self.sys_arch.default}-base:latest"
|
||||
|
||||
# Evaluate correct base image
|
||||
arch = self.sys_arch.match(list(self._data[ATTR_BUILD_FROM].keys()))
|
||||
|
12
supervisor/addons/const.py
Normal file
12
supervisor/addons/const.py
Normal file
@@ -0,0 +1,12 @@
|
||||
"""Add-on static data."""
|
||||
from enum import Enum
|
||||
|
||||
|
||||
class AddonBackupMode(str, Enum):
|
||||
"""Backup mode of an Add-on."""
|
||||
|
||||
HOT = "hot"
|
||||
COLD = "cold"
|
||||
|
||||
|
||||
ATTR_BACKUP = "backup"
|
@@ -5,12 +5,17 @@ from typing import Any, Awaitable, Dict, List, Optional
|
||||
|
||||
from awesomeversion import AwesomeVersion, AwesomeVersionException
|
||||
|
||||
from supervisor.addons.const import AddonBackupMode
|
||||
|
||||
from ..const import (
|
||||
ATTR_ADVANCED,
|
||||
ATTR_APPARMOR,
|
||||
ATTR_ARCH,
|
||||
ATTR_AUDIO,
|
||||
ATTR_AUTH_API,
|
||||
ATTR_BACKUP_EXCLUDE,
|
||||
ATTR_BACKUP_POST,
|
||||
ATTR_BACKUP_PRE,
|
||||
ATTR_BOOT,
|
||||
ATTR_DESCRIPTON,
|
||||
ATTR_DEVICES,
|
||||
@@ -30,6 +35,7 @@ from ..const import (
|
||||
ATTR_HOST_PID,
|
||||
ATTR_IMAGE,
|
||||
ATTR_INGRESS,
|
||||
ATTR_INGRESS_STREAM,
|
||||
ATTR_INIT,
|
||||
ATTR_JOURNALD,
|
||||
ATTR_KERNEL_MODULES,
|
||||
@@ -50,9 +56,6 @@ from ..const import (
|
||||
ATTR_SCHEMA,
|
||||
ATTR_SERVICES,
|
||||
ATTR_SLUG,
|
||||
ATTR_SNAPSHOT_EXCLUDE,
|
||||
ATTR_SNAPSHOT_POST,
|
||||
ATTR_SNAPSHOT_PRE,
|
||||
ATTR_STAGE,
|
||||
ATTR_STARTUP,
|
||||
ATTR_STDIN,
|
||||
@@ -76,6 +79,7 @@ from ..const import (
|
||||
)
|
||||
from ..coresys import CoreSys, CoreSysAttributes
|
||||
from ..docker.const import Capabilities
|
||||
from .const import ATTR_BACKUP
|
||||
from .options import AddonOptions, UiOptions
|
||||
from .validate import RE_SERVICE, RE_VOLUME
|
||||
|
||||
@@ -356,19 +360,24 @@ class AddonModel(CoreSysAttributes, ABC):
|
||||
return self.data[ATTR_HASSIO_ROLE]
|
||||
|
||||
@property
|
||||
def snapshot_exclude(self) -> List[str]:
|
||||
"""Return Exclude list for snapshot."""
|
||||
return self.data.get(ATTR_SNAPSHOT_EXCLUDE, [])
|
||||
def backup_exclude(self) -> List[str]:
|
||||
"""Return Exclude list for backup."""
|
||||
return self.data.get(ATTR_BACKUP_EXCLUDE, [])
|
||||
|
||||
@property
|
||||
def snapshot_pre(self) -> Optional[str]:
|
||||
"""Return pre-snapshot command."""
|
||||
return self.data.get(ATTR_SNAPSHOT_PRE)
|
||||
def backup_pre(self) -> Optional[str]:
|
||||
"""Return pre-backup command."""
|
||||
return self.data.get(ATTR_BACKUP_PRE)
|
||||
|
||||
@property
|
||||
def snapshot_post(self) -> Optional[str]:
|
||||
"""Return post-snapshot command."""
|
||||
return self.data.get(ATTR_SNAPSHOT_POST)
|
||||
def backup_post(self) -> Optional[str]:
|
||||
"""Return post-backup command."""
|
||||
return self.data.get(ATTR_BACKUP_POST)
|
||||
|
||||
@property
|
||||
def backup_mode(self) -> AddonBackupMode:
|
||||
"""Return if backup is hot/cold."""
|
||||
return self.data[ATTR_BACKUP]
|
||||
|
||||
@property
|
||||
def default_init(self) -> bool:
|
||||
@@ -390,6 +399,11 @@ class AddonModel(CoreSysAttributes, ABC):
|
||||
"""Return True if the add-on access support ingress."""
|
||||
return None
|
||||
|
||||
@property
|
||||
def ingress_stream(self) -> bool:
|
||||
"""Return True if post requests to ingress should be streamed."""
|
||||
return self.data[ATTR_INGRESS_STREAM]
|
||||
|
||||
@property
|
||||
def with_gpio(self) -> bool:
|
||||
"""Return True if the add-on access to GPIO interface."""
|
||||
|
@@ -7,6 +7,8 @@ import uuid
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
from supervisor.addons.const import AddonBackupMode
|
||||
|
||||
from ..const import (
|
||||
ARCH_ALL,
|
||||
ATTR_ACCESS_TOKEN,
|
||||
@@ -19,6 +21,9 @@ from ..const import (
|
||||
ATTR_AUDIO_OUTPUT,
|
||||
ATTR_AUTH_API,
|
||||
ATTR_AUTO_UPDATE,
|
||||
ATTR_BACKUP_EXCLUDE,
|
||||
ATTR_BACKUP_POST,
|
||||
ATTR_BACKUP_PRE,
|
||||
ATTR_BOOT,
|
||||
ATTR_BUILD_FROM,
|
||||
ATTR_CONFIGURATION,
|
||||
@@ -43,6 +48,7 @@ from ..const import (
|
||||
ATTR_INGRESS_ENTRY,
|
||||
ATTR_INGRESS_PANEL,
|
||||
ATTR_INGRESS_PORT,
|
||||
ATTR_INGRESS_STREAM,
|
||||
ATTR_INGRESS_TOKEN,
|
||||
ATTR_INIT,
|
||||
ATTR_JOURNALD,
|
||||
@@ -67,9 +73,6 @@ from ..const import (
|
||||
ATTR_SCHEMA,
|
||||
ATTR_SERVICES,
|
||||
ATTR_SLUG,
|
||||
ATTR_SNAPSHOT_EXCLUDE,
|
||||
ATTR_SNAPSHOT_POST,
|
||||
ATTR_SNAPSHOT_PRE,
|
||||
ATTR_SQUASH,
|
||||
ATTR_STAGE,
|
||||
ATTR_STARTUP,
|
||||
@@ -107,6 +110,7 @@ from ..validate import (
|
||||
uuid_match,
|
||||
version_tag,
|
||||
)
|
||||
from .const import ATTR_BACKUP
|
||||
from .options import RE_SCHEMA_ELEMENT
|
||||
|
||||
_LOGGER: logging.Logger = logging.getLogger(__name__)
|
||||
@@ -161,6 +165,14 @@ def _warn_addon_config(config: Dict[str, Any]):
|
||||
name,
|
||||
)
|
||||
|
||||
if config.get(ATTR_BACKUP, AddonBackupMode.HOT) == AddonBackupMode.COLD and (
|
||||
config.get(ATTR_BACKUP_POST) or config.get(ATTR_BACKUP_PRE)
|
||||
):
|
||||
_LOGGER.warning(
|
||||
"Add-on which only support COLD backups trying to use post/pre commands. Please report this to the maintainer of %s",
|
||||
name,
|
||||
)
|
||||
|
||||
return config
|
||||
|
||||
|
||||
@@ -213,6 +225,23 @@ def _migrate_addon_config(protocol=False):
|
||||
)
|
||||
config[ATTR_TMPFS] = True
|
||||
|
||||
# 2021-06 "snapshot" renamed to "backup"
|
||||
for entry in (
|
||||
"snapshot_exclude",
|
||||
"snapshot_post",
|
||||
"snapshot_pre",
|
||||
"snapshot",
|
||||
):
|
||||
if entry in config:
|
||||
new_entry = entry.replace("snapshot", "backup")
|
||||
config[new_entry] = config.pop(entry)
|
||||
_LOGGER.warning(
|
||||
"Add-on config '%s' is deprecated, '%s' should be used instead. Please report this to the maintainer of %s",
|
||||
entry,
|
||||
new_entry,
|
||||
name,
|
||||
)
|
||||
|
||||
return config
|
||||
|
||||
return _migrate
|
||||
@@ -248,6 +277,7 @@ _SCHEMA_ADDON_CONFIG = vol.Schema(
|
||||
network_port, vol.Equal(0)
|
||||
),
|
||||
vol.Optional(ATTR_INGRESS_ENTRY): str,
|
||||
vol.Optional(ATTR_INGRESS_STREAM, default=False): vol.Boolean(),
|
||||
vol.Optional(ATTR_PANEL_ICON, default="mdi:puzzle"): str,
|
||||
vol.Optional(ATTR_PANEL_TITLE): str,
|
||||
vol.Optional(ATTR_PANEL_ADMIN, default=True): vol.Boolean(),
|
||||
@@ -281,9 +311,12 @@ _SCHEMA_ADDON_CONFIG = vol.Schema(
|
||||
vol.Optional(ATTR_AUTH_API, default=False): vol.Boolean(),
|
||||
vol.Optional(ATTR_SERVICES): [vol.Match(RE_SERVICE)],
|
||||
vol.Optional(ATTR_DISCOVERY): [valid_discovery_service],
|
||||
vol.Optional(ATTR_SNAPSHOT_EXCLUDE): [str],
|
||||
vol.Optional(ATTR_SNAPSHOT_PRE): str,
|
||||
vol.Optional(ATTR_SNAPSHOT_POST): str,
|
||||
vol.Optional(ATTR_BACKUP_EXCLUDE): [str],
|
||||
vol.Optional(ATTR_BACKUP_PRE): str,
|
||||
vol.Optional(ATTR_BACKUP_POST): str,
|
||||
vol.Optional(ATTR_BACKUP, default=AddonBackupMode.HOT): vol.Coerce(
|
||||
AddonBackupMode
|
||||
),
|
||||
vol.Optional(ATTR_OPTIONS, default={}): dict,
|
||||
vol.Optional(ATTR_SCHEMA, default={}): vol.Any(
|
||||
vol.Schema(
|
||||
@@ -391,7 +424,7 @@ SCHEMA_ADDONS_FILE = vol.Schema(
|
||||
)
|
||||
|
||||
|
||||
SCHEMA_ADDON_SNAPSHOT = vol.Schema(
|
||||
SCHEMA_ADDON_BACKUP = vol.Schema(
|
||||
{
|
||||
vol.Required(ATTR_USER): SCHEMA_ADDON_USER,
|
||||
vol.Required(ATTR_SYSTEM): SCHEMA_ADDON_SYSTEM,
|
||||
|
@@ -9,6 +9,7 @@ from ..coresys import CoreSys, CoreSysAttributes
|
||||
from .addons import APIAddons
|
||||
from .audio import APIAudio
|
||||
from .auth import APIAuth
|
||||
from .backups import APIBackups
|
||||
from .cli import APICli
|
||||
from .discovery import APIDiscovery
|
||||
from .dns import APICoreDNS
|
||||
@@ -28,7 +29,6 @@ from .proxy import APIProxy
|
||||
from .resolution import APIResoulution
|
||||
from .security import APISecurity
|
||||
from .services import APIServices
|
||||
from .snapshots import APISnapshots
|
||||
from .store import APIStore
|
||||
from .supervisor import APISupervisor
|
||||
|
||||
@@ -62,6 +62,7 @@ class RestAPI(CoreSysAttributes):
|
||||
self._register_addons()
|
||||
self._register_audio()
|
||||
self._register_auth()
|
||||
self._register_backups()
|
||||
self._register_cli()
|
||||
self._register_discovery()
|
||||
self._register_dns()
|
||||
@@ -80,7 +81,6 @@ class RestAPI(CoreSysAttributes):
|
||||
self._register_proxy()
|
||||
self._register_resolution()
|
||||
self._register_services()
|
||||
self._register_snapshots()
|
||||
self._register_supervisor()
|
||||
self._register_store()
|
||||
self._register_security()
|
||||
@@ -393,30 +393,41 @@ class RestAPI(CoreSysAttributes):
|
||||
]
|
||||
)
|
||||
|
||||
def _register_snapshots(self) -> None:
|
||||
"""Register snapshots functions."""
|
||||
api_snapshots = APISnapshots()
|
||||
api_snapshots.coresys = self.coresys
|
||||
def _register_backups(self) -> None:
|
||||
"""Register backups functions."""
|
||||
api_backups = APIBackups()
|
||||
api_backups.coresys = self.coresys
|
||||
|
||||
self.webapp.add_routes(
|
||||
[
|
||||
web.get("/snapshots", api_snapshots.list),
|
||||
web.post("/snapshots/reload", api_snapshots.reload),
|
||||
web.post("/snapshots/new/full", api_snapshots.snapshot_full),
|
||||
web.post("/snapshots/new/partial", api_snapshots.snapshot_partial),
|
||||
web.post("/snapshots/new/upload", api_snapshots.upload),
|
||||
web.get("/snapshots/{snapshot}/info", api_snapshots.info),
|
||||
web.delete("/snapshots/{snapshot}", api_snapshots.remove),
|
||||
web.get("/snapshots", api_backups.list),
|
||||
web.post("/snapshots/reload", api_backups.reload),
|
||||
web.post("/snapshots/new/full", api_backups.backup_full),
|
||||
web.post("/snapshots/new/partial", api_backups.backup_partial),
|
||||
web.post("/snapshots/new/upload", api_backups.upload),
|
||||
web.get("/snapshots/{slug}/info", api_backups.info),
|
||||
web.delete("/snapshots/{slug}", api_backups.remove),
|
||||
web.post("/snapshots/{slug}/restore/full", api_backups.restore_full),
|
||||
web.post(
|
||||
"/snapshots/{snapshot}/restore/full", api_snapshots.restore_full
|
||||
"/snapshots/{slug}/restore/partial",
|
||||
api_backups.restore_partial,
|
||||
),
|
||||
web.get("/snapshots/{slug}/download", api_backups.download),
|
||||
web.post("/snapshots/{slug}/remove", api_backups.remove),
|
||||
# June 2021: /snapshots was renamed to /backups
|
||||
web.get("/backups", api_backups.list),
|
||||
web.post("/backups/reload", api_backups.reload),
|
||||
web.post("/backups/new/full", api_backups.backup_full),
|
||||
web.post("/backups/new/partial", api_backups.backup_partial),
|
||||
web.post("/backups/new/upload", api_backups.upload),
|
||||
web.get("/backups/{slug}/info", api_backups.info),
|
||||
web.delete("/backups/{slug}", api_backups.remove),
|
||||
web.post("/backups/{slug}/restore/full", api_backups.restore_full),
|
||||
web.post(
|
||||
"/snapshots/{snapshot}/restore/partial",
|
||||
api_snapshots.restore_partial,
|
||||
"/backups/{slug}/restore/partial",
|
||||
api_backups.restore_partial,
|
||||
),
|
||||
web.get("/snapshots/{snapshot}/download", api_snapshots.download),
|
||||
# Old, remove at end of 2020
|
||||
web.post("/snapshots/{snapshot}/remove", api_snapshots.remove),
|
||||
web.get("/backups/{slug}/download", api_backups.download),
|
||||
]
|
||||
)
|
||||
|
||||
|
219
supervisor/api/backups.py
Normal file
219
supervisor/api/backups.py
Normal file
@@ -0,0 +1,219 @@
|
||||
"""Backups RESTful API."""
|
||||
import asyncio
|
||||
import logging
|
||||
from pathlib import Path
|
||||
import re
|
||||
from tempfile import TemporaryDirectory
|
||||
|
||||
from aiohttp import web
|
||||
from aiohttp.hdrs import CONTENT_DISPOSITION
|
||||
import voluptuous as vol
|
||||
|
||||
from ..backups.validate import ALL_FOLDERS
|
||||
from ..const import (
|
||||
ATTR_ADDONS,
|
||||
ATTR_BACKUPS,
|
||||
ATTR_CONTENT,
|
||||
ATTR_DATE,
|
||||
ATTR_FOLDERS,
|
||||
ATTR_HOMEASSISTANT,
|
||||
ATTR_NAME,
|
||||
ATTR_PASSWORD,
|
||||
ATTR_PROTECTED,
|
||||
ATTR_REPOSITORIES,
|
||||
ATTR_SIZE,
|
||||
ATTR_SLUG,
|
||||
ATTR_TYPE,
|
||||
ATTR_VERSION,
|
||||
CONTENT_TYPE_TAR,
|
||||
)
|
||||
from ..coresys import CoreSysAttributes
|
||||
from ..exceptions import APIError
|
||||
from .utils import api_process, api_validate
|
||||
|
||||
_LOGGER: logging.Logger = logging.getLogger(__name__)
|
||||
|
||||
RE_SLUGIFY_NAME = re.compile(r"[^A-Za-z0-9]+")
|
||||
|
||||
# pylint: disable=no-value-for-parameter
|
||||
SCHEMA_RESTORE_PARTIAL = vol.Schema(
|
||||
{
|
||||
vol.Optional(ATTR_PASSWORD): vol.Any(None, vol.Coerce(str)),
|
||||
vol.Optional(ATTR_HOMEASSISTANT): vol.Boolean(),
|
||||
vol.Optional(ATTR_ADDONS): vol.All([vol.Coerce(str)], vol.Unique()),
|
||||
vol.Optional(ATTR_FOLDERS): vol.All([vol.In(ALL_FOLDERS)], vol.Unique()),
|
||||
}
|
||||
)
|
||||
|
||||
SCHEMA_RESTORE_FULL = vol.Schema(
|
||||
{vol.Optional(ATTR_PASSWORD): vol.Any(None, vol.Coerce(str))}
|
||||
)
|
||||
|
||||
SCHEMA_BACKUP_FULL = vol.Schema(
|
||||
{
|
||||
vol.Optional(ATTR_NAME): vol.Coerce(str),
|
||||
vol.Optional(ATTR_PASSWORD): vol.Any(None, vol.Coerce(str)),
|
||||
}
|
||||
)
|
||||
|
||||
SCHEMA_BACKUP_PARTIAL = SCHEMA_BACKUP_FULL.extend(
|
||||
{
|
||||
vol.Optional(ATTR_ADDONS): vol.All([vol.Coerce(str)], vol.Unique()),
|
||||
vol.Optional(ATTR_FOLDERS): vol.All([vol.In(ALL_FOLDERS)], vol.Unique()),
|
||||
vol.Optional(ATTR_HOMEASSISTANT): vol.Boolean(),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
class APIBackups(CoreSysAttributes):
|
||||
"""Handle RESTful API for backups functions."""
|
||||
|
||||
def _extract_slug(self, request):
|
||||
"""Return backup, throw an exception if it doesn't exist."""
|
||||
backup = self.sys_backups.get(request.match_info.get("slug"))
|
||||
if not backup:
|
||||
raise APIError("Backup does not exist")
|
||||
return backup
|
||||
|
||||
@api_process
|
||||
async def list(self, request):
|
||||
"""Return backup list."""
|
||||
data_backups = []
|
||||
for backup in self.sys_backups.list_backups:
|
||||
data_backups.append(
|
||||
{
|
||||
ATTR_SLUG: backup.slug,
|
||||
ATTR_NAME: backup.name,
|
||||
ATTR_DATE: backup.date,
|
||||
ATTR_TYPE: backup.sys_type,
|
||||
ATTR_PROTECTED: backup.protected,
|
||||
ATTR_CONTENT: {
|
||||
ATTR_HOMEASSISTANT: backup.homeassistant_version is not None,
|
||||
ATTR_ADDONS: backup.addon_list,
|
||||
ATTR_FOLDERS: backup.folders,
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
if request.path == "/snapshots":
|
||||
# Kept for backwards compability
|
||||
return {"snapshots": data_backups}
|
||||
|
||||
return {ATTR_BACKUPS: data_backups}
|
||||
|
||||
@api_process
|
||||
async def reload(self, request):
|
||||
"""Reload backup list."""
|
||||
await asyncio.shield(self.sys_backups.reload())
|
||||
return True
|
||||
|
||||
@api_process
|
||||
async def info(self, request):
|
||||
"""Return backup info."""
|
||||
backup = self._extract_slug(request)
|
||||
|
||||
data_addons = []
|
||||
for addon_data in backup.addons:
|
||||
data_addons.append(
|
||||
{
|
||||
ATTR_SLUG: addon_data[ATTR_SLUG],
|
||||
ATTR_NAME: addon_data[ATTR_NAME],
|
||||
ATTR_VERSION: addon_data[ATTR_VERSION],
|
||||
ATTR_SIZE: addon_data[ATTR_SIZE],
|
||||
}
|
||||
)
|
||||
|
||||
return {
|
||||
ATTR_SLUG: backup.slug,
|
||||
ATTR_TYPE: backup.sys_type,
|
||||
ATTR_NAME: backup.name,
|
||||
ATTR_DATE: backup.date,
|
||||
ATTR_SIZE: backup.size,
|
||||
ATTR_PROTECTED: backup.protected,
|
||||
ATTR_HOMEASSISTANT: backup.homeassistant_version,
|
||||
ATTR_ADDONS: data_addons,
|
||||
ATTR_REPOSITORIES: backup.repositories,
|
||||
ATTR_FOLDERS: backup.folders,
|
||||
}
|
||||
|
||||
@api_process
|
||||
async def backup_full(self, request):
|
||||
"""Create full backup."""
|
||||
body = await api_validate(SCHEMA_BACKUP_FULL, request)
|
||||
backup = await asyncio.shield(self.sys_backups.do_backup_full(**body))
|
||||
|
||||
if backup:
|
||||
return {ATTR_SLUG: backup.slug}
|
||||
return False
|
||||
|
||||
@api_process
|
||||
async def backup_partial(self, request):
|
||||
"""Create a partial backup."""
|
||||
body = await api_validate(SCHEMA_BACKUP_PARTIAL, request)
|
||||
backup = await asyncio.shield(self.sys_backups.do_backup_partial(**body))
|
||||
|
||||
if backup:
|
||||
return {ATTR_SLUG: backup.slug}
|
||||
return False
|
||||
|
||||
@api_process
|
||||
async def restore_full(self, request):
|
||||
"""Full restore of a backup."""
|
||||
backup = self._extract_slug(request)
|
||||
body = await api_validate(SCHEMA_RESTORE_FULL, request)
|
||||
|
||||
return await asyncio.shield(self.sys_backups.do_restore_full(backup, **body))
|
||||
|
||||
@api_process
|
||||
async def restore_partial(self, request):
|
||||
"""Partial restore a backup."""
|
||||
backup = self._extract_slug(request)
|
||||
body = await api_validate(SCHEMA_RESTORE_PARTIAL, request)
|
||||
|
||||
return await asyncio.shield(self.sys_backups.do_restore_partial(backup, **body))
|
||||
|
||||
@api_process
|
||||
async def remove(self, request):
|
||||
"""Remove a backup."""
|
||||
backup = self._extract_slug(request)
|
||||
return self.sys_backups.remove(backup)
|
||||
|
||||
async def download(self, request):
|
||||
"""Download a backup file."""
|
||||
backup = self._extract_slug(request)
|
||||
|
||||
_LOGGER.info("Downloading backup %s", backup.slug)
|
||||
response = web.FileResponse(backup.tarfile)
|
||||
response.content_type = CONTENT_TYPE_TAR
|
||||
response.headers[
|
||||
CONTENT_DISPOSITION
|
||||
] = f"attachment; filename={RE_SLUGIFY_NAME.sub('_', backup.name)}.tar"
|
||||
return response
|
||||
|
||||
@api_process
|
||||
async def upload(self, request):
|
||||
"""Upload a backup file."""
|
||||
with TemporaryDirectory(dir=str(self.sys_config.path_tmp)) as temp_dir:
|
||||
tar_file = Path(temp_dir, "backup.tar")
|
||||
reader = await request.multipart()
|
||||
contents = await reader.next()
|
||||
try:
|
||||
with tar_file.open("wb") as backup:
|
||||
while True:
|
||||
chunk = await contents.read_chunk()
|
||||
if not chunk:
|
||||
break
|
||||
backup.write(chunk)
|
||||
|
||||
except OSError as err:
|
||||
_LOGGER.error("Can't write new backup file: %s", err)
|
||||
return False
|
||||
|
||||
except asyncio.CancelledError:
|
||||
return False
|
||||
|
||||
backup = await asyncio.shield(self.sys_backups.import_backup(tar_file))
|
||||
|
||||
if backup:
|
||||
return {ATTR_SLUG: backup.slug}
|
||||
return False
|
@@ -5,7 +5,7 @@ import logging
|
||||
from typing import Any, Dict, Union
|
||||
|
||||
import aiohttp
|
||||
from aiohttp import hdrs, web
|
||||
from aiohttp import ClientTimeout, hdrs, web
|
||||
from aiohttp.web_exceptions import (
|
||||
HTTPBadGateway,
|
||||
HTTPServiceUnavailable,
|
||||
@@ -162,9 +162,18 @@ class APIIngress(CoreSysAttributes):
|
||||
) -> Union[web.Response, web.StreamResponse]:
|
||||
"""Ingress route for request."""
|
||||
url = self._create_url(addon, path)
|
||||
data = await request.read()
|
||||
source_header = _init_header(request, addon)
|
||||
|
||||
# Passing the raw stream breaks requests for some webservers
|
||||
# since we just need it for POST requests really, for all other methods
|
||||
# we read the bytes and pass that to the request to the add-on
|
||||
# add-ons needs to add support with that in the configuration
|
||||
data = (
|
||||
request.content
|
||||
if request.method == "POST" and addon.ingress_stream
|
||||
else await request.read()
|
||||
)
|
||||
|
||||
async with self.sys_websession.request(
|
||||
request.method,
|
||||
url,
|
||||
@@ -172,6 +181,7 @@ class APIIngress(CoreSysAttributes):
|
||||
params=request.query,
|
||||
allow_redirects=False,
|
||||
data=data,
|
||||
timeout=ClientTimeout(total=None),
|
||||
) as result:
|
||||
headers = _response_header(result)
|
||||
|
||||
@@ -219,6 +229,7 @@ def _init_header(
|
||||
if name in (
|
||||
hdrs.CONTENT_LENGTH,
|
||||
hdrs.CONTENT_ENCODING,
|
||||
hdrs.TRANSFER_ENCODING,
|
||||
hdrs.SEC_WEBSOCKET_EXTENSIONS,
|
||||
hdrs.SEC_WEBSOCKET_PROTOCOL,
|
||||
hdrs.SEC_WEBSOCKET_VERSION,
|
||||
|
@@ -76,6 +76,7 @@ ADDONS_ROLE_ACCESS = {
|
||||
ROLE_BACKUP: re.compile(
|
||||
r"^(?:"
|
||||
r"|/.+/info"
|
||||
r"|/backups.*"
|
||||
r"|/snapshots.*"
|
||||
r")$"
|
||||
),
|
||||
@@ -99,6 +100,7 @@ ADDONS_ROLE_ACCESS = {
|
||||
r"|/observer/.+"
|
||||
r"|/os/.+"
|
||||
r"|/resolution/.+"
|
||||
r"|/backups.*"
|
||||
r"|/snapshots.*"
|
||||
r"|/store.*"
|
||||
r"|/supervisor/.+"
|
||||
|
@@ -1,9 +1,16 @@
|
||||
|
||||
try {
|
||||
new Function("import('/api/hassio/app/frontend_latest/entrypoint.586ea840.js')")();
|
||||
} catch (err) {
|
||||
function loadES5() {
|
||||
var el = document.createElement('script');
|
||||
el.src = '/api/hassio/app/frontend_es5/entrypoint.8daaaeda.js';
|
||||
el.src = '/api/hassio/app/frontend_es5/entrypoint.2bed31c0.js';
|
||||
document.body.appendChild(el);
|
||||
}
|
||||
if (/.*Version\/(?:11|12)(?:\.\d+)*.*Safari\//.test(navigator.userAgent)) {
|
||||
loadES5();
|
||||
} else {
|
||||
try {
|
||||
new Function("import('/api/hassio/app/frontend_latest/entrypoint.74ae643f.js')")();
|
||||
} catch (err) {
|
||||
loadES5();
|
||||
}
|
||||
}
|
||||
|
Binary file not shown.
@@ -1,2 +1,2 @@
|
||||
(self.webpackChunkhome_assistant_frontend=self.webpackChunkhome_assistant_frontend||[]).push([[534],{68441:function(e,t){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var l=new WeakMap;t.default=function(e){var t=l.get(e);return t||(t=Object.create(null),l.set(e,t)),t}},78643:function(e,t,l){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.PluralRules=void 0;var a=l(87480),n=l(43965),r=a.__importDefault(l(68441));function o(e,t){if(!(e instanceof u))throw new TypeError("Method Intl.PluralRules.prototype."+t+" called on incompatible receiver "+String(e))}function i(e,t,l,a){var n=a.IntegerDigits,r=a.NumberOfFractionDigits,o=a.FractionDigits;return u.localeData[e].fn(r?n+"."+o:n,"ordinal"===t)}var u=function(){function e(t,l){if(!(this&&this instanceof e?this.constructor:void 0))throw new TypeError("Intl.PluralRules must be called with 'new'");return n.InitializePluralRules(this,t,l,{availableLocales:e.availableLocales,relevantExtensionKeys:e.relevantExtensionKeys,localeData:e.localeData,getDefaultLocale:e.getDefaultLocale,getInternalSlots:r.default})}return e.prototype.resolvedOptions=function(){o(this,"resolvedOptions");var t=Object.create(null),l=r.default(this);return t.locale=l.locale,t.type=l.type,["minimumIntegerDigits","minimumFractionDigits","maximumFractionDigits","minimumSignificantDigits","maximumSignificantDigits"].forEach((function(e){var a=l[e];void 0!==a&&(t[e]=a)})),t.pluralCategories=a.__spreadArray([],e.localeData[t.locale].categories[t.type]),t},e.prototype.select=function(e){o(this,"select");var t=n.ToNumber(e);return n.ResolvePlural(this,t,{getInternalSlots:r.default,PluralRuleSelect:i})},e.prototype.toString=function(){return"[object Intl.PluralRules]"},e.supportedLocalesOf=function(t,l){return n.SupportedLocales(e.availableLocales,n.CanonicalizeLocaleList(t),l)},e.__addLocaleData=function(){for(var t=[],l=0;l<arguments.length;l++)t[l]=arguments[l];for(var a=0,n=t;a<n.length;a++){var r=n[a],o=r.data,i=r.locale;e.localeData[i]=o,e.availableLocales.add(i),e.__defaultLocale||(e.__defaultLocale=i)}},e.getDefaultLocale=function(){return e.__defaultLocale},e.localeData={},e.availableLocales=new Set,e.__defaultLocale="",e.relevantExtensionKeys=[],e.polyfilled=!0,e}();t.PluralRules=u;try{"undefined"!=typeof Symbol&&Object.defineProperty(u.prototype,Symbol.toStringTag,{value:"Intl.PluralRules",writable:!1,enumerable:!1,configurable:!0});try{Object.defineProperty(u,"length",{value:0,writable:!1,enumerable:!1,configurable:!0})}catch(c){}Object.defineProperty(u.prototype.constructor,"length",{value:0,writable:!1,enumerable:!1,configurable:!0}),Object.defineProperty(u.supportedLocalesOf,"length",{value:1,writable:!1,enumerable:!1,configurable:!0})}catch(s){}},25534:function(e,t,l){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var a=l(78643);l(27815).shouldPolyfill()&&Object.defineProperty(Intl,"PluralRules",{value:a.PluralRules,writable:!0,enumerable:!1,configurable:!0})},27815:function(e,t){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.shouldPolyfill=void 0,t.shouldPolyfill=function(){return"undefined"==typeof Intl||!("PluralRules"in Intl)||"one"===new Intl.PluralRules("en",{minimumFractionDigits:2}).select(1)}}}]);
|
||||
//# sourceMappingURL=chunk.1000be27cd087c21fa92.js.map
|
||||
//# sourceMappingURL=01378878.js.map
|
BIN
supervisor/api/panel/frontend_es5/01378878.js.gz
Normal file
BIN
supervisor/api/panel/frontend_es5/01378878.js.gz
Normal file
Binary file not shown.
1
supervisor/api/panel/frontend_es5/01378878.js.map
Normal file
1
supervisor/api/panel/frontend_es5/01378878.js.map
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"01378878.js","sources":["webpack://home-assistant-frontend/01378878.js"],"mappings":"AAAA","sourceRoot":""}
|
2
supervisor/api/panel/frontend_es5/02e63dc5.js
Normal file
2
supervisor/api/panel/frontend_es5/02e63dc5.js
Normal file
File diff suppressed because one or more lines are too long
BIN
supervisor/api/panel/frontend_es5/02e63dc5.js.gz
Normal file
BIN
supervisor/api/panel/frontend_es5/02e63dc5.js.gz
Normal file
Binary file not shown.
1
supervisor/api/panel/frontend_es5/02e63dc5.js.map
Normal file
1
supervisor/api/panel/frontend_es5/02e63dc5.js.map
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"02e63dc5.js","sources":["webpack://home-assistant-frontend/02e63dc5.js"],"mappings":"AAAA","sourceRoot":""}
|
3
supervisor/api/panel/frontend_es5/0bbd5196.js
Normal file
3
supervisor/api/panel/frontend_es5/0bbd5196.js
Normal file
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
|
||||
/*! js-yaml 4.1.0 https://github.com/nodeca/js-yaml @license MIT */
|
BIN
supervisor/api/panel/frontend_es5/0bbd5196.js.gz
Normal file
BIN
supervisor/api/panel/frontend_es5/0bbd5196.js.gz
Normal file
Binary file not shown.
1
supervisor/api/panel/frontend_es5/0bbd5196.js.map
Normal file
1
supervisor/api/panel/frontend_es5/0bbd5196.js.map
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"0bbd5196.js","sources":["webpack://home-assistant-frontend/0bbd5196.js"],"mappings":";AAAA","sourceRoot":""}
|
File diff suppressed because one or more lines are too long
BIN
supervisor/api/panel/frontend_es5/2995ba79.js.gz
Normal file
BIN
supervisor/api/panel/frontend_es5/2995ba79.js.gz
Normal file
Binary file not shown.
1
supervisor/api/panel/frontend_es5/2995ba79.js.map
Normal file
1
supervisor/api/panel/frontend_es5/2995ba79.js.map
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"2995ba79.js","sources":["webpack://home-assistant-frontend/2995ba79.js"],"mappings":"AAAA","sourceRoot":""}
|
2
supervisor/api/panel/frontend_es5/2f1a948c.js
Normal file
2
supervisor/api/panel/frontend_es5/2f1a948c.js
Normal file
File diff suppressed because one or more lines are too long
BIN
supervisor/api/panel/frontend_es5/2f1a948c.js.gz
Normal file
BIN
supervisor/api/panel/frontend_es5/2f1a948c.js.gz
Normal file
Binary file not shown.
1
supervisor/api/panel/frontend_es5/2f1a948c.js.map
Normal file
1
supervisor/api/panel/frontend_es5/2f1a948c.js.map
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"2f1a948c.js","sources":["webpack://home-assistant-frontend/2f1a948c.js"],"mappings":"AAAA","sourceRoot":""}
|
File diff suppressed because one or more lines are too long
Binary file not shown.
1
supervisor/api/panel/frontend_es5/4b7385c7.js.map
Normal file
1
supervisor/api/panel/frontend_es5/4b7385c7.js.map
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"4b7385c7.js","sources":["webpack://home-assistant-frontend/4b7385c7.js"],"mappings":"AAAA","sourceRoot":""}
|
3
supervisor/api/panel/frontend_es5/5891fe3e.js
Normal file
3
supervisor/api/panel/frontend_es5/5891fe3e.js
Normal file
File diff suppressed because one or more lines are too long
BIN
supervisor/api/panel/frontend_es5/5891fe3e.js.gz
Normal file
BIN
supervisor/api/panel/frontend_es5/5891fe3e.js.gz
Normal file
Binary file not shown.
1
supervisor/api/panel/frontend_es5/5891fe3e.js.map
Normal file
1
supervisor/api/panel/frontend_es5/5891fe3e.js.map
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"5891fe3e.js","sources":["webpack://home-assistant-frontend/5891fe3e.js"],"mappings":";AAAA","sourceRoot":""}
|
2
supervisor/api/panel/frontend_es5/799b0b1b.js
Normal file
2
supervisor/api/panel/frontend_es5/799b0b1b.js
Normal file
@@ -0,0 +1,2 @@
|
||||
!function(){"use strict";var r,t,n={5425:function(r,t,n){var e=n(91107);n(58556);function o(r,t){return function(r){if(Array.isArray(r))return r}(r)||function(r,t){var n=null==r?null:"undefined"!=typeof Symbol&&r[Symbol.iterator]||r["@@iterator"];if(null==n)return;var e,o,u=[],i=!0,a=!1;try{for(n=n.call(r);!(i=(e=n.next()).done)&&(u.push(e.value),!t||u.length!==t);i=!0);}catch(f){a=!0,o=f}finally{try{i||null==n.return||n.return()}finally{if(a)throw o}}return u}(r,t)||function(r,t){if(!r)return;if("string"==typeof r)return u(r,t);var n=Object.prototype.toString.call(r).slice(8,-1);"Object"===n&&r.constructor&&(n=r.constructor.name);if("Map"===n||"Set"===n)return Array.from(r);if("Arguments"===n||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n))return u(r,t)}(r,t)||function(){throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}()}function u(r,t){(null==t||t>r.length)&&(t=r.length);for(var n=0,e=new Array(t);n<t;n++)e[n]=r[n];return e}var i={filterData:function(r,t,n){return n=n.toUpperCase(),r.filter((function(r){return Object.entries(t).some((function(t){var e=o(t,2),u=e[0],i=e[1];return!(!i.filterable||!String(i.filterKey?r[i.valueColumn||u][i.filterKey]:r[i.valueColumn||u]).toUpperCase().includes(n))}))}))},sortData:function(r,t,n,e){return r.sort((function(r,o){var u=1;"desc"===n&&(u=-1);var i=t.filterKey?r[t.valueColumn||e][t.filterKey]:r[t.valueColumn||e],a=t.filterKey?o[t.valueColumn||e][t.filterKey]:o[t.valueColumn||e];return"string"==typeof i&&(i=i.toUpperCase()),"string"==typeof a&&(a=a.toUpperCase()),void 0===i&&void 0!==a?1:void 0===a&&void 0!==i?-1:i<a?-1*u:i>a?1*u:0}))}};(0,e.Jj)(i)}},e={};function o(r){var t=e[r];if(void 0!==t)return t.exports;var u=e[r]={exports:{}};return n[r](u,u.exports,o),u.exports}o.m=n,o.x=function(){var r=o.O(void 0,[354],(function(){return o(5425)}));return r=o.O(r)},r=[],o.O=function(t,n,e,u){if(!n){var i=1/0;for(l=0;l<r.length;l++){n=r[l][0],e=r[l][1],u=r[l][2];for(var a=!0,f=0;f<n.length;f++)(!1&u||i>=u)&&Object.keys(o.O).every((function(r){return o.O[r](n[f])}))?n.splice(f--,1):(a=!1,u<i&&(i=u));a&&(r.splice(l--,1),t=e())}return t}u=u||0;for(var l=r.length;l>0&&r[l-1][2]>u;l--)r[l]=r[l-1];r[l]=[n,e,u]},o.n=function(r){var t=r&&r.__esModule?function(){return r.default}:function(){return r};return o.d(t,{a:t}),t},o.d=function(r,t){for(var n in t)o.o(t,n)&&!o.o(r,n)&&Object.defineProperty(r,n,{enumerable:!0,get:t[n]})},o.f={},o.e=function(r){return Promise.all(Object.keys(o.f).reduce((function(t,n){return o.f[n](r,t),t}),[]))},o.u=function(r){return"e612d98f.js"},o.o=function(r,t){return Object.prototype.hasOwnProperty.call(r,t)},o.p="/api/hassio/app/frontend_es5/",function(){var r={425:1,477:1};o.f.i=function(t,n){r[t]||importScripts(o.p+o.u(t))};var t=self.webpackChunkhome_assistant_frontend=self.webpackChunkhome_assistant_frontend||[],n=t.push.bind(t);t.push=function(t){var e=t[0],u=t[1],i=t[2];for(var a in u)o.o(u,a)&&(o.m[a]=u[a]);for(i&&i(o);e.length;)r[e.pop()]=1;n(t)}}(),t=o.x,o.x=function(){return o.e(354).then(t)};o.x()}();
|
||||
//# sourceMappingURL=799b0b1b.js.map
|
BIN
supervisor/api/panel/frontend_es5/799b0b1b.js.gz
Normal file
BIN
supervisor/api/panel/frontend_es5/799b0b1b.js.gz
Normal file
Binary file not shown.
1
supervisor/api/panel/frontend_es5/799b0b1b.js.map
Normal file
1
supervisor/api/panel/frontend_es5/799b0b1b.js.map
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"799b0b1b.js","sources":["webpack://home-assistant-frontend/799b0b1b.js"],"mappings":"AAAA","sourceRoot":""}
|
File diff suppressed because one or more lines are too long
BIN
supervisor/api/panel/frontend_es5/7f7c3271.js.gz
Normal file
BIN
supervisor/api/panel/frontend_es5/7f7c3271.js.gz
Normal file
Binary file not shown.
1
supervisor/api/panel/frontend_es5/7f7c3271.js.map
Normal file
1
supervisor/api/panel/frontend_es5/7f7c3271.js.map
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"7f7c3271.js","sources":["webpack://home-assistant-frontend/7f7c3271.js"],"mappings":"AAAA","sourceRoot":""}
|
2
supervisor/api/panel/frontend_es5/9476142e.js
Normal file
2
supervisor/api/panel/frontend_es5/9476142e.js
Normal file
File diff suppressed because one or more lines are too long
BIN
supervisor/api/panel/frontend_es5/9476142e.js.gz
Normal file
BIN
supervisor/api/panel/frontend_es5/9476142e.js.gz
Normal file
Binary file not shown.
1
supervisor/api/panel/frontend_es5/9476142e.js.map
Normal file
1
supervisor/api/panel/frontend_es5/9476142e.js.map
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"9476142e.js","sources":["webpack://home-assistant-frontend/9476142e.js"],"mappings":"AAAA","sourceRoot":""}
|
@@ -1,2 +1,2 @@
|
||||
(self.webpackChunkhome_assistant_frontend=self.webpackChunkhome_assistant_frontend||[]).push([[827],{64827:function(){Intl.PluralRules&&"function"==typeof Intl.PluralRules.__addLocaleData&&Intl.PluralRules.__addLocaleData({data:{categories:{cardinal:["one","other"],ordinal:["one","two","few","other"]},fn:function(e,n){var t=String(e).split("."),a=!t[1],l=Number(t[0])==e,o=l&&t[0].slice(-1),r=l&&t[0].slice(-2);return n?1==o&&11!=r?"one":2==o&&12!=r?"two":3==o&&13!=r?"few":"other":1==e&&a?"one":"other"}},locale:"en"})}}]);
|
||||
//# sourceMappingURL=chunk.a6e93a1b6b86c2e55367.js.map
|
||||
//# sourceMappingURL=9b327635.js.map
|
BIN
supervisor/api/panel/frontend_es5/9b327635.js.gz
Normal file
BIN
supervisor/api/panel/frontend_es5/9b327635.js.gz
Normal file
Binary file not shown.
1
supervisor/api/panel/frontend_es5/9b327635.js.map
Normal file
1
supervisor/api/panel/frontend_es5/9b327635.js.map
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"9b327635.js","sources":["webpack://home-assistant-frontend/9b327635.js"],"mappings":"AAAA","sourceRoot":""}
|
File diff suppressed because one or more lines are too long
BIN
supervisor/api/panel/frontend_es5/9e6dea06.js.gz
Normal file
BIN
supervisor/api/panel/frontend_es5/9e6dea06.js.gz
Normal file
Binary file not shown.
1
supervisor/api/panel/frontend_es5/9e6dea06.js.map
Normal file
1
supervisor/api/panel/frontend_es5/9e6dea06.js.map
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"9e6dea06.js","sources":["webpack://home-assistant-frontend/9e6dea06.js"],"mappings":"AAAA","sourceRoot":""}
|
2
supervisor/api/panel/frontend_es5/a17514d4.js
Normal file
2
supervisor/api/panel/frontend_es5/a17514d4.js
Normal file
File diff suppressed because one or more lines are too long
BIN
supervisor/api/panel/frontend_es5/a17514d4.js.gz
Normal file
BIN
supervisor/api/panel/frontend_es5/a17514d4.js.gz
Normal file
Binary file not shown.
1
supervisor/api/panel/frontend_es5/a17514d4.js.map
Normal file
1
supervisor/api/panel/frontend_es5/a17514d4.js.map
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"a17514d4.js","sources":["webpack://home-assistant-frontend/a17514d4.js"],"mappings":"AAAA","sourceRoot":""}
|
2
supervisor/api/panel/frontend_es5/a43f16e9.js
Normal file
2
supervisor/api/panel/frontend_es5/a43f16e9.js
Normal file
File diff suppressed because one or more lines are too long
BIN
supervisor/api/panel/frontend_es5/a43f16e9.js.gz
Normal file
BIN
supervisor/api/panel/frontend_es5/a43f16e9.js.gz
Normal file
Binary file not shown.
1
supervisor/api/panel/frontend_es5/a43f16e9.js.map
Normal file
1
supervisor/api/panel/frontend_es5/a43f16e9.js.map
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"a43f16e9.js","sources":["webpack://home-assistant-frontend/a43f16e9.js"],"mappings":"AAAA","sourceRoot":""}
|
2
supervisor/api/panel/frontend_es5/af420a02.js
Normal file
2
supervisor/api/panel/frontend_es5/af420a02.js
Normal file
File diff suppressed because one or more lines are too long
BIN
supervisor/api/panel/frontend_es5/af420a02.js.gz
Normal file
BIN
supervisor/api/panel/frontend_es5/af420a02.js.gz
Normal file
Binary file not shown.
1
supervisor/api/panel/frontend_es5/af420a02.js.map
Normal file
1
supervisor/api/panel/frontend_es5/af420a02.js.map
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"af420a02.js","sources":["webpack://home-assistant-frontend/af420a02.js"],"mappings":"AAAA","sourceRoot":""}
|
2
supervisor/api/panel/frontend_es5/b7bea667.js
Normal file
2
supervisor/api/panel/frontend_es5/b7bea667.js
Normal file
@@ -0,0 +1,2 @@
|
||||
!function(){"use strict";var n,t,r={14971:function(n,t,r){var e,o,i=r(91107),u=r(9902),a=r.n(u),f=(r(58556),r(62173)),s={renderMarkdown:function(n,t){var r,i=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{};return e||(e=Object.assign({},(0,f.getDefaultWhiteList)(),{"ha-icon":["icon"],"ha-svg-icon":["path"]})),i.allowSvg?(o||(o=Object.assign({},e,{svg:["xmlns","height","width"],path:["transform","stroke","d"],img:["src"]})),r=o):r=e,(0,f.filterXSS)(a()(n,t),{whiteList:r})}};(0,i.Jj)(s)}},e={};function o(n){var t=e[n];if(void 0!==t)return t.exports;var i=e[n]={exports:{}};return r[n].call(i.exports,i,i.exports,o),i.exports}o.m=r,o.x=function(){var n=o.O(void 0,[354,468],(function(){return o(14971)}));return n=o.O(n)},n=[],o.O=function(t,r,e,i){if(!r){var u=1/0;for(s=0;s<n.length;s++){r=n[s][0],e=n[s][1],i=n[s][2];for(var a=!0,f=0;f<r.length;f++)(!1&i||u>=i)&&Object.keys(o.O).every((function(n){return o.O[n](r[f])}))?r.splice(f--,1):(a=!1,i<u&&(u=i));a&&(n.splice(s--,1),t=e())}return t}i=i||0;for(var s=n.length;s>0&&n[s-1][2]>i;s--)n[s]=n[s-1];n[s]=[r,e,i]},o.n=function(n){var t=n&&n.__esModule?function(){return n.default}:function(){return n};return o.d(t,{a:t}),t},o.d=function(n,t){for(var r in t)o.o(t,r)&&!o.o(n,r)&&Object.defineProperty(n,r,{enumerable:!0,get:t[r]})},o.f={},o.e=function(n){return Promise.all(Object.keys(o.f).reduce((function(t,r){return o.f[r](n,t),t}),[]))},o.u=function(n){return{354:"e612d98f",468:"2995ba79"}[n]+".js"},o.o=function(n,t){return Object.prototype.hasOwnProperty.call(n,t)},o.p="/api/hassio/app/frontend_es5/",function(){var n={971:1};o.f.i=function(t,r){n[t]||importScripts(o.p+o.u(t))};var t=self.webpackChunkhome_assistant_frontend=self.webpackChunkhome_assistant_frontend||[],r=t.push.bind(t);t.push=function(t){var e=t[0],i=t[1],u=t[2];for(var a in i)o.o(i,a)&&(o.m[a]=i[a]);for(u&&u(o);e.length;)n[e.pop()]=1;r(t)}}(),t=o.x,o.x=function(){return Promise.all([o.e(354),o.e(468)]).then(t)};o.x()}();
|
||||
//# sourceMappingURL=b7bea667.js.map
|
BIN
supervisor/api/panel/frontend_es5/b7bea667.js.gz
Normal file
BIN
supervisor/api/panel/frontend_es5/b7bea667.js.gz
Normal file
Binary file not shown.
1
supervisor/api/panel/frontend_es5/b7bea667.js.map
Normal file
1
supervisor/api/panel/frontend_es5/b7bea667.js.map
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"b7bea667.js","sources":["webpack://home-assistant-frontend/b7bea667.js"],"mappings":"AAAA","sourceRoot":""}
|
2
supervisor/api/panel/frontend_es5/c858d7b3.js
Normal file
2
supervisor/api/panel/frontend_es5/c858d7b3.js
Normal file
File diff suppressed because one or more lines are too long
BIN
supervisor/api/panel/frontend_es5/c858d7b3.js.gz
Normal file
BIN
supervisor/api/panel/frontend_es5/c858d7b3.js.gz
Normal file
Binary file not shown.
1
supervisor/api/panel/frontend_es5/c858d7b3.js.map
Normal file
1
supervisor/api/panel/frontend_es5/c858d7b3.js.map
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"c858d7b3.js","sources":["webpack://home-assistant-frontend/c858d7b3.js"],"mappings":"AAAA","sourceRoot":""}
|
3
supervisor/api/panel/frontend_es5/ce866b43.js
Normal file
3
supervisor/api/panel/frontend_es5/ce866b43.js
Normal file
File diff suppressed because one or more lines are too long
14
supervisor/api/panel/frontend_es5/ce866b43.js.LICENSE.txt
Normal file
14
supervisor/api/panel/frontend_es5/ce866b43.js.LICENSE.txt
Normal file
@@ -0,0 +1,14 @@
|
||||
/*! *****************************************************************************
|
||||
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.
|
||||
***************************************************************************** */
|
BIN
supervisor/api/panel/frontend_es5/ce866b43.js.gz
Normal file
BIN
supervisor/api/panel/frontend_es5/ce866b43.js.gz
Normal file
Binary file not shown.
1
supervisor/api/panel/frontend_es5/ce866b43.js.map
Normal file
1
supervisor/api/panel/frontend_es5/ce866b43.js.map
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"ce866b43.js","sources":["webpack://home-assistant-frontend/ce866b43.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.022ea7e862bfe9729114.js","sources":["webpack://home-assistant-frontend/chunk.022ea7e862bfe9729114.js"],"mappings":"AAAA","sourceRoot":""}
|
Binary file not shown.
@@ -1 +0,0 @@
|
||||
{"version":3,"file":"chunk.1000be27cd087c21fa92.js","sources":["webpack://home-assistant-frontend/chunk.1000be27cd087c21fa92.js"],"mappings":"AAAA","sourceRoot":""}
|
Binary file not shown.
@@ -1 +0,0 @@
|
||||
{"version":3,"file":"chunk.1c11636c7973052c4d31.js","sources":["webpack://home-assistant-frontend/chunk.1c11636c7973052c4d31.js"],"mappings":"AAAA","sourceRoot":""}
|
@@ -1,2 +0,0 @@
|
||||
!function(){"use strict";var r,t={5425:function(r,t,e){var n=e(91107);e(58556);function o(r,t){return function(r){if(Array.isArray(r))return r}(r)||function(r,t){var e=r&&("undefined"!=typeof Symbol&&r[Symbol.iterator]||r["@@iterator"]);if(null==e)return;var n,o,i=[],u=!0,a=!1;try{for(e=e.call(r);!(u=(n=e.next()).done)&&(i.push(n.value),!t||i.length!==t);u=!0);}catch(f){a=!0,o=f}finally{try{u||null==e.return||e.return()}finally{if(a)throw o}}return i}(r,t)||function(r,t){if(!r)return;if("string"==typeof r)return i(r,t);var e=Object.prototype.toString.call(r).slice(8,-1);"Object"===e&&r.constructor&&(e=r.constructor.name);if("Map"===e||"Set"===e)return Array.from(r);if("Arguments"===e||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(e))return i(r,t)}(r,t)||function(){throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}()}function i(r,t){(null==t||t>r.length)&&(t=r.length);for(var e=0,n=new Array(t);e<t;e++)n[e]=r[e];return n}var u={filterData:function(r,t,e){return e=e.toUpperCase(),r.filter((function(r){return Object.entries(t).some((function(t){var n=o(t,2),i=n[0],u=n[1];return!(!u.filterable||!String(u.filterKey?r[i][u.filterKey]:r[i]).toUpperCase().includes(e))}))}))},sortData:function(r,t,e,n){return r.sort((function(r,o){var i=1;"desc"===e&&(i=-1);var u=t.filterKey?r[n][t.filterKey]:r[n],a=t.filterKey?o[n][t.filterKey]:o[n];return"string"==typeof u&&(u=u.toUpperCase()),"string"==typeof a&&(a=a.toUpperCase()),void 0===u&&void 0!==a?1:void 0===a&&void 0!==u?-1:u<a?-1*i:u>a?1*i:0}))}};(0,n.Jj)(u)}},e={};function n(r){if(e[r])return e[r].exports;var o=e[r]={exports:{}};return t[r](o,o.exports,n),o.exports}n.m=t,n.x=function(){return n(5425)},n.n=function(r){var t=r&&r.__esModule?function(){return r.default}:function(){return r};return n.d(t,{a:t}),t},n.d=function(r,t){for(var e in t)n.o(t,e)&&!n.o(r,e)&&Object.defineProperty(r,e,{enumerable:!0,get:t[e]})},n.f={},n.e=function(r){return Promise.all(Object.keys(n.f).reduce((function(t,e){return n.f[e](r,t),t}),[]))},n.u=function(r){return"chunk.aa4c3f9e4936a956fbc9.js"},n.o=function(r,t){return Object.prototype.hasOwnProperty.call(r,t)},r=n.x,n.x=function(){return n.e(354).then(r)},n.p="/api/hassio/app/frontend_es5/",function(){var r={425:1,477:1};n.f.i=function(t,e){r[t]||importScripts(""+n.u(t))};var t=self.webpackChunkhome_assistant_frontend=self.webpackChunkhome_assistant_frontend||[],e=t.push.bind(t);t.push=function(t){var o=t[0],i=t[1],u=t[2];for(var a in i)n.o(i,a)&&(n.m[a]=i[a]);for(u&&u(n);o.length;)r[o.pop()]=1;e(t)}}();n.x()}();
|
||||
//# sourceMappingURL=chunk.232ece16c0cd5fc459b7.js.map
|
Binary file not shown.
@@ -1 +0,0 @@
|
||||
{"version":3,"file":"chunk.232ece16c0cd5fc459b7.js","sources":["webpack://home-assistant-frontend/chunk.232ece16c0cd5fc459b7.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.35c154979120736d0193.js","sources":["webpack://home-assistant-frontend/chunk.35c154979120736d0193.js"],"mappings":"AAAA","sourceRoot":""}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user