Compare commits

..

1 Commits

Author SHA1 Message Date
ludeeus
e415923553 Trigger backup sync when backup is complete 2024-10-16 04:54:29 +00:00
3260 changed files with 502523 additions and 6910 deletions

View File

@@ -53,7 +53,7 @@ jobs:
requirements: ${{ steps.requirements.outputs.changed }} requirements: ${{ steps.requirements.outputs.changed }}
steps: steps:
- name: Checkout the repository - name: Checkout the repository
uses: actions/checkout@v4.2.2 uses: actions/checkout@v4.2.1
with: with:
fetch-depth: 0 fetch-depth: 0
@@ -92,7 +92,7 @@ jobs:
arch: ${{ fromJson(needs.init.outputs.architectures) }} arch: ${{ fromJson(needs.init.outputs.architectures) }}
steps: steps:
- name: Checkout the repository - name: Checkout the repository
uses: actions/checkout@v4.2.2 uses: actions/checkout@v4.2.1
with: with:
fetch-depth: 0 fetch-depth: 0
@@ -106,7 +106,7 @@ jobs:
- name: Build wheels - name: Build wheels
if: needs.init.outputs.requirements == 'true' if: needs.init.outputs.requirements == 'true'
uses: home-assistant/wheels@2024.11.0 uses: home-assistant/wheels@2024.07.1
with: with:
abi: cp312 abi: cp312
tag: musllinux_1_2 tag: musllinux_1_2
@@ -125,7 +125,7 @@ jobs:
- name: Set up Python ${{ env.DEFAULT_PYTHON }} - name: Set up Python ${{ env.DEFAULT_PYTHON }}
if: needs.init.outputs.publish == 'true' if: needs.init.outputs.publish == 'true'
uses: actions/setup-python@v5.3.0 uses: actions/setup-python@v5.2.0
with: with:
python-version: ${{ env.DEFAULT_PYTHON }} python-version: ${{ env.DEFAULT_PYTHON }}
@@ -178,7 +178,7 @@ jobs:
steps: steps:
- name: Checkout the repository - name: Checkout the repository
if: needs.init.outputs.publish == 'true' if: needs.init.outputs.publish == 'true'
uses: actions/checkout@v4.2.2 uses: actions/checkout@v4.2.1
- name: Initialize git - name: Initialize git
if: needs.init.outputs.publish == 'true' if: needs.init.outputs.publish == 'true'
@@ -203,7 +203,7 @@ jobs:
timeout-minutes: 60 timeout-minutes: 60
steps: steps:
- name: Checkout the repository - name: Checkout the repository
uses: actions/checkout@v4.2.2 uses: actions/checkout@v4.2.1
- name: Build the Supervisor - name: Build the Supervisor
if: needs.init.outputs.publish != 'true' if: needs.init.outputs.publish != 'true'

View File

@@ -25,15 +25,15 @@ jobs:
name: Prepare Python dependencies name: Prepare Python dependencies
steps: steps:
- name: Check out code from GitHub - name: Check out code from GitHub
uses: actions/checkout@v4.2.2 uses: actions/checkout@v4.2.1
- name: Set up Python - name: Set up Python
id: python id: python
uses: actions/setup-python@v5.3.0 uses: actions/setup-python@v5.2.0
with: with:
python-version: ${{ env.DEFAULT_PYTHON }} python-version: ${{ env.DEFAULT_PYTHON }}
- name: Restore Python virtual environment - name: Restore Python virtual environment
id: cache-venv id: cache-venv
uses: actions/cache@v4.2.0 uses: actions/cache@v4.1.1
with: with:
path: venv path: venv
key: | key: |
@@ -47,7 +47,7 @@ jobs:
pip install -r requirements.txt -r requirements_tests.txt pip install -r requirements.txt -r requirements_tests.txt
- name: Restore pre-commit environment from cache - name: Restore pre-commit environment from cache
id: cache-precommit id: cache-precommit
uses: actions/cache@v4.2.0 uses: actions/cache@v4.1.1
with: with:
path: ${{ env.PRE_COMMIT_CACHE }} path: ${{ env.PRE_COMMIT_CACHE }}
lookup-only: true lookup-only: true
@@ -67,15 +67,15 @@ jobs:
needs: prepare needs: prepare
steps: steps:
- name: Check out code from GitHub - name: Check out code from GitHub
uses: actions/checkout@v4.2.2 uses: actions/checkout@v4.2.1
- name: Set up Python ${{ needs.prepare.outputs.python-version }} - name: Set up Python ${{ needs.prepare.outputs.python-version }}
uses: actions/setup-python@v5.3.0 uses: actions/setup-python@v5.2.0
id: python id: python
with: with:
python-version: ${{ needs.prepare.outputs.python-version }} python-version: ${{ needs.prepare.outputs.python-version }}
- name: Restore Python virtual environment - name: Restore Python virtual environment
id: cache-venv id: cache-venv
uses: actions/cache@v4.2.0 uses: actions/cache@v4.1.1
with: with:
path: venv path: venv
key: | key: |
@@ -87,7 +87,7 @@ jobs:
exit 1 exit 1
- name: Restore pre-commit environment from cache - name: Restore pre-commit environment from cache
id: cache-precommit id: cache-precommit
uses: actions/cache@v4.2.0 uses: actions/cache@v4.1.1
with: with:
path: ${{ env.PRE_COMMIT_CACHE }} path: ${{ env.PRE_COMMIT_CACHE }}
key: | key: |
@@ -110,15 +110,15 @@ jobs:
needs: prepare needs: prepare
steps: steps:
- name: Check out code from GitHub - name: Check out code from GitHub
uses: actions/checkout@v4.2.2 uses: actions/checkout@v4.2.1
- name: Set up Python ${{ needs.prepare.outputs.python-version }} - name: Set up Python ${{ needs.prepare.outputs.python-version }}
uses: actions/setup-python@v5.3.0 uses: actions/setup-python@v5.2.0
id: python id: python
with: with:
python-version: ${{ needs.prepare.outputs.python-version }} python-version: ${{ needs.prepare.outputs.python-version }}
- name: Restore Python virtual environment - name: Restore Python virtual environment
id: cache-venv id: cache-venv
uses: actions/cache@v4.2.0 uses: actions/cache@v4.1.1
with: with:
path: venv path: venv
key: | key: |
@@ -130,7 +130,7 @@ jobs:
exit 1 exit 1
- name: Restore pre-commit environment from cache - name: Restore pre-commit environment from cache
id: cache-precommit id: cache-precommit
uses: actions/cache@v4.2.0 uses: actions/cache@v4.1.1
with: with:
path: ${{ env.PRE_COMMIT_CACHE }} path: ${{ env.PRE_COMMIT_CACHE }}
key: | key: |
@@ -153,7 +153,7 @@ jobs:
needs: prepare needs: prepare
steps: steps:
- name: Check out code from GitHub - name: Check out code from GitHub
uses: actions/checkout@v4.2.2 uses: actions/checkout@v4.2.1
- name: Register hadolint problem matcher - name: Register hadolint problem matcher
run: | run: |
echo "::add-matcher::.github/workflows/matchers/hadolint.json" echo "::add-matcher::.github/workflows/matchers/hadolint.json"
@@ -168,15 +168,15 @@ jobs:
needs: prepare needs: prepare
steps: steps:
- name: Check out code from GitHub - name: Check out code from GitHub
uses: actions/checkout@v4.2.2 uses: actions/checkout@v4.2.1
- name: Set up Python ${{ needs.prepare.outputs.python-version }} - name: Set up Python ${{ needs.prepare.outputs.python-version }}
uses: actions/setup-python@v5.3.0 uses: actions/setup-python@v5.2.0
id: python id: python
with: with:
python-version: ${{ needs.prepare.outputs.python-version }} python-version: ${{ needs.prepare.outputs.python-version }}
- name: Restore Python virtual environment - name: Restore Python virtual environment
id: cache-venv id: cache-venv
uses: actions/cache@v4.2.0 uses: actions/cache@v4.1.1
with: with:
path: venv path: venv
key: | key: |
@@ -188,7 +188,7 @@ jobs:
exit 1 exit 1
- name: Restore pre-commit environment from cache - name: Restore pre-commit environment from cache
id: cache-precommit id: cache-precommit
uses: actions/cache@v4.2.0 uses: actions/cache@v4.1.1
with: with:
path: ${{ env.PRE_COMMIT_CACHE }} path: ${{ env.PRE_COMMIT_CACHE }}
key: | key: |
@@ -212,15 +212,15 @@ jobs:
needs: prepare needs: prepare
steps: steps:
- name: Check out code from GitHub - name: Check out code from GitHub
uses: actions/checkout@v4.2.2 uses: actions/checkout@v4.2.1
- name: Set up Python ${{ needs.prepare.outputs.python-version }} - name: Set up Python ${{ needs.prepare.outputs.python-version }}
uses: actions/setup-python@v5.3.0 uses: actions/setup-python@v5.2.0
id: python id: python
with: with:
python-version: ${{ needs.prepare.outputs.python-version }} python-version: ${{ needs.prepare.outputs.python-version }}
- name: Restore Python virtual environment - name: Restore Python virtual environment
id: cache-venv id: cache-venv
uses: actions/cache@v4.2.0 uses: actions/cache@v4.1.1
with: with:
path: venv path: venv
key: | key: |
@@ -232,7 +232,7 @@ jobs:
exit 1 exit 1
- name: Restore pre-commit environment from cache - name: Restore pre-commit environment from cache
id: cache-precommit id: cache-precommit
uses: actions/cache@v4.2.0 uses: actions/cache@v4.1.1
with: with:
path: ${{ env.PRE_COMMIT_CACHE }} path: ${{ env.PRE_COMMIT_CACHE }}
key: | key: |
@@ -256,15 +256,15 @@ jobs:
needs: prepare needs: prepare
steps: steps:
- name: Check out code from GitHub - name: Check out code from GitHub
uses: actions/checkout@v4.2.2 uses: actions/checkout@v4.2.1
- name: Set up Python ${{ needs.prepare.outputs.python-version }} - name: Set up Python ${{ needs.prepare.outputs.python-version }}
uses: actions/setup-python@v5.3.0 uses: actions/setup-python@v5.2.0
id: python id: python
with: with:
python-version: ${{ needs.prepare.outputs.python-version }} python-version: ${{ needs.prepare.outputs.python-version }}
- name: Restore Python virtual environment - name: Restore Python virtual environment
id: cache-venv id: cache-venv
uses: actions/cache@v4.2.0 uses: actions/cache@v4.1.1
with: with:
path: venv path: venv
key: | key: |
@@ -274,10 +274,6 @@ jobs:
run: | run: |
echo "Failed to restore Python virtual environment from cache" echo "Failed to restore Python virtual environment from cache"
exit 1 exit 1
- name: Install additional system dependencies
run: |
sudo apt-get update
sudo apt-get install -y --no-install-recommends libpulse0
- name: Register pylint problem matcher - name: Register pylint problem matcher
run: | run: |
echo "::add-matcher::.github/workflows/matchers/pylint.json" echo "::add-matcher::.github/workflows/matchers/pylint.json"
@@ -292,9 +288,9 @@ jobs:
name: Run tests Python ${{ needs.prepare.outputs.python-version }} name: Run tests Python ${{ needs.prepare.outputs.python-version }}
steps: steps:
- name: Check out code from GitHub - name: Check out code from GitHub
uses: actions/checkout@v4.2.2 uses: actions/checkout@v4.2.1
- name: Set up Python ${{ needs.prepare.outputs.python-version }} - name: Set up Python ${{ needs.prepare.outputs.python-version }}
uses: actions/setup-python@v5.3.0 uses: actions/setup-python@v5.2.0
id: python id: python
with: with:
python-version: ${{ needs.prepare.outputs.python-version }} python-version: ${{ needs.prepare.outputs.python-version }}
@@ -304,7 +300,7 @@ jobs:
cosign-release: "v2.4.0" cosign-release: "v2.4.0"
- name: Restore Python virtual environment - name: Restore Python virtual environment
id: cache-venv id: cache-venv
uses: actions/cache@v4.2.0 uses: actions/cache@v4.1.1
with: with:
path: venv path: venv
key: | key: |
@@ -339,7 +335,7 @@ jobs:
-o console_output_style=count \ -o console_output_style=count \
tests tests
- name: Upload coverage artifact - name: Upload coverage artifact
uses: actions/upload-artifact@v4.6.0 uses: actions/upload-artifact@v4.4.3
with: with:
name: coverage-${{ matrix.python-version }} name: coverage-${{ matrix.python-version }}
path: .coverage path: .coverage
@@ -351,15 +347,15 @@ jobs:
needs: ["pytest", "prepare"] needs: ["pytest", "prepare"]
steps: steps:
- name: Check out code from GitHub - name: Check out code from GitHub
uses: actions/checkout@v4.2.2 uses: actions/checkout@v4.2.1
- name: Set up Python ${{ needs.prepare.outputs.python-version }} - name: Set up Python ${{ needs.prepare.outputs.python-version }}
uses: actions/setup-python@v5.3.0 uses: actions/setup-python@v5.2.0
id: python id: python
with: with:
python-version: ${{ needs.prepare.outputs.python-version }} python-version: ${{ needs.prepare.outputs.python-version }}
- name: Restore Python virtual environment - name: Restore Python virtual environment
id: cache-venv id: cache-venv
uses: actions/cache@v4.2.0 uses: actions/cache@v4.1.1
with: with:
path: venv path: venv
key: | key: |
@@ -378,4 +374,4 @@ jobs:
coverage report coverage report
coverage xml coverage xml
- name: Upload coverage to Codecov - name: Upload coverage to Codecov
uses: codecov/codecov-action@v5.1.2 uses: codecov/codecov-action@v4.6.0

View File

