mirror of
https://github.com/home-assistant/supervisor.git
synced 2025-09-14 23:49:36 +00:00
Compare commits
168 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
3b45fb417b | ||
![]() |
2a2d92e3c5 | ||
![]() |
a320e42ed5 | ||
![]() |
fdef712e01 | ||
![]() |
5717ac19d7 | ||
![]() |
33d7d76fee | ||
![]() |
73bdaa623c | ||
![]() |
8ca8f59a0b | ||
![]() |
745af3c039 | ||
![]() |
5d17e1011a | ||
![]() |
826464c41b | ||
![]() |
a643df8cac | ||
![]() |
24ded99286 | ||
![]() |
6646eee504 | ||
![]() |
f55c10914e | ||
![]() |
b1e768f69e | ||
![]() |
4702f8bd5e | ||
![]() |
69959b2c97 | ||
![]() |
9d6f4f5392 | ||
![]() |
36b9a609bf | ||
![]() |
36ae0c82b6 | ||
![]() |
e11011ee51 | ||
![]() |
9125211a57 | ||
![]() |
3a4ef6ceb3 | ||
![]() |
ca82993278 | ||
![]() |
0925af91e3 | ||
![]() |
80bc32243c | ||
![]() |
f0d232880d | ||
![]() |
7c790dbbd9 | ||
![]() |
899b17e992 | ||
![]() |
d1b4521290 | ||
![]() |
9bb4feef29 | ||
![]() |
4bcdc98a31 | ||
![]() |
26f8c1df92 | ||
![]() |
a481ad73f3 | ||
![]() |
e4ac17fea6 | ||
![]() |
bcd940e95b | ||
![]() |
5365aa4466 | ||
![]() |
a0d106529c | ||
![]() |
bf1a9ec42d | ||
![]() |
fc5d97562f | ||
![]() |
f5c171e44f | ||
![]() |
a3c3f15806 | ||
![]() |
ef58a219ec | ||
![]() |
6708fe36e3 | ||
![]() |
e02fa2824c | ||
![]() |
a20f927082 | ||
![]() |
6d71e3fe81 | ||
![]() |
4056fcd75d | ||
![]() |
1e723cf0e3 | ||
![]() |
ce3f670597 | ||
![]() |
ce3d3d58ec | ||
![]() |
a92cab48e0 | ||
![]() |
ee76317392 | ||
![]() |
380ca13be1 | ||
![]() |
93f4c5e207 | ||
![]() |
e438858da0 | ||
![]() |
428a4dd849 | ||
![]() |
39cc8aaa13 | ||
![]() |
39a62864de | ||
![]() |
71a162a871 | ||
![]() |
05d7eff09a | ||
![]() |
7b8ad0782d | ||
![]() |
df3e9e3a5e | ||
![]() |
8cdc769ec8 | ||
![]() |
76e1304241 | ||
![]() |
eb9b1ff03d | ||
![]() |
b3b12d35fd | ||
![]() |
74485262e7 | ||
![]() |
615e68b29b | ||
![]() |
927b4695c9 | ||
![]() |
11811701d0 | ||
![]() |
05c8022db3 | ||
![]() |
a9ebb147c5 | ||
![]() |
ba8ca4d9ee | ||
![]() |
3574df1385 | ||
![]() |
b4497d231b | ||
![]() |
5aa9b0245a | ||
![]() |
4c72c3aafc | ||
![]() |
bf4f40f991 | ||
![]() |
603334f4f3 | ||
![]() |
46548af165 | ||
![]() |
8ef32b40c8 | ||
![]() |
fb25377087 | ||
![]() |
a75fd2d07e | ||
![]() |
e30f39e97e | ||
![]() |
4818ad7465 | ||
![]() |
5e4e9740c7 | ||
![]() |
d4e41dbf80 | ||
![]() |
cea1a1a15f | ||
![]() |
c2700b14dc | ||
![]() |
07d27170db | ||
![]() |
8eb8c07df6 | ||
![]() |
7bee6f884c | ||
![]() |
78dd20e314 | ||
![]() |
2a011b6448 | ||
![]() |
5c90370ec8 | ||
![]() |
120465b88d | ||
![]() |
c77292439a | ||
![]() |
0a0209f81a | ||
![]() |
69a7ed8a5c | ||
![]() |
8df35ab488 | ||
![]() |
a12567d0a8 | ||
![]() |
64fe190119 | ||
![]() |
e3ede66943 | ||
![]() |
2672b800d4 | ||
![]() |
c60d4bda92 | ||
![]() |
db9d0f2639 | ||
![]() |
02d4045ec3 | ||
![]() |
a308ea6927 | ||
![]() |
edc5e5e812 | ||
![]() |
23b65cb479 | ||
![]() |
e5eabd2143 | ||
![]() |
b0dd043975 | ||
![]() |
435a1096ed | ||
![]() |
21a9084ca0 | ||
![]() |
10d9135d86 | ||
![]() |
272d8b29f3 | ||
![]() |
3d665b9eec | ||
![]() |
c563f484c9 | ||
![]() |
38268ea4ea | ||
![]() |
c1ad64cddf | ||
![]() |
b898cd2a3a | ||
![]() |
937b31d845 | ||
![]() |
e4e655493b | ||
![]() |
387d2dcc2e | ||
![]() |
8abe33d48a | ||
![]() |
860442d5c4 | ||
![]() |
ce5183ce16 | ||
![]() |
3e69b04b86 | ||
![]() |
8b9cd4f122 | ||
![]() |
c0e3ccdb83 | ||
![]() |
e8cc85c487 | ||
![]() |
b3eff41692 | ||
![]() |
1ea63f185c | ||
![]() |
a513d5c09a | ||
![]() |
fb8216c102 | ||
![]() |
4f381d01df | ||
![]() |
de3382226e | ||
![]() |
77be830b72 | ||
![]() |
09c0e1320f | ||
![]() |
cc4ee59542 | ||
![]() |
1f448744f3 | ||
![]() |
ee2c257057 | ||
![]() |
be8439d4ac | ||
![]() |
981f2b193c | ||
![]() |
39087e09ce | ||
![]() |
59960efb9c | ||
![]() |
5a53bb5981 | ||
![]() |
a67fe69cbb | ||
![]() |
9ce2b0765f | ||
![]() |
2e53a48504 | ||
![]() |
8e4db0c3ec | ||
![]() |
4072b06faf | ||
![]() |
a2cf7ece70 | ||
![]() |
734fe3afde | ||
![]() |
7f3bc91c1d | ||
![]() |
9c2c95757d | ||
![]() |
b5ed6c586a | ||
![]() |
35033d1f76 | ||
![]() |
9e41d0c5b0 | ||
![]() |
62e92fada9 | ||
![]() |
ae0a1a657f | ||
![]() |
81e511ba8e | ||
![]() |
d89cb91c8c | ||
![]() |
dc31b6e6fe | ||
![]() |
930a32de1a | ||
![]() |
e40f2ed8e3 |
@@ -34,10 +34,10 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
|
|||||||
&& rm -rf /var/lib/apt/lists/*
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
# Install Python dependencies from requirements.txt if it exists
|
# Install Python dependencies from requirements.txt if it exists
|
||||||
COPY requirements.txt requirements_tests.txt /workspaces/
|
COPY requirements.txt requirements_tests.txt ./
|
||||||
RUN pip install -r requirements.txt \
|
RUN pip3 install -r requirements.txt -r requirements_tests.txt \
|
||||||
&& pip3 install -r requirements_tests.txt \
|
&& pip3 install tox \
|
||||||
&& pip install black tox
|
&& rm -f requirements.txt requirements_tests.txt
|
||||||
|
|
||||||
# Set the default shell to bash instead of sh
|
# Set the default shell to bash instead of sh
|
||||||
ENV SHELL /bin/bash
|
ENV SHELL /bin/bash
|
||||||
|
@@ -6,11 +6,13 @@
|
|||||||
"appPort": "9123:8123",
|
"appPort": "9123:8123",
|
||||||
"runArgs": [
|
"runArgs": [
|
||||||
"-e",
|
"-e",
|
||||||
"GIT_EDITOR='code --wait'",
|
"GIT_EDITOR=code --wait",
|
||||||
"--privileged"
|
"--privileged"
|
||||||
],
|
],
|
||||||
"extensions": [
|
"extensions": [
|
||||||
"ms-python.python"
|
"ms-python.python",
|
||||||
|
"visualstudioexptteam.vscodeintellicode",
|
||||||
|
"esbenp.prettier-vscode"
|
||||||
],
|
],
|
||||||
"settings": {
|
"settings": {
|
||||||
"python.pythonPath": "/usr/local/bin/python",
|
"python.pythonPath": "/usr/local/bin/python",
|
||||||
|
27
.github/lock.yml
vendored
Normal file
27
.github/lock.yml
vendored
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
# Configuration for Lock Threads - https://github.com/dessant/lock-threads
|
||||||
|
|
||||||
|
# Number of days of inactivity before a closed issue or pull request is locked
|
||||||
|
daysUntilLock: 1
|
||||||
|
|
||||||
|
# Skip issues and pull requests created before a given timestamp. Timestamp must
|
||||||
|
# follow ISO 8601 (`YYYY-MM-DD`). Set to `false` to disable
|
||||||
|
skipCreatedBefore: 2020-01-01
|
||||||
|
|
||||||
|
# Issues and pull requests with these labels will be ignored. Set to `[]` to disable
|
||||||
|
exemptLabels: []
|
||||||
|
|
||||||
|
# Label to add before locking, such as `outdated`. Set to `false` to disable
|
||||||
|
lockLabel: false
|
||||||
|
|
||||||
|
# Comment to post before locking. Set to `false` to disable
|
||||||
|
lockComment: false
|
||||||
|
|
||||||
|
# Assign `resolved` as the reason for locking. Set to `false` to disable
|
||||||
|
setLockReason: false
|
||||||
|
|
||||||
|
# Limit to only `issues` or `pulls`
|
||||||
|
only: pulls
|
||||||
|
|
||||||
|
# Optionally, specify configuration settings just for `issues` or `pulls`
|
||||||
|
issues:
|
||||||
|
daysUntilLock: 30
|
@@ -9,7 +9,6 @@ RUN apk add --no-cache \
|
|||||||
git \
|
git \
|
||||||
socat \
|
socat \
|
||||||
glib \
|
glib \
|
||||||
libstdc++ \
|
|
||||||
eudev \
|
eudev \
|
||||||
eudev-libs
|
eudev-libs
|
||||||
|
|
||||||
|
@@ -4,165 +4,151 @@ trigger:
|
|||||||
batch: true
|
batch: true
|
||||||
branches:
|
branches:
|
||||||
include:
|
include:
|
||||||
- master
|
- master
|
||||||
- dev
|
- dev
|
||||||
tags:
|
tags:
|
||||||
include:
|
include:
|
||||||
- '*'
|
- "*"
|
||||||
exclude:
|
exclude:
|
||||||
- untagged*
|
- untagged*
|
||||||
pr:
|
pr:
|
||||||
- dev
|
- dev
|
||||||
variables:
|
variables:
|
||||||
- name: basePythonTag
|
- name: basePythonTag
|
||||||
value: '3.7-alpine3.10'
|
value: "3.7-alpine3.11"
|
||||||
- name: versionHadolint
|
- name: versionHadolint
|
||||||
value: 'v1.16.3'
|
value: "v1.16.3"
|
||||||
- name: versionBuilder
|
- name: versionBuilder
|
||||||
value: '4.4'
|
value: "6.9"
|
||||||
- name: versionWheels
|
- name: versionWheels
|
||||||
value: '1.0-3.7-alpine3.10'
|
value: "1.6-3.7-alpine3.11"
|
||||||
- group: docker
|
- group: docker
|
||||||
- group: wheels
|
- group: wheels
|
||||||
|
|
||||||
|
|
||||||
stages:
|
stages:
|
||||||
|
- stage: "Test"
|
||||||
|
jobs:
|
||||||
|
- job: "Tox"
|
||||||
|
pool:
|
||||||
|
vmImage: "ubuntu-latest"
|
||||||
|
steps:
|
||||||
|
- task: UsePythonVersion@0
|
||||||
|
displayName: "Use Python 3.7"
|
||||||
|
inputs:
|
||||||
|
versionSpec: "3.7"
|
||||||
|
- script: pip install tox
|
||||||
|
displayName: "Install Tox"
|
||||||
|
- script: tox
|
||||||
|
displayName: "Run Tox"
|
||||||
|
- job: "JQ"
|
||||||
|
pool:
|
||||||
|
vmImage: "ubuntu-latest"
|
||||||
|
steps:
|
||||||
|
- script: sudo apt-get install -y jq
|
||||||
|
displayName: "Install JQ"
|
||||||
|
- bash: |
|
||||||
|
shopt -s globstar
|
||||||
|
cat **/*.json | jq '.'
|
||||||
|
displayName: "Run JQ"
|
||||||
|
- job: "Hadolint"
|
||||||
|
pool:
|
||||||
|
vmImage: "ubuntu-latest"
|
||||||
|
steps:
|
||||||
|
- script: sudo docker pull hadolint/hadolint:$(versionHadolint)
|
||||||
|
displayName: "Install Hadolint"
|
||||||
|
- script: |
|
||||||
|
sudo docker run --rm -i \
|
||||||
|
-v $(pwd)/.hadolint.yaml:/.hadolint.yaml:ro \
|
||||||
|
hadolint/hadolint:$(versionHadolint) < Dockerfile
|
||||||
|
displayName: "Run Hadolint"
|
||||||
|
|
||||||
- stage: 'Test'
|
- stage: "Wheels"
|
||||||
jobs:
|
jobs:
|
||||||
- job: 'Tox'
|
- job: "Wheels"
|
||||||
pool:
|
condition: eq(variables['Build.SourceBranchName'], 'dev')
|
||||||
vmImage: 'ubuntu-latest'
|
timeoutInMinutes: 360
|
||||||
steps:
|
pool:
|
||||||
- task: UsePythonVersion@0
|
vmImage: "ubuntu-latest"
|
||||||
displayName: 'Use Python 3.7'
|
strategy:
|
||||||
inputs:
|
maxParallel: 5
|
||||||
versionSpec: '3.7'
|
matrix:
|
||||||
- script: pip install tox
|
amd64:
|
||||||
displayName: 'Install Tox'
|
buildArch: "amd64"
|
||||||
- script: tox
|
i386:
|
||||||
displayName: 'Run Tox'
|
buildArch: "i386"
|
||||||
- job: 'Black'
|
armhf:
|
||||||
pool:
|
buildArch: "armhf"
|
||||||
vmImage: 'ubuntu-latest'
|
armv7:
|
||||||
steps:
|
buildArch: "armv7"
|
||||||
- task: UsePythonVersion@0
|
aarch64:
|
||||||
displayName: 'Use Python $(python.version)'
|
buildArch: "aarch64"
|
||||||
inputs:
|
steps:
|
||||||
versionSpec: '3.7'
|
- script: |
|
||||||
- script: pip install black
|
sudo apt-get update
|
||||||
displayName: 'Install black'
|
sudo apt-get install -y --no-install-recommends \
|
||||||
- script: black --target-version py37 --check hassio tests
|
qemu-user-static \
|
||||||
displayName: 'Run Black'
|
binfmt-support \
|
||||||
- job: 'JQ'
|
curl
|
||||||
pool:
|
|
||||||
vmImage: 'ubuntu-latest'
|
|
||||||
steps:
|
|
||||||
- script: sudo apt-get install -y jq
|
|
||||||
displayName: 'Install JQ'
|
|
||||||
- bash: |
|
|
||||||
shopt -s globstar
|
|
||||||
cat **/*.json | jq '.'
|
|
||||||
displayName: 'Run JQ'
|
|
||||||
- job: 'Hadolint'
|
|
||||||
pool:
|
|
||||||
vmImage: 'ubuntu-latest'
|
|
||||||
steps:
|
|
||||||
- script: sudo docker pull hadolint/hadolint:$(versionHadolint)
|
|
||||||
displayName: 'Install Hadolint'
|
|
||||||
- script: |
|
|
||||||
sudo docker run --rm -i \
|
|
||||||
-v $(pwd)/.hadolint.yaml:/.hadolint.yaml:ro \
|
|
||||||
hadolint/hadolint:$(versionHadolint) < Dockerfile
|
|
||||||
displayName: 'Run Hadolint'
|
|
||||||
|
|
||||||
- stage: 'Wheels'
|
sudo mount binfmt_misc -t binfmt_misc /proc/sys/fs/binfmt_misc
|
||||||
jobs:
|
sudo update-binfmts --enable qemu-arm
|
||||||
- job: 'Wheels'
|
sudo update-binfmts --enable qemu-aarch64
|
||||||
condition: eq(variables['Build.SourceBranchName'], 'dev')
|
displayName: "Initial cross build"
|
||||||
timeoutInMinutes: 360
|
- script: |
|
||||||
pool:
|
mkdir -p .ssh
|
||||||
vmImage: 'ubuntu-latest'
|
echo -e "-----BEGIN RSA PRIVATE KEY-----\n$(wheelsSSH)\n-----END RSA PRIVATE KEY-----" >> .ssh/id_rsa
|
||||||
strategy:
|
ssh-keyscan -H $(wheelsHost) >> .ssh/known_hosts
|
||||||
maxParallel: 5
|
chmod 600 .ssh/*
|
||||||
matrix:
|
displayName: "Install ssh key"
|
||||||
amd64:
|
- script: sudo docker pull homeassistant/$(buildArch)-wheels:$(versionWheels)
|
||||||
buildArch: 'amd64'
|
displayName: "Install wheels builder"
|
||||||
i386:
|
- script: |
|
||||||
buildArch: 'i386'
|
sudo docker run --rm -v $(pwd):/data:ro -v $(pwd)/.ssh:/root/.ssh:rw \
|
||||||
armhf:
|
homeassistant/$(buildArch)-wheels:$(versionWheels) \
|
||||||
buildArch: 'armhf'
|
--apk "build-base;libffi-dev;openssl-dev" \
|
||||||
armv7:
|
--index $(wheelsIndex) \
|
||||||
buildArch: 'armv7'
|
--requirement requirements.txt \
|
||||||
aarch64:
|
--upload rsync \
|
||||||
buildArch: 'aarch64'
|
--remote wheels@$(wheelsHost):/opt/wheels
|
||||||
steps:
|
displayName: "Run wheels build"
|
||||||
- script: |
|
|
||||||
sudo apt-get update
|
|
||||||
sudo apt-get install -y --no-install-recommends \
|
|
||||||
qemu-user-static \
|
|
||||||
binfmt-support \
|
|
||||||
curl
|
|
||||||
|
|
||||||
sudo mount binfmt_misc -t binfmt_misc /proc/sys/fs/binfmt_misc
|
- stage: "Deploy"
|
||||||
sudo update-binfmts --enable qemu-arm
|
jobs:
|
||||||
sudo update-binfmts --enable qemu-aarch64
|
- job: "VersionValidate"
|
||||||
displayName: 'Initial cross build'
|
condition: or(startsWith(variables['Build.SourceBranch'], 'refs/tags'), eq(variables['Build.SourceBranchName'], 'dev'))
|
||||||
- script: |
|
pool:
|
||||||
mkdir -p .ssh
|
vmImage: "ubuntu-latest"
|
||||||
echo -e "-----BEGIN RSA PRIVATE KEY-----\n$(wheelsSSH)\n-----END RSA PRIVATE KEY-----" >> .ssh/id_rsa
|
steps:
|
||||||
ssh-keyscan -H $(wheelsHost) >> .ssh/known_hosts
|
- task: UsePythonVersion@0
|
||||||
chmod 600 .ssh/*
|
displayName: "Use Python 3.7"
|
||||||
displayName: 'Install ssh key'
|
inputs:
|
||||||
- script: sudo docker pull homeassistant/$(buildArch)-wheels:$(versionWheels)
|
versionSpec: "3.7"
|
||||||
displayName: 'Install wheels builder'
|
- script: |
|
||||||
- script: |
|
setup_version="$(python setup.py -V)"
|
||||||
sudo docker run --rm -v $(pwd):/data:ro -v $(pwd)/.ssh:/root/.ssh:rw \
|
branch_version="$(Build.SourceBranchName)"
|
||||||
homeassistant/$(buildArch)-wheels:$(versionWheels) \
|
|
||||||
--apk "build-base;libffi-dev;openssl-dev" \
|
|
||||||
--index $(wheelsIndex) \
|
|
||||||
--requirement requirements.txt \
|
|
||||||
--upload rsync \
|
|
||||||
--remote wheels@$(wheelsHost):/opt/wheels
|
|
||||||
displayName: 'Run wheels build'
|
|
||||||
|
|
||||||
- stage: 'Deploy'
|
if [ "${branch_version}" == "dev" ]; then
|
||||||
jobs:
|
exit 0
|
||||||
- job: 'VersionValidate'
|
elif [ "${setup_version}" != "${branch_version}" ]; then
|
||||||
condition: or(startsWith(variables['Build.SourceBranch'], 'refs/tags'), eq(variables['Build.SourceBranchName'], 'dev'))
|
echo "Version of tag ${branch_version} don't match with ${setup_version}!"
|
||||||
pool:
|
exit 1
|
||||||
vmImage: 'ubuntu-latest'
|
fi
|
||||||
steps:
|
displayName: "Check version of branch/tag"
|
||||||
- task: UsePythonVersion@0
|
- job: "Release"
|
||||||
displayName: 'Use Python 3.7'
|
dependsOn:
|
||||||
inputs:
|
- "VersionValidate"
|
||||||
versionSpec: '3.7'
|
pool:
|
||||||
- script: |
|
vmImage: "ubuntu-latest"
|
||||||
setup_version="$(python setup.py -V)"
|
steps:
|
||||||
branch_version="$(Build.SourceBranchName)"
|
- script: sudo docker login -u $(dockerUser) -p $(dockerPassword)
|
||||||
|
displayName: "Docker hub login"
|
||||||
if [ "${branch_version}" == "dev" ]; then
|
- script: sudo docker pull homeassistant/amd64-builder:$(versionBuilder)
|
||||||
exit 0
|
displayName: "Install Builder"
|
||||||
elif [ "${setup_version}" != "${branch_version}" ]; then
|
- script: |
|
||||||
echo "Version of tag ${branch_version} don't match with ${setup_version}!"
|
sudo docker run --rm --privileged \
|
||||||
exit 1
|
-v ~/.docker:/root/.docker \
|
||||||
fi
|
-v /run/docker.sock:/run/docker.sock:rw -v $(pwd):/data:ro \
|
||||||
displayName: 'Check version of branch/tag'
|
homeassistant/amd64-builder:$(versionBuilder) \
|
||||||
- job: 'Release'
|
--supervisor $(basePythonTag) --version $(Build.SourceBranchName) \
|
||||||
dependsOn:
|
--all -t /data --docker-hub homeassistant
|
||||||
- 'VersionValidate'
|
displayName: "Build Release"
|
||||||
pool:
|
|
||||||
vmImage: 'ubuntu-latest'
|
|
||||||
steps:
|
|
||||||
- script: sudo docker login -u $(dockerUser) -p $(dockerPassword)
|
|
||||||
displayName: 'Docker hub login'
|
|
||||||
- script: sudo docker pull homeassistant/amd64-builder:$(versionBuilder)
|
|
||||||
displayName: 'Install Builder'
|
|
||||||
- script: |
|
|
||||||
sudo docker run --rm --privileged \
|
|
||||||
-v ~/.docker:/root/.docker \
|
|
||||||
-v /run/docker.sock:/run/docker.sock:rw -v $(pwd):/data:ro \
|
|
||||||
homeassistant/amd64-builder:$(versionBuilder) \
|
|
||||||
--supervisor $(basePythonTag) --version $(Build.SourceBranchName) \
|
|
||||||
--all -t /data --docker-hub homeassistant
|
|
||||||
displayName: 'Build Release'
|
|
||||||
|
@@ -6,12 +6,13 @@ import sys
|
|||||||
|
|
||||||
from hassio import bootstrap
|
from hassio import bootstrap
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER: logging.Logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def initialize_event_loop():
|
def initialize_event_loop():
|
||||||
"""Attempt to use uvloop."""
|
"""Attempt to use uvloop."""
|
||||||
try:
|
try:
|
||||||
|
# pylint: disable=import-outside-toplevel
|
||||||
import uvloop
|
import uvloop
|
||||||
|
|
||||||
uvloop.install()
|
uvloop.install()
|
||||||
|
@@ -10,14 +10,16 @@ from ..coresys import CoreSys, CoreSysAttributes
|
|||||||
from ..exceptions import (
|
from ..exceptions import (
|
||||||
AddonsError,
|
AddonsError,
|
||||||
AddonsNotSupportedError,
|
AddonsNotSupportedError,
|
||||||
|
CoreDNSError,
|
||||||
DockerAPIError,
|
DockerAPIError,
|
||||||
|
HomeAssistantAPIError,
|
||||||
HostAppArmorError,
|
HostAppArmorError,
|
||||||
)
|
)
|
||||||
from ..store.addon import AddonStore
|
from ..store.addon import AddonStore
|
||||||
from .addon import Addon
|
from .addon import Addon
|
||||||
from .data import AddonsData
|
from .data import AddonsData
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER: logging.Logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
AnyAddon = Union[Addon, AddonStore]
|
AnyAddon = Union[Addon, AddonStore]
|
||||||
|
|
||||||
@@ -73,6 +75,9 @@ class AddonManager(CoreSysAttributes):
|
|||||||
if tasks:
|
if tasks:
|
||||||
await asyncio.wait(tasks)
|
await asyncio.wait(tasks)
|
||||||
|
|
||||||
|
# Sync DNS
|
||||||
|
await self.sync_dns()
|
||||||
|
|
||||||
async def boot(self, stage: str) -> None:
|
async def boot(self, stage: str) -> None:
|
||||||
"""Boot add-ons with mode auto."""
|
"""Boot add-ons with mode auto."""
|
||||||
tasks = []
|
tasks = []
|
||||||
@@ -155,8 +160,15 @@ class AddonManager(CoreSysAttributes):
|
|||||||
with suppress(HostAppArmorError):
|
with suppress(HostAppArmorError):
|
||||||
await addon.uninstall_apparmor()
|
await addon.uninstall_apparmor()
|
||||||
|
|
||||||
|
# Cleanup Ingress panel from sidebar
|
||||||
|
if addon.ingress_panel:
|
||||||
|
addon.ingress_panel = False
|
||||||
|
with suppress(HomeAssistantAPIError):
|
||||||
|
await self.sys_ingress.update_hass_panel(addon)
|
||||||
|
|
||||||
# Cleanup internal data
|
# Cleanup internal data
|
||||||
addon.remove_discovery()
|
addon.remove_discovery()
|
||||||
|
|
||||||
self.data.uninstall(addon)
|
self.data.uninstall(addon)
|
||||||
self.local.pop(slug)
|
self.local.pop(slug)
|
||||||
|
|
||||||
@@ -233,7 +245,7 @@ class AddonManager(CoreSysAttributes):
|
|||||||
raise AddonsError() from None
|
raise AddonsError() from None
|
||||||
else:
|
else:
|
||||||
self.data.update(store)
|
self.data.update(store)
|
||||||
_LOGGER.info("Add-on '%s' successfully rebuilded", slug)
|
_LOGGER.info("Add-on '%s' successfully rebuilt", slug)
|
||||||
|
|
||||||
# restore state
|
# restore state
|
||||||
if last_state == STATE_STARTED:
|
if last_state == STATE_STARTED:
|
||||||
@@ -242,10 +254,10 @@ class AddonManager(CoreSysAttributes):
|
|||||||
async def restore(self, slug: str, tar_file: tarfile.TarFile) -> None:
|
async def restore(self, slug: str, tar_file: tarfile.TarFile) -> None:
|
||||||
"""Restore state of an add-on."""
|
"""Restore state of an add-on."""
|
||||||
if slug not in self.local:
|
if slug not in self.local:
|
||||||
_LOGGER.debug("Add-on %s is not local available for restore")
|
_LOGGER.debug("Add-on %s is not local available for restore", slug)
|
||||||
addon = Addon(self.coresys, slug)
|
addon = Addon(self.coresys, slug)
|
||||||
else:
|
else:
|
||||||
_LOGGER.debug("Add-on %s is local available for restore")
|
_LOGGER.debug("Add-on %s is local available for restore", slug)
|
||||||
addon = self.local[slug]
|
addon = self.local[slug]
|
||||||
|
|
||||||
await addon.restore(tar_file)
|
await addon.restore(tar_file)
|
||||||
@@ -273,6 +285,9 @@ class AddonManager(CoreSysAttributes):
|
|||||||
|
|
||||||
for addon in needs_repair:
|
for addon in needs_repair:
|
||||||
_LOGGER.info("Start repair for add-on: %s", addon.slug)
|
_LOGGER.info("Start repair for add-on: %s", addon.slug)
|
||||||
|
await self.sys_run_in_executor(
|
||||||
|
self.sys_docker.network.stale_cleanup, addon.instance.name
|
||||||
|
)
|
||||||
|
|
||||||
with suppress(DockerAPIError, KeyError):
|
with suppress(DockerAPIError, KeyError):
|
||||||
# Need pull a image again
|
# Need pull a image again
|
||||||
@@ -281,7 +296,7 @@ class AddonManager(CoreSysAttributes):
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
# Need local lookup
|
# Need local lookup
|
||||||
elif addon.need_build and not addon.is_detached:
|
if addon.need_build and not addon.is_detached:
|
||||||
store = self.store[addon.slug]
|
store = self.store[addon.slug]
|
||||||
# If this add-on is available for rebuild
|
# If this add-on is available for rebuild
|
||||||
if addon.version == store.version:
|
if addon.version == store.version:
|
||||||
@@ -291,3 +306,17 @@ class AddonManager(CoreSysAttributes):
|
|||||||
_LOGGER.error("Can't repair %s", addon.slug)
|
_LOGGER.error("Can't repair %s", addon.slug)
|
||||||
with suppress(AddonsError):
|
with suppress(AddonsError):
|
||||||
await self.uninstall(addon.slug)
|
await self.uninstall(addon.slug)
|
||||||
|
|
||||||
|
async def sync_dns(self) -> None:
|
||||||
|
"""Sync add-ons DNS names."""
|
||||||
|
# Update hosts
|
||||||
|
for addon in self.installed:
|
||||||
|
if not await addon.instance.is_running():
|
||||||
|
continue
|
||||||
|
self.sys_dns.add_host(
|
||||||
|
ipv4=addon.ip_address, names=[addon.hostname], write=False
|
||||||
|
)
|
||||||
|
|
||||||
|
# Write hosts files
|
||||||
|
with suppress(CoreDNSError):
|
||||||
|
self.sys_dns.write_hosts()
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
"""Init file for Hass.io add-ons."""
|
"""Init file for Hass.io add-ons."""
|
||||||
from contextlib import suppress
|
from contextlib import suppress
|
||||||
from copy import deepcopy
|
from copy import deepcopy
|
||||||
from ipaddress import IPv4Address, ip_address
|
from ipaddress import IPv4Address
|
||||||
import logging
|
import logging
|
||||||
from pathlib import Path, PurePath
|
from pathlib import Path, PurePath
|
||||||
import re
|
import re
|
||||||
@@ -36,7 +36,6 @@ from ..const import (
|
|||||||
ATTR_UUID,
|
ATTR_UUID,
|
||||||
ATTR_VERSION,
|
ATTR_VERSION,
|
||||||
DNS_SUFFIX,
|
DNS_SUFFIX,
|
||||||
STATE_NONE,
|
|
||||||
STATE_STARTED,
|
STATE_STARTED,
|
||||||
STATE_STOPPED,
|
STATE_STOPPED,
|
||||||
)
|
)
|
||||||
@@ -52,11 +51,12 @@ from ..exceptions import (
|
|||||||
)
|
)
|
||||||
from ..utils.apparmor import adjust_profile
|
from ..utils.apparmor import adjust_profile
|
||||||
from ..utils.json import read_json_file, write_json_file
|
from ..utils.json import read_json_file, write_json_file
|
||||||
|
from ..utils.tar import exclude_filter, secure_path
|
||||||
from .model import AddonModel, Data
|
from .model import AddonModel, Data
|
||||||
from .utils import remove_data
|
from .utils import remove_data
|
||||||
from .validate import SCHEMA_ADDON_SNAPSHOT, validate_options
|
from .validate import SCHEMA_ADDON_SNAPSHOT, validate_options
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER: logging.Logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
RE_WEBUI = re.compile(
|
RE_WEBUI = re.compile(
|
||||||
r"^(?:(?P<s_prefix>https?)|\[PROTO:(?P<t_proto>\w+)\])"
|
r"^(?:(?P<s_prefix>https?)|\[PROTO:(?P<t_proto>\w+)\])"
|
||||||
@@ -81,8 +81,6 @@ class Addon(AddonModel):
|
|||||||
@property
|
@property
|
||||||
def ip_address(self) -> IPv4Address:
|
def ip_address(self) -> IPv4Address:
|
||||||
"""Return IP of Add-on instance."""
|
"""Return IP of Add-on instance."""
|
||||||
if not self.is_installed:
|
|
||||||
return ip_address("0.0.0.0")
|
|
||||||
return self.instance.ip_address
|
return self.instance.ip_address
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@@ -348,13 +346,16 @@ class Addon(AddonModel):
|
|||||||
"""Save data of add-on."""
|
"""Save data of add-on."""
|
||||||
self.sys_addons.data.save_data()
|
self.sys_addons.data.save_data()
|
||||||
|
|
||||||
def write_options(self):
|
async def write_options(self):
|
||||||
"""Return True if add-on options is written to data."""
|
"""Return True if add-on options is written to data."""
|
||||||
schema = self.schema
|
schema = self.schema
|
||||||
options = self.options
|
options = self.options
|
||||||
|
|
||||||
|
# Update secrets for validation
|
||||||
|
await self.sys_secrets.reload()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
schema(options)
|
options = schema(options)
|
||||||
write_json_file(self.path_options, options)
|
write_json_file(self.path_options, options)
|
||||||
except vol.Invalid as ex:
|
except vol.Invalid as ex:
|
||||||
_LOGGER.error(
|
_LOGGER.error(
|
||||||
@@ -441,7 +442,9 @@ class Addon(AddonModel):
|
|||||||
options = {**self.persist[ATTR_OPTIONS], **default_options}
|
options = {**self.persist[ATTR_OPTIONS], **default_options}
|
||||||
|
|
||||||
# create voluptuous
|
# create voluptuous
|
||||||
new_schema = vol.Schema(vol.All(dict, validate_options(new_raw_schema)))
|
new_schema = vol.Schema(
|
||||||
|
vol.All(dict, validate_options(self.coresys, new_raw_schema))
|
||||||
|
)
|
||||||
|
|
||||||
# validate
|
# validate
|
||||||
try:
|
try:
|
||||||
@@ -453,9 +456,6 @@ class Addon(AddonModel):
|
|||||||
|
|
||||||
async def state(self) -> str:
|
async def state(self) -> str:
|
||||||
"""Return running state of add-on."""
|
"""Return running state of add-on."""
|
||||||
if not self.is_installed:
|
|
||||||
return STATE_NONE
|
|
||||||
|
|
||||||
if await self.instance.is_running():
|
if await self.instance.is_running():
|
||||||
return STATE_STARTED
|
return STATE_STARTED
|
||||||
return STATE_STOPPED
|
return STATE_STOPPED
|
||||||
@@ -471,12 +471,13 @@ class Addon(AddonModel):
|
|||||||
self.save_persist()
|
self.save_persist()
|
||||||
|
|
||||||
# Options
|
# Options
|
||||||
self.write_options()
|
await self.write_options()
|
||||||
|
|
||||||
# Sound
|
# Sound
|
||||||
if self.with_audio:
|
if self.with_audio:
|
||||||
self.write_asound()
|
self.write_asound()
|
||||||
|
|
||||||
|
# Start Add-on
|
||||||
try:
|
try:
|
||||||
await self.instance.run()
|
await self.instance.run()
|
||||||
except DockerAPIError:
|
except DockerAPIError:
|
||||||
@@ -525,7 +526,7 @@ class Addon(AddonModel):
|
|||||||
|
|
||||||
async def snapshot(self, tar_file: tarfile.TarFile) -> None:
|
async def snapshot(self, tar_file: tarfile.TarFile) -> None:
|
||||||
"""Snapshot state of an add-on."""
|
"""Snapshot state of an add-on."""
|
||||||
with TemporaryDirectory(dir=str(self.sys_config.path_tmp)) as temp:
|
with TemporaryDirectory(dir=self.sys_config.path_tmp) as temp:
|
||||||
# store local image
|
# store local image
|
||||||
if self.need_build:
|
if self.need_build:
|
||||||
try:
|
try:
|
||||||
@@ -560,8 +561,15 @@ class Addon(AddonModel):
|
|||||||
def _write_tarfile():
|
def _write_tarfile():
|
||||||
"""Write tar inside loop."""
|
"""Write tar inside loop."""
|
||||||
with tar_file as snapshot:
|
with tar_file as snapshot:
|
||||||
|
# Snapshot system
|
||||||
snapshot.add(temp, arcname=".")
|
snapshot.add(temp, arcname=".")
|
||||||
snapshot.add(self.path_data, arcname="data")
|
|
||||||
|
# Snapshot data
|
||||||
|
snapshot.add(
|
||||||
|
self.path_data,
|
||||||
|
arcname="data",
|
||||||
|
filter=exclude_filter(self.snapshot_exclude),
|
||||||
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
_LOGGER.info("Build snapshot for add-on %s", self.slug)
|
_LOGGER.info("Build snapshot for add-on %s", self.slug)
|
||||||
@@ -574,12 +582,12 @@ class Addon(AddonModel):
|
|||||||
|
|
||||||
async def restore(self, tar_file: tarfile.TarFile) -> None:
|
async def restore(self, tar_file: tarfile.TarFile) -> None:
|
||||||
"""Restore state of an add-on."""
|
"""Restore state of an add-on."""
|
||||||
with TemporaryDirectory(dir=str(self.sys_config.path_tmp)) as temp:
|
with TemporaryDirectory(dir=self.sys_config.path_tmp) as temp:
|
||||||
# extract snapshot
|
# extract snapshot
|
||||||
def _extract_tarfile():
|
def _extract_tarfile():
|
||||||
"""Extract tar snapshot."""
|
"""Extract tar snapshot."""
|
||||||
with tar_file as snapshot:
|
with tar_file as snapshot:
|
||||||
snapshot.extractall(path=Path(temp))
|
snapshot.extractall(path=Path(temp), members=secure_path(snapshot))
|
||||||
|
|
||||||
try:
|
try:
|
||||||
await self.sys_run_in_executor(_extract_tarfile)
|
await self.sys_run_in_executor(_extract_tarfile)
|
||||||
@@ -640,7 +648,7 @@ class Addon(AddonModel):
|
|||||||
# Restore data
|
# Restore data
|
||||||
def _restore_data():
|
def _restore_data():
|
||||||
"""Restore data."""
|
"""Restore data."""
|
||||||
shutil.copytree(str(Path(temp, "data")), str(self.path_data))
|
shutil.copytree(Path(temp, "data"), self.path_data)
|
||||||
|
|
||||||
_LOGGER.info("Restore data for addon %s", self.slug)
|
_LOGGER.info("Restore data for addon %s", self.slug)
|
||||||
if self.path_data.is_dir():
|
if self.path_data.is_dir():
|
||||||
|
@@ -17,7 +17,7 @@ from ..store.addon import AddonStore
|
|||||||
from .addon import Addon
|
from .addon import Addon
|
||||||
from .validate import SCHEMA_ADDONS_FILE
|
from .validate import SCHEMA_ADDONS_FILE
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER: logging.Logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
Config = Dict[str, Any]
|
Config = Dict[str, Any]
|
||||||
|
|
||||||
|
@@ -1,11 +1,12 @@
|
|||||||
"""Init file for Hass.io add-ons."""
|
"""Init file for Hass.io add-ons."""
|
||||||
from distutils.version import StrictVersion
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any, Awaitable, Dict, List, Optional
|
from typing import Any, Awaitable, Dict, List, Optional
|
||||||
|
|
||||||
|
from packaging import version as pkg_version
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
from ..const import (
|
from ..const import (
|
||||||
|
ATTR_ADVANCED,
|
||||||
ATTR_APPARMOR,
|
ATTR_APPARMOR,
|
||||||
ATTR_ARCH,
|
ATTR_ARCH,
|
||||||
ATTR_AUDIO,
|
ATTR_AUDIO,
|
||||||
@@ -47,6 +48,7 @@ from ..const import (
|
|||||||
ATTR_SCHEMA,
|
ATTR_SCHEMA,
|
||||||
ATTR_SERVICES,
|
ATTR_SERVICES,
|
||||||
ATTR_SLUG,
|
ATTR_SLUG,
|
||||||
|
ATTR_SNAPSHOT_EXCLUDE,
|
||||||
ATTR_STARTUP,
|
ATTR_STARTUP,
|
||||||
ATTR_STDIN,
|
ATTR_STDIN,
|
||||||
ATTR_TIMEOUT,
|
ATTR_TIMEOUT,
|
||||||
@@ -60,7 +62,7 @@ from ..const import (
|
|||||||
SECURITY_PROFILE,
|
SECURITY_PROFILE,
|
||||||
)
|
)
|
||||||
from ..coresys import CoreSysAttributes
|
from ..coresys import CoreSysAttributes
|
||||||
from .validate import RE_SERVICE, RE_VOLUME, validate_options
|
from .validate import RE_SERVICE, RE_VOLUME, validate_options, schema_ui_options
|
||||||
|
|
||||||
Data = Dict[str, Any]
|
Data = Dict[str, Any]
|
||||||
|
|
||||||
@@ -188,6 +190,11 @@ class AddonModel(CoreSysAttributes):
|
|||||||
"""Return startup type of add-on."""
|
"""Return startup type of add-on."""
|
||||||
return self.data.get(ATTR_STARTUP)
|
return self.data.get(ATTR_STARTUP)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def advanced(self) -> bool:
|
||||||
|
"""Return advanced mode of add-on."""
|
||||||
|
return self.data[ATTR_ADVANCED]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def services_role(self) -> Dict[str, str]:
|
def services_role(self) -> Dict[str, str]:
|
||||||
"""Return dict of services with rights."""
|
"""Return dict of services with rights."""
|
||||||
@@ -324,6 +331,11 @@ class AddonModel(CoreSysAttributes):
|
|||||||
"""Return Hass.io role for API."""
|
"""Return Hass.io role for API."""
|
||||||
return self.data[ATTR_HASSIO_ROLE]
|
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, [])
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def with_stdin(self) -> bool:
|
def with_stdin(self) -> bool:
|
||||||
"""Return True if the add-on access use stdin input."""
|
"""Return True if the add-on access use stdin input."""
|
||||||
@@ -399,6 +411,11 @@ class AddonModel(CoreSysAttributes):
|
|||||||
"""Return True if a changelog exists."""
|
"""Return True if a changelog exists."""
|
||||||
return self.path_changelog.exists()
|
return self.path_changelog.exists()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def with_documentation(self) -> bool:
|
||||||
|
"""Return True if a documentation exists."""
|
||||||
|
return self.path_documentation.exists()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def supported_arch(self) -> List[str]:
|
def supported_arch(self) -> List[str]:
|
||||||
"""Return list of supported arch."""
|
"""Return list of supported arch."""
|
||||||
@@ -449,6 +466,11 @@ class AddonModel(CoreSysAttributes):
|
|||||||
"""Return path to add-on changelog."""
|
"""Return path to add-on changelog."""
|
||||||
return Path(self.path_location, "CHANGELOG.md")
|
return Path(self.path_location, "CHANGELOG.md")
|
||||||
|
|
||||||
|
@property
|
||||||
|
def path_documentation(self) -> Path:
|
||||||
|
"""Return path to add-on changelog."""
|
||||||
|
return Path(self.path_location, "DOCS.md")
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def path_apparmor(self) -> Path:
|
def path_apparmor(self) -> Path:
|
||||||
"""Return path to custom AppArmor profile."""
|
"""Return path to custom AppArmor profile."""
|
||||||
@@ -461,7 +483,16 @@ class AddonModel(CoreSysAttributes):
|
|||||||
|
|
||||||
if isinstance(raw_schema, bool):
|
if isinstance(raw_schema, bool):
|
||||||
return vol.Schema(dict)
|
return vol.Schema(dict)
|
||||||
return vol.Schema(vol.All(dict, validate_options(raw_schema)))
|
return vol.Schema(vol.All(dict, validate_options(self.coresys, raw_schema)))
|
||||||
|
|
||||||
|
@property
|
||||||
|
def schema_ui(self) -> Optional[List[Dict[str, Any]]]:
|
||||||
|
"""Create a UI schema for add-on options."""
|
||||||
|
raw_schema = self.data[ATTR_SCHEMA]
|
||||||
|
|
||||||
|
if isinstance(raw_schema, bool):
|
||||||
|
return None
|
||||||
|
return schema_ui_options(raw_schema)
|
||||||
|
|
||||||
def __eq__(self, other):
|
def __eq__(self, other):
|
||||||
"""Compaired add-on objects."""
|
"""Compaired add-on objects."""
|
||||||
@@ -482,7 +513,9 @@ class AddonModel(CoreSysAttributes):
|
|||||||
|
|
||||||
# Home Assistant
|
# Home Assistant
|
||||||
version = config.get(ATTR_HOMEASSISTANT) or self.sys_homeassistant.version
|
version = config.get(ATTR_HOMEASSISTANT) or self.sys_homeassistant.version
|
||||||
if StrictVersion(self.sys_homeassistant.version) < StrictVersion(version):
|
if pkg_version.parse(self.sys_homeassistant.version) < pkg_version.parse(
|
||||||
|
version
|
||||||
|
):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
@@ -22,7 +22,7 @@ from ..const import (
|
|||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from .model import AddonModel
|
from .model import AddonModel
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER: logging.Logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def rating_security(addon: AddonModel) -> int:
|
def rating_security(addon: AddonModel) -> int:
|
||||||
@@ -39,8 +39,10 @@ def rating_security(addon: AddonModel) -> int:
|
|||||||
elif addon.apparmor == SECURITY_PROFILE:
|
elif addon.apparmor == SECURITY_PROFILE:
|
||||||
rating += 1
|
rating += 1
|
||||||
|
|
||||||
# Home Assistant Login
|
# Home Assistant Login & Ingress
|
||||||
if addon.access_auth_api:
|
if addon.with_ingress:
|
||||||
|
rating += 2
|
||||||
|
elif addon.access_auth_api:
|
||||||
rating += 1
|
rating += 1
|
||||||
|
|
||||||
# Privileged options
|
# Privileged options
|
||||||
|
@@ -2,6 +2,7 @@
|
|||||||
import logging
|
import logging
|
||||||
import re
|
import re
|
||||||
import secrets
|
import secrets
|
||||||
|
from typing import Any, Dict, List
|
||||||
import uuid
|
import uuid
|
||||||
|
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
@@ -9,6 +10,7 @@ import voluptuous as vol
|
|||||||
from ..const import (
|
from ..const import (
|
||||||
ARCH_ALL,
|
ARCH_ALL,
|
||||||
ATTR_ACCESS_TOKEN,
|
ATTR_ACCESS_TOKEN,
|
||||||
|
ATTR_ADVANCED,
|
||||||
ATTR_APPARMOR,
|
ATTR_APPARMOR,
|
||||||
ATTR_ARCH,
|
ATTR_ARCH,
|
||||||
ATTR_ARGS,
|
ATTR_ARGS,
|
||||||
@@ -61,6 +63,7 @@ from ..const import (
|
|||||||
ATTR_SCHEMA,
|
ATTR_SCHEMA,
|
||||||
ATTR_SERVICES,
|
ATTR_SERVICES,
|
||||||
ATTR_SLUG,
|
ATTR_SLUG,
|
||||||
|
ATTR_SNAPSHOT_EXCLUDE,
|
||||||
ATTR_SQUASH,
|
ATTR_SQUASH,
|
||||||
ATTR_STARTUP,
|
ATTR_STARTUP,
|
||||||
ATTR_STATE,
|
ATTR_STATE,
|
||||||
@@ -85,40 +88,57 @@ from ..const import (
|
|||||||
STATE_STARTED,
|
STATE_STARTED,
|
||||||
STATE_STOPPED,
|
STATE_STOPPED,
|
||||||
)
|
)
|
||||||
|
from ..coresys import CoreSys
|
||||||
from ..discovery.validate import valid_discovery_service
|
from ..discovery.validate import valid_discovery_service
|
||||||
from ..validate import (
|
from ..validate import (
|
||||||
ALSA_DEVICE,
|
|
||||||
DOCKER_PORTS,
|
DOCKER_PORTS,
|
||||||
DOCKER_PORTS_DESCRIPTION,
|
DOCKER_PORTS_DESCRIPTION,
|
||||||
NETWORK_PORT,
|
alsa_device,
|
||||||
TOKEN,
|
network_port,
|
||||||
UUID_MATCH,
|
token,
|
||||||
|
uuid_match,
|
||||||
)
|
)
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER: logging.Logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
RE_VOLUME = re.compile(r"^(config|ssl|addons|backup|share)(?::(rw|ro))?$")
|
RE_VOLUME = re.compile(r"^(config|ssl|addons|backup|share)(?::(rw|ro))?$")
|
||||||
RE_SERVICE = re.compile(r"^(?P<service>mqtt):(?P<rights>provide|want|need)$")
|
RE_SERVICE = re.compile(r"^(?P<service>mqtt|mysql):(?P<rights>provide|want|need)$")
|
||||||
|
|
||||||
V_STR = "str"
|
V_STR = "str"
|
||||||
V_INT = "int"
|
V_INT = "int"
|
||||||
V_FLOAT = "float"
|
V_FLOAT = "float"
|
||||||
V_BOOL = "bool"
|
V_BOOL = "bool"
|
||||||
|
V_PASSWORD = "password"
|
||||||
V_EMAIL = "email"
|
V_EMAIL = "email"
|
||||||
V_URL = "url"
|
V_URL = "url"
|
||||||
V_PORT = "port"
|
V_PORT = "port"
|
||||||
V_MATCH = "match"
|
V_MATCH = "match"
|
||||||
|
V_LIST = "list"
|
||||||
|
|
||||||
RE_SCHEMA_ELEMENT = re.compile(
|
RE_SCHEMA_ELEMENT = re.compile(
|
||||||
r"^(?:"
|
r"^(?:"
|
||||||
r"|str|bool|email|url|port"
|
r"|bool|email|url|port"
|
||||||
|
r"|str(?:\((?P<s_min>\d+)?,(?P<s_max>\d+)?\))?"
|
||||||
|
r"|password(?:\((?P<p_min>\d+)?,(?P<p_max>\d+)?\))?"
|
||||||
r"|int(?:\((?P<i_min>\d+)?,(?P<i_max>\d+)?\))?"
|
r"|int(?:\((?P<i_min>\d+)?,(?P<i_max>\d+)?\))?"
|
||||||
r"|float(?:\((?P<f_min>[\d\.]+)?,(?P<f_max>[\d\.]+)?\))?"
|
r"|float(?:\((?P<f_min>[\d\.]+)?,(?P<f_max>[\d\.]+)?\))?"
|
||||||
r"|match\((?P<match>.*)\)"
|
r"|match\((?P<match>.*)\)"
|
||||||
|
r"|list\((?P<list>.+)\)"
|
||||||
r")\??$"
|
r")\??$"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
_SCHEMA_LENGTH_PARTS = (
|
||||||
|
"i_min",
|
||||||
|
"i_max",
|
||||||
|
"f_min",
|
||||||
|
"f_max",
|
||||||
|
"s_min",
|
||||||
|
"s_max",
|
||||||
|
"p_min",
|
||||||
|
"p_max",
|
||||||
|
)
|
||||||
|
|
||||||
RE_DOCKER_IMAGE = re.compile(r"^([a-zA-Z\-\.:\d{}]+/)*?([\-\w{}]+)/([\-\w{}]+)$")
|
RE_DOCKER_IMAGE = re.compile(r"^([a-zA-Z\-\.:\d{}]+/)*?([\-\w{}]+)/([\-\w{}]+)$")
|
||||||
RE_DOCKER_IMAGE_BUILD = re.compile(
|
RE_DOCKER_IMAGE_BUILD = re.compile(
|
||||||
r"^([a-zA-Z\-\.:\d{}]+/)*?([\-\w{}]+)/([\-\w{}]+)(:[\.\-\w{}]+)?$"
|
r"^([a-zA-Z\-\.:\d{}]+/)*?([\-\w{}]+)/([\-\w{}]+)(:[\.\-\w{}]+)?$"
|
||||||
@@ -130,18 +150,18 @@ SCHEMA_ELEMENT = vol.Match(RE_SCHEMA_ELEMENT)
|
|||||||
MACHINE_ALL = [
|
MACHINE_ALL = [
|
||||||
"intel-nuc",
|
"intel-nuc",
|
||||||
"odroid-c2",
|
"odroid-c2",
|
||||||
|
"odroid-n2",
|
||||||
"odroid-xu",
|
"odroid-xu",
|
||||||
"orangepi-prime",
|
|
||||||
"qemux86",
|
|
||||||
"qemux86-64",
|
|
||||||
"qemuarm",
|
|
||||||
"qemuarm-64",
|
"qemuarm-64",
|
||||||
|
"qemuarm",
|
||||||
|
"qemux86-64",
|
||||||
|
"qemux86",
|
||||||
"raspberrypi",
|
"raspberrypi",
|
||||||
"raspberrypi2",
|
"raspberrypi2",
|
||||||
"raspberrypi3",
|
|
||||||
"raspberrypi3-64",
|
"raspberrypi3-64",
|
||||||
"raspberrypi4",
|
"raspberrypi3",
|
||||||
"raspberrypi4-64",
|
"raspberrypi4-64",
|
||||||
|
"raspberrypi4",
|
||||||
"tinker",
|
"tinker",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -167,6 +187,7 @@ SCHEMA_ADDON_CONFIG = vol.Schema(
|
|||||||
vol.Optional(ATTR_URL): vol.Url(),
|
vol.Optional(ATTR_URL): vol.Url(),
|
||||||
vol.Required(ATTR_STARTUP): vol.All(_simple_startup, vol.In(STARTUP_ALL)),
|
vol.Required(ATTR_STARTUP): vol.All(_simple_startup, vol.In(STARTUP_ALL)),
|
||||||
vol.Required(ATTR_BOOT): vol.In([BOOT_AUTO, BOOT_MANUAL]),
|
vol.Required(ATTR_BOOT): vol.In([BOOT_AUTO, BOOT_MANUAL]),
|
||||||
|
vol.Optional(ATTR_ADVANCED, default=False): vol.Boolean(),
|
||||||
vol.Optional(ATTR_PORTS): DOCKER_PORTS,
|
vol.Optional(ATTR_PORTS): DOCKER_PORTS,
|
||||||
vol.Optional(ATTR_PORTS_DESCRIPTION): DOCKER_PORTS_DESCRIPTION,
|
vol.Optional(ATTR_PORTS_DESCRIPTION): DOCKER_PORTS_DESCRIPTION,
|
||||||
vol.Optional(ATTR_WEBUI): vol.Match(
|
vol.Optional(ATTR_WEBUI): vol.Match(
|
||||||
@@ -174,7 +195,7 @@ SCHEMA_ADDON_CONFIG = vol.Schema(
|
|||||||
),
|
),
|
||||||
vol.Optional(ATTR_INGRESS, default=False): vol.Boolean(),
|
vol.Optional(ATTR_INGRESS, default=False): vol.Boolean(),
|
||||||
vol.Optional(ATTR_INGRESS_PORT, default=8099): vol.Any(
|
vol.Optional(ATTR_INGRESS_PORT, default=8099): vol.Any(
|
||||||
NETWORK_PORT, vol.Equal(0)
|
network_port, vol.Equal(0)
|
||||||
),
|
),
|
||||||
vol.Optional(ATTR_INGRESS_ENTRY): vol.Coerce(str),
|
vol.Optional(ATTR_INGRESS_ENTRY): vol.Coerce(str),
|
||||||
vol.Optional(ATTR_PANEL_ICON, default="mdi:puzzle"): vol.Coerce(str),
|
vol.Optional(ATTR_PANEL_ICON, default="mdi:puzzle"): vol.Coerce(str),
|
||||||
@@ -207,6 +228,7 @@ SCHEMA_ADDON_CONFIG = vol.Schema(
|
|||||||
vol.Optional(ATTR_AUTH_API, default=False): vol.Boolean(),
|
vol.Optional(ATTR_AUTH_API, default=False): vol.Boolean(),
|
||||||
vol.Optional(ATTR_SERVICES): [vol.Match(RE_SERVICE)],
|
vol.Optional(ATTR_SERVICES): [vol.Match(RE_SERVICE)],
|
||||||
vol.Optional(ATTR_DISCOVERY): [valid_discovery_service],
|
vol.Optional(ATTR_DISCOVERY): [valid_discovery_service],
|
||||||
|
vol.Optional(ATTR_SNAPSHOT_EXCLUDE): [vol.Coerce(str)],
|
||||||
vol.Required(ATTR_OPTIONS): dict,
|
vol.Required(ATTR_OPTIONS): dict,
|
||||||
vol.Required(ATTR_SCHEMA): vol.Any(
|
vol.Required(ATTR_SCHEMA): vol.Any(
|
||||||
vol.Schema(
|
vol.Schema(
|
||||||
@@ -260,8 +282,8 @@ SCHEMA_ADDON_USER = vol.Schema(
|
|||||||
{
|
{
|
||||||
vol.Required(ATTR_VERSION): vol.Coerce(str),
|
vol.Required(ATTR_VERSION): vol.Coerce(str),
|
||||||
vol.Optional(ATTR_IMAGE): vol.Coerce(str),
|
vol.Optional(ATTR_IMAGE): vol.Coerce(str),
|
||||||
vol.Optional(ATTR_UUID, default=lambda: uuid.uuid4().hex): UUID_MATCH,
|
vol.Optional(ATTR_UUID, default=lambda: uuid.uuid4().hex): uuid_match,
|
||||||
vol.Optional(ATTR_ACCESS_TOKEN): TOKEN,
|
vol.Optional(ATTR_ACCESS_TOKEN): token,
|
||||||
vol.Optional(ATTR_INGRESS_TOKEN, default=secrets.token_urlsafe): vol.Coerce(
|
vol.Optional(ATTR_INGRESS_TOKEN, default=secrets.token_urlsafe): vol.Coerce(
|
||||||
str
|
str
|
||||||
),
|
),
|
||||||
@@ -269,8 +291,8 @@ SCHEMA_ADDON_USER = vol.Schema(
|
|||||||
vol.Optional(ATTR_AUTO_UPDATE, default=False): vol.Boolean(),
|
vol.Optional(ATTR_AUTO_UPDATE, default=False): vol.Boolean(),
|
||||||
vol.Optional(ATTR_BOOT): vol.In([BOOT_AUTO, BOOT_MANUAL]),
|
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): ALSA_DEVICE,
|
vol.Optional(ATTR_AUDIO_OUTPUT): alsa_device,
|
||||||
vol.Optional(ATTR_AUDIO_INPUT): ALSA_DEVICE,
|
vol.Optional(ATTR_AUDIO_INPUT): alsa_device,
|
||||||
vol.Optional(ATTR_PROTECTED, default=True): vol.Boolean(),
|
vol.Optional(ATTR_PROTECTED, default=True): vol.Boolean(),
|
||||||
vol.Optional(ATTR_INGRESS_PANEL, default=False): vol.Boolean(),
|
vol.Optional(ATTR_INGRESS_PANEL, default=False): vol.Boolean(),
|
||||||
},
|
},
|
||||||
@@ -305,7 +327,7 @@ SCHEMA_ADDON_SNAPSHOT = vol.Schema(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def validate_options(raw_schema):
|
def validate_options(coresys: CoreSys, raw_schema: Dict[str, Any]):
|
||||||
"""Validate schema."""
|
"""Validate schema."""
|
||||||
|
|
||||||
def validate(struct):
|
def validate(struct):
|
||||||
@@ -323,13 +345,13 @@ def validate_options(raw_schema):
|
|||||||
try:
|
try:
|
||||||
if isinstance(typ, list):
|
if isinstance(typ, list):
|
||||||
# nested value list
|
# nested value list
|
||||||
options[key] = _nested_validate_list(typ[0], value, key)
|
options[key] = _nested_validate_list(coresys, typ[0], value, key)
|
||||||
elif isinstance(typ, dict):
|
elif isinstance(typ, dict):
|
||||||
# nested value dict
|
# nested value dict
|
||||||
options[key] = _nested_validate_dict(typ, value, key)
|
options[key] = _nested_validate_dict(coresys, typ, value, key)
|
||||||
else:
|
else:
|
||||||
# normal value
|
# normal value
|
||||||
options[key] = _single_validate(typ, value, key)
|
options[key] = _single_validate(coresys, typ, value, key)
|
||||||
except (IndexError, KeyError):
|
except (IndexError, KeyError):
|
||||||
raise vol.Invalid(f"Type error for {key}") from None
|
raise vol.Invalid(f"Type error for {key}") from None
|
||||||
|
|
||||||
@@ -341,24 +363,31 @@ def validate_options(raw_schema):
|
|||||||
|
|
||||||
# pylint: disable=no-value-for-parameter
|
# pylint: disable=no-value-for-parameter
|
||||||
# pylint: disable=inconsistent-return-statements
|
# pylint: disable=inconsistent-return-statements
|
||||||
def _single_validate(typ, value, key):
|
def _single_validate(coresys: CoreSys, typ: str, value: Any, key: str):
|
||||||
"""Validate a single element."""
|
"""Validate a single element."""
|
||||||
# if required argument
|
# if required argument
|
||||||
if value is None:
|
if value is None:
|
||||||
raise vol.Invalid(f"Missing required option '{key}'")
|
raise vol.Invalid(f"Missing required option '{key}'")
|
||||||
|
|
||||||
|
# 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}")
|
||||||
|
|
||||||
# parse extend data from type
|
# parse extend data from type
|
||||||
match = RE_SCHEMA_ELEMENT.match(typ)
|
match = RE_SCHEMA_ELEMENT.match(typ)
|
||||||
|
|
||||||
# prepare range
|
# prepare range
|
||||||
range_args = {}
|
range_args = {}
|
||||||
for group_name in ("i_min", "i_max", "f_min", "f_max"):
|
for group_name in _SCHEMA_LENGTH_PARTS:
|
||||||
group_value = match.group(group_name)
|
group_value = match.group(group_name)
|
||||||
if group_value:
|
if group_value:
|
||||||
range_args[group_name[2:]] = float(group_value)
|
range_args[group_name[2:]] = float(group_value)
|
||||||
|
|
||||||
if typ.startswith(V_STR):
|
if typ.startswith(V_STR) or typ.startswith(V_PASSWORD):
|
||||||
return str(value)
|
return vol.All(str(value), vol.Range(**range_args))(value)
|
||||||
elif typ.startswith(V_INT):
|
elif typ.startswith(V_INT):
|
||||||
return vol.All(vol.Coerce(int), vol.Range(**range_args))(value)
|
return vol.All(vol.Coerce(int), vol.Range(**range_args))(value)
|
||||||
elif typ.startswith(V_FLOAT):
|
elif typ.startswith(V_FLOAT):
|
||||||
@@ -370,29 +399,31 @@ def _single_validate(typ, value, key):
|
|||||||
elif typ.startswith(V_URL):
|
elif typ.startswith(V_URL):
|
||||||
return vol.Url()(value)
|
return vol.Url()(value)
|
||||||
elif typ.startswith(V_PORT):
|
elif typ.startswith(V_PORT):
|
||||||
return NETWORK_PORT(value)
|
return network_port(value)
|
||||||
elif typ.startswith(V_MATCH):
|
elif typ.startswith(V_MATCH):
|
||||||
return vol.Match(match.group("match"))(str(value))
|
return vol.Match(match.group("match"))(str(value))
|
||||||
|
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}")
|
||||||
|
|
||||||
|
|
||||||
def _nested_validate_list(typ, data_list, key):
|
def _nested_validate_list(coresys, typ, data_list, key):
|
||||||
"""Validate nested items."""
|
"""Validate nested items."""
|
||||||
options = []
|
options = []
|
||||||
|
|
||||||
for element in data_list:
|
for element in data_list:
|
||||||
# Nested?
|
# Nested?
|
||||||
if isinstance(typ, dict):
|
if isinstance(typ, dict):
|
||||||
c_options = _nested_validate_dict(typ, element, key)
|
c_options = _nested_validate_dict(coresys, typ, element, key)
|
||||||
options.append(c_options)
|
options.append(c_options)
|
||||||
else:
|
else:
|
||||||
options.append(_single_validate(typ, element, key))
|
options.append(_single_validate(coresys, typ, element, key))
|
||||||
|
|
||||||
return options
|
return options
|
||||||
|
|
||||||
|
|
||||||
def _nested_validate_dict(typ, data_dict, key):
|
def _nested_validate_dict(coresys, typ, data_dict, key):
|
||||||
"""Validate nested items."""
|
"""Validate nested items."""
|
||||||
options = {}
|
options = {}
|
||||||
|
|
||||||
@@ -404,9 +435,11 @@ def _nested_validate_dict(typ, data_dict, key):
|
|||||||
|
|
||||||
# Nested?
|
# Nested?
|
||||||
if isinstance(typ[c_key], list):
|
if isinstance(typ[c_key], list):
|
||||||
options[c_key] = _nested_validate_list(typ[c_key][0], c_value, c_key)
|
options[c_key] = _nested_validate_list(
|
||||||
|
coresys, typ[c_key][0], c_value, c_key
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
options[c_key] = _single_validate(typ[c_key], c_value, c_key)
|
options[c_key] = _single_validate(coresys, typ[c_key], c_value, c_key)
|
||||||
|
|
||||||
_check_missing_options(typ, options, key)
|
_check_missing_options(typ, options, key)
|
||||||
return options
|
return options
|
||||||
@@ -419,3 +452,117 @@ def _check_missing_options(origin, exists, root):
|
|||||||
if isinstance(origin[miss_opt], str) and origin[miss_opt].endswith("?"):
|
if isinstance(origin[miss_opt], str) and origin[miss_opt].endswith("?"):
|
||||||
continue
|
continue
|
||||||
raise vol.Invalid(f"Missing option {miss_opt} in {root}")
|
raise vol.Invalid(f"Missing option {miss_opt} in {root}")
|
||||||
|
|
||||||
|
|
||||||
|
def schema_ui_options(raw_schema: Dict[str, Any]) -> List[Dict[str, Any]]:
|
||||||
|
"""Generate UI schema."""
|
||||||
|
ui_schema = []
|
||||||
|
|
||||||
|
# read options
|
||||||
|
for key, value in raw_schema.items():
|
||||||
|
if isinstance(value, list):
|
||||||
|
# nested value list
|
||||||
|
_nested_ui_list(ui_schema, value, key)
|
||||||
|
elif isinstance(value, dict):
|
||||||
|
# nested value dict
|
||||||
|
_nested_ui_dict(ui_schema, value, key)
|
||||||
|
else:
|
||||||
|
# normal value
|
||||||
|
_single_ui_option(ui_schema, value, key)
|
||||||
|
|
||||||
|
return ui_schema
|
||||||
|
|
||||||
|
|
||||||
|
def _single_ui_option(
|
||||||
|
ui_schema: List[Dict[str, Any]], value: str, key: str, multiple: bool = False
|
||||||
|
) -> None:
|
||||||
|
"""Validate a single element."""
|
||||||
|
ui_node = {"name": key}
|
||||||
|
|
||||||
|
# If multiple
|
||||||
|
if multiple:
|
||||||
|
ui_node["multiple"] = True
|
||||||
|
|
||||||
|
# Parse extend data from type
|
||||||
|
match = RE_SCHEMA_ELEMENT.match(value)
|
||||||
|
|
||||||
|
# Prepare range
|
||||||
|
for group_name in _SCHEMA_LENGTH_PARTS:
|
||||||
|
group_value = match.group(group_name)
|
||||||
|
if not group_value:
|
||||||
|
continue
|
||||||
|
if group_name[2:] == "min":
|
||||||
|
ui_node["lengthMin"] = float(group_value)
|
||||||
|
elif group_name[2:] == "max":
|
||||||
|
ui_node["lengthMax"] = float(group_value)
|
||||||
|
|
||||||
|
# If required
|
||||||
|
if value.endswith("?"):
|
||||||
|
ui_node["optional"] = True
|
||||||
|
else:
|
||||||
|
ui_node["required"] = True
|
||||||
|
|
||||||
|
# Data types
|
||||||
|
if value.startswith(V_STR):
|
||||||
|
ui_node["type"] = "string"
|
||||||
|
elif value.startswith(V_PASSWORD):
|
||||||
|
ui_node["type"] = "string"
|
||||||
|
ui_node["format"] = "password"
|
||||||
|
elif value.startswith(V_INT):
|
||||||
|
ui_node["type"] = "integer"
|
||||||
|
elif value.startswith(V_FLOAT):
|
||||||
|
ui_node["type"] = "float"
|
||||||
|
elif value.startswith(V_BOOL):
|
||||||
|
ui_node["type"] = "boolean"
|
||||||
|
elif value.startswith(V_EMAIL):
|
||||||
|
ui_node["type"] = "string"
|
||||||
|
ui_node["format"] = "email"
|
||||||
|
elif value.startswith(V_URL):
|
||||||
|
ui_node["type"] = "string"
|
||||||
|
ui_node["format"] = "url"
|
||||||
|
elif value.startswith(V_PORT):
|
||||||
|
ui_node["type"] = "integer"
|
||||||
|
elif value.startswith(V_MATCH):
|
||||||
|
ui_node["type"] = "string"
|
||||||
|
elif value.startswith(V_LIST):
|
||||||
|
ui_node["type"] = "select"
|
||||||
|
ui_node["options"] = match.group("list").split("|")
|
||||||
|
|
||||||
|
ui_schema.append(ui_node)
|
||||||
|
|
||||||
|
|
||||||
|
def _nested_ui_list(
|
||||||
|
ui_schema: List[Dict[str, Any]], option_list: List[Any], key: str
|
||||||
|
) -> None:
|
||||||
|
"""UI nested list items."""
|
||||||
|
try:
|
||||||
|
element = option_list[0]
|
||||||
|
except IndexError:
|
||||||
|
_LOGGER.error("Invalid schema %s", key)
|
||||||
|
return
|
||||||
|
|
||||||
|
if isinstance(element, dict):
|
||||||
|
_nested_ui_dict(ui_schema, element, key, multiple=True)
|
||||||
|
else:
|
||||||
|
_single_ui_option(ui_schema, element, key, multiple=True)
|
||||||
|
|
||||||
|
|
||||||
|
def _nested_ui_dict(
|
||||||
|
ui_schema: List[Dict[str, Any]],
|
||||||
|
option_dict: Dict[str, Any],
|
||||||
|
key: str,
|
||||||
|
multiple: bool = False,
|
||||||
|
) -> None:
|
||||||
|
"""UI nested dict items."""
|
||||||
|
ui_node = {"name": key, "type": "schema", "optional": True, "multiple": multiple}
|
||||||
|
|
||||||
|
nested_schema = []
|
||||||
|
for c_key, c_value in option_dict.items():
|
||||||
|
# Nested?
|
||||||
|
if isinstance(c_value, list):
|
||||||
|
_nested_ui_list(nested_schema, c_value, c_key)
|
||||||
|
else:
|
||||||
|
_single_ui_option(nested_schema, c_value, c_key)
|
||||||
|
|
||||||
|
ui_node["schema"] = nested_schema
|
||||||
|
ui_schema.append(ui_node)
|
||||||
|
@@ -22,7 +22,10 @@ from .services import APIServices
|
|||||||
from .snapshots import APISnapshots
|
from .snapshots import APISnapshots
|
||||||
from .supervisor import APISupervisor
|
from .supervisor import APISupervisor
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER: logging.Logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
MAX_CLIENT_SIZE: int = 1024 ** 2 * 16
|
||||||
|
|
||||||
|
|
||||||
class RestAPI(CoreSysAttributes):
|
class RestAPI(CoreSysAttributes):
|
||||||
@@ -33,7 +36,8 @@ class RestAPI(CoreSysAttributes):
|
|||||||
self.coresys: CoreSys = coresys
|
self.coresys: CoreSys = coresys
|
||||||
self.security: SecurityMiddleware = SecurityMiddleware(coresys)
|
self.security: SecurityMiddleware = SecurityMiddleware(coresys)
|
||||||
self.webapp: web.Application = web.Application(
|
self.webapp: web.Application = web.Application(
|
||||||
middlewares=[self.security.token_validation]
|
client_max_size=MAX_CLIENT_SIZE,
|
||||||
|
middlewares=[self.security.token_validation],
|
||||||
)
|
)
|
||||||
|
|
||||||
# service stuff
|
# service stuff
|
||||||
@@ -101,6 +105,7 @@ class RestAPI(CoreSysAttributes):
|
|||||||
[
|
[
|
||||||
web.get("/hardware/info", api_hardware.info),
|
web.get("/hardware/info", api_hardware.info),
|
||||||
web.get("/hardware/audio", api_hardware.audio),
|
web.get("/hardware/audio", api_hardware.audio),
|
||||||
|
web.post("/hardware/trigger", api_hardware.trigger),
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -116,7 +121,9 @@ class RestAPI(CoreSysAttributes):
|
|||||||
api_auth = APIAuth()
|
api_auth = APIAuth()
|
||||||
api_auth.coresys = self.coresys
|
api_auth.coresys = self.coresys
|
||||||
|
|
||||||
self.webapp.add_routes([web.post("/auth", api_auth.auth)])
|
self.webapp.add_routes(
|
||||||
|
[web.post("/auth", api_auth.auth), web.post("/auth/reset", api_auth.reset)]
|
||||||
|
)
|
||||||
|
|
||||||
def _register_supervisor(self) -> None:
|
def _register_supervisor(self) -> None:
|
||||||
"""Register Supervisor functions."""
|
"""Register Supervisor functions."""
|
||||||
@@ -194,6 +201,7 @@ class RestAPI(CoreSysAttributes):
|
|||||||
web.get("/addons/{addon}/icon", api_addons.icon),
|
web.get("/addons/{addon}/icon", api_addons.icon),
|
||||||
web.get("/addons/{addon}/logo", api_addons.logo),
|
web.get("/addons/{addon}/logo", api_addons.logo),
|
||||||
web.get("/addons/{addon}/changelog", api_addons.changelog),
|
web.get("/addons/{addon}/changelog", api_addons.changelog),
|
||||||
|
web.get("/addons/{addon}/documentation", api_addons.documentation),
|
||||||
web.post("/addons/{addon}/stdin", api_addons.stdin),
|
web.post("/addons/{addon}/stdin", api_addons.stdin),
|
||||||
web.post("/addons/{addon}/security", api_addons.security),
|
web.post("/addons/{addon}/security", api_addons.security),
|
||||||
web.get("/addons/{addon}/stats", api_addons.stats),
|
web.get("/addons/{addon}/stats", api_addons.stats),
|
||||||
@@ -278,6 +286,8 @@ class RestAPI(CoreSysAttributes):
|
|||||||
web.get("/dns/logs", api_dns.logs),
|
web.get("/dns/logs", api_dns.logs),
|
||||||
web.post("/dns/update", api_dns.update),
|
web.post("/dns/update", api_dns.update),
|
||||||
web.post("/dns/options", api_dns.options),
|
web.post("/dns/options", api_dns.options),
|
||||||
|
web.post("/dns/restart", api_dns.restart),
|
||||||
|
web.post("/dns/reset", api_dns.reset),
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@@ -5,13 +5,12 @@ from typing import Any, Awaitable, Dict, List
|
|||||||
|
|
||||||
from aiohttp import web
|
from aiohttp import web
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
from voluptuous.humanize import humanize_error
|
|
||||||
|
|
||||||
from ..addons import AnyAddon
|
from ..addons import AnyAddon
|
||||||
from ..docker.stats import DockerStats
|
|
||||||
from ..addons.utils import rating_security
|
from ..addons.utils import rating_security
|
||||||
from ..const import (
|
from ..const import (
|
||||||
ATTR_ADDONS,
|
ATTR_ADDONS,
|
||||||
|
ATTR_ADVANCED,
|
||||||
ATTR_APPARMOR,
|
ATTR_APPARMOR,
|
||||||
ATTR_ARCH,
|
ATTR_ARCH,
|
||||||
ATTR_AUDIO,
|
ATTR_AUDIO,
|
||||||
@@ -33,6 +32,7 @@ from ..const import (
|
|||||||
ATTR_DISCOVERY,
|
ATTR_DISCOVERY,
|
||||||
ATTR_DNS,
|
ATTR_DNS,
|
||||||
ATTR_DOCKER_API,
|
ATTR_DOCKER_API,
|
||||||
|
ATTR_DOCUMENTATION,
|
||||||
ATTR_FULL_ACCESS,
|
ATTR_FULL_ACCESS,
|
||||||
ATTR_GPIO,
|
ATTR_GPIO,
|
||||||
ATTR_HASSIO_API,
|
ATTR_HASSIO_API,
|
||||||
@@ -72,6 +72,7 @@ from ..const import (
|
|||||||
ATTR_RATING,
|
ATTR_RATING,
|
||||||
ATTR_REPOSITORIES,
|
ATTR_REPOSITORIES,
|
||||||
ATTR_REPOSITORY,
|
ATTR_REPOSITORY,
|
||||||
|
ATTR_SCHEMA,
|
||||||
ATTR_SERVICES,
|
ATTR_SERVICES,
|
||||||
ATTR_SLUG,
|
ATTR_SLUG,
|
||||||
ATTR_SOURCE,
|
ATTR_SOURCE,
|
||||||
@@ -90,11 +91,12 @@ from ..const import (
|
|||||||
STATE_NONE,
|
STATE_NONE,
|
||||||
)
|
)
|
||||||
from ..coresys import CoreSysAttributes
|
from ..coresys import CoreSysAttributes
|
||||||
|
from ..docker.stats import DockerStats
|
||||||
from ..exceptions import APIError
|
from ..exceptions import APIError
|
||||||
from ..validate import ALSA_DEVICE, DOCKER_PORTS
|
from ..validate import DOCKER_PORTS, alsa_device
|
||||||
from .utils import api_process, api_process_raw, api_validate
|
from .utils import api_process, api_process_raw, api_validate
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER: logging.Logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
SCHEMA_VERSION = vol.Schema({vol.Optional(ATTR_VERSION): vol.Coerce(str)})
|
SCHEMA_VERSION = vol.Schema({vol.Optional(ATTR_VERSION): vol.Coerce(str)})
|
||||||
|
|
||||||
@@ -104,8 +106,8 @@ SCHEMA_OPTIONS = vol.Schema(
|
|||||||
vol.Optional(ATTR_BOOT): vol.In([BOOT_AUTO, BOOT_MANUAL]),
|
vol.Optional(ATTR_BOOT): vol.In([BOOT_AUTO, BOOT_MANUAL]),
|
||||||
vol.Optional(ATTR_NETWORK): vol.Any(None, DOCKER_PORTS),
|
vol.Optional(ATTR_NETWORK): vol.Any(None, DOCKER_PORTS),
|
||||||
vol.Optional(ATTR_AUTO_UPDATE): vol.Boolean(),
|
vol.Optional(ATTR_AUTO_UPDATE): vol.Boolean(),
|
||||||
vol.Optional(ATTR_AUDIO_OUTPUT): ALSA_DEVICE,
|
vol.Optional(ATTR_AUDIO_OUTPUT): alsa_device,
|
||||||
vol.Optional(ATTR_AUDIO_INPUT): ALSA_DEVICE,
|
vol.Optional(ATTR_AUDIO_INPUT): alsa_device,
|
||||||
vol.Optional(ATTR_INGRESS_PANEL): vol.Boolean(),
|
vol.Optional(ATTR_INGRESS_PANEL): vol.Boolean(),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -146,6 +148,7 @@ class APIAddons(CoreSysAttributes):
|
|||||||
ATTR_NAME: addon.name,
|
ATTR_NAME: addon.name,
|
||||||
ATTR_SLUG: addon.slug,
|
ATTR_SLUG: addon.slug,
|
||||||
ATTR_DESCRIPTON: addon.description,
|
ATTR_DESCRIPTON: addon.description,
|
||||||
|
ATTR_ADVANCED: addon.advanced,
|
||||||
ATTR_VERSION: addon.latest_version,
|
ATTR_VERSION: addon.latest_version,
|
||||||
ATTR_INSTALLED: addon.version if addon.is_installed else None,
|
ATTR_INSTALLED: addon.version if addon.is_installed else None,
|
||||||
ATTR_AVAILABLE: addon.available,
|
ATTR_AVAILABLE: addon.available,
|
||||||
@@ -189,6 +192,7 @@ class APIAddons(CoreSysAttributes):
|
|||||||
ATTR_DNS: addon.dns,
|
ATTR_DNS: addon.dns,
|
||||||
ATTR_DESCRIPTON: addon.description,
|
ATTR_DESCRIPTON: addon.description,
|
||||||
ATTR_LONG_DESCRIPTION: addon.long_description,
|
ATTR_LONG_DESCRIPTION: addon.long_description,
|
||||||
|
ATTR_ADVANCED: addon.advanced,
|
||||||
ATTR_AUTO_UPDATE: None,
|
ATTR_AUTO_UPDATE: None,
|
||||||
ATTR_REPOSITORY: addon.repository,
|
ATTR_REPOSITORY: addon.repository,
|
||||||
ATTR_VERSION: None,
|
ATTR_VERSION: None,
|
||||||
@@ -197,6 +201,7 @@ class APIAddons(CoreSysAttributes):
|
|||||||
ATTR_RATING: rating_security(addon),
|
ATTR_RATING: rating_security(addon),
|
||||||
ATTR_BOOT: addon.boot,
|
ATTR_BOOT: addon.boot,
|
||||||
ATTR_OPTIONS: addon.options,
|
ATTR_OPTIONS: addon.options,
|
||||||
|
ATTR_SCHEMA: addon.schema_ui,
|
||||||
ATTR_ARCH: addon.supported_arch,
|
ATTR_ARCH: addon.supported_arch,
|
||||||
ATTR_MACHINE: addon.supported_machine,
|
ATTR_MACHINE: addon.supported_machine,
|
||||||
ATTR_HOMEASSISTANT: addon.homeassistant_version,
|
ATTR_HOMEASSISTANT: addon.homeassistant_version,
|
||||||
@@ -218,6 +223,7 @@ class APIAddons(CoreSysAttributes):
|
|||||||
ATTR_ICON: addon.with_icon,
|
ATTR_ICON: addon.with_icon,
|
||||||
ATTR_LOGO: addon.with_logo,
|
ATTR_LOGO: addon.with_logo,
|
||||||
ATTR_CHANGELOG: addon.with_changelog,
|
ATTR_CHANGELOG: addon.with_changelog,
|
||||||
|
ATTR_DOCUMENTATION: addon.with_documentation,
|
||||||
ATTR_STDIN: addon.with_stdin,
|
ATTR_STDIN: addon.with_stdin,
|
||||||
ATTR_WEBUI: None,
|
ATTR_WEBUI: None,
|
||||||
ATTR_HASSIO_API: addon.access_hassio_api,
|
ATTR_HASSIO_API: addon.access_hassio_api,
|
||||||
@@ -266,11 +272,16 @@ class APIAddons(CoreSysAttributes):
|
|||||||
"""Store user options for add-on."""
|
"""Store user options for add-on."""
|
||||||
addon: AnyAddon = self._extract_addon(request)
|
addon: AnyAddon = self._extract_addon(request)
|
||||||
|
|
||||||
|
# Update secrets for validation
|
||||||
|
await self.sys_secrets.reload()
|
||||||
|
|
||||||
|
# Extend schema with add-on specific validation
|
||||||
addon_schema = SCHEMA_OPTIONS.extend(
|
addon_schema = SCHEMA_OPTIONS.extend(
|
||||||
{vol.Optional(ATTR_OPTIONS): vol.Any(None, addon.schema)}
|
{vol.Optional(ATTR_OPTIONS): vol.Any(None, addon.schema)}
|
||||||
)
|
)
|
||||||
body: Dict[str, Any] = await api_validate(addon_schema, request)
|
|
||||||
|
|
||||||
|
# Validate/Process Body
|
||||||
|
body = await api_validate(addon_schema, request, origin=[ATTR_OPTIONS])
|
||||||
if ATTR_OPTIONS in body:
|
if ATTR_OPTIONS in body:
|
||||||
addon.options = body[ATTR_OPTIONS]
|
addon.options = body[ATTR_OPTIONS]
|
||||||
if ATTR_BOOT in body:
|
if ATTR_BOOT in body:
|
||||||
@@ -334,14 +345,6 @@ class APIAddons(CoreSysAttributes):
|
|||||||
def start(self, request: web.Request) -> Awaitable[None]:
|
def start(self, request: web.Request) -> Awaitable[None]:
|
||||||
"""Start add-on."""
|
"""Start add-on."""
|
||||||
addon: AnyAddon = self._extract_addon(request)
|
addon: AnyAddon = self._extract_addon(request)
|
||||||
|
|
||||||
# check options
|
|
||||||
options = addon.options
|
|
||||||
try:
|
|
||||||
addon.schema(options)
|
|
||||||
except vol.Invalid as ex:
|
|
||||||
raise APIError(humanize_error(options, ex)) from None
|
|
||||||
|
|
||||||
return asyncio.shield(addon.start())
|
return asyncio.shield(addon.start())
|
||||||
|
|
||||||
@api_process
|
@api_process
|
||||||
@@ -411,6 +414,16 @@ class APIAddons(CoreSysAttributes):
|
|||||||
with addon.path_changelog.open("r") as changelog:
|
with addon.path_changelog.open("r") as changelog:
|
||||||
return changelog.read()
|
return changelog.read()
|
||||||
|
|
||||||
|
@api_process_raw(CONTENT_TYPE_TEXT)
|
||||||
|
async def documentation(self, request: web.Request) -> str:
|
||||||
|
"""Return documentation from add-on."""
|
||||||
|
addon: AnyAddon = self._extract_addon(request, check_installed=False)
|
||||||
|
if not addon.with_documentation:
|
||||||
|
raise APIError("No documentation found!")
|
||||||
|
|
||||||
|
with addon.path_documentation.open("r") as documentation:
|
||||||
|
return documentation.read()
|
||||||
|
|
||||||
@api_process
|
@api_process
|
||||||
async def stdin(self, request: web.Request) -> None:
|
async def stdin(self, request: web.Request) -> None:
|
||||||
"""Write to stdin of add-on."""
|
"""Write to stdin of add-on."""
|
||||||
|
@@ -1,22 +1,39 @@
|
|||||||
"""Init file for Hass.io auth/SSO RESTful API."""
|
"""Init file for Hass.io auth/SSO RESTful API."""
|
||||||
|
import asyncio
|
||||||
import logging
|
import logging
|
||||||
|
from typing import Dict
|
||||||
|
|
||||||
from aiohttp import BasicAuth
|
from aiohttp import BasicAuth, web
|
||||||
|
from aiohttp.hdrs import AUTHORIZATION, CONTENT_TYPE, WWW_AUTHENTICATE
|
||||||
from aiohttp.web_exceptions import HTTPUnauthorized
|
from aiohttp.web_exceptions import HTTPUnauthorized
|
||||||
from aiohttp.hdrs import CONTENT_TYPE, AUTHORIZATION, WWW_AUTHENTICATE
|
import voluptuous as vol
|
||||||
|
|
||||||
from .utils import api_process
|
from ..addons.addon import Addon
|
||||||
from ..const import REQUEST_FROM, CONTENT_TYPE_JSON, CONTENT_TYPE_URL
|
from ..const import (
|
||||||
|
ATTR_PASSWORD,
|
||||||
|
ATTR_USERNAME,
|
||||||
|
CONTENT_TYPE_JSON,
|
||||||
|
CONTENT_TYPE_URL,
|
||||||
|
REQUEST_FROM,
|
||||||
|
)
|
||||||
from ..coresys import CoreSysAttributes
|
from ..coresys import CoreSysAttributes
|
||||||
from ..exceptions import APIForbidden
|
from ..exceptions import APIForbidden
|
||||||
|
from .utils import api_process, api_validate
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER: logging.Logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
SCHEMA_PASSWORD_RESET = vol.Schema(
|
||||||
|
{
|
||||||
|
vol.Required(ATTR_USERNAME): vol.Coerce(str),
|
||||||
|
vol.Required(ATTR_PASSWORD): vol.Coerce(str),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class APIAuth(CoreSysAttributes):
|
class APIAuth(CoreSysAttributes):
|
||||||
"""Handle RESTful API for auth functions."""
|
"""Handle RESTful API for auth functions."""
|
||||||
|
|
||||||
def _process_basic(self, request, addon):
|
def _process_basic(self, request: web.Request, addon: Addon) -> bool:
|
||||||
"""Process login request with basic auth.
|
"""Process login request with basic auth.
|
||||||
|
|
||||||
Return a coroutine.
|
Return a coroutine.
|
||||||
@@ -24,7 +41,9 @@ class APIAuth(CoreSysAttributes):
|
|||||||
auth = BasicAuth.decode(request.headers[AUTHORIZATION])
|
auth = BasicAuth.decode(request.headers[AUTHORIZATION])
|
||||||
return self.sys_auth.check_login(addon, auth.login, auth.password)
|
return self.sys_auth.check_login(addon, auth.login, auth.password)
|
||||||
|
|
||||||
def _process_dict(self, request, addon, data):
|
def _process_dict(
|
||||||
|
self, request: web.Request, addon: Addon, data: Dict[str, str]
|
||||||
|
) -> bool:
|
||||||
"""Process login with dict data.
|
"""Process login with dict data.
|
||||||
|
|
||||||
Return a coroutine.
|
Return a coroutine.
|
||||||
@@ -35,7 +54,7 @@ class APIAuth(CoreSysAttributes):
|
|||||||
return self.sys_auth.check_login(addon, username, password)
|
return self.sys_auth.check_login(addon, username, password)
|
||||||
|
|
||||||
@api_process
|
@api_process
|
||||||
async def auth(self, request):
|
async def auth(self, request: web.Request) -> bool:
|
||||||
"""Process login request."""
|
"""Process login request."""
|
||||||
addon = request[REQUEST_FROM]
|
addon = request[REQUEST_FROM]
|
||||||
|
|
||||||
@@ -59,3 +78,11 @@ class APIAuth(CoreSysAttributes):
|
|||||||
raise HTTPUnauthorized(
|
raise HTTPUnauthorized(
|
||||||
headers={WWW_AUTHENTICATE: 'Basic realm="Hass.io Authentication"'}
|
headers={WWW_AUTHENTICATE: 'Basic realm="Hass.io Authentication"'}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@api_process
|
||||||
|
async def reset(self, request: web.Request) -> None:
|
||||||
|
"""Process reset password request."""
|
||||||
|
body: Dict[str, str] = await api_validate(SCHEMA_PASSWORD_RESET, request)
|
||||||
|
await asyncio.shield(
|
||||||
|
self.sys_auth.change_password(body[ATTR_USERNAME], body[ATTR_PASSWORD])
|
||||||
|
)
|
||||||
|
@@ -12,9 +12,10 @@ from ..const import (
|
|||||||
ATTR_CPU_PERCENT,
|
ATTR_CPU_PERCENT,
|
||||||
ATTR_HOST,
|
ATTR_HOST,
|
||||||
ATTR_LATEST_VERSION,
|
ATTR_LATEST_VERSION,
|
||||||
|
ATTR_LOCALS,
|
||||||
ATTR_MEMORY_LIMIT,
|
ATTR_MEMORY_LIMIT,
|
||||||
ATTR_MEMORY_USAGE,
|
|
||||||
ATTR_MEMORY_PERCENT,
|
ATTR_MEMORY_PERCENT,
|
||||||
|
ATTR_MEMORY_USAGE,
|
||||||
ATTR_NETWORK_RX,
|
ATTR_NETWORK_RX,
|
||||||
ATTR_NETWORK_TX,
|
ATTR_NETWORK_TX,
|
||||||
ATTR_SERVERS,
|
ATTR_SERVERS,
|
||||||
@@ -23,13 +24,13 @@ from ..const import (
|
|||||||
)
|
)
|
||||||
from ..coresys import CoreSysAttributes
|
from ..coresys import CoreSysAttributes
|
||||||
from ..exceptions import APIError
|
from ..exceptions import APIError
|
||||||
from ..validate import DNS_SERVER_LIST
|
from ..validate import dns_server_list
|
||||||
from .utils import api_process, api_process_raw, api_validate
|
from .utils import api_process, api_process_raw, api_validate
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER: logging.Logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
# pylint: disable=no-value-for-parameter
|
# pylint: disable=no-value-for-parameter
|
||||||
SCHEMA_OPTIONS = vol.Schema({vol.Optional(ATTR_SERVERS): DNS_SERVER_LIST})
|
SCHEMA_OPTIONS = vol.Schema({vol.Optional(ATTR_SERVERS): dns_server_list})
|
||||||
|
|
||||||
SCHEMA_VERSION = vol.Schema({vol.Optional(ATTR_VERSION): vol.Coerce(str)})
|
SCHEMA_VERSION = vol.Schema({vol.Optional(ATTR_VERSION): vol.Coerce(str)})
|
||||||
|
|
||||||
@@ -45,6 +46,7 @@ class APICoreDNS(CoreSysAttributes):
|
|||||||
ATTR_LATEST_VERSION: self.sys_dns.latest_version,
|
ATTR_LATEST_VERSION: self.sys_dns.latest_version,
|
||||||
ATTR_HOST: str(self.sys_docker.network.dns),
|
ATTR_HOST: str(self.sys_docker.network.dns),
|
||||||
ATTR_SERVERS: self.sys_dns.servers,
|
ATTR_SERVERS: self.sys_dns.servers,
|
||||||
|
ATTR_LOCALS: self.sys_host.network.dns_servers,
|
||||||
}
|
}
|
||||||
|
|
||||||
@api_process
|
@api_process
|
||||||
@@ -54,6 +56,7 @@ class APICoreDNS(CoreSysAttributes):
|
|||||||
|
|
||||||
if ATTR_SERVERS in body:
|
if ATTR_SERVERS in body:
|
||||||
self.sys_dns.servers = body[ATTR_SERVERS]
|
self.sys_dns.servers = body[ATTR_SERVERS]
|
||||||
|
self.sys_create_task(self.sys_dns.restart())
|
||||||
|
|
||||||
self.sys_dns.save_data()
|
self.sys_dns.save_data()
|
||||||
|
|
||||||
@@ -87,3 +90,13 @@ class APICoreDNS(CoreSysAttributes):
|
|||||||
def logs(self, request: web.Request) -> Awaitable[bytes]:
|
def logs(self, request: web.Request) -> Awaitable[bytes]:
|
||||||
"""Return DNS Docker logs."""
|
"""Return DNS Docker logs."""
|
||||||
return self.sys_dns.logs()
|
return self.sys_dns.logs()
|
||||||
|
|
||||||
|
@api_process
|
||||||
|
def restart(self, request: web.Request) -> Awaitable[None]:
|
||||||
|
"""Restart CoreDNS plugin."""
|
||||||
|
return asyncio.shield(self.sys_dns.restart())
|
||||||
|
|
||||||
|
@api_process
|
||||||
|
def reset(self, request: web.Request) -> Awaitable[None]:
|
||||||
|
"""Reset CoreDNS plugin."""
|
||||||
|
return asyncio.shield(self.sys_dns.reset())
|
||||||
|
@@ -1,5 +1,9 @@
|
|||||||
"""Init file for Hass.io hardware RESTful API."""
|
"""Init file for Hass.io hardware RESTful API."""
|
||||||
|
import asyncio
|
||||||
import logging
|
import logging
|
||||||
|
from typing import Any, Dict
|
||||||
|
|
||||||
|
from aiohttp import web
|
||||||
|
|
||||||
from .utils import api_process
|
from .utils import api_process
|
||||||
from ..const import (
|
from ..const import (
|
||||||
@@ -12,14 +16,14 @@ from ..const import (
|
|||||||
)
|
)
|
||||||
from ..coresys import CoreSysAttributes
|
from ..coresys import CoreSysAttributes
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER: logging.Logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class APIHardware(CoreSysAttributes):
|
class APIHardware(CoreSysAttributes):
|
||||||
"""Handle RESTful API for hardware functions."""
|
"""Handle RESTful API for hardware functions."""
|
||||||
|
|
||||||
@api_process
|
@api_process
|
||||||
async def info(self, request):
|
async def info(self, request: web.Request) -> Dict[str, Any]:
|
||||||
"""Show hardware info."""
|
"""Show hardware info."""
|
||||||
return {
|
return {
|
||||||
ATTR_SERIAL: list(
|
ATTR_SERIAL: list(
|
||||||
@@ -32,7 +36,7 @@ class APIHardware(CoreSysAttributes):
|
|||||||
}
|
}
|
||||||
|
|
||||||
@api_process
|
@api_process
|
||||||
async def audio(self, request):
|
async def audio(self, request: web.Request) -> Dict[str, Any]:
|
||||||
"""Show ALSA audio devices."""
|
"""Show ALSA audio devices."""
|
||||||
return {
|
return {
|
||||||
ATTR_AUDIO: {
|
ATTR_AUDIO: {
|
||||||
@@ -40,3 +44,8 @@ class APIHardware(CoreSysAttributes):
|
|||||||
ATTR_OUTPUT: self.sys_host.alsa.output_devices,
|
ATTR_OUTPUT: self.sys_host.alsa.output_devices,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@api_process
|
||||||
|
def trigger(self, request: web.Request) -> None:
|
||||||
|
"""Trigger a udev device reload."""
|
||||||
|
return asyncio.shield(self.sys_hardware.udev_trigger())
|
||||||
|
@@ -3,11 +3,12 @@ import asyncio
|
|||||||
import logging
|
import logging
|
||||||
from typing import Any, Awaitable, Dict
|
from typing import Any, Awaitable, Dict
|
||||||
|
|
||||||
import voluptuous as vol
|
|
||||||
from aiohttp import web
|
from aiohttp import web
|
||||||
|
import voluptuous as vol
|
||||||
|
|
||||||
from ..const import (
|
from ..const import (
|
||||||
ATTR_BOARD,
|
ATTR_BOARD,
|
||||||
|
ATTR_BOOT,
|
||||||
ATTR_VERSION,
|
ATTR_VERSION,
|
||||||
ATTR_VERSION_CLI,
|
ATTR_VERSION_CLI,
|
||||||
ATTR_VERSION_CLI_LATEST,
|
ATTR_VERSION_CLI_LATEST,
|
||||||
@@ -16,7 +17,7 @@ from ..const import (
|
|||||||
from ..coresys import CoreSysAttributes
|
from ..coresys import CoreSysAttributes
|
||||||
from .utils import api_process, api_validate
|
from .utils import api_process, api_validate
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER: logging.Logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
SCHEMA_VERSION = vol.Schema({vol.Optional(ATTR_VERSION): vol.Coerce(str)})
|
SCHEMA_VERSION = vol.Schema({vol.Optional(ATTR_VERSION): vol.Coerce(str)})
|
||||||
|
|
||||||
@@ -33,6 +34,7 @@ class APIHassOS(CoreSysAttributes):
|
|||||||
ATTR_VERSION_LATEST: self.sys_hassos.version_latest,
|
ATTR_VERSION_LATEST: self.sys_hassos.version_latest,
|
||||||
ATTR_VERSION_CLI_LATEST: self.sys_hassos.version_cli_latest,
|
ATTR_VERSION_CLI_LATEST: self.sys_hassos.version_cli_latest,
|
||||||
ATTR_BOARD: self.sys_hassos.board,
|
ATTR_BOARD: self.sys_hassos.board,
|
||||||
|
ATTR_BOOT: self.sys_dbus.rauc.boot_slot,
|
||||||
}
|
}
|
||||||
|
|
||||||
@api_process
|
@api_process
|
||||||
|
@@ -21,7 +21,6 @@ from ..const import (
|
|||||||
ATTR_MEMORY_PERCENT,
|
ATTR_MEMORY_PERCENT,
|
||||||
ATTR_NETWORK_RX,
|
ATTR_NETWORK_RX,
|
||||||
ATTR_NETWORK_TX,
|
ATTR_NETWORK_TX,
|
||||||
ATTR_PASSWORD,
|
|
||||||
ATTR_PORT,
|
ATTR_PORT,
|
||||||
ATTR_REFRESH_TOKEN,
|
ATTR_REFRESH_TOKEN,
|
||||||
ATTR_SSL,
|
ATTR_SSL,
|
||||||
@@ -33,19 +32,18 @@ from ..const import (
|
|||||||
)
|
)
|
||||||
from ..coresys import CoreSysAttributes
|
from ..coresys import CoreSysAttributes
|
||||||
from ..exceptions import APIError
|
from ..exceptions import APIError
|
||||||
from ..validate import DOCKER_IMAGE, NETWORK_PORT
|
from ..validate import docker_image, network_port
|
||||||
from .utils import api_process, api_process_raw, api_validate
|
from .utils import api_process, api_process_raw, api_validate
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER: logging.Logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
# pylint: disable=no-value-for-parameter
|
# pylint: disable=no-value-for-parameter
|
||||||
SCHEMA_OPTIONS = vol.Schema(
|
SCHEMA_OPTIONS = vol.Schema(
|
||||||
{
|
{
|
||||||
vol.Optional(ATTR_BOOT): vol.Boolean(),
|
vol.Optional(ATTR_BOOT): vol.Boolean(),
|
||||||
vol.Inclusive(ATTR_IMAGE, "custom_hass"): vol.Maybe(DOCKER_IMAGE),
|
vol.Inclusive(ATTR_IMAGE, "custom_hass"): vol.Maybe(docker_image),
|
||||||
vol.Inclusive(ATTR_LAST_VERSION, "custom_hass"): vol.Maybe(vol.Coerce(str)),
|
vol.Inclusive(ATTR_LAST_VERSION, "custom_hass"): vol.Maybe(vol.Coerce(str)),
|
||||||
vol.Optional(ATTR_PORT): NETWORK_PORT,
|
vol.Optional(ATTR_PORT): network_port,
|
||||||
vol.Optional(ATTR_PASSWORD): vol.Maybe(vol.Coerce(str)),
|
|
||||||
vol.Optional(ATTR_SSL): vol.Boolean(),
|
vol.Optional(ATTR_SSL): vol.Boolean(),
|
||||||
vol.Optional(ATTR_WATCHDOG): vol.Boolean(),
|
vol.Optional(ATTR_WATCHDOG): vol.Boolean(),
|
||||||
vol.Optional(ATTR_WAIT_BOOT): vol.All(vol.Coerce(int), vol.Range(min=60)),
|
vol.Optional(ATTR_WAIT_BOOT): vol.All(vol.Coerce(int), vol.Range(min=60)),
|
||||||
@@ -92,10 +90,6 @@ class APIHomeAssistant(CoreSysAttributes):
|
|||||||
if ATTR_PORT in body:
|
if ATTR_PORT in body:
|
||||||
self.sys_homeassistant.api_port = body[ATTR_PORT]
|
self.sys_homeassistant.api_port = body[ATTR_PORT]
|
||||||
|
|
||||||
if ATTR_PASSWORD in body:
|
|
||||||
self.sys_homeassistant.api_password = body[ATTR_PASSWORD]
|
|
||||||
self.sys_homeassistant.refresh_token = None
|
|
||||||
|
|
||||||
if ATTR_SSL in body:
|
if ATTR_SSL in body:
|
||||||
self.sys_homeassistant.api_ssl = body[ATTR_SSL]
|
self.sys_homeassistant.api_ssl = body[ATTR_SSL]
|
||||||
|
|
||||||
@@ -107,7 +101,6 @@ class APIHomeAssistant(CoreSysAttributes):
|
|||||||
|
|
||||||
if ATTR_REFRESH_TOKEN in body:
|
if ATTR_REFRESH_TOKEN in body:
|
||||||
self.sys_homeassistant.refresh_token = body[ATTR_REFRESH_TOKEN]
|
self.sys_homeassistant.refresh_token = body[ATTR_REFRESH_TOKEN]
|
||||||
self.sys_homeassistant.api_password = None
|
|
||||||
|
|
||||||
self.sys_homeassistant.save_data()
|
self.sys_homeassistant.save_data()
|
||||||
|
|
||||||
|
@@ -20,7 +20,7 @@ from ..const import (
|
|||||||
)
|
)
|
||||||
from ..coresys import CoreSysAttributes
|
from ..coresys import CoreSysAttributes
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER: logging.Logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
SERVICE = "service"
|
SERVICE = "service"
|
||||||
|
|
||||||
|
@@ -19,7 +19,7 @@ from ..const import (
|
|||||||
from ..coresys import CoreSysAttributes
|
from ..coresys import CoreSysAttributes
|
||||||
from .utils import api_process
|
from .utils import api_process
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER: logging.Logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class APIInfo(CoreSysAttributes):
|
class APIInfo(CoreSysAttributes):
|
||||||
|
@@ -28,7 +28,7 @@ from ..const import (
|
|||||||
from ..coresys import CoreSysAttributes
|
from ..coresys import CoreSysAttributes
|
||||||
from .utils import api_process
|
from .utils import api_process
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER: logging.Logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class APIIngress(CoreSysAttributes):
|
class APIIngress(CoreSysAttributes):
|
||||||
@@ -204,7 +204,15 @@ def _init_header(
|
|||||||
|
|
||||||
# filter flags
|
# filter flags
|
||||||
for name, value in request.headers.items():
|
for name, value in request.headers.items():
|
||||||
if name in (hdrs.CONTENT_LENGTH, hdrs.CONTENT_ENCODING, istr(HEADER_TOKEN)):
|
if name in (
|
||||||
|
hdrs.CONTENT_LENGTH,
|
||||||
|
hdrs.CONTENT_ENCODING,
|
||||||
|
hdrs.SEC_WEBSOCKET_EXTENSIONS,
|
||||||
|
hdrs.SEC_WEBSOCKET_PROTOCOL,
|
||||||
|
hdrs.SEC_WEBSOCKET_VERSION,
|
||||||
|
hdrs.SEC_WEBSOCKET_KEY,
|
||||||
|
istr(HEADER_TOKEN),
|
||||||
|
):
|
||||||
continue
|
continue
|
||||||
headers[name] = value
|
headers[name] = value
|
||||||
|
|
||||||
|
2
hassio/api/panel/201359fd5a526afe13ef.worker.js
Normal file
2
hassio/api/panel/201359fd5a526afe13ef.worker.js
Normal file
File diff suppressed because one or more lines are too long
BIN
hassio/api/panel/201359fd5a526afe13ef.worker.js.gz
Normal file
BIN
hassio/api/panel/201359fd5a526afe13ef.worker.js.gz
Normal file
Binary file not shown.
1
hassio/api/panel/201359fd5a526afe13ef.worker.js.map
Normal file
1
hassio/api/panel/201359fd5a526afe13ef.worker.js.map
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Binary file not shown.
File diff suppressed because one or more lines are too long
3
hassio/api/panel/chunk.12902324b918e12549ba.js
Normal file
3
hassio/api/panel/chunk.12902324b918e12549ba.js
Normal file
File diff suppressed because one or more lines are too long
@@ -51,6 +51,27 @@ See the License for the specific language governing permissions and
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
@license
|
||||||
|
Copyright (c) 2016 The Polymer Project Authors. All rights reserved.
|
||||||
|
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
||||||
|
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
||||||
|
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
||||||
|
Code distributed by Google as part of the polymer project is also
|
||||||
|
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
@license
|
||||||
|
Copyright (c) 2017 The Polymer Project Authors. All rights reserved.
|
||||||
|
This code may only be used under the BSD style license found at
|
||||||
|
http://polymer.github.io/LICENSE.txt The complete set of authors may be found at
|
||||||
|
http://polymer.github.io/AUTHORS.txt The complete set of contributors may be
|
||||||
|
found at http://polymer.github.io/CONTRIBUTORS.txt Code distributed by Google as
|
||||||
|
part of the polymer project is also subject to an additional IP rights grant
|
||||||
|
found at http://polymer.github.io/PATENTS.txt
|
||||||
|
*/
|
||||||
|
|
||||||
/*! *****************************************************************************
|
/*! *****************************************************************************
|
||||||
Copyright (c) Microsoft Corporation. All rights reserved.
|
Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
Licensed under the Apache License, Version 2.0 (the "License"); you may not use
|
Licensed under the Apache License, Version 2.0 (the "License"); you may not use
|
||||||
@@ -66,6 +87,31 @@ See the Apache Version 2.0 License for specific language governing permissions
|
|||||||
and limitations under the License.
|
and limitations under the License.
|
||||||
***************************************************************************** */
|
***************************************************************************** */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @license
|
||||||
|
* Copyright (c) 2018 The Polymer Project Authors. All rights reserved.
|
||||||
|
* This code may only be used under the BSD style license found at
|
||||||
|
* http://polymer.github.io/LICENSE.txt
|
||||||
|
* The complete set of authors may be found at
|
||||||
|
* http://polymer.github.io/AUTHORS.txt
|
||||||
|
* The complete set of contributors may be found at
|
||||||
|
* http://polymer.github.io/CONTRIBUTORS.txt
|
||||||
|
* Code distributed by Google as part of the polymer project is also
|
||||||
|
* subject to an additional IP rights grant found at
|
||||||
|
* http://polymer.github.io/PATENTS.txt
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
@license
|
||||||
|
Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
|
||||||
|
This code may only be used under the BSD style license found at
|
||||||
|
http://polymer.github.io/LICENSE.txt The complete set of authors may be found at
|
||||||
|
http://polymer.github.io/AUTHORS.txt The complete set of contributors may be
|
||||||
|
found at http://polymer.github.io/CONTRIBUTORS.txt Code distributed by Google as
|
||||||
|
part of the polymer project is also subject to an additional IP rights grant
|
||||||
|
found at http://polymer.github.io/PATENTS.txt
|
||||||
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @license
|
* @license
|
||||||
* Copyright 2016 Google Inc.
|
* Copyright 2016 Google Inc.
|
||||||
@@ -89,6 +135,39 @@ and limitations under the License.
|
|||||||
* THE SOFTWARE.
|
* THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
@license
|
||||||
|
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
|
||||||
|
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
||||||
|
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
||||||
|
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
||||||
|
Code distributed by Google as part of the polymer project is also
|
||||||
|
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @license
|
||||||
|
* Copyright 2019 Google Inc.
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in
|
||||||
|
* all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
* THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @license
|
* @license
|
||||||
* Copyright 2018 Google Inc.
|
* Copyright 2018 Google Inc.
|
||||||
@@ -112,62 +191,6 @@ and limitations under the License.
|
|||||||
* THE SOFTWARE.
|
* THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
|
||||||
@license
|
|
||||||
Copyright (c) 2016 The Polymer Project Authors. All rights reserved.
|
|
||||||
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
|
||||||
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
|
||||||
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
|
||||||
Code distributed by Google as part of the polymer project is also
|
|
||||||
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
@license
|
|
||||||
Copyright (c) 2017 The Polymer Project Authors. All rights reserved.
|
|
||||||
This code may only be used under the BSD style license found at
|
|
||||||
http://polymer.github.io/LICENSE.txt The complete set of authors may be found at
|
|
||||||
http://polymer.github.io/AUTHORS.txt The complete set of contributors may be
|
|
||||||
found at http://polymer.github.io/CONTRIBUTORS.txt Code distributed by Google as
|
|
||||||
part of the polymer project is also subject to an additional IP rights grant
|
|
||||||
found at http://polymer.github.io/PATENTS.txt
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @license
|
|
||||||
* Copyright (c) 2018 The Polymer Project Authors. All rights reserved.
|
|
||||||
* This code may only be used under the BSD style license found at
|
|
||||||
* http://polymer.github.io/LICENSE.txt
|
|
||||||
* The complete set of authors may be found at
|
|
||||||
* http://polymer.github.io/AUTHORS.txt
|
|
||||||
* The complete set of contributors may be found at
|
|
||||||
* http://polymer.github.io/CONTRIBUTORS.txt
|
|
||||||
* Code distributed by Google as part of the polymer project is also
|
|
||||||
* subject to an additional IP rights grant found at
|
|
||||||
* http://polymer.github.io/PATENTS.txt
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
@license
|
|
||||||
Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
|
|
||||||
This code may only be used under the BSD style license found at
|
|
||||||
http://polymer.github.io/LICENSE.txt The complete set of authors may be found at
|
|
||||||
http://polymer.github.io/AUTHORS.txt The complete set of contributors may be
|
|
||||||
found at http://polymer.github.io/CONTRIBUTORS.txt Code distributed by Google as
|
|
||||||
part of the polymer project is also subject to an additional IP rights grant
|
|
||||||
found at http://polymer.github.io/PATENTS.txt
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
@license
|
|
||||||
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
|
|
||||||
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
|
||||||
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
|
||||||
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
|
||||||
Code distributed by Google as part of the polymer project is also
|
|
||||||
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@license
|
@license
|
||||||
Copyright (c) 2016 The Polymer Project Authors. All rights reserved.
|
Copyright (c) 2016 The Polymer Project Authors. All rights reserved.
|
BIN
hassio/api/panel/chunk.12902324b918e12549ba.js.gz
Normal file
BIN
hassio/api/panel/chunk.12902324b918e12549ba.js.gz
Normal file
Binary file not shown.
1
hassio/api/panel/chunk.12902324b918e12549ba.js.map
Normal file
1
hassio/api/panel/chunk.12902324b918e12549ba.js.map
Normal file
File diff suppressed because one or more lines are too long
3
hassio/api/panel/chunk.50202a3f8d4670c9454d.js
Normal file
3
hassio/api/panel/chunk.50202a3f8d4670c9454d.js
Normal file
File diff suppressed because one or more lines are too long
BIN
hassio/api/panel/chunk.50202a3f8d4670c9454d.js.gz
Normal file
BIN
hassio/api/panel/chunk.50202a3f8d4670c9454d.js.gz
Normal file
Binary file not shown.
1
hassio/api/panel/chunk.50202a3f8d4670c9454d.js.map
Normal file
1
hassio/api/panel/chunk.50202a3f8d4670c9454d.js.map
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Binary file not shown.
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Binary file not shown.
@@ -1 +0,0 @@
|
|||||||
{"version":3,"sources":["webpack:///./src/ingress-view/hassio-ingress-view.ts"],"names":["customElement","HassioIngressView","property","this","_addon","html","_templateObject2","name","ingress_url","_templateObject","changedProps","_get","_getPrototypeOf","prototype","call","has","addon","route","path","substr","oldRoute","get","oldAddon","undefined","_fetchData","_callee","addonSlug","_ref","_ref2","regeneratorRuntime","wrap","_context","prev","next","Promise","all","fetchHassioAddonInfo","hass","catch","Error","createHassioSession","sent","_slicedToArray","ingress","t0","console","error","alert","message","history","back","stop","css","_templateObject3","LitElement"],"mappings":"4gSAmBCA,YAAc,0CACTC,smBACHC,kEACAA,mEACAA,4EAED,WACE,OAAKC,KAAKC,OAMHC,YAAPC,IAC0BH,KAAKC,OAAOG,KACpBJ,KAAKC,OAAOI,aAPrBH,YAAPI,0CAYJ,SAAkBC,GAGhB,GAFAC,EAAAC,EApBEX,EAoBFY,WAAA,eAAAV,MAAAW,KAAAX,KAAmBO,GAEdA,EAAaK,IAAI,SAAtB,CAIA,IAAMC,EAAQb,KAAKc,MAAMC,KAAKC,OAAO,GAE/BC,EAAWV,EAAaW,IAAI,SAC5BC,EAAWF,EAAWA,EAASF,KAAKC,OAAO,QAAKI,EAElDP,GAASA,IAAUM,GACrBnB,KAAKqB,WAAWR,0FAIpB,SAAAS,EAAyBC,GAAzB,IAAAC,EAAAC,EAAAZ,EAAA,OAAAa,mBAAAC,KAAA,SAAAC,GAAA,cAAAA,EAAAC,KAAAD,EAAAE,MAAA,cAAAF,EAAAC,KAAA,EAAAD,EAAAE,KAAA,EAE0BC,QAAQC,IAAI,CAChCC,YAAqBjC,KAAKkC,KAAMX,GAAWY,MAAM,WAC/C,MAAM,IAAIC,MAAM,iCAElBC,YAAoBrC,KAAKkC,MAAMC,MAAM,WACnC,MAAM,IAAIC,MAAM,2CAPxB,UAAAZ,EAAAI,EAAAU,KAAAb,EAAAc,EAAAf,EAAA,IAEWX,EAFXY,EAAA,IAWee,QAXf,CAAAZ,EAAAE,KAAA,cAYY,IAAIM,MAAM,wCAZtB,OAeIpC,KAAKC,OAASY,EAflBe,EAAAE,KAAA,iBAAAF,EAAAC,KAAA,GAAAD,EAAAa,GAAAb,EAAA,SAkBIc,QAAQC,MAARf,EAAAa,IACAG,MAAMhB,EAAAa,GAAII,SAAW,mCACrBC,QAAQC,OApBZ,yBAAAnB,EAAAoB,SAAA1B,EAAAtB,KAAA,yRAwBA,WACE,OAAOiD,YAAPC,UA7D4BC","file":"chunk.5dd33a3a20657ed46a19.js","sourcesContent":["import {\n LitElement,\n customElement,\n property,\n TemplateResult,\n html,\n PropertyValues,\n CSSResult,\n css,\n} from \"lit-element\";\nimport { HomeAssistant, Route } from \"../../../src/types\";\nimport {\n createHassioSession,\n HassioAddonDetails,\n fetchHassioAddonInfo,\n} from \"../../../src/data/hassio\";\nimport \"../../../src/layouts/hass-loading-screen\";\nimport \"../../../src/layouts/hass-subpage\";\n\n@customElement(\"hassio-ingress-view\")\nclass HassioIngressView extends LitElement {\n @property() public hass!: HomeAssistant;\n @property() public route!: Route;\n @property() private _addon?: HassioAddonDetails;\n\n protected render(): TemplateResult | void {\n if (!this._addon) {\n return html`\n <hass-loading-screen></hass-loading-screen>\n `;\n }\n\n return html`\n <hass-subpage .header=${this._addon.name} hassio>\n <iframe src=${this._addon.ingress_url}></iframe>\n </hass-subpage>\n `;\n }\n\n protected updated(changedProps: PropertyValues) {\n super.firstUpdated(changedProps);\n\n if (!changedProps.has(\"route\")) {\n return;\n }\n\n const addon = this.route.path.substr(1);\n\n const oldRoute = changedProps.get(\"route\") as this[\"route\"] | undefined;\n const oldAddon = oldRoute ? oldRoute.path.substr(1) : undefined;\n\n if (addon && addon !== oldAddon) {\n this._fetchData(addon);\n }\n }\n\n private async _fetchData(addonSlug: string) {\n try {\n const [addon] = await Promise.all([\n fetchHassioAddonInfo(this.hass, addonSlug).catch(() => {\n throw new Error(\"Failed to fetch add-on info\");\n }),\n createHassioSession(this.hass).catch(() => {\n throw new Error(\"Failed to create an ingress session\");\n }),\n ]);\n\n if (!addon.ingress) {\n throw new Error(\"This add-on does not support ingress\");\n }\n\n this._addon = addon;\n } catch (err) {\n // tslint:disable-next-line\n console.error(err);\n alert(err.message || \"Unknown error starting ingress.\");\n history.back();\n }\n }\n\n static get styles(): CSSResult {\n return css`\n iframe {\n display: block;\n width: 100%;\n height: 100%;\n border: 0;\n }\n paper-icon-button {\n color: var(--text-primary-color);\n }\n `;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n \"hassio-ingress-view\": HassioIngressView;\n }\n}\n"],"sourceRoot":""}
|
|
File diff suppressed because one or more lines are too long
Binary file not shown.
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Binary file not shown.
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Binary file not shown.
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1,21 +0,0 @@
|
|||||||
/**
|
|
||||||
@license
|
|
||||||
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
|
|
||||||
This code may only be used under the BSD style license found at
|
|
||||||
http://polymer.github.io/LICENSE.txt The complete set of authors may be found at
|
|
||||||
http://polymer.github.io/AUTHORS.txt The complete set of contributors may be
|
|
||||||
found at http://polymer.github.io/CONTRIBUTORS.txt Code distributed by Google as
|
|
||||||
part of the polymer project is also subject to an additional IP rights grant
|
|
||||||
found at http://polymer.github.io/PATENTS.txt
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
@license
|
|
||||||
Copyright (c) 2017 The Polymer Project Authors. All rights reserved.
|
|
||||||
This code may only be used under the BSD style license found at
|
|
||||||
http://polymer.github.io/LICENSE.txt The complete set of authors may be found at
|
|
||||||
http://polymer.github.io/AUTHORS.txt The complete set of contributors may be
|
|
||||||
found at http://polymer.github.io/CONTRIBUTORS.txt Code distributed by Google as
|
|
||||||
part of the polymer project is also subject to an additional IP rights grant
|
|
||||||
found at http://polymer.github.io/PATENTS.txt
|
|
||||||
*/
|
|
Binary file not shown.
File diff suppressed because one or more lines are too long
@@ -1,2 +0,0 @@
|
|||||||
(window.webpackJsonp=window.webpackJsonp||[]).push([[9],{101:function(n,r,t){"use strict";t.r(r),t.d(r,"marked",function(){return a}),t.d(r,"filterXSS",function(){return c});var e=t(124),i=t.n(e),o=t(126),u=t.n(o),a=i.a,c=u.a}}]);
|
|
||||||
//# sourceMappingURL=chunk.7f8cce5798f837214ef8.js.map
|
|
Binary file not shown.
@@ -1 +0,0 @@
|
|||||||
{"version":3,"sources":["webpack:///../src/resources/load_markdown.js"],"names":["__webpack_require__","r","__webpack_exports__","d","marked","filterXSS","marked__WEBPACK_IMPORTED_MODULE_0__","marked__WEBPACK_IMPORTED_MODULE_0___default","n","xss__WEBPACK_IMPORTED_MODULE_1__","xss__WEBPACK_IMPORTED_MODULE_1___default","marked_","filterXSS_"],"mappings":"0FAAAA,EAAAC,EAAAC,GAAAF,EAAAG,EAAAD,EAAA,2BAAAE,IAAAJ,EAAAG,EAAAD,EAAA,8BAAAG,IAAA,IAAAC,EAAAN,EAAA,KAAAO,EAAAP,EAAAQ,EAAAF,GAAAG,EAAAT,EAAA,KAAAU,EAAAV,EAAAQ,EAAAC,GAGaL,EAASO,IACTN,EAAYO","file":"chunk.7f8cce5798f837214ef8.js","sourcesContent":["import marked_ from \"marked\";\nimport filterXSS_ from \"xss\";\n\nexport const marked = marked_;\nexport const filterXSS = filterXSS_;\n"],"sourceRoot":""}
|
|
3
hassio/api/panel/chunk.84aaaba4c4734f1c2e21.js
Normal file
3
hassio/api/panel/chunk.84aaaba4c4734f1c2e21.js
Normal file
File diff suppressed because one or more lines are too long
@@ -1,3 +1,20 @@
|
|||||||
|
/**
|
||||||
|
@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
|
@license
|
||||||
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
|
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
|
||||||
@@ -9,16 +26,6 @@ part of the polymer project is also subject to an additional IP rights grant
|
|||||||
found at http://polymer.github.io/PATENTS.txt
|
found at http://polymer.github.io/PATENTS.txt
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
|
||||||
@license
|
|
||||||
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
|
|
||||||
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
|
||||||
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
|
||||||
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
|
||||||
Code distributed by Google as part of the polymer project is also
|
|
||||||
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@license
|
@license
|
||||||
Copyright (c) 2016 The Polymer Project Authors. All rights reserved.
|
Copyright (c) 2016 The Polymer Project Authors. All rights reserved.
|
BIN
hassio/api/panel/chunk.84aaaba4c4734f1c2e21.js.gz
Normal file
BIN
hassio/api/panel/chunk.84aaaba4c4734f1c2e21.js.gz
Normal file
Binary file not shown.
1
hassio/api/panel/chunk.84aaaba4c4734f1c2e21.js.map
Normal file
1
hassio/api/panel/chunk.84aaaba4c4734f1c2e21.js.map
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Binary file not shown.
File diff suppressed because one or more lines are too long
2
hassio/api/panel/chunk.884d6e32c83f99e41040.js
Normal file
2
hassio/api/panel/chunk.884d6e32c83f99e41040.js
Normal file
File diff suppressed because one or more lines are too long
BIN
hassio/api/panel/chunk.884d6e32c83f99e41040.js.gz
Normal file
BIN
hassio/api/panel/chunk.884d6e32c83f99e41040.js.gz
Normal file
Binary file not shown.
1
hassio/api/panel/chunk.884d6e32c83f99e41040.js.map
Normal file
1
hassio/api/panel/chunk.884d6e32c83f99e41040.js.map
Normal file
File diff suppressed because one or more lines are too long
2
hassio/api/panel/chunk.900c5d3fab8b6ebdcbc6.js
Normal file
2
hassio/api/panel/chunk.900c5d3fab8b6ebdcbc6.js
Normal file
File diff suppressed because one or more lines are too long
BIN
hassio/api/panel/chunk.900c5d3fab8b6ebdcbc6.js.gz
Normal file
BIN
hassio/api/panel/chunk.900c5d3fab8b6ebdcbc6.js.gz
Normal file
Binary file not shown.
1
hassio/api/panel/chunk.900c5d3fab8b6ebdcbc6.js.map
Normal file
1
hassio/api/panel/chunk.900c5d3fab8b6ebdcbc6.js.map
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"version":3,"sources":["webpack:///./hassio/src/ingress-view/hassio-ingress-view.ts"],"names":["customElement","HassioIngressView","property","this","_addon","html","_templateObject2","name","ingress_url","_templateObject","changedProps","_get","_getPrototypeOf","prototype","call","has","addon","route","path","substr","oldRoute","get","oldAddon","undefined","_fetchData","addonSlug","_ref","_ref2","regeneratorRuntime","async","_context","prev","next","awrap","Promise","all","fetchHassioAddonInfo","hass","Error","createHassioSession","sent","_slicedToArray","ingress","t0","console","error","alert","message","history","back","stop","css","_templateObject3","LitElement"],"mappings":"2/RAmBCA,YAAc,0CACTC,smBACHC,kEACAA,mEACAA,4EAED,WACE,OAAKC,KAAKC,OAMHC,YAAPC,IAC0BH,KAAKC,OAAOG,KACpBJ,KAAKC,OAAOI,aAPrBH,YAAPI,0CAYJ,SAAkBC,GAGhB,GAFAC,EAAAC,EApBEX,EAoBFY,WAAA,eAAAV,MAAAW,KAAAX,KAAmBO,GAEdA,EAAaK,IAAI,SAAtB,CAIA,IAAMC,EAAQb,KAAKc,MAAMC,KAAKC,OAAO,GAE/BC,EAAWV,EAAaW,IAAI,SAC5BC,EAAWF,EAAWA,EAASF,KAAKC,OAAO,QAAKI,EAElDP,GAASA,IAAUM,GACrBnB,KAAKqB,WAAWR,4CAIpB,SAAyBS,GAAzB,IAAAC,EAAAC,EAAAX,EAAA,OAAAY,mBAAAC,MAAA,SAAAC,GAAA,cAAAA,EAAAC,KAAAD,EAAAE,MAAA,cAAAF,EAAAC,KAAA,EAAAD,EAAAE,KAAA,EAAAJ,mBAAAK,MAE0BC,QAAQC,IAAI,CAChCC,YAAqBjC,KAAKkC,KAAMZ,GAAhC,MAAiD,WAC/C,MAAM,IAAIa,MAAM,iCAElBC,YAAoBpC,KAAKkC,MAAzB,MAAqC,WACnC,MAAM,IAAIC,MAAM,4CAPxB,UAAAZ,EAAAI,EAAAU,KAAAb,EAAAc,EAAAf,EAAA,IAEWV,EAFXW,EAAA,IAWee,QAXf,CAAAZ,EAAAE,KAAA,cAYY,IAAIM,MAAM,wCAZtB,OAeInC,KAAKC,OAASY,EAflBc,EAAAE,KAAA,iBAAAF,EAAAC,KAAA,GAAAD,EAAAa,GAAAb,EAAA,SAkBIc,QAAQC,MAARf,EAAAa,IACAG,MAAMhB,EAAAa,GAAII,SAAW,mCACrBC,QAAQC,OApBZ,yBAAAnB,EAAAoB,SAAA,KAAA/C,KAAA,qDAwBA,WACE,OAAOgD,YAAPC,UA7D4BC","file":"chunk.900c5d3fab8b6ebdcbc6.js","sourcesContent":["import {\n LitElement,\n customElement,\n property,\n TemplateResult,\n html,\n PropertyValues,\n CSSResult,\n css,\n} from \"lit-element\";\nimport { HomeAssistant, Route } from \"../../../src/types\";\nimport { createHassioSession } from \"../../../src/data/hassio/supervisor\";\nimport {\n HassioAddonDetails,\n fetchHassioAddonInfo,\n} from \"../../../src/data/hassio/addon\";\nimport \"../../../src/layouts/hass-loading-screen\";\nimport \"../../../src/layouts/hass-subpage\";\n\n@customElement(\"hassio-ingress-view\")\nclass HassioIngressView extends LitElement {\n @property() public hass!: HomeAssistant;\n @property() public route!: Route;\n @property() private _addon?: HassioAddonDetails;\n\n protected render(): TemplateResult {\n if (!this._addon) {\n return html`\n <hass-loading-screen></hass-loading-screen>\n `;\n }\n\n return html`\n <hass-subpage .header=${this._addon.name} hassio>\n <iframe src=${this._addon.ingress_url}></iframe>\n </hass-subpage>\n `;\n }\n\n protected updated(changedProps: PropertyValues) {\n super.firstUpdated(changedProps);\n\n if (!changedProps.has(\"route\")) {\n return;\n }\n\n const addon = this.route.path.substr(1);\n\n const oldRoute = changedProps.get(\"route\") as this[\"route\"] | undefined;\n const oldAddon = oldRoute ? oldRoute.path.substr(1) : undefined;\n\n if (addon && addon !== oldAddon) {\n this._fetchData(addon);\n }\n }\n\n private async _fetchData(addonSlug: string) {\n try {\n const [addon] = await Promise.all([\n fetchHassioAddonInfo(this.hass, addonSlug).catch(() => {\n throw new Error(\"Failed to fetch add-on info\");\n }),\n createHassioSession(this.hass).catch(() => {\n throw new Error(\"Failed to create an ingress session\");\n }),\n ]);\n\n if (!addon.ingress) {\n throw new Error(\"This add-on does not support ingress\");\n }\n\n this._addon = addon;\n } catch (err) {\n // tslint:disable-next-line\n console.error(err);\n alert(err.message || \"Unknown error starting ingress.\");\n history.back();\n }\n }\n\n static get styles(): CSSResult {\n return css`\n iframe {\n display: block;\n width: 100%;\n height: 100%;\n border: 0;\n }\n paper-icon-button {\n color: var(--text-primary-color);\n }\n `;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n \"hassio-ingress-view\": HassioIngressView;\n }\n}\n"],"sourceRoot":""}
|
3
hassio/api/panel/chunk.9cea224f33b375867edd.js
Normal file
3
hassio/api/panel/chunk.9cea224f33b375867edd.js
Normal file
File diff suppressed because one or more lines are too long
BIN
hassio/api/panel/chunk.9cea224f33b375867edd.js.gz
Normal file
BIN
hassio/api/panel/chunk.9cea224f33b375867edd.js.gz
Normal file
Binary file not shown.
1
hassio/api/panel/chunk.9cea224f33b375867edd.js.map
Normal file
1
hassio/api/panel/chunk.9cea224f33b375867edd.js.map
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Binary file not shown.
File diff suppressed because one or more lines are too long
2
hassio/api/panel/chunk.a1b6b616fc89c412f5b6.js
Normal file
2
hassio/api/panel/chunk.a1b6b616fc89c412f5b6.js
Normal file
File diff suppressed because one or more lines are too long
BIN
hassio/api/panel/chunk.a1b6b616fc89c412f5b6.js.gz
Normal file
BIN
hassio/api/panel/chunk.a1b6b616fc89c412f5b6.js.gz
Normal file
Binary file not shown.
1
hassio/api/panel/chunk.a1b6b616fc89c412f5b6.js.map
Normal file
1
hassio/api/panel/chunk.a1b6b616fc89c412f5b6.js.map
Normal file
File diff suppressed because one or more lines are too long
2
hassio/api/panel/chunk.a4f9950b101883805252.js
Normal file
2
hassio/api/panel/chunk.a4f9950b101883805252.js
Normal file
File diff suppressed because one or more lines are too long
BIN
hassio/api/panel/chunk.a4f9950b101883805252.js.gz
Normal file
BIN
hassio/api/panel/chunk.a4f9950b101883805252.js.gz
Normal file
Binary file not shown.
1
hassio/api/panel/chunk.a4f9950b101883805252.js.map
Normal file
1
hassio/api/panel/chunk.a4f9950b101883805252.js.map
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Binary file not shown.
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Binary file not shown.
File diff suppressed because one or more lines are too long
2
hassio/api/panel/chunk.b21a4609308c9b8ef180.js
Normal file
2
hassio/api/panel/chunk.b21a4609308c9b8ef180.js
Normal file
File diff suppressed because one or more lines are too long
BIN
hassio/api/panel/chunk.b21a4609308c9b8ef180.js.gz
Normal file
BIN
hassio/api/panel/chunk.b21a4609308c9b8ef180.js.gz
Normal file
Binary file not shown.
1
hassio/api/panel/chunk.b21a4609308c9b8ef180.js.map
Normal file
1
hassio/api/panel/chunk.b21a4609308c9b8ef180.js.map
Normal file
File diff suppressed because one or more lines are too long
3
hassio/api/panel/chunk.c0a46a38d689ab648885.js
Normal file
3
hassio/api/panel/chunk.c0a46a38d689ab648885.js
Normal file
File diff suppressed because one or more lines are too long
BIN
hassio/api/panel/chunk.c0a46a38d689ab648885.js.gz
Normal file
BIN
hassio/api/panel/chunk.c0a46a38d689ab648885.js.gz
Normal file
Binary file not shown.
1
hassio/api/panel/chunk.c0a46a38d689ab648885.js.map
Normal file
1
hassio/api/panel/chunk.c0a46a38d689ab648885.js.map
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user