@@ -11,7 +11,7 @@ jobs:
name: Release Drafter name: Release Drafter
steps: steps:
- name: Checkout the repository - name: Checkout the repository
uses: actions/checkout@v4.2.2 uses: actions/checkout@v4.2.1
with: with:
fetch-depth: 0 fetch-depth: 0

View File

@@ -10,9 +10,9 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Check out code from GitHub - name: Check out code from GitHub
uses: actions/checkout@v4.2.2 uses: actions/checkout@v4.2.1
- name: Sentry Release - name: Sentry Release
uses: getsentry/action-release@v1.9.0 uses: getsentry/action-release@v1.7.0
env: env:
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
SENTRY_ORG: ${{ secrets.SENTRY_ORG }} SENTRY_ORG: ${{ secrets.SENTRY_ORG }}

View File

@@ -1,74 +0,0 @@
name: Update frontend
on:
schedule: # once a day
- cron: "0 0 * * *"
workflow_dispatch:
jobs:
check-version:
runs-on: ubuntu-latest
outputs:
skip: ${{ steps.check_version.outputs.skip || steps.check_existing_pr.outputs.skip }}
latest_tag: ${{ steps.latest_frontend_version.outputs.latest_tag }}
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Get latest frontend release
id: latest_frontend_version
uses: abatilo/release-info-action@v1.3.3
with:
owner: home-assistant
repo: frontend
- name: Check if version is up to date
id: check_version
run: |
SUPERVISOR_VERSION=$(cat .ha-frontend-version)
LATEST_VERSION=${{ steps.latest_frontend_version.outputs.latest_tag }}
echo "SUPERVISOR_VERSION=$SUPERVISOR_VERSION" >> $GITHUB_ENV
echo "LATEST_VERSION=$LATEST_VERSION" >> $GITHUB_ENV
if [[ ! "$SUPERVISOR_VERSION" < "$LATEST_VERSION" ]]; then
echo "Frontend version is up to date"
echo "skip=true" >> $GITHUB_OUTPUT
fi
- name: Check if there is no open PR with this version
if: steps.check_version.outputs.skip != 'true'
id: check_existing_pr
env:
GH_TOKEN: ${{ github.token }}
run: |
PR=$(gh pr list --state open --base main --json title --search "Autoupdate frontend to version $LATEST_VERSION")
if [[ "$PR" != "[]" ]]; then
echo "Skipping - There is already a PR open for version $LATEST_VERSION"
echo "skip=true" >> $GITHUB_OUTPUT
fi
create-pr:
runs-on: ubuntu-latest
needs: check-version
if: needs.check-version.outputs.skip != 'true'
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Clear www folder
run: |
rm -rf supervisor/api/panel/*
- name: Update version file
run: |
echo "${{ needs.check-version.outputs.latest_tag }}" > .ha-frontend-version
- name: Download release assets
uses: robinraju/release-downloader@v1
with:
repository: 'home-assistant/frontend'
tag: ${{ needs.check-version.outputs.latest_tag }}
fileName: home_assistant_frontend_supervisor-${{ needs.check-version.outputs.latest_tag }}.tar.gz
extract: true
out-file-path: supervisor/api/panel/
- name: Create PR
uses: peter-evans/create-pull-request@v7
with:
commit-message: "Autoupdate frontend to version ${{ needs.check-version.outputs.latest_tag }}"
branch: autoupdate-frontend
base: main
draft: true
sign-commits: true
title: "Autoupdate frontend to version ${{ needs.check-version.outputs.latest_tag }}"

4
.gitmodules vendored Normal file
View File

@@ -0,0 +1,4 @@
[submodule "home-assistant-polymer"]
path = home-assistant-polymer
url = https://github.com/home-assistant/home-assistant-polymer
branch = dev

View File

@@ -1 +0,0 @@
20241127.8

View File

@@ -1,5 +1,5 @@
[build-system] [build-system]
requires = ["setuptools~=75.8.0", "wheel~=0.45.0"] requires = ["setuptools~=68.0.0", "wheel~=0.40.0"]
build-backend = "setuptools.build_meta" build-backend = "setuptools.build_meta"
[project] [project]

View File

@@ -1,29 +1,29 @@
aiodns==3.2.0 aiodns==3.2.0
aiohttp==3.11.11 aiohttp==3.10.10
atomicwrites-homeassistant==1.4.1 atomicwrites-homeassistant==1.4.1
attrs==24.3.0 attrs==24.2.0
awesomeversion==24.6.0 awesomeversion==24.6.0
brotli==1.1.0 brotli==1.1.0
ciso8601==2.3.2 ciso8601==2.3.1
colorlog==6.9.0 colorlog==6.8.2
cpe==1.3.1 cpe==1.3.1
cryptography==44.0.0 cryptography==43.0.1
debugpy==1.8.12 debugpy==1.8.7
deepmerge==2.0 deepmerge==2.0
dirhash==0.5.0 dirhash==0.5.0
docker==7.1.0 docker==7.1.0
faust-cchardet==2.1.19 faust-cchardet==2.1.19
gitpython==3.1.44 gitpython==3.1.43
jinja2==3.1.5 jinja2==3.1.4
orjson==3.10.12 orjson==3.10.7
pulsectl==24.12.0 pulsectl==24.8.0
pyudev==0.24.3 pyudev==0.24.3
PyYAML==6.0.2 PyYAML==6.0.2
requests==2.32.3 requests==2.32.3
securetar==2025.1.3 securetar==2024.2.1
sentry-sdk==2.20.0 sentry-sdk==2.16.0
setuptools==75.8.0 setuptools==75.1.0
voluptuous==0.15.2 voluptuous==0.15.2
dbus-fast==2.28.0 dbus-fast==2.24.3
typing_extensions==4.12.2 typing_extensions==4.12.2
zlib-fast==0.2.0 zlib-fast==0.2.0

View File

@@ -1,13 +1,12 @@
astroid==3.3.8 coverage==7.6.3
coverage==7.6.10
pre-commit==4.0.1 pre-commit==4.0.1
pylint==3.3.3 pylint==3.3.1
pytest-aiohttp==1.0.5 pytest-aiohttp==1.0.5
pytest-asyncio==0.23.6 pytest-asyncio==0.23.6
pytest-cov==6.0.0 pytest-cov==5.0.0
pytest-timeout==2.3.1 pytest-timeout==2.3.1
pytest==8.3.4 pytest==8.3.3
ruff==0.9.2 ruff==0.6.9
time-machine==2.16.0 time-machine==2.16.0
typing_extensions==4.12.2 typing_extensions==4.12.2
urllib3==2.3.0 urllib3==2.2.3

30
scripts/update-frontend.sh Executable file
View File

@@ -0,0 +1,30 @@
#!/bin/bash
source "/etc/supervisor_scripts/common"
set -e
# Update frontend
git submodule update --init --recursive --remote
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"
cd home-assistant-polymer
nvm install
script/bootstrap
# Download translations
start_docker
./script/translations_download
# build frontend
cd hassio
./script/build_hassio
# Copy frontend
rm -rf ../../supervisor/api/panel/*
cp -rf build/* ../../supervisor/api/panel/
# Reset frontend git
cd ..
git reset --hard HEAD
stop_docker

View File

@@ -6,7 +6,6 @@ from contextlib import suppress
from copy import deepcopy from copy import deepcopy
from datetime import datetime from datetime import datetime
import errno import errno
from functools import partial
from ipaddress import IPv4Address from ipaddress import IPv4Address
import logging import logging
from pathlib import Path, PurePath from pathlib import Path, PurePath
@@ -82,8 +81,7 @@ from ..hardware.data import Device
from ..homeassistant.const import WSEvent, WSType from ..homeassistant.const import WSEvent, WSType
from ..jobs.const import JobExecutionLimit from ..jobs.const import JobExecutionLimit
from ..jobs.decorator import Job from ..jobs.decorator import Job
from ..resolution.const import ContextType, IssueType, UnhealthyReason from ..resolution.const import UnhealthyReason
from ..resolution.data import Issue
from ..store.addon import AddonStore from ..store.addon import AddonStore
from ..utils import check_port from ..utils import check_port
from ..utils.apparmor import adjust_profile from ..utils.apparmor import adjust_profile
@@ -146,27 +144,11 @@ class Addon(AddonModel):
self._listeners: list[EventListener] = [] self._listeners: list[EventListener] = []
self._startup_event = asyncio.Event() self._startup_event = asyncio.Event()
self._startup_task: asyncio.Task | None = None self._startup_task: asyncio.Task | None = None
self._boot_failed_issue = Issue(
IssueType.BOOT_FAIL, ContextType.ADDON, reference=self.slug
)
self._device_access_missing_issue = Issue(
IssueType.DEVICE_ACCESS_MISSING, ContextType.ADDON, reference=self.slug
)
def __repr__(self) -> str: def __repr__(self) -> str:
"""Return internal representation.""" """Return internal representation."""
return f"<Addon: {self.slug}>" return f"<Addon: {self.slug}>"
@property
def boot_failed_issue(self) -> Issue:
"""Get issue used if start on boot failed."""
return self._boot_failed_issue
@property
def device_access_missing_issue(self) -> Issue:
"""Get issue used if device access is missing and can't be automatically added."""
return self._device_access_missing_issue
@property @property
def state(self) -> AddonState: def state(self) -> AddonState:
"""Return state of the add-on.""" """Return state of the add-on."""
@@ -184,20 +166,6 @@ class Addon(AddonModel):
if new_state == AddonState.STARTED or old_state == AddonState.STARTUP: if new_state == AddonState.STARTED or old_state == AddonState.STARTUP:
self._startup_event.set() self._startup_event.set()
# Dismiss boot failed issue if present and we started
if (
new_state == AddonState.STARTED
and self.boot_failed_issue in self.sys_resolution.issues
):
self.sys_resolution.dismiss_issue(self.boot_failed_issue)
# Dismiss device access missing issue if present and we stopped
if (
new_state == AddonState.STOPPED
and self.device_access_missing_issue in self.sys_resolution.issues
):
self.sys_resolution.dismiss_issue(self.device_access_missing_issue)
self.sys_homeassistant.websocket.send_message( self.sys_homeassistant.websocket.send_message(
{ {
ATTR_TYPE: WSType.SUPERVISOR_EVENT, ATTR_TYPE: WSType.SUPERVISOR_EVENT,
@@ -354,13 +322,6 @@ class Addon(AddonModel):
"""Store user boot options.""" """Store user boot options."""
self.persist[ATTR_BOOT] = value self.persist[ATTR_BOOT] = value
# Dismiss boot failed issue if present and boot at start disabled
if (
value == AddonBoot.MANUAL
and self._boot_failed_issue in self.sys_resolution.issues
):
self.sys_resolution.dismiss_issue(self._boot_failed_issue)
@property @property
def auto_update(self) -> bool: def auto_update(self) -> bool:
"""Return if auto update is enable.""" """Return if auto update is enable."""
@@ -1208,25 +1169,6 @@ class Addon(AddonModel):
await self._backup_command(self.backup_post) await self._backup_command(self.backup_post)
return None return None
def _is_excluded_by_filter(
self, origin_path: Path, arcname: str, item_arcpath: PurePath
) -> bool:
"""Filter out files from backup based on filters provided by addon developer.
This tests the dev provided filters against the full path of the file as
Supervisor sees them using match. This is done for legacy reasons, testing
against the relative path makes more sense and may be changed in the future.
"""
full_path = origin_path / item_arcpath.relative_to(arcname)
for exclude in self.backup_exclude:
if not full_path.match(exclude):
continue
_LOGGER.debug("Ignoring %s because of %s", full_path, exclude)
return True
return False
@Job( @Job(
name="addon_backup", name="addon_backup",
limit=JobExecutionLimit.GROUP_ONCE, limit=JobExecutionLimit.GROUP_ONCE,
@@ -1286,9 +1228,7 @@ class Addon(AddonModel):
atomic_contents_add( atomic_contents_add(
backup, backup,
self.path_data, self.path_data,
file_filter=partial( excludes=self.backup_exclude,
self._is_excluded_by_filter, self.path_data, "data"
),
arcname="data", arcname="data",
) )
@@ -1297,9 +1237,7 @@ class Addon(AddonModel):
atomic_contents_add( atomic_contents_add(
backup, backup,
self.path_config, self.path_config,
file_filter=partial( excludes=self.backup_exclude,
self._is_excluded_by_filter, self.path_config, "config"
),
arcname="config", arcname="config",
) )

View File

@@ -7,22 +7,24 @@ import logging
import tarfile import tarfile
from typing import Union from typing import Union
from attr import evolve
from ..const import AddonBoot, AddonStartup, AddonState from ..const import AddonBoot, AddonStartup, AddonState
from ..coresys import CoreSys, CoreSysAttributes from ..coresys import CoreSys, CoreSysAttributes
from ..exceptions import ( from ..exceptions import (
AddonConfigurationError,
AddonsError, AddonsError,
AddonsJobError, AddonsJobError,
AddonsNotSupportedError, AddonsNotSupportedError,
CoreDNSError, CoreDNSError,
DockerAPIError,
DockerError, DockerError,
DockerNotFound,
HassioError, HassioError,
HomeAssistantAPIError, HomeAssistantAPIError,
) )
from ..jobs.decorator import Job, JobCondition from ..jobs.decorator import Job, JobCondition
from ..resolution.const import ContextType, IssueType, SuggestionType from ..resolution.const import ContextType, IssueType, SuggestionType
from ..store.addon import AddonStore from ..store.addon import AddonStore
from ..utils import check_exception_chain
from ..utils.sentry import capture_exception from ..utils.sentry import capture_exception
from .addon import Addon from .addon import Addon
from .const import ADDON_UPDATE_CONDITIONS from .const import ADDON_UPDATE_CONDITIONS
@@ -116,14 +118,15 @@ class AddonManager(CoreSysAttributes):
try: try:
if start_task := await addon.start(): if start_task := await addon.start():
wait_boot.append(start_task) wait_boot.append(start_task)
except AddonsError as err:
# Check if there is an system/user issue
if check_exception_chain(
err, (DockerAPIError, DockerNotFound, AddonConfigurationError)
):
addon.boot = AddonBoot.MANUAL
addon.save_persist()
except HassioError: except HassioError:
self.sys_resolution.add_issue( pass # These are already handled
evolve(addon.boot_failed_issue),
suggestions=[
SuggestionType.EXECUTE_START,
SuggestionType.DISABLE_BOOT,
],
)
else: else:
continue continue
@@ -132,19 +135,6 @@ class AddonManager(CoreSysAttributes):
# Ignore exceptions from waiting for addon startup, addon errors handled elsewhere # Ignore exceptions from waiting for addon startup, addon errors handled elsewhere
await asyncio.gather(*wait_boot, return_exceptions=True) await asyncio.gather(*wait_boot, return_exceptions=True)
# After waiting for startup, create an issue for boot addons that are error or unknown state
# Ignore stopped as single shot addons can be run at boot and this is successful exit
# Timeout waiting for startup is not a failure, addon is probably just slow
for addon in tasks:
if addon.state in {AddonState.ERROR, AddonState.UNKNOWN}:
self.sys_resolution.add_issue(
evolve(addon.boot_failed_issue),
suggestions=[
SuggestionType.EXECUTE_START,
SuggestionType.DISABLE_BOOT,
],
)
async def shutdown(self, stage: AddonStartup) -> None: async def shutdown(self, stage: AddonStartup) -> None:
"""Shutdown addons.""" """Shutdown addons."""
tasks: list[Addon] = [] tasks: list[Addon] = []

View File

@@ -47,7 +47,7 @@ from ..const import (
ATTR_JOURNALD, ATTR_JOURNALD,
ATTR_KERNEL_MODULES, ATTR_KERNEL_MODULES,
ATTR_LEGACY, ATTR_LEGACY,
ATTR_LOCATION, ATTR_LOCATON,
ATTR_MACHINE, ATTR_MACHINE,
ATTR_MAP, ATTR_MAP,
ATTR_NAME, ATTR_NAME,
@@ -581,7 +581,7 @@ class AddonModel(JobGroup, ABC):
@property @property
def path_location(self) -> Path: def path_location(self) -> Path:
"""Return path to this add-on.""" """Return path to this add-on."""
return Path(self.data[ATTR_LOCATION]) return Path(self.data[ATTR_LOCATON])
@property @property
def path_icon(self) -> Path: def path_icon(self) -> Path:

View File

@@ -46,7 +46,6 @@ def rating_security(addon: AddonModel) -> int:
privilege in addon.privileged privilege in addon.privileged
for privilege in ( for privilege in (
Capabilities.BPF, Capabilities.BPF,
Capabilities.CHECKPOINT_RESTORE,
Capabilities.DAC_READ_SEARCH, Capabilities.DAC_READ_SEARCH,
Capabilities.NET_ADMIN, Capabilities.NET_ADMIN,
Capabilities.NET_RAW, Capabilities.NET_RAW,

View File

@@ -55,7 +55,7 @@ from ..const import (
ATTR_KERNEL_MODULES, ATTR_KERNEL_MODULES,
ATTR_LABELS, ATTR_LABELS,
ATTR_LEGACY, ATTR_LEGACY,
ATTR_LOCATION, ATTR_LOCATON,
ATTR_MACHINE, ATTR_MACHINE,
ATTR_MAP, ATTR_MAP,
ATTR_NAME, ATTR_NAME,
@@ -483,7 +483,7 @@ SCHEMA_ADDON_SYSTEM = vol.All(
_migrate_addon_config(), _migrate_addon_config(),
_SCHEMA_ADDON_CONFIG.extend( _SCHEMA_ADDON_CONFIG.extend(
{ {
vol.Required(ATTR_LOCATION): str, vol.Required(ATTR_LOCATON): str,
vol.Required(ATTR_REPOSITORY): str, vol.Required(ATTR_REPOSITORY): str,
vol.Required(ATTR_TRANSLATIONS, default=dict): { vol.Required(ATTR_TRANSLATIONS, default=dict): {
str: SCHEMA_ADDON_TRANSLATIONS str: SCHEMA_ADDON_TRANSLATIONS

View File

@@ -413,7 +413,6 @@ class RestAPI(CoreSysAttributes):
# No need to capture HostNotSupportedError to Sentry, the cause # No need to capture HostNotSupportedError to Sentry, the cause
# is known and reported to the user using the resolution center. # is known and reported to the user using the resolution center.
capture_exception(err) capture_exception(err)
kwargs.pop("follow", None) # Follow is not supported for Docker logs
return await api_supervisor.logs(*args, **kwargs) return await api_supervisor.logs(*args, **kwargs)
self.webapp.add_routes( self.webapp.add_routes(

View File

@@ -106,7 +106,6 @@ from ..exceptions import (
APIAddonNotInstalled, APIAddonNotInstalled,
APIError, APIError,
APIForbidden, APIForbidden,
APINotFound,
PwnedError, PwnedError,
PwnedSecret, PwnedSecret,
) )
@@ -162,7 +161,7 @@ class APIAddons(CoreSysAttributes):
addon = self.sys_addons.get(addon_slug) addon = self.sys_addons.get(addon_slug)
if not addon: if not addon:
raise APINotFound(f"Addon {addon_slug} does not exist") raise APIError(f"Addon {addon_slug} does not exist")
if not isinstance(addon, Addon) or not addon.is_installed: if not isinstance(addon, Addon) or not addon.is_installed:
raise APIAddonNotInstalled("Addon is not installed") raise APIAddonNotInstalled("Addon is not installed")

View File

@@ -1,7 +1,5 @@
"""Backups RESTful API.""" """Backups RESTful API."""
from __future__ import annotations
import asyncio import asyncio
from collections.abc import Callable from collections.abc import Callable
import errno import errno
@@ -16,7 +14,6 @@ from aiohttp.hdrs import CONTENT_DISPOSITION
import voluptuous as vol import voluptuous as vol
from ..backups.backup import Backup from ..backups.backup import Backup
from ..backups.const import LOCATION_CLOUD_BACKUP, LOCATION_TYPE
from ..backups.validate import ALL_FOLDERS, FOLDER_HOMEASSISTANT, days_until_stale from ..backups.validate import ALL_FOLDERS, FOLDER_HOMEASSISTANT, days_until_stale
from ..const import ( from ..const import (
ATTR_ADDONS, ATTR_ADDONS,
@@ -25,12 +22,10 @@ from ..const import (
ATTR_CONTENT, ATTR_CONTENT,
ATTR_DATE, ATTR_DATE,
ATTR_DAYS_UNTIL_STALE, ATTR_DAYS_UNTIL_STALE,
ATTR_EXTRA,
ATTR_FOLDERS, ATTR_FOLDERS,
ATTR_HOMEASSISTANT, ATTR_HOMEASSISTANT,
ATTR_HOMEASSISTANT_EXCLUDE_DATABASE, ATTR_HOMEASSISTANT_EXCLUDE_DATABASE,
ATTR_JOB_ID, ATTR_LOCATON,
ATTR_LOCATION,
ATTR_NAME, ATTR_NAME,
ATTR_PASSWORD, ATTR_PASSWORD,
ATTR_PROTECTED, ATTR_PROTECTED,
@@ -41,49 +36,30 @@ from ..const import (
ATTR_TIMEOUT, ATTR_TIMEOUT,
ATTR_TYPE, ATTR_TYPE,
ATTR_VERSION, ATTR_VERSION,
REQUEST_FROM,
BusEvent, BusEvent,
CoreState, CoreState,
) )
from ..coresys import CoreSysAttributes from ..coresys import CoreSysAttributes
from ..exceptions import APIError, APIForbidden, APINotFound from ..exceptions import APIError
from ..jobs import JobSchedulerOptions from ..jobs import JobSchedulerOptions
from ..mounts.const import MountUsage from ..mounts.const import MountUsage
from ..resolution.const import UnhealthyReason from ..resolution.const import UnhealthyReason
from .const import ( from .const import ATTR_BACKGROUND, ATTR_JOB_ID, CONTENT_TYPE_TAR
ATTR_ADDITIONAL_LOCATIONS,
ATTR_BACKGROUND,
ATTR_LOCATIONS,
ATTR_SIZE_BYTES,
CONTENT_TYPE_TAR,
)
from .utils import api_process, api_validate from .utils import api_process, api_validate
_LOGGER: logging.Logger = logging.getLogger(__name__) _LOGGER: logging.Logger = logging.getLogger(__name__)
ALL_ADDONS_FLAG = "ALL"
RE_SLUGIFY_NAME = re.compile(r"[^A-Za-z0-9]+") RE_SLUGIFY_NAME = re.compile(r"[^A-Za-z0-9]+")
RE_BACKUP_FILENAME = re.compile(r"^[^\\\/]+\.tar$")
# Backwards compatible # Backwards compatible
# Remove: 2022.08 # Remove: 2022.08
_ALL_FOLDERS = ALL_FOLDERS + [FOLDER_HOMEASSISTANT] _ALL_FOLDERS = ALL_FOLDERS + [FOLDER_HOMEASSISTANT]
def _ensure_list(item: Any) -> list:
"""Ensure value is a list."""
if not isinstance(item, list):
return [item]
return item
# pylint: disable=no-value-for-parameter # pylint: disable=no-value-for-parameter
SCHEMA_RESTORE_FULL = vol.Schema( SCHEMA_RESTORE_FULL = vol.Schema(
{ {
vol.Optional(ATTR_PASSWORD): vol.Maybe(str), vol.Optional(ATTR_PASSWORD): vol.Maybe(str),
vol.Optional(ATTR_BACKGROUND, default=False): vol.Boolean(), vol.Optional(ATTR_BACKGROUND, default=False): vol.Boolean(),
vol.Optional(ATTR_LOCATION): vol.Maybe(str),
} }
) )
@@ -100,20 +76,15 @@ SCHEMA_BACKUP_FULL = vol.Schema(
vol.Optional(ATTR_NAME): str, vol.Optional(ATTR_NAME): str,
vol.Optional(ATTR_PASSWORD): vol.Maybe(str), vol.Optional(ATTR_PASSWORD): vol.Maybe(str),
vol.Optional(ATTR_COMPRESSED): vol.Maybe(vol.Boolean()), vol.Optional(ATTR_COMPRESSED): vol.Maybe(vol.Boolean()),
vol.Optional(ATTR_LOCATION): vol.All( vol.Optional(ATTR_LOCATON): vol.Maybe(str),
_ensure_list, [vol.Maybe(str)], vol.Unique()
),
vol.Optional(ATTR_HOMEASSISTANT_EXCLUDE_DATABASE): vol.Boolean(), vol.Optional(ATTR_HOMEASSISTANT_EXCLUDE_DATABASE): vol.Boolean(),
vol.Optional(ATTR_BACKGROUND, default=False): vol.Boolean(), vol.Optional(ATTR_BACKGROUND, default=False): vol.Boolean(),
vol.Optional(ATTR_EXTRA): dict,
} }
) )
SCHEMA_BACKUP_PARTIAL = SCHEMA_BACKUP_FULL.extend( SCHEMA_BACKUP_PARTIAL = SCHEMA_BACKUP_FULL.extend(
{ {
vol.Optional(ATTR_ADDONS): vol.Or( vol.Optional(ATTR_ADDONS): vol.All([str], vol.Unique()),
ALL_ADDONS_FLAG, vol.All([str], vol.Unique())
),
vol.Optional(ATTR_FOLDERS): vol.All([vol.In(_ALL_FOLDERS)], vol.Unique()), vol.Optional(ATTR_FOLDERS): vol.All([vol.In(_ALL_FOLDERS)], vol.Unique()),
vol.Optional(ATTR_HOMEASSISTANT): vol.Boolean(), vol.Optional(ATTR_HOMEASSISTANT): vol.Boolean(),
} }
@@ -131,14 +102,6 @@ SCHEMA_FREEZE = vol.Schema(
} }
) )
SCHEMA_REMOVE = vol.Schema(
{
vol.Optional(ATTR_LOCATION): vol.All(
_ensure_list, [vol.Maybe(str)], vol.Unique()
),
}
)
class APIBackups(CoreSysAttributes): class APIBackups(CoreSysAttributes):
"""Handle RESTful API for backups functions.""" """Handle RESTful API for backups functions."""
@@ -147,7 +110,7 @@ class APIBackups(CoreSysAttributes):
"""Return backup, throw an exception if it doesn't exist.""" """Return backup, throw an exception if it doesn't exist."""
backup = self.sys_backups.get(request.match_info.get("slug")) backup = self.sys_backups.get(request.match_info.get("slug"))
if not backup: if not backup:
raise APINotFound("Backup does not exist") raise APIError("Backup does not exist")
return backup return backup
def _list_backups(self): def _list_backups(self):
@@ -159,9 +122,7 @@ class APIBackups(CoreSysAttributes):
ATTR_DATE: backup.date, ATTR_DATE: backup.date,
ATTR_TYPE: backup.sys_type, ATTR_TYPE: backup.sys_type,
ATTR_SIZE: backup.size, ATTR_SIZE: backup.size,
ATTR_SIZE_BYTES: backup.size_bytes, ATTR_LOCATON: backup.location,
ATTR_LOCATION: backup.location,
ATTR_LOCATIONS: backup.locations,
ATTR_PROTECTED: backup.protected, ATTR_PROTECTED: backup.protected,
ATTR_COMPRESSED: backup.compressed, ATTR_COMPRESSED: backup.compressed,
ATTR_CONTENT: { ATTR_CONTENT: {
@@ -171,7 +132,6 @@ class APIBackups(CoreSysAttributes):
}, },
} }
for backup in self.sys_backups.list_backups for backup in self.sys_backups.list_backups
if backup.location != LOCATION_CLOUD_BACKUP
] ]
@api_process @api_process
@@ -231,52 +191,30 @@ class APIBackups(CoreSysAttributes):
ATTR_NAME: backup.name, ATTR_NAME: backup.name,
ATTR_DATE: backup.date, ATTR_DATE: backup.date,
ATTR_SIZE: backup.size, ATTR_SIZE: backup.size,
ATTR_SIZE_BYTES: backup.size_bytes,
ATTR_COMPRESSED: backup.compressed, ATTR_COMPRESSED: backup.compressed,
ATTR_PROTECTED: backup.protected, ATTR_PROTECTED: backup.protected,
ATTR_SUPERVISOR_VERSION: backup.supervisor_version, ATTR_SUPERVISOR_VERSION: backup.supervisor_version,
ATTR_HOMEASSISTANT: backup.homeassistant_version, ATTR_HOMEASSISTANT: backup.homeassistant_version,
ATTR_LOCATION: backup.location, ATTR_LOCATON: backup.location,
ATTR_LOCATIONS: backup.locations,
ATTR_ADDONS: data_addons, ATTR_ADDONS: data_addons,
ATTR_REPOSITORIES: backup.repositories, ATTR_REPOSITORIES: backup.repositories,
ATTR_FOLDERS: backup.folders, ATTR_FOLDERS: backup.folders,
ATTR_HOMEASSISTANT_EXCLUDE_DATABASE: backup.homeassistant_exclude_database, ATTR_HOMEASSISTANT_EXCLUDE_DATABASE: backup.homeassistant_exclude_database,
ATTR_EXTRA: backup.extra,
} }
def _location_to_mount(self, location: str | None) -> LOCATION_TYPE: def _location_to_mount(self, body: dict[str, Any]) -> dict[str, Any]:
"""Convert a single location to a mount if possible."""
if not location or location == LOCATION_CLOUD_BACKUP:
return location
mount = self.sys_mounts.get(location)
if mount.usage != MountUsage.BACKUP:
raise APIError(
f"Mount {mount.name} is not used for backups, cannot backup to there"
)
return mount
def _location_field_to_mount(self, body: dict[str, Any]) -> dict[str, Any]:
"""Change location field to mount if necessary.""" """Change location field to mount if necessary."""
body[ATTR_LOCATION] = self._location_to_mount(body.get(ATTR_LOCATION)) if not body.get(ATTR_LOCATON):
return body return body
def _validate_cloud_backup_location( body[ATTR_LOCATON] = self.sys_mounts.get(body[ATTR_LOCATON])
self, request: web.Request, location: list[str | None] | str | None if body[ATTR_LOCATON].usage != MountUsage.BACKUP:
) -> None: raise APIError(
"""Cloud backup location is only available to Home Assistant.""" f"Mount {body[ATTR_LOCATON].name} is not used for backups, cannot backup to there"
if not isinstance(location, list):
location = [location]
if (
LOCATION_CLOUD_BACKUP in location
and request.get(REQUEST_FROM) != self.sys_homeassistant
):
raise APIForbidden(
f"Location {LOCATION_CLOUD_BACKUP} is only available for Home Assistant"
) )
return body
async def _background_backup_task( async def _background_backup_task(
self, backup_method: Callable, *args, **kwargs self, backup_method: Callable, *args, **kwargs
) -> tuple[asyncio.Task, str]: ) -> tuple[asyncio.Task, str]:
@@ -296,42 +234,24 @@ class APIBackups(CoreSysAttributes):
BusEvent.SUPERVISOR_STATE_CHANGE, release_on_freeze BusEvent.SUPERVISOR_STATE_CHANGE, release_on_freeze
) )
try: try:
event_task = self.sys_create_task(event.wait()) await asyncio.wait(
_, pending = await asyncio.wait(
( (
backup_task, backup_task,
event_task, self.sys_create_task(event.wait()),
), ),
return_when=asyncio.FIRST_COMPLETED, return_when=asyncio.FIRST_COMPLETED,
) )
# It seems backup returned early (error or something), make sure to cancel
# the event task to avoid "Task was destroyed but it is pending!" errors.
if event_task in pending:
event_task.cancel()
return (backup_task, job.uuid) return (backup_task, job.uuid)
finally: finally:
self.sys_bus.remove_listener(listener) self.sys_bus.remove_listener(listener)
@api_process @api_process
async def backup_full(self, request: web.Request): async def backup_full(self, request):
"""Create full backup.""" """Create full backup."""
body = await api_validate(SCHEMA_BACKUP_FULL, request) body = await api_validate(SCHEMA_BACKUP_FULL, request)
locations: list[LOCATION_TYPE] | None = None
if ATTR_LOCATION in body:
location_names: list[str | None] = body.pop(ATTR_LOCATION)
self._validate_cloud_backup_location(request, location_names)
locations = [
self._location_to_mount(location) for location in location_names
]
body[ATTR_LOCATION] = locations.pop(0)
if locations:
body[ATTR_ADDITIONAL_LOCATIONS] = locations
background = body.pop(ATTR_BACKGROUND) background = body.pop(ATTR_BACKGROUND)
backup_task, job_id = await self._background_backup_task( backup_task, job_id = await self._background_backup_task(
self.sys_backups.do_backup_full, **body self.sys_backups.do_backup_full, **self._location_to_mount(body)
) )
if background and not backup_task.done(): if background and not backup_task.done():
@@ -346,28 +266,12 @@ class APIBackups(CoreSysAttributes):
) )
@api_process @api_process
async def backup_partial(self, request: web.Request): async def backup_partial(self, request):
"""Create a partial backup.""" """Create a partial backup."""
body = await api_validate(SCHEMA_BACKUP_PARTIAL, request) body = await api_validate(SCHEMA_BACKUP_PARTIAL, request)
locations: list[LOCATION_TYPE] | None = None
if ATTR_LOCATION in body:
location_names: list[str | None] = body.pop(ATTR_LOCATION)
self._validate_cloud_backup_location(request, location_names)
locations = [
self._location_to_mount(location) for location in location_names
]
body[ATTR_LOCATION] = locations.pop(0)
if locations:
body[ATTR_ADDITIONAL_LOCATIONS] = locations
if body.get(ATTR_ADDONS) == ALL_ADDONS_FLAG:
body[ATTR_ADDONS] = list(self.sys_addons.local)
background = body.pop(ATTR_BACKGROUND) background = body.pop(ATTR_BACKGROUND)
backup_task, job_id = await self._background_backup_task( backup_task, job_id = await self._background_backup_task(
self.sys_backups.do_backup_partial, **body self.sys_backups.do_backup_partial, **self._location_to_mount(body)
) )
if background and not backup_task.done(): if background and not backup_task.done():
@@ -382,13 +286,10 @@ class APIBackups(CoreSysAttributes):
) )
@api_process @api_process
async def restore_full(self, request: web.Request): async def restore_full(self, request):
"""Full restore of a backup.""" """Full restore of a backup."""
backup = self._extract_slug(request) backup = self._extract_slug(request)
body = await api_validate(SCHEMA_RESTORE_FULL, request) body = await api_validate(SCHEMA_RESTORE_FULL, request)
self._validate_cloud_backup_location(
request, body.get(ATTR_LOCATION, backup.location)
)
background = body.pop(ATTR_BACKGROUND) background = body.pop(ATTR_BACKGROUND)
restore_task, job_id = await self._background_backup_task( restore_task, job_id = await self._background_backup_task(
self.sys_backups.do_restore_full, backup, **body self.sys_backups.do_restore_full, backup, **body
@@ -402,13 +303,10 @@ class APIBackups(CoreSysAttributes):
) )
@api_process @api_process
async def restore_partial(self, request: web.Request): async def restore_partial(self, request):
"""Partial restore a backup.""" """Partial restore a backup."""
backup = self._extract_slug(request) backup = self._extract_slug(request)
body = await api_validate(SCHEMA_RESTORE_PARTIAL, request) body = await api_validate(SCHEMA_RESTORE_PARTIAL, request)
self._validate_cloud_backup_location(
request, body.get(ATTR_LOCATION, backup.location)
)
background = body.pop(ATTR_BACKGROUND) background = body.pop(ATTR_BACKGROUND)
restore_task, job_id = await self._background_backup_task( restore_task, job_id = await self._background_backup_task(
self.sys_backups.do_restore_partial, backup, **body self.sys_backups.do_restore_partial, backup, **body
@@ -422,43 +320,28 @@ class APIBackups(CoreSysAttributes):
) )
@api_process @api_process
async def freeze(self, request: web.Request): async def freeze(self, request):
"""Initiate manual freeze for external backup.""" """Initiate manual freeze for external backup."""
body = await api_validate(SCHEMA_FREEZE, request) body = await api_validate(SCHEMA_FREEZE, request)
await asyncio.shield(self.sys_backups.freeze_all(**body)) await asyncio.shield(self.sys_backups.freeze_all(**body))
@api_process @api_process
async def thaw(self, request: web.Request): async def thaw(self, request):
"""Begin thaw after manual freeze.""" """Begin thaw after manual freeze."""
await self.sys_backups.thaw_all() await self.sys_backups.thaw_all()
@api_process @api_process
async def remove(self, request: web.Request): async def remove(self, request):
"""Remove a backup.""" """Remove a backup."""
backup = self._extract_slug(request) backup = self._extract_slug(request)
body = await api_validate(SCHEMA_REMOVE, request) return self.sys_backups.remove(backup)
locations: list[LOCATION_TYPE] | None = None
if ATTR_LOCATION in body: async def download(self, request):
self._validate_cloud_backup_location(request, body[ATTR_LOCATION])
locations = [self._location_to_mount(name) for name in body[ATTR_LOCATION]]
else:
self._validate_cloud_backup_location(request, backup.location)
return self.sys_backups.remove(backup, locations=locations)
@api_process
async def download(self, request: web.Request):
"""Download a backup file.""" """Download a backup file."""
backup = self._extract_slug(request) backup = self._extract_slug(request)
# Query will give us '' for /backups, convert value to None
location = request.query.get(ATTR_LOCATION, backup.location) or None
self._validate_cloud_backup_location(request, location)
if location not in backup.all_locations:
raise APIError(f"Backup {backup.slug} is not in location {location}")
_LOGGER.info("Downloading backup %s", backup.slug) _LOGGER.info("Downloading backup %s", backup.slug)
response = web.FileResponse(backup.all_locations[location]) response = web.FileResponse(backup.tarfile)
response.content_type = CONTENT_TYPE_TAR response.content_type = CONTENT_TYPE_TAR
response.headers[CONTENT_DISPOSITION] = ( response.headers[CONTENT_DISPOSITION] = (
f"attachment; filename={RE_SLUGIFY_NAME.sub('_', backup.name)}.tar" f"attachment; filename={RE_SLUGIFY_NAME.sub('_', backup.name)}.tar"
@@ -466,25 +349,9 @@ class APIBackups(CoreSysAttributes):
return response return response
@api_process @api_process
async def upload(self, request: web.Request): async def upload(self, request):
"""Upload a backup file.""" """Upload a backup file."""
location: LOCATION_TYPE = None with TemporaryDirectory(dir=str(self.sys_config.path_tmp)) as temp_dir:
locations: list[LOCATION_TYPE] | None = None
tmp_path = self.sys_config.path_tmp
if ATTR_LOCATION in request.query:
location_names: list[str] = request.query.getall(ATTR_LOCATION)
self._validate_cloud_backup_location(request, location_names)
# Convert empty string to None if necessary
locations = [
self._location_to_mount(location) if location else None
for location in location_names
]
location = locations.pop(0)
if location and location != LOCATION_CLOUD_BACKUP:
tmp_path = location.local_where
with TemporaryDirectory(dir=tmp_path.as_posix()) as temp_dir:
tar_file = Path(temp_dir, "backup.tar") tar_file = Path(temp_dir, "backup.tar")
reader = await request.multipart() reader = await request.multipart()
contents = await reader.next() contents = await reader.next()
@@ -497,10 +364,7 @@ class APIBackups(CoreSysAttributes):
backup.write(chunk) backup.write(chunk)
except OSError as err: except OSError as err:
if err.errno == errno.EBADMSG and location in { if err.errno == errno.EBADMSG:
LOCATION_CLOUD_BACKUP,
None,
}:
self.sys_resolution.unhealthy = UnhealthyReason.OSERROR_BAD_MESSAGE self.sys_resolution.unhealthy = UnhealthyReason.OSERROR_BAD_MESSAGE
_LOGGER.error("Can't write new backup file: %s", err) _LOGGER.error("Can't write new backup file: %s", err)
return False return False
@@ -508,11 +372,7 @@ class APIBackups(CoreSysAttributes):
except asyncio.CancelledError: except asyncio.CancelledError:
return False return False
backup = await asyncio.shield( backup = await asyncio.shield(self.sys_backups.import_backup(tar_file))
self.sys_backups.import_backup(
tar_file, location=location, additional_locations=locations
)
)
if backup: if backup:
return {ATTR_SLUG: backup.slug} return {ATTR_SLUG: backup.slug}

View File

@@ -12,7 +12,6 @@ CONTENT_TYPE_X_LOG = "text/x-log"
COOKIE_INGRESS = "ingress_session" COOKIE_INGRESS = "ingress_session"
ATTR_ADDITIONAL_LOCATIONS = "additional_locations"
ATTR_AGENT_VERSION = "agent_version" ATTR_AGENT_VERSION = "agent_version"
ATTR_APPARMOR_VERSION = "apparmor_version" ATTR_APPARMOR_VERSION = "apparmor_version"
ATTR_ATTRIBUTES = "attributes" ATTR_ATTRIBUTES = "attributes"
@@ -43,11 +42,11 @@ ATTR_GROUP_IDS = "group_ids"
ATTR_IDENTIFIERS = "identifiers" ATTR_IDENTIFIERS = "identifiers"
ATTR_IS_ACTIVE = "is_active" ATTR_IS_ACTIVE = "is_active"
ATTR_IS_OWNER = "is_owner" ATTR_IS_OWNER = "is_owner"
ATTR_JOB_ID = "job_id"
ATTR_JOBS = "jobs" ATTR_JOBS = "jobs"
ATTR_LLMNR = "llmnr" ATTR_LLMNR = "llmnr"
ATTR_LLMNR_HOSTNAME = "llmnr_hostname" ATTR_LLMNR_HOSTNAME = "llmnr_hostname"
ATTR_LOCAL_ONLY = "local_only" ATTR_LOCAL_ONLY = "local_only"
ATTR_LOCATIONS = "locations"
ATTR_MDNS = "mdns" ATTR_MDNS = "mdns"
ATTR_MODEL = "model" ATTR_MODEL = "model"
ATTR_MOUNTS = "mounts" ATTR_MOUNTS = "mounts"
@@ -59,7 +58,6 @@ ATTR_REVISION = "revision"
ATTR_SAFE_MODE = "safe_mode" ATTR_SAFE_MODE = "safe_mode"
ATTR_SEAT = "seat" ATTR_SEAT = "seat"
ATTR_SIGNED = "signed" ATTR_SIGNED = "signed"
ATTR_SIZE_BYTES = "size_bytes"
ATTR_STARTUP_TIME = "startup_time" ATTR_STARTUP_TIME = "startup_time"
ATTR_STATUS = "status" ATTR_STATUS = "status"
ATTR_SUBSYSTEM = "subsystem" ATTR_SUBSYSTEM = "subsystem"
@@ -70,7 +68,6 @@ ATTR_UPDATE_TYPE = "update_type"
ATTR_USAGE = "usage" ATTR_USAGE = "usage"
ATTR_USE_NTP = "use_ntp" ATTR_USE_NTP = "use_ntp"
ATTR_USERS = "users" ATTR_USERS = "users"
ATTR_USER_PATH = "user_path"
ATTR_VENDOR = "vendor" ATTR_VENDOR = "vendor"
ATTR_VIRTUALIZATION = "virtualization" ATTR_VIRTUALIZATION = "virtualization"

View File

@@ -16,7 +16,7 @@ from ..const import (
AddonState, AddonState,
) )
from ..coresys import CoreSysAttributes from ..coresys import CoreSysAttributes
from ..exceptions import APIForbidden, APINotFound from ..exceptions import APIError, APIForbidden
from .utils import api_process, api_validate, require_home_assistant from .utils import api_process, api_validate, require_home_assistant
_LOGGER: logging.Logger = logging.getLogger(__name__) _LOGGER: logging.Logger = logging.getLogger(__name__)
@@ -36,7 +36,7 @@ class APIDiscovery(CoreSysAttributes):
"""Extract discovery message from URL.""" """Extract discovery message from URL."""
message = self.sys_discovery.get(request.match_info.get("uuid")) message = self.sys_discovery.get(request.match_info.get("uuid"))
if not message: if not message:
raise APINotFound("Discovery message not found") raise APIError("Discovery message not found")
return message return message
@api_process @api_process

View File

@@ -16,7 +16,6 @@ from ..const import (
ATTR_VERSION, ATTR_VERSION,
) )
from ..coresys import CoreSysAttributes from ..coresys import CoreSysAttributes
from ..exceptions import APINotFound
from .utils import api_process, api_validate from .utils import api_process, api_validate
_LOGGER: logging.Logger = logging.getLogger(__name__) _LOGGER: logging.Logger = logging.getLogger(__name__)
@@ -59,9 +58,6 @@ class APIDocker(CoreSysAttributes):
async def remove_registry(self, request: web.Request): async def remove_registry(self, request: web.Request):
"""Delete a docker registry.""" """Delete a docker registry."""
hostname = request.match_info.get(ATTR_HOSTNAME) hostname = request.match_info.get(ATTR_HOSTNAME)
if hostname not in self.sys_docker.config.registries:
raise APINotFound(f"Hostname {hostname} does not exist in registries")
del self.sys_docker.config.registries[hostname] del self.sys_docker.config.registries[hostname]
self.sys_docker.config.save_data() self.sys_docker.config.save_data()

View File

@@ -4,7 +4,7 @@ import asyncio
from contextlib import suppress from contextlib import suppress
import logging import logging
from aiohttp import ClientConnectionResetError, web from aiohttp import web
from aiohttp.hdrs import ACCEPT, RANGE from aiohttp.hdrs import ACCEPT, RANGE
import voluptuous as vol import voluptuous as vol
from voluptuous.error import CoerceInvalid from voluptuous.error import CoerceInvalid
@@ -258,13 +258,9 @@ class APIHost(CoreSysAttributes):
if not headers_returned: if not headers_returned:
if cursor: if cursor:
response.headers["X-First-Cursor"] = cursor response.headers["X-First-Cursor"] = cursor
response.headers["X-Accel-Buffering"] = "no"
await response.prepare(request) await response.prepare(request)
headers_returned = True headers_returned = True
# When client closes the connection while reading busy logs, we await response.write(line.encode("utf-8") + b"\n")
# sometimes get this exception. It should be safe to ignore it.
with suppress(ClientConnectionResetError):
await response.write(line.encode("utf-8") + b"\n")
except ConnectionResetError as ex: except ConnectionResetError as ex:
raise APIError( raise APIError(
"Connection reset when trying to fetch data from systemd-journald." "Connection reset when trying to fetch data from systemd-journald."

View File

@@ -277,7 +277,6 @@ class APIIngress(CoreSysAttributes):
response.content_type = content_type response.content_type = content_type
try: try:
response.headers["X-Accel-Buffering"] = "no"
await response.prepare(request) await response.prepare(request)
async for data in result.content.iter_chunked(4096): async for data in result.content.iter_chunked(4096):
await response.write(data) await response.write(data)

View File

@@ -7,7 +7,7 @@ from aiohttp import web
import voluptuous as vol import voluptuous as vol
from ..coresys import CoreSysAttributes from ..coresys import CoreSysAttributes
from ..exceptions import APIError, APINotFound, JobNotFound from ..exceptions import APIError
from ..jobs import SupervisorJob from ..jobs import SupervisorJob
from ..jobs.const import ATTR_IGNORE_CONDITIONS, JobCondition from ..jobs.const import ATTR_IGNORE_CONDITIONS, JobCondition
from .const import ATTR_JOBS from .const import ATTR_JOBS
@@ -23,24 +23,10 @@ SCHEMA_OPTIONS = vol.Schema(
class APIJobs(CoreSysAttributes): class APIJobs(CoreSysAttributes):
"""Handle RESTful API for OS functions.""" """Handle RESTful API for OS functions."""
def _extract_job(self, request: web.Request) -> SupervisorJob:
"""Extract job from request or raise."""
try:
return self.sys_jobs.get_job(request.match_info.get("uuid"))
except JobNotFound:
raise APINotFound("Job does not exist") from None
def _list_jobs(self, start: SupervisorJob | None = None) -> list[dict[str, Any]]: def _list_jobs(self, start: SupervisorJob | None = None) -> list[dict[str, Any]]:
"""Return current job tree. """Return current job tree."""
Jobs are added to cache as they are created so by default they are in oldest to newest.
This is correct ordering for child jobs as it makes logical sense to present those in
the order they occurred within the parent. For the list as a whole, sort from newest
to oldest as its likely any client is most interested in the newer ones.
"""
# Initially sort oldest to newest so all child lists end up in correct order
jobs_by_parent: dict[str | None, list[SupervisorJob]] = {} jobs_by_parent: dict[str | None, list[SupervisorJob]] = {}
for job in sorted(self.sys_jobs.jobs): for job in self.sys_jobs.jobs:
if job.internal: if job.internal:
continue continue
@@ -49,15 +35,11 @@ class APIJobs(CoreSysAttributes):
else: else:
jobs_by_parent[job.parent_id].append(job) jobs_by_parent[job.parent_id].append(job)
# After parent-child organization, sort the root jobs only from newest to oldest
job_list: list[dict[str, Any]] = [] job_list: list[dict[str, Any]] = []
queue: list[tuple[list[dict[str, Any]], SupervisorJob]] = ( queue: list[tuple[list[dict[str, Any]], SupervisorJob]] = (
[(job_list, start)] [(job_list, start)]
if start if start
else [ else [(job_list, job) for job in jobs_by_parent.get(None, [])]
(job_list, job)
for job in sorted(jobs_by_parent.get(None, []), reverse=True)
]
) )
while queue: while queue:
@@ -104,13 +86,13 @@ class APIJobs(CoreSysAttributes):
@api_process @api_process
async def job_info(self, request: web.Request) -> dict[str, Any]: async def job_info(self, request: web.Request) -> dict[str, Any]:
"""Get details of a job by ID.""" """Get details of a job by ID."""
job = self._extract_job(request) job = self.sys_jobs.get_job(request.match_info.get("uuid"))
return self._list_jobs(job)[0] return self._list_jobs(job)[0]
@api_process @api_process
async def remove_job(self, request: web.Request) -> None: async def remove_job(self, request: web.Request) -> None:
"""Remove a completed job.""" """Remove a completed job."""
job = self._extract_job(request) job = self.sys_jobs.get_job(request.match_info.get("uuid"))
if not job.done: if not job.done:
raise APIError(f"Job {job.uuid} is not done!") raise APIError(f"Job {job.uuid} is not done!")

View File

@@ -7,11 +7,11 @@ import voluptuous as vol
from ..const import ATTR_NAME, ATTR_STATE from ..const import ATTR_NAME, ATTR_STATE
from ..coresys import CoreSysAttributes from ..coresys import CoreSysAttributes
from ..exceptions import APIError, APINotFound from ..exceptions import APIError
from ..mounts.const import ATTR_DEFAULT_BACKUP_MOUNT, MountUsage from ..mounts.const import ATTR_DEFAULT_BACKUP_MOUNT, MountUsage
from ..mounts.mount import Mount from ..mounts.mount import Mount
from ..mounts.validate import SCHEMA_MOUNT_CONFIG from ..mounts.validate import SCHEMA_MOUNT_CONFIG
from .const import ATTR_MOUNTS, ATTR_USER_PATH from .const import ATTR_MOUNTS
from .utils import api_process, api_validate from .utils import api_process, api_validate
SCHEMA_OPTIONS = vol.Schema( SCHEMA_OPTIONS = vol.Schema(
@@ -24,13 +24,6 @@ SCHEMA_OPTIONS = vol.Schema(
class APIMounts(CoreSysAttributes): class APIMounts(CoreSysAttributes):
"""Handle REST API for mounting options.""" """Handle REST API for mounting options."""
def _extract_mount(self, request: web.Request) -> Mount:
"""Extract mount from request or raise."""
name = request.match_info.get("mount")
if name not in self.sys_mounts:
raise APINotFound(f"No mount exists with name {name}")
return self.sys_mounts.get(name)
@api_process @api_process
async def info(self, request: web.Request) -> dict[str, Any]: async def info(self, request: web.Request) -> dict[str, Any]:
"""Return MountManager info.""" """Return MountManager info."""
@@ -39,13 +32,7 @@ class APIMounts(CoreSysAttributes):
if self.sys_mounts.default_backup_mount if self.sys_mounts.default_backup_mount
else None, else None,
ATTR_MOUNTS: [ ATTR_MOUNTS: [
mount.to_dict() mount.to_dict() | {ATTR_STATE: mount.state}
| {
ATTR_STATE: mount.state,
ATTR_USER_PATH: mount.container_where.as_posix()
if mount.container_where
else None,
}
for mount in self.sys_mounts.mounts for mount in self.sys_mounts.mounts
], ],
} }
@@ -92,13 +79,15 @@ class APIMounts(CoreSysAttributes):
@api_process @api_process
async def update_mount(self, request: web.Request) -> None: async def update_mount(self, request: web.Request) -> None:
"""Update an existing mount in supervisor.""" """Update an existing mount in supervisor."""
current = self._extract_mount(request) name = request.match_info.get("mount")
name_schema = vol.Schema( name_schema = vol.Schema(
{vol.Optional(ATTR_NAME, default=current.name): current.name}, {vol.Optional(ATTR_NAME, default=name): name}, extra=vol.ALLOW_EXTRA
extra=vol.ALLOW_EXTRA,
) )
body = await api_validate(vol.All(name_schema, SCHEMA_MOUNT_CONFIG), request) body = await api_validate(vol.All(name_schema, SCHEMA_MOUNT_CONFIG), request)
if name not in self.sys_mounts:
raise APIError(f"No mount exists with name {name}")
mount = Mount.from_dict(self.coresys, body) mount = Mount.from_dict(self.coresys, body)
await self.sys_mounts.create_mount(mount) await self.sys_mounts.create_mount(mount)
@@ -115,8 +104,8 @@ class APIMounts(CoreSysAttributes):
@api_process @api_process
async def delete_mount(self, request: web.Request) -> None: async def delete_mount(self, request: web.Request) -> None:
"""Delete an existing mount in supervisor.""" """Delete an existing mount in supervisor."""
current = self._extract_mount(request) name = request.match_info.get("mount")
mount = await self.sys_mounts.remove_mount(current.name) mount = await self.sys_mounts.remove_mount(name)
# If it was a backup mount, reload backups # If it was a backup mount, reload backups
if mount.usage == MountUsage.BACKUP: if mount.usage == MountUsage.BACKUP:
@@ -127,9 +116,9 @@ class APIMounts(CoreSysAttributes):
@api_process @api_process
async def reload_mount(self, request: web.Request) -> None: async def reload_mount(self, request: web.Request) -> None:
"""Reload an existing mount in supervisor.""" """Reload an existing mount in supervisor."""
mount = self._extract_mount(request) name = request.match_info.get("mount")
await self.sys_mounts.reload_mount(mount.name) await self.sys_mounts.reload_mount(name)
# If it's a backup mount, reload backups # If it's a backup mount, reload backups
if mount.usage == MountUsage.BACKUP: if self.sys_mounts.get(name).usage == MountUsage.BACKUP:
self.sys_create_task(self.sys_backups.reload()) self.sys_create_task(self.sys_backups.reload())

View File

@@ -42,7 +42,7 @@ from ..const import (
DOCKER_NETWORK_MASK, DOCKER_NETWORK_MASK,
) )
from ..coresys import CoreSysAttributes from ..coresys import CoreSysAttributes
from ..exceptions import APIError, APINotFound, HostNetworkNotFound from ..exceptions import APIError, HostNetworkNotFound
from ..host.configuration import ( from ..host.configuration import (
AccessPoint, AccessPoint,
Interface, Interface,
@@ -167,7 +167,7 @@ class APINetwork(CoreSysAttributes):
except HostNetworkNotFound: except HostNetworkNotFound:
pass pass
raise APINotFound(f"Interface {name} does not exist") from None raise APIError(f"Interface {name} does not exist") from None
@api_process @api_process
async def info(self, request: web.Request) -> dict[str, Any]: async def info(self, request: web.Request) -> dict[str, Any]:

View File

@@ -1 +1 @@
!function(){function d(d){var e=document.createElement("script");e.src=d,document.body.appendChild(e)}if(/Edge?\/(12[1-9]|1[3-9]\d|[2-9]\d{2}|\d{4,})\.\d+(\.\d+|)|Firefox\/(1{2}[5-9]|1[2-9]\d|[2-9]\d{2}|\d{4,})\.\d+(\.\d+|)|Chrom(ium|e)\/(109|1[1-9]\d|[2-9]\d{2}|\d{4,})\.\d+(\.\d+|)|(Maci|X1{2}).+ Version\/(17\.([3-9]|\d{2,})|(1[89]|[2-9]\d|\d{3,})\.\d+)([,.]\d+|)( \(\w+\)|)( Mobile\/\w+|) Safari\/|Chrome.+OPR\/(10[6-9]|1[1-9]\d|[2-9]\d{2}|\d{4,})\.\d+\.\d+|(CPU[ +]OS|iPhone[ +]OS|CPU[ +]iPhone|CPU IPhone OS|CPU iPad OS)[ +]+(15[._]([6-9]|\d{2,})|(1[6-9]|[2-9]\d|\d{3,})[._]\d+)([._]\d+|)|Android:?[ /-](12[1-9]|1[3-9]\d|[2-9]\d{2}|\d{4,})(\.\d+|)(\.\d+|)|Mobile Safari.+OPR\/([89]\d|\d{3,})\.\d+\.\d+|Android.+Firefox\/(12[1-9]|1[3-9]\d|[2-9]\d{2}|\d{4,})\.\d+(\.\d+|)|Android.+Chrom(ium|e)\/(12[1-9]|1[3-9]\d|[2-9]\d{2}|\d{4,})\.\d+(\.\d+|)|SamsungBrowser\/(2[4-9]|[3-9]\d|\d{3,})\.\d+|Home As{2}istant\/[\d.]+ \(.+; macOS (1[2-9]|[2-9]\d|\d{3,})\.\d+(\.\d+)?\)/.test(navigator.userAgent))try{new Function("import('/api/hassio/app/frontend_latest/entrypoint.553a7707b827808b.js')")()}catch(e){d("/api/hassio/app/frontend_es5/entrypoint.ff48ee24e0742761.js")}else d("/api/hassio/app/frontend_es5/entrypoint.ff48ee24e0742761.js")}() !function(){function n(n){var t=document.createElement("script");t.src=n,document.body.appendChild(t)}if(/.*Version\/(?:11|12)(?:\.\d+)*.*Safari\//.test(navigator.userAgent))n("/api/hassio/app/frontend_es5/entrypoint-5yRSddAJzJ4.js");else try{new Function("import('/api/hassio/app/frontend_latest/entrypoint-qzB1D0O4L9U.js')")()}catch(t){n("/api/hassio/app/frontend_es5/entrypoint-5yRSddAJzJ4.js")}}()

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,2 @@
"use strict";(self.webpackChunkhome_assistant_frontend=self.webpackChunkhome_assistant_frontend||[]).push([[1047],{32594:function(e,t,r){r.d(t,{U:function(){return n}});var n=function(e){return e.stopPropagation()}},75054:function(e,t,r){r.r(t),r.d(t,{HaTimeDuration:function(){return f}});var n,a=r(88962),i=r(33368),o=r(71650),d=r(82390),u=r(69205),l=r(70906),s=r(91808),c=r(68144),v=r(79932),f=(r(47289),(0,s.Z)([(0,v.Mo)("ha-selector-duration")],(function(e,t){var r=function(t){(0,u.Z)(n,t);var r=(0,l.Z)(n);function n(){var t;(0,o.Z)(this,n);for(var a=arguments.length,i=new Array(a),u=0;u<a;u++)i[u]=arguments[u];return t=r.call.apply(r,[this].concat(i)),e((0,d.Z)(t)),t}return(0,i.Z)(n)}(t);return{F:r,d:[{kind:"field",decorators:[(0,v.Cb)({attribute:!1})],key:"hass",value:void 0},{kind:"field",decorators:[(0,v.Cb)({attribute:!1})],key:"selector",value:void 0},{kind:"field",decorators:[(0,v.Cb)({attribute:!1})],key:"value",value:void 0},{kind:"field",decorators:[(0,v.Cb)()],key:"label",value:void 0},{kind:"field",decorators:[(0,v.Cb)()],key:"helper",value:void 0},{kind:"field",decorators:[(0,v.Cb)({type:Boolean})],key:"disabled",value:function(){return!1}},{kind:"field",decorators:[(0,v.Cb)({type:Boolean})],key:"required",value:function(){return!0}},{kind:"method",key:"render",value:function(){var e;return(0,c.dy)(n||(n=(0,a.Z)([' <ha-duration-input .label="','" .helper="','" .data="','" .disabled="','" .required="','" ?enableDay="','"></ha-duration-input> '])),this.label,this.helper,this.value,this.disabled,this.required,null===(e=this.selector.duration)||void 0===e?void 0:e.enable_day)}}]}}),c.oi))}}]);
//# sourceMappingURL=1047-g7fFLS9eP4I.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"1047-g7fFLS9eP4I.js","mappings":"yKAAO,IAAMA,EAAkB,SAACC,GAAE,OAAKA,EAAGD,iBAAiB,C,qLCQ9CE,G,UAAcC,EAAAA,EAAAA,GAAA,EAD1BC,EAAAA,EAAAA,IAAc,0BAAuB,SAAAC,EAAAC,GAAA,IACzBJ,EAAc,SAAAK,IAAAC,EAAAA,EAAAA,GAAAN,EAAAK,GAAA,IAAAE,GAAAC,EAAAA,EAAAA,GAAAR,GAAA,SAAAA,IAAA,IAAAS,GAAAC,EAAAA,EAAAA,GAAA,KAAAV,GAAA,QAAAW,EAAAC,UAAAC,OAAAC,EAAA,IAAAC,MAAAJ,GAAAK,EAAA,EAAAA,EAAAL,EAAAK,IAAAF,EAAAE,GAAAJ,UAAAI,GAAA,OAAAP,EAAAF,EAAAU,KAAAC,MAAAX,EAAA,OAAAY,OAAAL,IAAAX,GAAAiB,EAAAA,EAAAA,GAAAX,IAAAA,CAAA,QAAAY,EAAAA,EAAAA,GAAArB,EAAA,EAAAI,GAAA,OAAAkB,EAAdtB,EAAcuB,EAAA,EAAAC,KAAA,QAAAC,WAAA,EACxBC,EAAAA,EAAAA,IAAS,CAAEC,WAAW,KAAQC,IAAA,OAAAC,WAAA,IAAAL,KAAA,QAAAC,WAAA,EAE9BC,EAAAA,EAAAA,IAAS,CAAEC,WAAW,KAAQC,IAAA,WAAAC,WAAA,IAAAL,KAAA,QAAAC,WAAA,EAE9BC,EAAAA,EAAAA,IAAS,CAAEC,WAAW,KAAQC,IAAA,QAAAC,WAAA,IAAAL,KAAA,QAAAC,WAAA,EAE9BC,EAAAA,EAAAA,OAAUE,IAAA,QAAAC,WAAA,IAAAL,KAAA,QAAAC,WAAA,EAEVC,EAAAA,EAAAA,OAAUE,IAAA,SAAAC,WAAA,IAAAL,KAAA,QAAAC,WAAA,EAEVC,EAAAA,EAAAA,IAAS,CAAEI,KAAMC,WAAUH,IAAA,WAAAC,MAAA,kBAAmB,CAAK,IAAAL,KAAA,QAAAC,WAAA,EAEnDC,EAAAA,EAAAA,IAAS,CAAEI,KAAMC,WAAUH,IAAA,WAAAC,MAAA,kBAAmB,CAAI,IAAAL,KAAA,SAAAI,IAAA,SAAAC,MAEnD,WAAmB,IAAAG,EACjB,OAAOC,EAAAA,EAAAA,IAAIC,IAAAA,GAAAC,EAAAA,EAAAA,GAAA,wIAEEC,KAAKC,MACJD,KAAKE,OACPF,KAAKP,MACDO,KAAKG,SACLH,KAAKI,SACkB,QADVR,EACZI,KAAKK,SAASC,gBAAQ,IAAAV,OAAA,EAAtBA,EAAwBW,WAG3C,IAAC,GA1BiCC,EAAAA,I","sources":["https://raw.githubusercontent.com/home-assistant/frontend/20230703.0/src/common/dom/stop_propagation.ts","https://raw.githubusercontent.com/home-assistant/frontend/20230703.0/src/components/ha-selector/ha-selector-duration.ts"],"names":["stopPropagation","ev","HaTimeDuration","_decorate","customElement","_initialize","_LitElement","_LitElement2","_inherits","_super","_createSuper","_this","_classCallCheck","_len","arguments","length","args","Array","_key","call","apply","concat","_assertThisInitialized","_createClass","F","d","kind","decorators","property","attribute","key","value","type","Boolean","_this$selector$durati","html","_templateObject","_taggedTemplateLiteral","this","label","helper","disabled","required","selector","duration","enable_day","LitElement"],"sourceRoot":""}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1,2 +0,0 @@
"use strict";(self.webpackChunkhome_assistant_frontend=self.webpackChunkhome_assistant_frontend||[]).push([["12"],{5739:function(e,a,t){t.a(e,(async function(e,i){try{t.r(a),t.d(a,{HaNavigationSelector:function(){return b}});var n,r=t(63038),d=t(27862),o=t(52565),l=t(92776),u=t(5776),s=t(21475),c=(t(38419),t(57243)),h=t(50778),v=t(36522),k=t(63297),f=e([k]);k=(f.then?(await f)():f)[0];var b=(0,s.Z)([(0,h.Mo)("ha-selector-navigation")],(function(e,a){var t=function(a){function t(){var a;(0,o.Z)(this,t);for(var i=arguments.length,n=new Array(i),r=0;r<i;r++)n[r]=arguments[r];return a=(0,l.Z)(this,t,[].concat(n)),e(a),a}return(0,u.Z)(t,a),(0,d.Z)(t)}(a);return{F:t,d:[{kind:"field",decorators:[(0,h.Cb)({attribute:!1})],key:"hass",value:void 0},{kind:"field",decorators:[(0,h.Cb)({attribute:!1})],key:"selector",value:void 0},{kind:"field",decorators:[(0,h.Cb)()],key:"value",value:void 0},{kind:"field",decorators:[(0,h.Cb)()],key:"label",value:void 0},{kind:"field",decorators:[(0,h.Cb)()],key:"helper",value:void 0},{kind:"field",decorators:[(0,h.Cb)({type:Boolean,reflect:!0})],key:"disabled",value:function(){return!1}},{kind:"field",decorators:[(0,h.Cb)({type:Boolean})],key:"required",value:function(){return!0}},{kind:"method",key:"render",value:function(){return(0,c.dy)(n||(n=(0,r.Z)([' <ha-navigation-picker .hass="','" .label="','" .value="','" .required="','" .disabled="','" .helper="','" @value-changed="','"></ha-navigation-picker> '])),this.hass,this.label,this.value,this.required,this.disabled,this.helper,this._valueChanged)}},{kind:"method",key:"_valueChanged",value:function(e){(0,v.B)(this,"value-changed",{value:e.detail.value})}}]}}),c.oi);i()}catch(y){i(y)}}))}}]);
//# sourceMappingURL=12.cd76ff0e6ff4d214.js.map

View File

@@ -1 +0,0 @@
{"version":3,"file":"12.cd76ff0e6ff4d214.js","sources":["https://raw.githubusercontent.com/home-assistant/frontend/20241127.8/src/components/ha-selector/ha-selector-navigation.ts"],"names":["HaNavigationSelector","_decorate","customElement","_initialize","_LitElement","_LitElement2","_this","_classCallCheck","_len","arguments","length","args","Array","_key","_callSuper","concat","_inherits","_createClass","F","d","kind","decorators","property","attribute","key","value","type","Boolean","reflect","html","_templateObject","_taggedTemplateLiteral","this","hass","label","required","disabled","helper","_valueChanged","ev","fireEvent","detail","LitElement"],"mappings":"oYAOA,IACaA,GAAoBC,EAAAA,EAAAA,GAAA,EADhCC,EAAAA,EAAAA,IAAc,4BAAyB,SAAAC,EAAAC,GAAA,IAC3BJ,EAAoB,SAAAK,GAAA,SAAAL,IAAA,IAAAM,GAAAC,EAAAA,EAAAA,GAAA,KAAAP,GAAA,QAAAQ,EAAAC,UAAAC,OAAAC,EAAA,IAAAC,MAAAJ,GAAAK,EAAA,EAAAA,EAAAL,EAAAK,IAAAF,EAAAE,GAAAJ,UAAAI,GAAA,OAAAP,GAAAQ,EAAAA,EAAAA,GAAA,KAAAd,EAAA,GAAAe,OAAAJ,IAAAR,EAAAG,GAAAA,CAAA,QAAAU,EAAAA,EAAAA,GAAAhB,EAAAK,IAAAY,EAAAA,EAAAA,GAAAjB,EAAA,EAAAI,GAAA,OAAAc,EAApBlB,EAAoBmB,EAAA,EAAAC,KAAA,QAAAC,WAAA,EAC9BC,EAAAA,EAAAA,IAAS,CAAEC,WAAW,KAAQC,IAAA,OAAAC,WAAA,IAAAL,KAAA,QAAAC,WAAA,EAE9BC,EAAAA,EAAAA,IAAS,CAAEC,WAAW,KAAQC,IAAA,WAAAC,WAAA,IAAAL,KAAA,QAAAC,WAAA,EAE9BC,EAAAA,EAAAA,OAAUE,IAAA,QAAAC,WAAA,IAAAL,KAAA,QAAAC,WAAA,EAEVC,EAAAA,EAAAA,OAAUE,IAAA,QAAAC,WAAA,IAAAL,KAAA,QAAAC,WAAA,EAEVC,EAAAA,EAAAA,OAAUE,IAAA,SAAAC,WAAA,IAAAL,KAAA,QAAAC,WAAA,EAEVC,EAAAA,EAAAA,IAAS,CAAEI,KAAMC,QAASC,SAAS,KAAOJ,IAAA,WAAAC,MAAA,kBAAmB,CAAK,IAAAL,KAAA,QAAAC,WAAA,EAElEC,EAAAA,EAAAA,IAAS,CAAEI,KAAMC,WAAUH,IAAA,WAAAC,MAAA,kBAAmB,CAAI,IAAAL,KAAA,SAAAI,IAAA,SAAAC,MAEnD,WACE,OAAOI,EAAAA,EAAAA,IAAIC,IAAAA,GAAAC,EAAAA,EAAAA,GAAA,+JAECC,KAAKC,KACJD,KAAKE,MACLF,KAAKP,MACFO,KAAKG,SACLH,KAAKI,SACPJ,KAAKK,OACEL,KAAKM,cAG5B,GAAC,CAAAlB,KAAA,SAAAI,IAAA,gBAAAC,MAED,SAAsBc,IACpBC,EAAAA,EAAAA,GAAUR,KAAM,gBAAiB,CAAEP,MAAOc,EAAGE,OAAOhB,OACtD,IAAC,GA/BuCiB,EAAAA,I"}

View File

@@ -1,2 +0,0 @@
(self.webpackChunkhome_assistant_frontend=self.webpackChunkhome_assistant_frontend||[]).push([["1236"],{4121:function(e,n,t){t(451),t(23509),Intl.PluralRules&&"function"==typeof Intl.PluralRules.__addLocaleData&&Intl.PluralRules.__addLocaleData({data:{categories:{cardinal:["one","other"],ordinal:["one","two","few","other"]},fn:function(e,n){var t=String(e).split("."),a=!t[1],l=Number(t[0])==e,o=l&&t[0].slice(-1),r=l&&t[0].slice(-2);return n?1==o&&11!=r?"one":2==o&&12!=r?"two":3==o&&13!=r?"few":"other":1==e&&a?"one":"other"}},locale:"en"})}}]);
//# sourceMappingURL=1236.71e83acee5b952f8.js.map

View File

@@ -1 +0,0 @@
{"version":3,"file":"1236.71e83acee5b952f8.js","sources":["/unknown/node_modules/@formatjs/intl-pluralrules/locale-data/en.js"],"names":["Intl","PluralRules","__addLocaleData","n","ord","s","String","split","v0","t0","Number","n10","slice","n100"],"mappings":"6IAEIA,KAAKC,aAA2D,mBAArCD,KAAKC,YAAYC,iBAC9CF,KAAKC,YAAYC,gBAAgB,CAAC,KAAO,CAAC,WAAa,CAAC,SAAW,CAAC,MAAM,SAAS,QAAU,CAAC,MAAM,MAAM,MAAM,UAAU,GAAK,SAASC,EAAGC,GAC3I,IAAIC,EAAIC,OAAOH,GAAGI,MAAM,KAAMC,GAAMH,EAAE,GAAII,EAAKC,OAAOL,EAAE,KAAOF,EAAGQ,EAAMF,GAAMJ,EAAE,GAAGO,OAAO,GAAIC,EAAOJ,GAAMJ,EAAE,GAAGO,OAAO,GACvH,OAAIR,EAAmB,GAAPO,GAAoB,IAARE,EAAa,MAC9B,GAAPF,GAAoB,IAARE,EAAa,MAClB,GAAPF,GAAoB,IAARE,EAAa,MACzB,QACQ,GAALV,GAAUK,EAAK,MAAQ,OAChC,GAAG,OAAS,M"}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1 +0,0 @@
"use strict";(self.webpackChunkhome_assistant_frontend=self.webpackChunkhome_assistant_frontend||[]).push([["1295"],{21393:function(s,n,e){e.r(n)}}]);

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,2 @@
!function(){"use strict";var n,t,e={14595:function(n,t,e){e(58556);var r,i,o=e(93217),u=e(422),a=e(62173),s=function(n,t,e){if("input"===n){if("type"===t&&"checkbox"===e||"checked"===t||"disabled"===t)return;return""}},c={renderMarkdown:function(n,t){var e,o=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{};return r||(r=Object.assign(Object.assign({},(0,a.getDefaultWhiteList)()),{},{input:["type","disabled","checked"],"ha-icon":["icon"],"ha-svg-icon":["path"],"ha-alert":["alert-type","title"]})),o.allowSvg?(i||(i=Object.assign(Object.assign({},r),{},{svg:["xmlns","height","width"],path:["transform","stroke","d"],img:["src"]})),e=i):e=r,(0,a.filterXSS)((0,u.TU)(n,t),{whiteList:e,onTagAttr:s})}};(0,o.Jj)(c)}},r={};function i(n){var t=r[n];if(void 0!==t)return t.exports;var o=r[n]={exports:{}};return e[n](o,o.exports,i),o.exports}i.m=e,i.x=function(){var n=i.O(void 0,[9191,215],(function(){return i(14595)}));return n=i.O(n)},n=[],i.O=function(t,e,r,o){if(!e){var u=1/0;for(f=0;f<n.length;f++){e=n[f][0],r=n[f][1],o=n[f][2];for(var a=!0,s=0;s<e.length;s++)(!1&o||u>=o)&&Object.keys(i.O).every((function(n){return i.O[n](e[s])}))?e.splice(s--,1):(a=!1,o<u&&(u=o));if(a){n.splice(f--,1);var c=r();void 0!==c&&(t=c)}}return t}o=o||0;for(var f=n.length;f>0&&n[f-1][2]>o;f--)n[f]=n[f-1];n[f]=[e,r,o]},i.n=function(n){var t=n&&n.__esModule?function(){return n.default}:function(){return n};return i.d(t,{a:t}),t},i.d=function(n,t){for(var e in t)i.o(t,e)&&!i.o(n,e)&&Object.defineProperty(n,e,{enumerable:!0,get:t[e]})},i.f={},i.e=function(n){return Promise.all(Object.keys(i.f).reduce((function(t,e){return i.f[e](n,t),t}),[]))},i.u=function(n){return n+"-"+{215:"FPZmDYZTPdk",9191:"37260H-osZ4"}[n]+".js"},i.o=function(n,t){return Object.prototype.hasOwnProperty.call(n,t)},i.p="/api/hassio/app/frontend_es5/",function(){var n={1402:1};i.f.i=function(t,e){n[t]||importScripts(i.p+i.u(t))};var t=self.webpackChunkhome_assistant_frontend=self.webpackChunkhome_assistant_frontend||[],e=t.push.bind(t);t.push=function(t){var r=t[0],o=t[1],u=t[2];for(var a in o)i.o(o,a)&&(i.m[a]=o[a]);for(u&&u(i);r.length;)n[r.pop()]=1;e(t)}}(),t=i.x,i.x=function(){return Promise.all([i.e(9191),i.e(215)]).then(t)};i.x()}();
//# sourceMappingURL=1402-6WKUruvoXtM.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"1402-6WKUruvoXtM.js","mappings":"6BAAIA,ECAAC,E,sCCMAC,EACAC,E,+BAMEC,EAAY,SAChBC,EACAC,EACAC,GAEA,GAAY,UAARF,EAAiB,CACnB,GACY,SAATC,GAA6B,aAAVC,GACX,YAATD,GACS,aAATA,EAEA,OAEF,MAAO,EACT,CAEF,EA0CME,EAAM,CACVC,eAzCqB,SACrBC,EACAC,GAKW,IAWPC,EAfJC,EAGCC,UAAAC,OAAA,QAAAC,IAAAF,UAAA,GAAAA,UAAA,GAAG,CAAC,EA4BL,OA1BKZ,IACHA,EAAee,OAAAC,OAAAD,OAAAC,OAAA,IACVC,EAAAA,EAAAA,wBAAqB,IACxBC,MAAO,CAAC,OAAQ,WAAY,WAC5B,UAAW,CAAC,QACZ,cAAe,CAAC,QAChB,WAAY,CAAC,aAAc,YAM3BP,EAAYQ,UACTlB,IACHA,EAAYc,OAAAC,OAAAD,OAAAC,OAAA,GACPhB,GAAe,IAClBoB,IAAK,CAAC,QAAS,SAAU,SACzBC,KAAM,CAAC,YAAa,SAAU,KAC9BC,IAAK,CAAC,UAGVZ,EAAYT,GAEZS,EAAYV,GAGPuB,EAAAA,EAAAA,YAAUC,EAAAA,EAAAA,IAAOhB,EAASC,GAAgB,CAC/CC,UAAAA,EACAR,UAAAA,GAEJ,IAQAuB,EAAAA,EAAAA,IAAOnB,E,GC5EHoB,EAA2B,CAAC,EAGhC,SAASC,EAAoBC,GAE5B,IAAIC,EAAeH,EAAyBE,GAC5C,QAAqBd,IAAjBe,EACH,OAAOA,EAAaC,QAGrB,IAAIC,EAASL,EAAyBE,GAAY,CAGjDE,QAAS,CAAC,GAOX,OAHAE,EAAoBJ,GAAUG,EAAQA,EAAOD,QAASH,GAG/CI,EAAOD,OACf,CAGAH,EAAoBM,EAAID,EAGxBL,EAAoBO,EAAI,WAGvB,IAAIC,EAAsBR,EAAoBS,OAAEtB,EAAW,CAAC,KAAK,MAAM,WAAa,OAAOa,EAAoB,MAAQ,IAEvH,OADAQ,EAAsBR,EAAoBS,EAAED,EAE7C,EHlCIrC,EAAW,GACf6B,EAAoBS,EAAI,SAASC,EAAQC,EAAUC,EAAIC,GACtD,IAAGF,EAAH,CAMA,IAAIG,EAAeC,IACnB,IAASC,EAAI,EAAGA,EAAI7C,EAASe,OAAQ8B,IAAK,CACrCL,EAAWxC,EAAS6C,GAAG,GACvBJ,EAAKzC,EAAS6C,GAAG,GACjBH,EAAW1C,EAAS6C,GAAG,GAE3B,IAJA,IAGIC,GAAY,EACPC,EAAI,EAAGA,EAAIP,EAASzB,OAAQgC,MACpB,EAAXL,GAAsBC,GAAgBD,IAAazB,OAAO+B,KAAKnB,EAAoBS,GAAGW,OAAM,SAASC,GAAO,OAAOrB,EAAoBS,EAAEY,GAAKV,EAASO,GAAK,IAChKP,EAASW,OAAOJ,IAAK,IAErBD,GAAY,EACTJ,EAAWC,IAAcA,EAAeD,IAG7C,GAAGI,EAAW,CACb9C,EAASmD,OAAON,IAAK,GACrB,IAAIO,EAAIX,SACEzB,IAANoC,IAAiBb,EAASa,EAC/B,CACD,CACA,OAAOb,CArBP,CAJCG,EAAWA,GAAY,EACvB,IAAI,IAAIG,EAAI7C,EAASe,OAAQ8B,EAAI,GAAK7C,EAAS6C,EAAI,GAAG,GAAKH,EAAUG,IAAK7C,EAAS6C,GAAK7C,EAAS6C,EAAI,GACrG7C,EAAS6C,GAAK,CAACL,EAAUC,EAAIC,EAwB/B,EI5BAb,EAAoBwB,EAAI,SAASpB,GAChC,IAAIqB,EAASrB,GAAUA,EAAOsB,WAC7B,WAAa,OAAOtB,EAAgB,OAAG,EACvC,WAAa,OAAOA,CAAQ,EAE7B,OADAJ,EAAoB2B,EAAEF,EAAQ,CAAEG,EAAGH,IAC5BA,CACR,ECNAzB,EAAoB2B,EAAI,SAASxB,EAAS0B,GACzC,IAAI,IAAIR,KAAOQ,EACX7B,EAAoB8B,EAAED,EAAYR,KAASrB,EAAoB8B,EAAE3B,EAASkB,IAC5EjC,OAAO2C,eAAe5B,EAASkB,EAAK,CAAEW,YAAY,EAAMC,IAAKJ,EAAWR,IAG3E,ECPArB,EAAoBkC,EAAI,CAAC,EAGzBlC,EAAoBmC,EAAI,SAASC,GAChC,OAAOC,QAAQC,IAAIlD,OAAO+B,KAAKnB,EAAoBkC,GAAGK,QAAO,SAASC,EAAUnB,GAE/E,OADArB,EAAoBkC,EAAEb,GAAKe,EAASI,GAC7BA,CACR,GAAG,IACJ,ECPAxC,EAAoByC,EAAI,SAASL,GAEhC,OAAYA,EAAU,IAAM,CAAC,IAAM,cAAc,KAAO,eAAeA,GAAW,KACnF,ECJApC,EAAoB8B,EAAI,SAASY,EAAKC,GAAQ,OAAOvD,OAAOwD,UAAUC,eAAeC,KAAKJ,EAAKC,EAAO,ECAtG3C,EAAoB+C,EAAI,gC,WCIxB,IAAIC,EAAkB,CACrB,KAAM,GAkBPhD,EAAoBkC,EAAElB,EAAI,SAASoB,EAASI,GAEvCQ,EAAgBZ,IAElBa,cAAcjD,EAAoB+C,EAAI/C,EAAoByC,EAAEL,GAG/D,EAEA,IAAIc,EAAqBC,KAA0C,oCAAIA,KAA0C,qCAAK,GAClHC,EAA6BF,EAAmBG,KAAKC,KAAKJ,GAC9DA,EAAmBG,KAzBA,SAASE,GAC3B,IAAI5C,EAAW4C,EAAK,GAChBC,EAAcD,EAAK,GACnBE,EAAUF,EAAK,GACnB,IAAI,IAAItD,KAAYuD,EAChBxD,EAAoB8B,EAAE0B,EAAavD,KACrCD,EAAoBM,EAAEL,GAAYuD,EAAYvD,IAIhD,IADGwD,GAASA,EAAQzD,GACdW,EAASzB,QACd8D,EAAgBrC,EAAS+C,OAAS,EACnCN,EAA2BG,EAC5B,C,ITtBInF,EAAO4B,EAAoBO,EAC/BP,EAAoBO,EAAI,WACvB,OAAO8B,QAAQC,IAAI,CAClBtC,EAAoBmC,EAAE,MACtBnC,EAAoBmC,EAAE,OACpBwB,KAAKvF,EACT,EUL0B4B,EAAoBO,G","sources":["no-source/webpack/runtime/chunk loaded","no-source/webpack/runtime/startup chunk dependencies","https://raw.githubusercontent.com/home-assistant/frontend/20230703.0/src/resources/markdown-worker.ts","no-source/webpack/bootstrap","no-source/webpack/runtime/compat get default export","no-source/webpack/runtime/define property getters","no-source/webpack/runtime/ensure chunk","no-source/webpack/runtime/get javascript chunk filename","no-source/webpack/runtime/hasOwnProperty shorthand","no-source/webpack/runtime/publicPath","no-source/webpack/runtime/importScripts chunk loading","no-source/webpack/startup"],"names":["deferred","next","whiteListNormal","whiteListSvg","onTagAttr","tag","name","value","api","renderMarkdown","content","markedOptions","whiteList","hassOptions","arguments","length","undefined","Object","assign","getDefaultWhiteList","input","allowSvg","svg","path","img","filterXSS","marked","expose","__webpack_module_cache__","__webpack_require__","moduleId","cachedModule","exports","module","__webpack_modules__","m","x","__webpack_exports__","O","result","chunkIds","fn","priority","notFulfilled","Infinity","i","fulfilled","j","keys","every","key","splice","r","n","getter","__esModule","d","a","definition","o","defineProperty","enumerable","get","f","e","chunkId","Promise","all","reduce","promises","u","obj","prop","prototype","hasOwnProperty","call","p","installedChunks","importScripts","chunkLoadingGlobal","self","parentChunkLoadingFunction","push","bind","data","moreModules","runtime","pop","then"],"sourceRoot":""}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1 +0,0 @@
"use strict";(self.webpackChunkhome_assistant_frontend=self.webpackChunkhome_assistant_frontend||[]).push([["1468"],{93099:function(s,n,e){e.r(n)}}]);

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1,2 +0,0 @@
"use strict";(self.webpackChunkhome_assistant_frontend=self.webpackChunkhome_assistant_frontend||[]).push([["1559"],{54237:function(n,t,e){e.r(t),e.d(t,{HaIconButtonArrowNext:function(){return f}});var o,i=e(63038),r=e(27862),a=e(52565),d=e(92776),u=e(5776),s=e(21475),l=(e(38419),e(57243)),c=e(50778),h=e(14463),f=(e(23043),(0,s.Z)([(0,c.Mo)("ha-icon-button-arrow-next")],(function(n,t){var e=function(t){function e(){var t;(0,a.Z)(this,e);for(var o=arguments.length,i=new Array(o),r=0;r<o;r++)i[r]=arguments[r];return t=(0,d.Z)(this,e,[].concat(i)),n(t),t}return(0,u.Z)(e,t),(0,r.Z)(e)}(t);return{F:e,d:[{kind:"field",decorators:[(0,c.Cb)({attribute:!1})],key:"hass",value:void 0},{kind:"field",decorators:[(0,c.Cb)({type:Boolean})],key:"disabled",value:function(){return!1}},{kind:"field",decorators:[(0,c.Cb)()],key:"label",value:void 0},{kind:"field",decorators:[(0,c.SB)()],key:"_icon",value:function(){return"rtl"===h.E.document.dir?"M20,11V13H8L13.5,18.5L12.08,19.92L4.16,12L12.08,4.08L13.5,5.5L8,11H20Z":"M4,11V13H16L10.5,18.5L11.92,19.92L19.84,12L11.92,4.08L10.5,5.5L16,11H4Z"}},{kind:"method",key:"render",value:function(){var n;return(0,l.dy)(o||(o=(0,i.Z)([' <ha-icon-button .disabled="','" .label="','" .path="','"></ha-icon-button> '])),this.disabled,this.label||(null===(n=this.hass)||void 0===n?void 0:n.localize("ui.common.next"))||"Next",this._icon)}}]}}),l.oi))}}]);
//# sourceMappingURL=1559.f7125bf20e9428bb.js.map

View File

@@ -1 +0,0 @@
{"version":3,"file":"1559.f7125bf20e9428bb.js","sources":["https://raw.githubusercontent.com/home-assistant/frontend/20241127.8/src/components/ha-icon-button-arrow-next.ts"],"names":["HaIconButtonArrowNext","_decorate","customElement","_initialize","_LitElement","_LitElement2","_this","_classCallCheck","_len","arguments","length","args","Array","_key","_callSuper","concat","_inherits","_createClass","F","d","kind","decorators","property","attribute","key","value","type","Boolean","state","mainWindow","_this$hass","html","_templateObject","_taggedTemplateLiteral","this","disabled","label","hass","localize","_icon","LitElement"],"mappings":"yTASaA,G,UAAqBC,EAAAA,EAAAA,GAAA,EADjCC,EAAAA,EAAAA,IAAc,+BAA4B,SAAAC,EAAAC,GAAA,IAC9BJ,EAAqB,SAAAK,GAAA,SAAAL,IAAA,IAAAM,GAAAC,EAAAA,EAAAA,GAAA,KAAAP,GAAA,QAAAQ,EAAAC,UAAAC,OAAAC,EAAA,IAAAC,MAAAJ,GAAAK,EAAA,EAAAA,EAAAL,EAAAK,IAAAF,EAAAE,GAAAJ,UAAAI,GAAA,OAAAP,GAAAQ,EAAAA,EAAAA,GAAA,KAAAd,EAAA,GAAAe,OAAAJ,IAAAR,EAAAG,GAAAA,CAAA,QAAAU,EAAAA,EAAAA,GAAAhB,EAAAK,IAAAY,EAAAA,EAAAA,GAAAjB,EAAA,EAAAI,GAAA,OAAAc,EAArBlB,EAAqBmB,EAAA,EAAAC,KAAA,QAAAC,WAAA,EAC/BC,EAAAA,EAAAA,IAAS,CAAEC,WAAW,KAAQC,IAAA,OAAAC,WAAA,IAAAL,KAAA,QAAAC,WAAA,EAE9BC,EAAAA,EAAAA,IAAS,CAAEI,KAAMC,WAAUH,IAAA,WAAAC,MAAA,kBAAmB,CAAK,IAAAL,KAAA,QAAAC,WAAA,EAEnDC,EAAAA,EAAAA,OAAUE,IAAA,QAAAC,WAAA,IAAAL,KAAA,QAAAC,WAAA,EAEVO,EAAAA,EAAAA,OAAOJ,IAAA,QAAAC,MAAA,iBACsB,QAA5BI,EAAAA,EAAAA,SAAAA,I,kJAAgE,IAAAT,KAAA,SAAAI,IAAA,SAAAC,MAElE,WAAmC,IAAAK,EACjC,OAAOC,EAAAA,EAAAA,IAAIC,IAAAA,GAAAC,EAAAA,EAAAA,GAAA,mFAEKC,KAAKC,SACRD,KAAKE,QAAkB,QAAbN,EAAII,KAAKG,YAAI,IAAAP,OAAA,EAATA,EAAWQ,SAAS,oBAAqB,OACxDJ,KAAKK,MAGnB,IAAC,GAlBwCC,EAAAA,I"}

View File

@@ -1,2 +0,0 @@
"use strict";(self.webpackChunkhome_assistant_frontend=self.webpackChunkhome_assistant_frontend||[]).push([["1577"],{4635:function(n,t,e){e.r(t),e.d(t,{HaIconButtonNext:function(){return f}});var o,i=e(63038),r=e(27862),a=e(52565),d=e(92776),u=e(5776),s=e(21475),l=(e(38419),e(57243)),c=e(50778),h=e(14463),f=(e(23043),(0,s.Z)([(0,c.Mo)("ha-icon-button-next")],(function(n,t){var e=function(t){function e(){var t;(0,a.Z)(this,e);for(var o=arguments.length,i=new Array(o),r=0;r<o;r++)i[r]=arguments[r];return t=(0,d.Z)(this,e,[].concat(i)),n(t),t}return(0,u.Z)(e,t),(0,r.Z)(e)}(t);return{F:e,d:[{kind:"field",decorators:[(0,c.Cb)({attribute:!1})],key:"hass",value:void 0},{kind:"field",decorators:[(0,c.Cb)({type:Boolean})],key:"disabled",value:function(){return!1}},{kind:"field",decorators:[(0,c.Cb)()],key:"label",value:void 0},{kind:"field",decorators:[(0,c.SB)()],key:"_icon",value:function(){return"rtl"===h.E.document.dir?"M15.41,16.58L10.83,12L15.41,7.41L14,6L8,12L14,18L15.41,16.58Z":"M8.59,16.58L13.17,12L8.59,7.41L10,6L16,12L10,18L8.59,16.58Z"}},{kind:"method",key:"render",value:function(){var n;return(0,l.dy)(o||(o=(0,i.Z)([' <ha-icon-button .disabled="','" .label="','" .path="','"></ha-icon-button> '])),this.disabled,this.label||(null===(n=this.hass)||void 0===n?void 0:n.localize("ui.common.next"))||"Next",this._icon)}}]}}),l.oi))}}]);
//# sourceMappingURL=1577.fb97a0cedea9a3a8.js.map

View File

@@ -1 +0,0 @@
{"version":3,"file":"1577.fb97a0cedea9a3a8.js","sources":["https://raw.githubusercontent.com/home-assistant/frontend/20241127.8/src/components/ha-icon-button-next.ts"],"names":["HaIconButtonNext","_decorate","customElement","_initialize","_LitElement","_LitElement2","_this","_classCallCheck","_len","arguments","length","args","Array","_key","_callSuper","concat","_inherits","_createClass","F","d","kind","decorators","property","attribute","key","value","type","Boolean","state","mainWindow","_this$hass","html","_templateObject","_taggedTemplateLiteral","this","disabled","label","hass","localize","_icon","LitElement"],"mappings":"mTASaA,G,UAAgBC,EAAAA,EAAAA,GAAA,EAD5BC,EAAAA,EAAAA,IAAc,yBAAsB,SAAAC,EAAAC,GAAA,IACxBJ,EAAgB,SAAAK,GAAA,SAAAL,IAAA,IAAAM,GAAAC,EAAAA,EAAAA,GAAA,KAAAP,GAAA,QAAAQ,EAAAC,UAAAC,OAAAC,EAAA,IAAAC,MAAAJ,GAAAK,EAAA,EAAAA,EAAAL,EAAAK,IAAAF,EAAAE,GAAAJ,UAAAI,GAAA,OAAAP,GAAAQ,EAAAA,EAAAA,GAAA,KAAAd,EAAA,GAAAe,OAAAJ,IAAAR,EAAAG,GAAAA,CAAA,QAAAU,EAAAA,EAAAA,GAAAhB,EAAAK,IAAAY,EAAAA,EAAAA,GAAAjB,EAAA,EAAAI,GAAA,OAAAc,EAAhBlB,EAAgBmB,EAAA,EAAAC,KAAA,QAAAC,WAAA,EAC1BC,EAAAA,EAAAA,IAAS,CAAEC,WAAW,KAAQC,IAAA,OAAAC,WAAA,IAAAL,KAAA,QAAAC,WAAA,EAE9BC,EAAAA,EAAAA,IAAS,CAAEI,KAAMC,WAAUH,IAAA,WAAAC,MAAA,kBAAmB,CAAK,IAAAL,KAAA,QAAAC,WAAA,EAEnDC,EAAAA,EAAAA,OAAUE,IAAA,QAAAC,WAAA,IAAAL,KAAA,QAAAC,WAAA,EAEVO,EAAAA,EAAAA,OAAOJ,IAAA,QAAAC,MAAA,iBACsB,QAA5BI,EAAAA,EAAAA,SAAAA,I,6HAAoE,IAAAT,KAAA,SAAAI,IAAA,SAAAC,MAEtE,WAAmC,IAAAK,EACjC,OAAOC,EAAAA,EAAAA,IAAIC,IAAAA,GAAAC,EAAAA,EAAAA,GAAA,mFAEKC,KAAKC,SACRD,KAAKE,QAAkB,QAAbN,EAAII,KAAKG,YAAI,IAAAP,OAAA,EAATA,EAAWQ,SAAS,oBAAqB,OACxDJ,KAAKK,MAGnB,IAAC,GAlBmCC,EAAAA,I"}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Some files were not shown because too many files have changed in this diff Show More