Compare commits

..

2 Commits

Author SHA1 Message Date
Mike Degatano
1f7aafcfd7 Fix fixture that ensures .git presence 2025-08-07 20:51:02 +00:00
Mike Degatano
ed45651fd9 Handle git dir missing in load and pull 2025-08-07 20:42:22 +00:00
4147 changed files with 4875 additions and 9961 deletions

View File

@@ -53,7 +53,7 @@ jobs:
requirements: ${{ steps.requirements.outputs.changed }}
steps:
- name: Checkout the repository
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
uses: actions/checkout@v4.2.2
with:
fetch-depth: 0
@@ -70,7 +70,7 @@ jobs:
- name: Get changed files
id: changed_files
if: steps.version.outputs.publish == 'false'
uses: masesgroup/retrieve-changed-files@491e80760c0e28d36ca6240a27b1ccb8e1402c13 # v3.0.0
uses: masesgroup/retrieve-changed-files@v3.0.0
- name: Check if requirements files changed
id: requirements
@@ -92,7 +92,7 @@ jobs:
arch: ${{ fromJson(needs.init.outputs.architectures) }}
steps:
- name: Checkout the repository
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
uses: actions/checkout@v4.2.2
with:
fetch-depth: 0
@@ -104,10 +104,9 @@ jobs:
echo "CARGO_NET_GIT_FETCH_WITH_CLI=true"
) > .env_file
# home-assistant/wheels doesn't support sha pinning
- name: Build wheels
if: needs.init.outputs.requirements == 'true'
uses: home-assistant/wheels@2025.09.1
uses: home-assistant/wheels@2025.07.0
with:
abi: cp313
tag: musllinux_1_2
@@ -126,15 +125,15 @@ jobs:
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
if: needs.init.outputs.publish == 'true'
uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
uses: actions/setup-python@v5.6.0
with:
python-version: ${{ env.DEFAULT_PYTHON }}
- name: Install Cosign
if: needs.init.outputs.publish == 'true'
uses: sigstore/cosign-installer@d7543c93d881b35a8faa02e8e3605f69b7a1ce62 # v3.10.0
uses: sigstore/cosign-installer@v3.9.2
with:
cosign-release: "v2.5.3"
cosign-release: "v2.4.3"
- name: Install dirhash and calc hash
if: needs.init.outputs.publish == 'true'
@@ -150,7 +149,7 @@ jobs:
- name: Login to GitHub Container Registry
if: needs.init.outputs.publish == 'true'
uses: docker/login-action@184bdaa0721073962dff0199f1fb9940f07167d1 # v3.5.0
uses: docker/login-action@v3.5.0
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
@@ -160,9 +159,8 @@ jobs:
if: needs.init.outputs.publish == 'false'
run: echo "BUILD_ARGS=--test" >> $GITHUB_ENV
# home-assistant/builder doesn't support sha pinning
- name: Build supervisor
uses: home-assistant/builder@2025.09.0
uses: home-assistant/builder@2025.03.0
with:
args: |
$BUILD_ARGS \
@@ -180,7 +178,7 @@ jobs:
steps:
- name: Checkout the repository
if: needs.init.outputs.publish == 'true'
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
uses: actions/checkout@v4.2.2
- name: Initialize git
if: needs.init.outputs.publish == 'true'
@@ -205,12 +203,11 @@ jobs:
timeout-minutes: 60
steps:
- name: Checkout the repository
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
uses: actions/checkout@v4.2.2
# home-assistant/builder doesn't support sha pinning
- name: Build the Supervisor
if: needs.init.outputs.publish != 'true'
uses: home-assistant/builder@2025.09.0
uses: home-assistant/builder@2025.03.0
with:
args: |
--test \

View File

@@ -26,15 +26,15 @@ jobs:
name: Prepare Python dependencies
steps:
- name: Check out code from GitHub
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
uses: actions/checkout@v4.2.2
- name: Set up Python
id: python
uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
uses: actions/setup-python@v5.6.0
with:
python-version: ${{ env.DEFAULT_PYTHON }}
- name: Restore Python virtual environment
id: cache-venv
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
uses: actions/cache@v4.2.3
with:
path: venv
key: |
@@ -48,7 +48,7 @@ jobs:
pip install -r requirements.txt -r requirements_tests.txt
- name: Restore pre-commit environment from cache
id: cache-precommit
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
uses: actions/cache@v4.2.3
with:
path: ${{ env.PRE_COMMIT_CACHE }}
lookup-only: true
@@ -68,15 +68,15 @@ jobs:
needs: prepare
steps:
- name: Check out code from GitHub
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
uses: actions/checkout@v4.2.2
- name: Set up Python ${{ needs.prepare.outputs.python-version }}
uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
uses: actions/setup-python@v5.6.0
id: python
with:
python-version: ${{ needs.prepare.outputs.python-version }}
- name: Restore Python virtual environment
id: cache-venv
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
uses: actions/cache@v4.2.3
with:
path: venv
key: |
@@ -88,7 +88,7 @@ jobs:
exit 1
- name: Restore pre-commit environment from cache
id: cache-precommit
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
uses: actions/cache@v4.2.3
with:
path: ${{ env.PRE_COMMIT_CACHE }}
key: |
@@ -111,15 +111,15 @@ jobs:
needs: prepare
steps:
- name: Check out code from GitHub
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
uses: actions/checkout@v4.2.2
- name: Set up Python ${{ needs.prepare.outputs.python-version }}
uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
uses: actions/setup-python@v5.6.0
id: python
with:
python-version: ${{ needs.prepare.outputs.python-version }}
- name: Restore Python virtual environment
id: cache-venv
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
uses: actions/cache@v4.2.3
with:
path: venv
key: |
@@ -131,7 +131,7 @@ jobs:
exit 1
- name: Restore pre-commit environment from cache
id: cache-precommit
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
uses: actions/cache@v4.2.3
with:
path: ${{ env.PRE_COMMIT_CACHE }}
key: |
@@ -154,7 +154,7 @@ jobs:
needs: prepare
steps:
- name: Check out code from GitHub
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
uses: actions/checkout@v4.2.2
- name: Register hadolint problem matcher
run: |
echo "::add-matcher::.github/workflows/matchers/hadolint.json"
@@ -169,15 +169,15 @@ jobs:
needs: prepare
steps:
- name: Check out code from GitHub
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
uses: actions/checkout@v4.2.2
- name: Set up Python ${{ needs.prepare.outputs.python-version }}
uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
uses: actions/setup-python@v5.6.0
id: python
with:
python-version: ${{ needs.prepare.outputs.python-version }}
- name: Restore Python virtual environment
id: cache-venv
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
uses: actions/cache@v4.2.3
with:
path: venv
key: |
@@ -189,7 +189,7 @@ jobs:
exit 1
- name: Restore pre-commit environment from cache
id: cache-precommit
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
uses: actions/cache@v4.2.3
with:
path: ${{ env.PRE_COMMIT_CACHE }}
key: |
@@ -213,15 +213,15 @@ jobs:
needs: prepare
steps:
- name: Check out code from GitHub
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
uses: actions/checkout@v4.2.2
- name: Set up Python ${{ needs.prepare.outputs.python-version }}
uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
uses: actions/setup-python@v5.6.0
id: python
with:
python-version: ${{ needs.prepare.outputs.python-version }}
- name: Restore Python virtual environment
id: cache-venv
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
uses: actions/cache@v4.2.3
with:
path: venv
key: |
@@ -233,7 +233,7 @@ jobs:
exit 1
- name: Restore pre-commit environment from cache
id: cache-precommit
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
uses: actions/cache@v4.2.3
with:
path: ${{ env.PRE_COMMIT_CACHE }}
key: |
@@ -257,15 +257,15 @@ jobs:
needs: prepare
steps:
- name: Check out code from GitHub
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
uses: actions/checkout@v4.2.2
- name: Set up Python ${{ needs.prepare.outputs.python-version }}
uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
uses: actions/setup-python@v5.6.0
id: python
with:
python-version: ${{ needs.prepare.outputs.python-version }}
- name: Restore Python virtual environment
id: cache-venv
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
uses: actions/cache@v4.2.3
with:
path: venv
key: |
@@ -293,9 +293,9 @@ jobs:
needs: prepare
steps:
- name: Check out code from GitHub
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
uses: actions/checkout@v4.2.2
- name: Set up Python ${{ needs.prepare.outputs.python-version }}
uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
uses: actions/setup-python@v5.6.0
id: python
with:
python-version: ${{ needs.prepare.outputs.python-version }}
@@ -307,7 +307,7 @@ jobs:
echo "key=mypy-${{ env.MYPY_CACHE_VERSION }}-$mypy_version-$(date -u '+%Y-%m-%dT%H:%M:%s')" >> $GITHUB_OUTPUT
- name: Restore Python virtual environment
id: cache-venv
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
uses: actions/cache@v4.2.3
with:
path: venv
key: >-
@@ -318,7 +318,7 @@ jobs:
echo "Failed to restore Python virtual environment from cache"
exit 1
- name: Restore mypy cache
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
uses: actions/cache@v4.2.3
with:
path: .mypy_cache
key: >-
@@ -339,19 +339,19 @@ jobs:
name: Run tests Python ${{ needs.prepare.outputs.python-version }}
steps:
- name: Check out code from GitHub
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
uses: actions/checkout@v4.2.2
- name: Set up Python ${{ needs.prepare.outputs.python-version }}
uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
uses: actions/setup-python@v5.6.0
id: python
with:
python-version: ${{ needs.prepare.outputs.python-version }}
- name: Install Cosign
uses: sigstore/cosign-installer@d7543c93d881b35a8faa02e8e3605f69b7a1ce62 # v3.10.0
uses: sigstore/cosign-installer@v3.9.2
with:
cosign-release: "v2.5.3"
cosign-release: "v2.4.3"
- name: Restore Python virtual environment
id: cache-venv
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
uses: actions/cache@v4.2.3
with:
path: venv
key: |
@@ -386,7 +386,7 @@ jobs:
-o console_output_style=count \
tests
- name: Upload coverage artifact
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
uses: actions/upload-artifact@v4.6.2
with:
name: coverage
path: .coverage
@@ -398,15 +398,15 @@ jobs:
needs: ["pytest", "prepare"]
steps:
- name: Check out code from GitHub
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
uses: actions/checkout@v4.2.2
- name: Set up Python ${{ needs.prepare.outputs.python-version }}
uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
uses: actions/setup-python@v5.6.0
id: python
with:
python-version: ${{ needs.prepare.outputs.python-version }}
- name: Restore Python virtual environment
id: cache-venv
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
uses: actions/cache@v4.2.3
with:
path: venv
key: |
@@ -417,7 +417,7 @@ jobs:
echo "Failed to restore Python virtual environment from cache"
exit 1
- name: Download all coverage artifacts
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0
uses: actions/download-artifact@v5.0.0
with:
name: coverage
path: coverage/
@@ -428,4 +428,4 @@ jobs:
coverage report
coverage xml
- name: Upload coverage to Codecov
uses: codecov/codecov-action@5a1091511ad55cbe89839c7260b706298ca349f7 # v5.5.1
uses: codecov/codecov-action@v5.4.3

View File

@@ -9,7 +9,7 @@ jobs:
lock:
runs-on: ubuntu-latest
steps:
- uses: dessant/lock-threads@1bf7ec25051fe7c00bdd17e6a7cf3d7bfb7dc771 # v5.0.1
- uses: dessant/lock-threads@v5.0.1
with:
github-token: ${{ github.token }}
issue-inactive-days: "30"

View File

@@ -11,7 +11,7 @@ jobs:
name: Release Drafter
steps:
- name: Checkout the repository
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
uses: actions/checkout@v4.2.2
with:
fetch-depth: 0
@@ -36,7 +36,7 @@ jobs:
echo "version=$datepre.$newpost" >> "$GITHUB_OUTPUT"
- name: Run Release Drafter
uses: release-drafter/release-drafter@b1476f6e6eb133afa41ed8589daba6dc69b4d3f5 # v6.1.0
uses: release-drafter/release-drafter@v6.1.0
with:
tag: ${{ steps.version.outputs.version }}
name: ${{ steps.version.outputs.version }}

View File

@@ -9,10 +9,10 @@ jobs:
check-authorization:
runs-on: ubuntu-latest
# Only run if this is a Task issue type (from the issue form)
if: github.event.issue.type.name == 'Task'
if: github.event.issue.issue_type == 'Task'
steps:
- name: Check if user is authorized
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
uses: actions/github-script@v7
with:
script: |
const issueAuthor = context.payload.issue.user.login;

View File

@@ -10,9 +10,9 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Check out code from GitHub
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
uses: actions/checkout@v4.2.2
- name: Sentry Release
uses: getsentry/action-release@4f502acc1df792390abe36f2dcb03612ef144818 # v3.3.0
uses: getsentry/action-release@v3.2.0
env:
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
SENTRY_ORG: ${{ secrets.SENTRY_ORG }}

View File

@@ -9,7 +9,7 @@ jobs:
stale:
runs-on: ubuntu-latest
steps:
- uses: actions/stale@3a9db7e6a41a89f618792c92c0e97cc736e1b13f # v10.0.0
- uses: actions/stale@v9.1.0
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
days-before-stale: 30

View File

@@ -14,10 +14,10 @@ jobs:
latest_version: ${{ steps.latest_frontend_version.outputs.latest_tag }}
steps:
- name: Checkout code
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
uses: actions/checkout@v4
- name: Get latest frontend release
id: latest_frontend_version
uses: abatilo/release-info-action@32cb932219f1cee3fc4f4a298fd65ead5d35b661 # v1.3.3
uses: abatilo/release-info-action@v1.3.3
with:
owner: home-assistant
repo: frontend
@@ -49,7 +49,7 @@ jobs:
if: needs.check-version.outputs.skip != 'true'
steps:
- name: Checkout code
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
uses: actions/checkout@v4
- name: Clear www folder
run: |
rm -rf supervisor/api/panel/*
@@ -57,7 +57,7 @@ jobs:
run: |
echo "${{ needs.check-version.outputs.latest_version }}" > .ha-frontend-version
- name: Download release assets
uses: robinraju/release-downloader@daf26c55d821e836577a15f77d86ddc078948b05 # v1.12
uses: robinraju/release-downloader@v1
with:
repository: 'home-assistant/frontend'
tag: ${{ needs.check-version.outputs.latest_version }}
@@ -68,7 +68,7 @@ jobs:
run: |
rm -f supervisor/api/panel/home_assistant_frontend_supervisor-*.tar.gz
- name: Create PR
uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e # v7.0.8
uses: peter-evans/create-pull-request@v7
with:
commit-message: "Update frontend to version ${{ needs.check-version.outputs.latest_version }}"
branch: autoupdate-frontend

3
.gitignore vendored
View File

@@ -100,6 +100,3 @@ ENV/
# mypy
/.mypy_cache/*
/.dmypy.json
# Mac
.DS_Store

View File

@@ -1 +1 @@
20250925.1
20250806.0

View File

@@ -29,7 +29,7 @@ RUN \
\
&& curl -Lso /usr/bin/cosign "https://github.com/home-assistant/cosign/releases/download/${COSIGN_VERSION}/cosign_${BUILD_ARCH}" \
&& chmod a+x /usr/bin/cosign \
&& pip3 install uv==0.8.9
&& pip3 install uv==0.6.17
# Install requirements
COPY requirements.txt .

View File

@@ -12,7 +12,7 @@ cosign:
base_identity: https://github.com/home-assistant/docker-base/.*
identity: https://github.com/home-assistant/supervisor/.*
args:
COSIGN_VERSION: 2.5.3
COSIGN_VERSION: 2.4.3
labels:
io.hass.type: supervisor
org.opencontainers.image.title: Home Assistant Supervisor

View File

@@ -5,11 +5,11 @@ attrs==25.3.0
awesomeversion==25.8.0
blockbuster==1.5.25
brotli==1.1.0
ciso8601==2.3.3
ciso8601==2.3.2
colorlog==6.9.0
cpe==1.3.1
cryptography==46.0.1
debugpy==1.8.17
cryptography==45.0.6
debugpy==1.8.16
deepmerge==2.0
dirhash==0.5.0
docker==7.1.0
@@ -17,13 +17,13 @@ faust-cchardet==2.1.19
gitpython==3.1.45
jinja2==3.1.6
log-rate-limit==1.4.2
orjson==3.11.3
orjson==3.11.1
pulsectl==24.12.0
pyudev==0.24.3
PyYAML==6.0.3
requests==2.32.5
PyYAML==6.0.2
requests==2.32.4
securetar==2025.2.1
sentry-sdk==2.39.0
sentry-sdk==2.34.1
setuptools==80.9.0
voluptuous==0.15.2
dbus-fast==2.44.3

View File

@@ -1,16 +1,16 @@
astroid==3.3.11
coverage==7.10.7
mypy==1.18.2
pre-commit==4.3.0
pylint==3.3.8
coverage==7.10.2
mypy==1.17.1
pre-commit==4.2.0
pylint==3.3.7
pytest-aiohttp==1.1.0
pytest-asyncio==0.25.2
pytest-cov==7.0.0
pytest-cov==6.2.1
pytest-timeout==2.4.0
pytest==8.4.2
ruff==0.13.2
time-machine==2.19.0
types-docker==7.1.0.20250916
types-pyyaml==6.0.12.20250915
types-requests==2.32.4.20250913
pytest==8.4.1
ruff==0.12.7
time-machine==2.17.0
types-docker==7.1.0.20250705
types-pyyaml==6.0.12.20250516
types-requests==2.32.4.20250611
urllib3==2.5.0

View File

@@ -66,23 +66,10 @@ if __name__ == "__main__":
_LOGGER.info("Setting up Supervisor")
loop.run_until_complete(coresys.core.setup())
# Create startup task that can be cancelled gracefully
startup_task = loop.create_task(coresys.core.start())
def shutdown_handler() -> None:
"""Handle shutdown signals gracefully during startup."""
if not startup_task.done():
_LOGGER.warning("Supervisor startup interrupted by shutdown signal")
startup_task.cancel()
coresys.create_task(coresys.core.stop())
bootstrap.register_signal_handlers(loop, shutdown_handler)
bootstrap.register_signal_handlers(loop, coresys)
try:
loop.run_until_complete(startup_task)
except asyncio.CancelledError:
_LOGGER.warning("Supervisor startup cancelled")
loop.run_until_complete(coresys.core.start())
except Exception as err: # pylint: disable=broad-except
# Supervisor itself is running at this point, just something didn't
# start as expected. Log with traceback to get more insights for

View File

@@ -67,9 +67,9 @@ from ..docker.monitor import DockerContainerStateEvent
from ..docker.stats import DockerStats
from ..exceptions import (
AddonConfigurationError,
AddonNotSupportedError,
AddonsError,
AddonsJobError,
AddonsNotSupportedError,
ConfigurationFileError,
DockerError,
HomeAssistantAPIError,
@@ -1172,7 +1172,7 @@ class Addon(AddonModel):
async def write_stdin(self, data) -> None:
"""Write data to add-on stdin."""
if not self.with_stdin:
raise AddonNotSupportedError(
raise AddonsNotSupportedError(
f"Add-on {self.slug} does not support writing to stdin!", _LOGGER.error
)
@@ -1419,7 +1419,7 @@ class Addon(AddonModel):
# If available
if not self._available(data[ATTR_SYSTEM]):
raise AddonNotSupportedError(
raise AddonsNotSupportedError(
f"Add-on {self.slug} is not available for this platform",
_LOGGER.error,
)

View File

@@ -14,9 +14,9 @@ from supervisor.jobs.const import JobConcurrency
from ..const import AddonBoot, AddonStartup, AddonState
from ..coresys import CoreSys, CoreSysAttributes
from ..exceptions import (
AddonNotSupportedError,
AddonsError,
AddonsJobError,
AddonsNotSupportedError,
CoreDNSError,
DockerError,
HassioError,
@@ -184,9 +184,7 @@ class AddonManager(CoreSysAttributes):
on_condition=AddonsJobError,
concurrency=JobConcurrency.QUEUE,
)
async def install(
self, slug: str, *, validation_complete: asyncio.Event | None = None
) -> None:
async def install(self, slug: str) -> None:
"""Install an add-on."""
self.sys_jobs.current.reference = slug
@@ -199,10 +197,6 @@ class AddonManager(CoreSysAttributes):
store.validate_availability()
# If being run in the background, notify caller that validation has completed
if validation_complete:
validation_complete.set()
await Addon(self.coresys, slug).install()
_LOGGER.info("Add-on '%s' successfully installed", slug)
@@ -232,11 +226,7 @@ class AddonManager(CoreSysAttributes):
on_condition=AddonsJobError,
)
async def update(
self,
slug: str,
backup: bool | None = False,
*,
validation_complete: asyncio.Event | None = None,
self, slug: str, backup: bool | None = False
) -> asyncio.Task | None:
"""Update add-on.
@@ -261,10 +251,6 @@ class AddonManager(CoreSysAttributes):
# Check if available, Maybe something have changed
store.validate_availability()
# If being run in the background, notify caller that validation has completed
if validation_complete:
validation_complete.set()
if backup:
await self.sys_backups.do_backup_partial(
name=f"addon_{addon.slug}_{addon.version}",
@@ -307,7 +293,7 @@ class AddonManager(CoreSysAttributes):
"Version changed, use Update instead Rebuild", _LOGGER.error
)
if not force and not addon.need_build:
raise AddonNotSupportedError(
raise AddonsNotSupportedError(
"Can't rebuild a image based add-on", _LOGGER.error
)

View File

@@ -89,12 +89,7 @@ from ..const import (
)
from ..coresys import CoreSys
from ..docker.const import Capabilities
from ..exceptions import (
AddonNotSupportedArchitectureError,
AddonNotSupportedError,
AddonNotSupportedHomeAssistantVersionError,
AddonNotSupportedMachineTypeError,
)
from ..exceptions import AddonsNotSupportedError
from ..jobs.const import JOB_GROUP_ADDON
from ..jobs.job_group import JobGroup
from ..utils import version_is_new_enough
@@ -685,8 +680,9 @@ class AddonModel(JobGroup, ABC):
"""Validate if addon is available for current system."""
# Architecture
if not self.sys_arch.is_supported(config[ATTR_ARCH]):
raise AddonNotSupportedArchitectureError(
logger, slug=self.slug, architectures=config[ATTR_ARCH]
raise AddonsNotSupportedError(
f"Add-on {self.slug} not supported on this platform, supported architectures: {', '.join(config[ATTR_ARCH])}",
logger,
)
# Machine / Hardware
@@ -694,8 +690,9 @@ class AddonModel(JobGroup, ABC):
if machine and (
f"!{self.sys_machine}" in machine or self.sys_machine not in machine
):
raise AddonNotSupportedMachineTypeError(
logger, slug=self.slug, machine_types=machine
raise AddonsNotSupportedError(
f"Add-on {self.slug} not supported on this machine, supported machine types: {', '.join(machine)}",
logger,
)
# Home Assistant
@@ -704,15 +701,16 @@ class AddonModel(JobGroup, ABC):
if version and not version_is_new_enough(
self.sys_homeassistant.version, version
):
raise AddonNotSupportedHomeAssistantVersionError(
logger, slug=self.slug, version=str(version)
raise AddonsNotSupportedError(
f"Add-on {self.slug} not supported on this system, requires Home Assistant version {version} or greater",
logger,
)
def _available(self, config) -> bool:
"""Return True if this add-on is available on this platform."""
try:
self._validate_availability(config)
except AddonNotSupportedError:
except AddonsNotSupportedError:
return False
return True

View File

@@ -93,7 +93,15 @@ class AddonOptions(CoreSysAttributes):
typ = self.raw_schema[key]
try:
options[key] = self._validate_element(typ, value, key)
if isinstance(typ, list):
# nested value list
options[key] = self._nested_validate_list(typ[0], value, key)
elif isinstance(typ, dict):
# nested value dict
options[key] = self._nested_validate_dict(typ, value, key)
else:
# normal value
options[key] = self._single_validate(typ, value, key)
except (IndexError, KeyError):
raise vol.Invalid(
f"Type error for option '{key}' in {self._name} ({self._slug})"
@@ -103,20 +111,7 @@ class AddonOptions(CoreSysAttributes):
return options
# pylint: disable=no-value-for-parameter
def _validate_element(self, typ: Any, value: Any, key: str) -> Any:
"""Validate a value against a type specification."""
if isinstance(typ, list):
# nested value list
return self._nested_validate_list(typ[0], value, key)
elif isinstance(typ, dict):
# nested value dict
return self._nested_validate_dict(typ, value, key)
else:
# normal value
return self._single_validate(typ, value, key)
# pylint: disable=no-value-for-parameter
def _single_validate(self, typ: str, value: Any, key: str) -> Any:
def _single_validate(self, typ: str, value: Any, key: str):
"""Validate a single element."""
# if required argument
if value is None:
@@ -187,15 +182,13 @@ class AddonOptions(CoreSysAttributes):
# Device valid
self.devices.add(device)
return str(value)
return str(device.path)
raise vol.Invalid(
f"Fatal error for option '{key}' with type '{typ}' in {self._name} ({self._slug})"
) from None
def _nested_validate_list(
self, typ: Any, data_list: list[Any], key: str
) -> list[Any]:
def _nested_validate_list(self, typ: Any, data_list: list[Any], key: str):
"""Validate nested items."""
options = []
@@ -208,13 +201,17 @@ class AddonOptions(CoreSysAttributes):
# Process list
for element in data_list:
# Nested?
options.append(self._validate_element(typ, element, key))
if isinstance(typ, dict):
c_options = self._nested_validate_dict(typ, element, key)
options.append(c_options)
else:
options.append(self._single_validate(typ, element, key))
return options
def _nested_validate_dict(
self, typ: dict[Any, Any], data_dict: dict[Any, Any], key: str
) -> dict[Any, Any]:
):
"""Validate nested items."""
options = {}
@@ -234,7 +231,12 @@ class AddonOptions(CoreSysAttributes):
continue
# Nested?
options[c_key] = self._validate_element(typ[c_key], c_value, c_key)
if isinstance(typ[c_key], list):
options[c_key] = self._nested_validate_list(
typ[c_key][0], c_value, c_key
)
else:
options[c_key] = self._single_validate(typ[c_key], c_value, c_key)
self._check_missing_options(typ, options, key)
return options
@@ -272,28 +274,18 @@ class UiOptions(CoreSysAttributes):
# read options
for key, value in raw_schema.items():
self._ui_schema_element(ui_schema, value, key)
if isinstance(value, list):
# nested value list
self._nested_ui_list(ui_schema, value, key)
elif isinstance(value, dict):
# nested value dict
self._nested_ui_dict(ui_schema, value, key)
else:
# normal value
self._single_ui_option(ui_schema, value, key)
return ui_schema
def _ui_schema_element(
self,
ui_schema: list[dict[str, Any]],
value: str,
key: str,
multiple: bool = False,
):
if isinstance(value, list):
# nested value list
assert not multiple
self._nested_ui_list(ui_schema, value, key)
elif isinstance(value, dict):
# nested value dict
self._nested_ui_dict(ui_schema, value, key, multiple)
else:
# normal value
self._single_ui_option(ui_schema, value, key, multiple)
def _single_ui_option(
self,
ui_schema: list[dict[str, Any]],
@@ -385,7 +377,10 @@ class UiOptions(CoreSysAttributes):
_LOGGER.error("Invalid schema %s", key)
return
self._ui_schema_element(ui_schema, element, key, multiple=True)
if isinstance(element, dict):
self._nested_ui_dict(ui_schema, element, key, multiple=True)
else:
self._single_ui_option(ui_schema, element, key, multiple=True)
def _nested_ui_dict(
self,
@@ -404,7 +399,11 @@ class UiOptions(CoreSysAttributes):
nested_schema: list[dict[str, Any]] = []
for c_key, c_value in option_dict.items():
self._ui_schema_element(nested_schema, c_value, c_key)
# Nested?
if isinstance(c_value, list):
self._nested_ui_list(nested_schema, c_value, c_key)
else:
self._single_ui_option(nested_schema, c_value, c_key)
ui_node["schema"] = nested_schema
ui_schema.append(ui_node)

View File

@@ -32,7 +32,6 @@ from ..const import (
ATTR_DISCOVERY,
ATTR_DOCKER_API,
ATTR_ENVIRONMENT,
ATTR_FIELDS,
ATTR_FULL_ACCESS,
ATTR_GPIO,
ATTR_HASSIO_API,
@@ -138,19 +137,7 @@ RE_DOCKER_IMAGE_BUILD = re.compile(
r"^([a-zA-Z\-\.:\d{}]+/)*?([\-\w{}]+)/([\-\w{}]+)(:[\.\-\w{}]+)?$"
)
SCHEMA_ELEMENT = vol.Schema(
vol.Any(
vol.Match(RE_SCHEMA_ELEMENT),
[
# A list may not directly contain another list
vol.Any(
vol.Match(RE_SCHEMA_ELEMENT),
{str: vol.Self},
)
],
{str: vol.Self},
)
)
SCHEMA_ELEMENT = vol.Match(RE_SCHEMA_ELEMENT)
RE_MACHINE = re.compile(
r"^!?(?:"
@@ -279,23 +266,10 @@ def _migrate_addon_config(protocol=False):
volumes = []
for entry in config.get(ATTR_MAP, []):
if isinstance(entry, dict):
# Validate that dict entries have required 'type' field
if ATTR_TYPE not in entry:
_LOGGER.warning(
"Add-on config has invalid map entry missing 'type' field: %s. Skipping invalid entry for %s",
entry,
name,
)
continue
volumes.append(entry)
if isinstance(entry, str):
result = RE_VOLUME.match(entry)
if not result:
_LOGGER.warning(
"Add-on config has invalid map entry: %s. Skipping invalid entry for %s",
entry,
name,
)
continue
volumes.append(
{
@@ -304,8 +278,8 @@ def _migrate_addon_config(protocol=False):
}
)
# Always update config to clear potentially malformed ones
config[ATTR_MAP] = volumes
if volumes:
config[ATTR_MAP] = volumes
# 2023-10 "config" became "homeassistant" so /config can be used for addon's public config
if any(volume[ATTR_TYPE] == MappingType.CONFIG for volume in volumes):
@@ -419,7 +393,20 @@ _SCHEMA_ADDON_CONFIG = vol.Schema(
vol.Optional(ATTR_CODENOTARY): vol.Email(),
vol.Optional(ATTR_OPTIONS, default={}): dict,
vol.Optional(ATTR_SCHEMA, default={}): vol.Any(
vol.Schema({str: SCHEMA_ELEMENT}),
vol.Schema(
{
str: vol.Any(
SCHEMA_ELEMENT,
[
vol.Any(
SCHEMA_ELEMENT,
{str: vol.Any(SCHEMA_ELEMENT, [SCHEMA_ELEMENT])},
)
],
vol.Schema({str: vol.Any(SCHEMA_ELEMENT, [SCHEMA_ELEMENT])}),
)
}
),
False,
),
vol.Optional(ATTR_IMAGE): docker_image,
@@ -455,7 +442,6 @@ SCHEMA_TRANSLATION_CONFIGURATION = vol.Schema(
{
vol.Required(ATTR_NAME): str,
vol.Optional(ATTR_DESCRIPTON): vol.Maybe(str),
vol.Optional(ATTR_FIELDS): {str: vol.Self},
},
extra=vol.REMOVE_EXTRA,
)

View File

@@ -146,14 +146,6 @@ class RestAPI(CoreSysAttributes):
follow=True,
),
),
web.get(
f"{path}/logs/latest",
partial(
self._api_host.advanced_logs,
identifier=syslog_identifier,
latest=True,
),
),
web.get(
f"{path}/logs/boots/{{bootid}}",
partial(self._api_host.advanced_logs, identifier=syslog_identifier),
@@ -206,7 +198,6 @@ class RestAPI(CoreSysAttributes):
web.post("/host/reload", api_host.reload),
web.post("/host/options", api_host.options),
web.get("/host/services", api_host.services),
web.get("/host/disks/default/usage", api_host.disk_usage),
]
)
@@ -448,7 +439,6 @@ class RestAPI(CoreSysAttributes):
# is known and reported to the user using the resolution center.
await async_capture_exception(err)
kwargs.pop("follow", None) # Follow is not supported for Docker logs
kwargs.pop("latest", None) # Latest is not supported for Docker logs
return await api_supervisor.logs(*args, **kwargs)
self.webapp.add_routes(
@@ -458,10 +448,6 @@ class RestAPI(CoreSysAttributes):
"/supervisor/logs/follow",
partial(get_supervisor_logs, follow=True),
),
web.get(
"/supervisor/logs/latest",
partial(get_supervisor_logs, latest=True),
),
web.get("/supervisor/logs/boots/{bootid}", get_supervisor_logs),
web.get(
"/supervisor/logs/boots/{bootid}/follow",
@@ -574,10 +560,6 @@ class RestAPI(CoreSysAttributes):
"/addons/{addon}/logs/follow",
partial(get_addon_logs, follow=True),
),
web.get(
"/addons/{addon}/logs/latest",
partial(get_addon_logs, latest=True),
),
web.get("/addons/{addon}/logs/boots/{bootid}", get_addon_logs),
web.get(
"/addons/{addon}/logs/boots/{bootid}/follow",
@@ -752,10 +734,6 @@ class RestAPI(CoreSysAttributes):
"/store/addons/{addon}/documentation",
api_store.addons_addon_documentation,
),
web.get(
"/store/addons/{addon}/availability",
api_store.addons_addon_availability,
),
web.post(
"/store/addons/{addon}/install", api_store.addons_addon_install
),

View File

@@ -306,7 +306,7 @@ class APIAddons(CoreSysAttributes):
)
# Validate/Process Body
body = await api_validate(addon_schema, request)
body = await api_validate(addon_schema, request, origin=[ATTR_OPTIONS])
if ATTR_OPTIONS in body:
addon.options = body[ATTR_OPTIONS]
if ATTR_BOOT in body:

View File

@@ -3,6 +3,7 @@
from __future__ import annotations
import asyncio
from collections.abc import Callable
import errno
from io import IOBase
import logging
@@ -45,9 +46,12 @@ from ..const import (
ATTR_TYPE,
ATTR_VERSION,
REQUEST_FROM,
BusEvent,
CoreState,
)
from ..coresys import CoreSysAttributes
from ..exceptions import APIError, APIForbidden, APINotFound
from ..jobs import JobSchedulerOptions, SupervisorJob
from ..mounts.const import MountUsage
from ..resolution.const import UnhealthyReason
from .const import (
@@ -57,7 +61,7 @@ from .const import (
ATTR_LOCATIONS,
CONTENT_TYPE_TAR,
)
from .utils import api_process, api_validate, background_task
from .utils import api_process, api_validate
_LOGGER: logging.Logger = logging.getLogger(__name__)
@@ -285,6 +289,41 @@ class APIBackups(CoreSysAttributes):
f"Location {LOCATION_CLOUD_BACKUP} is only available for Home Assistant"
)
async def _background_backup_task(
self, backup_method: Callable, *args, **kwargs
) -> tuple[asyncio.Task, str]:
"""Start backup task in background and return task and job ID."""
event = asyncio.Event()
job, backup_task = cast(
tuple[SupervisorJob, asyncio.Task],
self.sys_jobs.schedule_job(
backup_method, JobSchedulerOptions(), *args, **kwargs
),
)
async def release_on_freeze(new_state: CoreState):
if new_state == CoreState.FREEZE:
event.set()
# Wait for system to get into freeze state before returning
# If the backup fails validation it will raise before getting there
listener = self.sys_bus.register_event(
BusEvent.SUPERVISOR_STATE_CHANGE, release_on_freeze
)
try:
event_task = self.sys_create_task(event.wait())
_, pending = await asyncio.wait(
(backup_task, event_task),
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)
finally:
self.sys_bus.remove_listener(listener)
@api_process
async def backup_full(self, request: web.Request):
"""Create full backup."""
@@ -303,8 +342,8 @@ class APIBackups(CoreSysAttributes):
body[ATTR_ADDITIONAL_LOCATIONS] = locations
background = body.pop(ATTR_BACKGROUND)
backup_task, job_id = await background_task(
self, self.sys_backups.do_backup_full, **body
backup_task, job_id = await self._background_backup_task(
self.sys_backups.do_backup_full, **body
)
if background and not backup_task.done():
@@ -339,8 +378,8 @@ class APIBackups(CoreSysAttributes):
body[ATTR_ADDONS] = list(self.sys_addons.local)
background = body.pop(ATTR_BACKGROUND)
backup_task, job_id = await background_task(
self, self.sys_backups.do_backup_partial, **body
backup_task, job_id = await self._background_backup_task(
self.sys_backups.do_backup_partial, **body
)
if background and not backup_task.done():
@@ -363,8 +402,8 @@ class APIBackups(CoreSysAttributes):
request, body.get(ATTR_LOCATION, backup.location)
)
background = body.pop(ATTR_BACKGROUND)
restore_task, job_id = await background_task(
self, self.sys_backups.do_restore_full, backup, **body
restore_task, job_id = await self._background_backup_task(
self.sys_backups.do_restore_full, backup, **body
)
if background and not restore_task.done() or await restore_task:
@@ -383,8 +422,8 @@ class APIBackups(CoreSysAttributes):
request, body.get(ATTR_LOCATION, backup.location)
)
background = body.pop(ATTR_BACKGROUND)
restore_task, job_id = await background_task(
self, self.sys_backups.do_restore_partial, backup, **body
restore_task, job_id = await self._background_backup_task(
self.sys_backups.do_restore_partial, backup, **body
)
if background and not restore_task.done() or await restore_task:

View File

@@ -49,7 +49,6 @@ ATTR_LLMNR_HOSTNAME = "llmnr_hostname"
ATTR_LOCAL_ONLY = "local_only"
ATTR_LOCATION_ATTRIBUTES = "location_attributes"
ATTR_LOCATIONS = "locations"
ATTR_MAX_DEPTH = "max_depth"
ATTR_MDNS = "mdns"
ATTR_MODEL = "model"
ATTR_MOUNTS = "mounts"

View File

@@ -12,7 +12,6 @@ from ..const import (
ATTR_ENABLE_IPV6,
ATTR_HOSTNAME,
ATTR_LOGGING,
ATTR_MTU,
ATTR_PASSWORD,
ATTR_REGISTRIES,
ATTR_STORAGE,
@@ -35,12 +34,7 @@ SCHEMA_DOCKER_REGISTRY = vol.Schema(
)
# pylint: disable=no-value-for-parameter
SCHEMA_OPTIONS = vol.Schema(
{
vol.Optional(ATTR_ENABLE_IPV6): vol.Maybe(vol.Boolean()),
vol.Optional(ATTR_MTU): vol.Maybe(vol.All(int, vol.Range(min=68, max=65535))),
}
)
SCHEMA_OPTIONS = vol.Schema({vol.Optional(ATTR_ENABLE_IPV6): vol.Maybe(vol.Boolean())})
class APIDocker(CoreSysAttributes):
@@ -57,7 +51,6 @@ class APIDocker(CoreSysAttributes):
return {
ATTR_VERSION: self.sys_docker.info.version,
ATTR_ENABLE_IPV6: self.sys_docker.config.enable_ipv6,
ATTR_MTU: self.sys_docker.config.mtu,
ATTR_STORAGE: self.sys_docker.info.storage,
ATTR_LOGGING: self.sys_docker.info.logging,
ATTR_REGISTRIES: data_registries,
@@ -68,23 +61,12 @@ class APIDocker(CoreSysAttributes):
"""Set docker options."""
body = await api_validate(SCHEMA_OPTIONS, request)
reboot_required = False
if (
ATTR_ENABLE_IPV6 in body
and self.sys_docker.config.enable_ipv6 != body[ATTR_ENABLE_IPV6]
):
self.sys_docker.config.enable_ipv6 = body[ATTR_ENABLE_IPV6]
reboot_required = True
if ATTR_MTU in body and self.sys_docker.config.mtu != body[ATTR_MTU]:
self.sys_docker.config.mtu = body[ATTR_MTU]
reboot_required = True
if reboot_required:
_LOGGER.info(
"Host system reboot required to apply Docker configuration changes"
)
_LOGGER.info("Host system reboot required to apply new IPv6 configuration")
self.sys_resolution.create_issue(
IssueType.REBOOT_REQUIRED,
ContextType.SYSTEM,

View File

@@ -20,7 +20,6 @@ from ..const import (
ATTR_CPU_PERCENT,
ATTR_IMAGE,
ATTR_IP_ADDRESS,
ATTR_JOB_ID,
ATTR_MACHINE,
ATTR_MEMORY_LIMIT,
ATTR_MEMORY_PERCENT,
@@ -38,8 +37,8 @@ from ..const import (
from ..coresys import CoreSysAttributes
from ..exceptions import APIDBMigrationInProgress, APIError
from ..validate import docker_image, network_port, version_tag
from .const import ATTR_BACKGROUND, ATTR_FORCE, ATTR_SAFE_MODE
from .utils import api_process, api_validate, background_task
from .const import ATTR_FORCE, ATTR_SAFE_MODE
from .utils import api_process, api_validate
_LOGGER: logging.Logger = logging.getLogger(__name__)
@@ -62,7 +61,6 @@ SCHEMA_UPDATE = vol.Schema(
{
vol.Optional(ATTR_VERSION): version_tag,
vol.Optional(ATTR_BACKUP): bool,
vol.Optional(ATTR_BACKGROUND, default=False): bool,
}
)
@@ -172,24 +170,18 @@ class APIHomeAssistant(CoreSysAttributes):
}
@api_process
async def update(self, request: web.Request) -> dict[str, str] | None:
async def update(self, request: web.Request) -> None:
"""Update Home Assistant."""
body = await api_validate(SCHEMA_UPDATE, request)
await self._check_offline_migration()
background = body[ATTR_BACKGROUND]
update_task, job_id = await background_task(
self,
self.sys_homeassistant.core.update,
version=body.get(ATTR_VERSION, self.sys_homeassistant.latest_version),
backup=body.get(ATTR_BACKUP),
await asyncio.shield(
self.sys_homeassistant.core.update(
version=body.get(ATTR_VERSION, self.sys_homeassistant.latest_version),
backup=body.get(ATTR_BACKUP),
)
)
if background and not update_task.done():
return {ATTR_JOB_ID: job_id}
return await update_task
@api_process
async def stop(self, request: web.Request) -> Awaitable[None]:
"""Stop Home Assistant."""

View File

@@ -2,17 +2,10 @@
import asyncio
from contextlib import suppress
import json
import logging
from typing import Any
from aiohttp import (
ClientConnectionResetError,
ClientError,
ClientPayloadError,
ClientTimeout,
web,
)
from aiohttp import ClientConnectionResetError, ClientPayloadError, web
from aiohttp.hdrs import ACCEPT, RANGE
import voluptuous as vol
from voluptuous.error import CoerceInvalid
@@ -58,7 +51,6 @@ from .const import (
ATTR_FORCE,
ATTR_IDENTIFIERS,
ATTR_LLMNR_HOSTNAME,
ATTR_MAX_DEPTH,
ATTR_STARTUP_TIME,
ATTR_USE_NTP,
ATTR_VIRTUALIZATION,
@@ -201,11 +193,7 @@ class APIHost(CoreSysAttributes):
return possible_offset
async def advanced_logs_handler(
self,
request: web.Request,
identifier: str | None = None,
follow: bool = False,
latest: bool = False,
self, request: web.Request, identifier: str | None = None, follow: bool = False
) -> web.StreamResponse:
"""Return systemd-journald logs."""
log_formatter = LogFormatter.PLAIN
@@ -224,20 +212,6 @@ class APIHost(CoreSysAttributes):
if follow:
params[PARAM_FOLLOW] = ""
if latest:
if not identifier:
raise APIError(
"Latest logs can only be fetched for a specific identifier."
)
try:
epoch = await self._get_container_last_epoch(identifier)
params["CONTAINER_LOG_EPOCH"] = epoch
except HostLogError as err:
raise APIError(
f"Cannot determine CONTAINER_LOG_EPOCH of {identifier}, latest logs not available."
) from err
if ACCEPT in request.headers and request.headers[ACCEPT] not in [
CONTENT_TYPE_TEXT,
CONTENT_TYPE_X_LOG,
@@ -266,8 +240,6 @@ class APIHost(CoreSysAttributes):
lines = max(2, lines)
# entries=cursor[[:num_skip]:num_entries]
range_header = f"entries=:-{lines - 1}:{SYSTEMD_JOURNAL_GATEWAYD_LINES_MAX if follow else lines}"
elif latest:
range_header = f"entries=:0:{SYSTEMD_JOURNAL_GATEWAYD_LINES_MAX}"
elif RANGE in request.headers:
range_header = request.headers[RANGE]
else:
@@ -313,81 +285,7 @@ class APIHost(CoreSysAttributes):
@api_process_raw(CONTENT_TYPE_TEXT, error_type=CONTENT_TYPE_TEXT)
async def advanced_logs(
self,
request: web.Request,
identifier: str | None = None,
follow: bool = False,
latest: bool = False,
self, request: web.Request, identifier: str | None = None, follow: bool = False
) -> web.StreamResponse:
"""Return systemd-journald logs. Wrapped as standard API handler."""
return await self.advanced_logs_handler(request, identifier, follow, latest)
@api_process
async def disk_usage(self, request: web.Request) -> dict:
"""Return a breakdown of storage usage for the system."""
max_depth = request.query.get(ATTR_MAX_DEPTH, 1)
try:
max_depth = int(max_depth)
except ValueError:
max_depth = 1
disk = self.sys_hardware.disk
total, used, _ = await self.sys_run_in_executor(
disk.disk_usage, self.sys_config.path_supervisor
)
known_paths = await self.sys_run_in_executor(
disk.get_dir_sizes,
{
"addons_data": self.sys_config.path_addons_data,
"addons_config": self.sys_config.path_addon_configs,
"media": self.sys_config.path_media,
"share": self.sys_config.path_share,
"backup": self.sys_config.path_backup,
"ssl": self.sys_config.path_ssl,
"homeassistant": self.sys_config.path_homeassistant,
},
max_depth,
)
return {
# this can be the disk/partition ID in the future
"id": "root",
"label": "Root",
"total_bytes": total,
"used_bytes": used,
"children": [
{
"id": "system",
"label": "System",
"used_bytes": used
- sum(path["used_bytes"] for path in known_paths),
},
*known_paths,
],
}
async def _get_container_last_epoch(self, identifier: str) -> str | None:
"""Get Docker's internal log epoch of the latest log entry for the given identifier."""
try:
async with self.sys_host.logs.journald_logs(
params={"CONTAINER_NAME": identifier},
range_header="entries=:-1:2", # -1 = next to the last entry
accept=LogFormat.JSON,
timeout=ClientTimeout(total=10),
) as resp:
text = await resp.text()
except (ClientError, TimeoutError) as err:
raise HostLogError(
"Could not get last container epoch from systemd-journal-gatewayd",
_LOGGER.error,
) from err
try:
return json.loads(text.strip().split("\n")[-1])["CONTAINER_LOG_EPOCH"]
except (json.JSONDecodeError, KeyError, IndexError) as err:
raise HostLogError(
f"Failed to parse CONTAINER_LOG_EPOCH of {identifier} container, got: {text}",
_LOGGER.error,
) from err
return await self.advanced_logs_handler(request, identifier, follow)

View File

@@ -199,25 +199,21 @@ class APIIngress(CoreSysAttributes):
url = f"{url}?{request.query_string}"
# Start proxy
try:
_LOGGER.debug("Proxing WebSocket to %s, upstream url: %s", addon.slug, url)
async with self.sys_websession.ws_connect(
url,
headers=source_header,
protocols=req_protocols,
autoclose=False,
autoping=False,
) as ws_client:
# Proxy requests
await asyncio.wait(
[
self.sys_create_task(_websocket_forward(ws_server, ws_client)),
self.sys_create_task(_websocket_forward(ws_client, ws_server)),
],
return_when=asyncio.FIRST_COMPLETED,
)
except TimeoutError:
_LOGGER.warning("WebSocket proxy to %s timed out", addon.slug)
async with self.sys_websession.ws_connect(
url,
headers=source_header,
protocols=req_protocols,
autoclose=False,
autoping=False,
) as ws_client:
# Proxy requests
await asyncio.wait(
[
self.sys_create_task(_websocket_forward(ws_server, ws_client)),
self.sys_create_task(_websocket_forward(ws_client, ws_server)),
],
return_when=asyncio.FIRST_COMPLETED,
)
return ws_server
@@ -290,7 +286,6 @@ class APIIngress(CoreSysAttributes):
aiohttp.ClientError,
aiohttp.ClientPayloadError,
ConnectionResetError,
ConnectionError,
) as err:
_LOGGER.error("Stream error with %s: %s", url, err)
@@ -391,9 +386,9 @@ async def _websocket_forward(ws_from, ws_to):
elif msg.type == aiohttp.WSMsgType.BINARY:
await ws_to.send_bytes(msg.data)
elif msg.type == aiohttp.WSMsgType.PING:
await ws_to.ping(msg.data)
await ws_to.ping()
elif msg.type == aiohttp.WSMsgType.PONG:
await ws_to.pong(msg.data)
await ws_to.pong()
elif ws_to.closed:
await ws_to.close(code=ws_to.close_code, message=msg.extra)
except RuntimeError:

View File

@@ -26,9 +26,7 @@ from ..const import (
ATTR_IP6_PRIVACY,
ATTR_IPV4,
ATTR_IPV6,
ATTR_LLMNR,
ATTR_MAC,
ATTR_MDNS,
ATTR_METHOD,
ATTR_MODE,
ATTR_NAMESERVERS,
@@ -56,7 +54,6 @@ from ..host.configuration import (
Ip6Setting,
IpConfig,
IpSetting,
MulticastDnsMode,
VlanConfig,
WifiConfig,
)
@@ -100,8 +97,6 @@ SCHEMA_UPDATE = vol.Schema(
vol.Optional(ATTR_IPV6): _SCHEMA_IPV6_CONFIG,
vol.Optional(ATTR_WIFI): _SCHEMA_WIFI_CONFIG,
vol.Optional(ATTR_ENABLED): vol.Boolean(),
vol.Optional(ATTR_MDNS): vol.Coerce(MulticastDnsMode),
vol.Optional(ATTR_LLMNR): vol.Coerce(MulticastDnsMode),
}
)
@@ -165,8 +160,6 @@ def interface_struct(interface: Interface) -> dict[str, Any]:
else None,
ATTR_WIFI: wifi_struct(interface.wifi) if interface.wifi else None,
ATTR_VLAN: vlan_struct(interface.vlan) if interface.vlan else None,
ATTR_MDNS: interface.mdns,
ATTR_LLMNR: interface.llmnr,
}
@@ -267,10 +260,6 @@ class APINetwork(CoreSysAttributes):
)
elif key == ATTR_ENABLED:
interface.enabled = config
elif key == ATTR_MDNS:
interface.mdns = config
elif key == ATTR_LLMNR:
interface.llmnr = config
await asyncio.shield(self.sys_host.network.apply_changes(interface))
@@ -311,15 +300,6 @@ class APINetwork(CoreSysAttributes):
vlan_config = VlanConfig(vlan, interface.name)
mdns_mode = MulticastDnsMode.DEFAULT
llmnr_mode = MulticastDnsMode.DEFAULT
if ATTR_MDNS in body:
mdns_mode = body[ATTR_MDNS]
if ATTR_LLMNR in body:
llmnr_mode = body[ATTR_LLMNR]
ipv4_setting = None
if ATTR_IPV4 in body:
ipv4_setting = IpSetting(
@@ -345,7 +325,7 @@ class APINetwork(CoreSysAttributes):
)
vlan_interface = Interface(
f"{interface.name}.{vlan}",
"",
"",
"",
True,
@@ -358,7 +338,5 @@ class APINetwork(CoreSysAttributes):
ipv6_setting,
None,
vlan_config,
mdns=mdns_mode,
llmnr=llmnr_mode,
)
await asyncio.shield(self.sys_host.network.create_vlan(vlan_interface))
await asyncio.shield(self.sys_host.network.apply_changes(vlan_interface))

View File

@@ -1 +1 @@
!function(){function d(d){var e=document.createElement("script");e.src=d,document.body.appendChild(e)}if(/Edge?\/(13\d|1[4-9]\d|[2-9]\d{2}|\d{4,})\.\d+(\.\d+|)|Firefox\/(13[1-9]|1[4-9]\d|[2-9]\d{2}|\d{4,})\.\d+(\.\d+|)|Chrom(ium|e)\/(10[5-9]|1[1-9]\d|[2-9]\d{2}|\d{4,})\.\d+(\.\d+|)|(Maci|X1{2}).+ Version\/(18\.([1-9]|\d{2,})|(19|[2-9]\d|\d{3,})\.\d+)([,.]\d+|)( \(\w+\)|)( Mobile\/\w+|) Safari\/|Chrome.+OPR\/(1{2}[5-9]|1[2-9]\d|[2-9]\d{2}|\d{4,})\.\d+\.\d+|(CPU[ +]OS|iPhone[ +]OS|CPU[ +]iPhone|CPU IPhone OS|CPU iPad OS)[ +]+(18[._]([1-9]|\d{2,})|(19|[2-9]\d|\d{3,})[._]\d+)([._]\d+|)|Android:?[ /-](13\d|1[4-9]\d|[2-9]\d{2}|\d{4,})(\.\d+|)(\.\d+|)|Mobile Safari.+OPR\/([89]\d|\d{3,})\.\d+\.\d+|Android.+Firefox\/(13[1-9]|1[4-9]\d|[2-9]\d{2}|\d{4,})\.\d+(\.\d+|)|Android.+Chrom(ium|e)\/(13\d|1[4-9]\d|[2-9]\d{2}|\d{4,})\.\d+(\.\d+|)|SamsungBrowser\/(2[89]|[3-9]\d|\d{3,})\.\d+|Home As{2}istant\/[\d.]+ \(.+; macOS (1[3-9]|[2-9]\d|\d{3,})\.\d+(\.\d+)?\)/.test(navigator.userAgent))try{new Function("import('/api/hassio/app/frontend_latest/entrypoint.1e251476306cafd4.js')")()}catch(e){d("/api/hassio/app/frontend_es5/entrypoint.601ff5d4dddd11f9.js")}else d("/api/hassio/app/frontend_es5/entrypoint.601ff5d4dddd11f9.js")}()
!function(){function d(d){var e=document.createElement("script");e.src=d,document.body.appendChild(e)}if(/Edge?\/(12[89]|1[3-9]\d|[2-9]\d{2}|\d{4,})\.\d+(\.\d+|)|Firefox\/(12[89]|1[3-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\/(18\.\d+|(19|[2-9]\d|\d{3,})\.\d+)([,.]\d+|)( \(\w+\)|)( Mobile\/\w+|) Safari\/|Chrome.+OPR\/(1{2}[3-9]|1[2-9]\d|[2-9]\d{2}|\d{4,})\.\d+\.\d+|(CPU[ +]OS|iPhone[ +]OS|CPU[ +]iPhone|CPU IPhone OS|CPU iPad OS)[ +]+(16[._]([6-9]|\d{2,})|(1[7-9]|[2-9]\d|\d{3,})[._]\d+)([._]\d+|)|Android:?[ /-](12[89]|1[3-9]\d|[2-9]\d{2}|\d{4,})(\.\d+|)(\.\d+|)|Mobile Safari.+OPR\/([89]\d|\d{3,})\.\d+\.\d+|Android.+Firefox\/(13\d|1[4-9]\d|[2-9]\d{2}|\d{4,})\.\d+(\.\d+|)|Android.+Chrom(ium|e)\/(12[89]|1[3-9]\d|[2-9]\d{2}|\d{4,})\.\d+(\.\d+|)|SamsungBrowser\/(2[7-9]|[3-9]\d|\d{3,})\.\d+|Home As{2}istant\/[\d.]+ \(.+; macOS (1[3-9]|[2-9]\d|\d{3,})\.\d+(\.\d+)?\)/.test(navigator.userAgent))try{new Function("import('/api/hassio/app/frontend_latest/entrypoint.9b475c5882bbf15f.js')")()}catch(e){d("/api/hassio/app/frontend_es5/entrypoint.73551a66a38f3359.js")}else d("/api/hassio/app/frontend_es5/entrypoint.73551a66a38f3359.js")}()

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

@@ -1 +0,0 @@
{"version":3,"file":"1057.d306824fd6aa0497.js","sources":["https://raw.githubusercontent.com/home-assistant/frontend/20250925.1/src/data/auth.ts","https://raw.githubusercontent.com/home-assistant/frontend/20250925.1/src/data/entity.ts","https://raw.githubusercontent.com/home-assistant/frontend/20250925.1/src/data/media-player.ts","https://raw.githubusercontent.com/home-assistant/frontend/20250925.1/src/data/tts.ts","https://raw.githubusercontent.com/home-assistant/frontend/20250925.1/src/util/brands-url.ts"],"names":["autocompleteLoginFields","schema","map","field","type","name","Object","assign","autocomplete","autofocus","getSignedPath","hass","path","callWS","UNAVAILABLE","UNKNOWN","ON","OFF","UNAVAILABLE_STATES","OFF_STATES","isUnavailableState","arrayLiteralIncludes","MediaPlayerEntityFeature","BROWSER_PLAYER","MediaClassBrowserSettings","album","icon","layout","app","show_list_images","artist","mdiAccountMusic","channel","mdiTelevisionClassic","thumbnail_ratio","composer","contributing_artist","directory","episode","game","genre","image","movie","music","playlist","podcast","season","track","tv_show","url","video","browseMediaPlayer","entityId","mediaContentId","mediaContentType","entity_id","media_content_id","media_content_type","convertTextToSpeech","data","callApi","TTS_MEDIA_SOURCE_PREFIX","isTTSMediaSource","startsWith","getProviderFromTTSMediaSource","substring","listTTSEngines","language","country","getTTSEngine","engine_id","listTTSVoices","brandsUrl","options","brand","useFallback","domain","darkOptimized","extractDomainFromBrandUrl","split","isBrandUrl","thumbnail"],"mappings":"2QAyBO,MAEMA,EAA2BC,GACtCA,EAAOC,IAAKC,IACV,GAAmB,WAAfA,EAAMC,KAAmB,OAAOD,EACpC,OAAQA,EAAME,MACZ,IAAK,WACH,OAAAC,OAAAC,OAAAD,OAAAC,OAAA,GAAYJ,GAAK,IAAEK,aAAc,WAAYC,WAAW,IAC1D,IAAK,WACH,OAAAH,OAAAC,OAAAD,OAAAC,OAAA,GAAYJ,GAAK,IAAEK,aAAc,qBACnC,IAAK,OACH,OAAAF,OAAAC,OAAAD,OAAAC,OAAA,GAAYJ,GAAK,IAAEK,aAAc,gBAAiBC,WAAW,IAC/D,QACE,OAAON,KAIFO,EAAgBA,CAC3BC,EACAC,IACwBD,EAAKE,OAAO,CAAET,KAAM,iBAAkBQ,Q,gMC3CzD,MAAME,EAAc,cACdC,EAAU,UACVC,EAAK,KACLC,EAAM,MAENC,EAAqB,CAACJ,EAAaC,GACnCI,EAAa,CAACL,EAAaC,EAASE,GAEpCG,GAAqBC,EAAAA,EAAAA,GAAqBH,IAC7BG,EAAAA,EAAAA,GAAqBF,E,+gCCuExC,IAAWG,EAAA,SAAAA,G,qnBAAAA,C,CAAA,C,IAyBX,MAAMC,EAAiB,UAWjBC,EAGT,CACFC,MAAO,CAAEC,K,mQAAgBC,OAAQ,QACjCC,IAAK,CAAEF,K,6GAAsBC,OAAQ,OAAQE,kBAAkB,GAC/DC,OAAQ,CAAEJ,KAAMK,EAAiBJ,OAAQ,OAAQE,kBAAkB,GACnEG,QAAS,CACPN,KAAMO,EACNC,gBAAiB,WACjBP,OAAQ,OACRE,kBAAkB,GAEpBM,SAAU,CACRT,K,4cACAC,OAAQ,OACRE,kBAAkB,GAEpBO,oBAAqB,CACnBV,KAAMK,EACNJ,OAAQ,OACRE,kBAAkB,GAEpBQ,UAAW,CAAEX,K,gGAAiBC,OAAQ,OAAQE,kBAAkB,GAChES,QAAS,CACPZ,KAAMO,EACNN,OAAQ,OACRO,gBAAiB,WACjBL,kBAAkB,GAEpBU,KAAM,CACJb,K,qWACAC,OAAQ,OACRO,gBAAiB,YAEnBM,MAAO,CAAEd,K,4hCAAqBC,OAAQ,OAAQE,kBAAkB,GAChEY,MAAO,CAAEf,K,sHAAgBC,OAAQ,OAAQE,kBAAkB,GAC3Da,MAAO,CACLhB,K,6GACAQ,gBAAiB,WACjBP,OAAQ,OACRE,kBAAkB,GAEpBc,MAAO,CAAEjB,K,+NAAgBG,kBAAkB,GAC3Ce,SAAU,CAAElB,K,mJAAwBC,OAAQ,OAAQE,kBAAkB,GACtEgB,QAAS,CAAEnB,K,qpBAAkBC,OAAQ,QACrCmB,OAAQ,CACNpB,KAAMO,EACNN,OAAQ,OACRO,gBAAiB,WACjBL,kBAAkB,GAEpBkB,MAAO,CAAErB,K,mLACTsB,QAAS,CACPtB,KAAMO,EACNN,OAAQ,OACRO,gBAAiB,YAEnBe,IAAK,CAAEvB,K,w5BACPwB,MAAO,CAAExB,K,2GAAgBC,OAAQ,OAAQE,kBAAkB,IAkChDsB,EAAoBA,CAC/BxC,EACAyC,EACAC,EACAC,IAEA3C,EAAKE,OAAwB,CAC3BT,KAAM,4BACNmD,UAAWH,EACXI,iBAAkBH,EAClBI,mBAAoBH,G,yLC/MjB,MAAMI,EAAsBA,CACjC/C,EACAgD,IAOGhD,EAAKiD,QAAuC,OAAQ,cAAeD,GAElEE,EAA0B,sBAEnBC,EAAoBT,GAC/BA,EAAeU,WAAWF,GAEfG,EAAiCX,GAC5CA,EAAeY,UAAUJ,IAEdK,EAAiBA,CAC5BvD,EACAwD,EACAC,IAEAzD,EAAKE,OAAO,CACVT,KAAM,kBACN+D,WACAC,YAGSC,EAAeA,CAC1B1D,EACA2D,IAEA3D,EAAKE,OAAO,CACVT,KAAM,iBACNkE,cAGSC,EAAgBA,CAC3B5D,EACA2D,EACAH,IAEAxD,EAAKE,OAAO,CACVT,KAAM,oBACNkE,YACAH,Y,kHC9CG,MAAMK,EAAaC,GACxB,oCAAoCA,EAAQC,MAAQ,UAAY,KAC9DD,EAAQE,YAAc,KAAO,KAC5BF,EAAQG,UAAUH,EAAQI,cAAgB,QAAU,KACrDJ,EAAQrE,WAQC0E,EAA6B7B,GAAgBA,EAAI8B,MAAM,KAAK,GAE5DC,EAAcC,GACzBA,EAAUlB,WAAW,oC"}

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 @@
"use strict";(self.webpackChunkhome_assistant_frontend=self.webpackChunkhome_assistant_frontend||[]).push([["113"],{51383:function(t,o,e){e.r(o),e.d(o,{HaIconButtonArrowNext:function(){return c}});e(26847),e(27530);var n=e(73742),a=e(59048),i=e(7616),r=e(88479);e(81777);let s,d=t=>t;class c extends a.oi{render(){var t;return(0,a.dy)(s||(s=d` <ha-icon-button .disabled="${0}" .label="${0}" .path="${0}"></ha-icon-button> `),this.disabled,this.label||(null===(t=this.hass)||void 0===t?void 0:t.localize("ui.common.next"))||"Next",this._icon)}constructor(...t){super(...t),this.disabled=!1,this._icon="rtl"===r.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"}}(0,n.__decorate)([(0,i.Cb)({attribute:!1})],c.prototype,"hass",void 0),(0,n.__decorate)([(0,i.Cb)({type:Boolean})],c.prototype,"disabled",void 0),(0,n.__decorate)([(0,i.Cb)()],c.prototype,"label",void 0),(0,n.__decorate)([(0,i.SB)()],c.prototype,"_icon",void 0),c=(0,n.__decorate)([(0,i.Mo)("ha-icon-button-arrow-next")],c)}}]);
//# sourceMappingURL=113.13cc15fa81bd492f.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"113.13cc15fa81bd492f.js","sources":["https://raw.githubusercontent.com/home-assistant/frontend/20250806.0/src/components/ha-icon-button-arrow-next.ts"],"names":["HaIconButtonArrowNext","LitElement","render","_this$hass","html","_t","_","this","disabled","label","hass","localize","_icon","args","mainWindow","attribute","type","Boolean"],"mappings":"4RASO,MAAMA,UAA8BC,EAAAA,GAU/BC,MAAAA,GAAyB,IAAAC,EACjC,OAAOC,EAAAA,EAAAA,IAAIC,IAAAA,EAAAC,CAAA,mFAEKC,KAAKC,SACRD,KAAKE,QAAkB,QAAbN,EAAII,KAAKG,YAAI,IAAAP,OAAA,EAATA,EAAWQ,SAAS,oBAAqB,OACxDJ,KAAKK,MAGnB,C,kBAlBK,SAAAC,GAAA,KAG+BL,UAAW,OAI9BI,MACa,QAA5BE,EAAAA,EAAAA,SAAAA,I,gLAPUC,WAAW,K,uDAEXC,KAAMC,W"}

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 @@
"use strict";(self.webpackChunkhome_assistant_frontend=self.webpackChunkhome_assistant_frontend||[]).push([["1303"],{29815:function(e,t,a){a.r(t),a.d(t,{HaFormConstant:function(){return h}});var s=a(73742),o=a(59048),n=a(7616);let r,l,i=e=>e;class h extends o.oi{render(){return(0,o.dy)(r||(r=i`<span class="label">${0}</span>${0}`),this.label,this.schema.value?`: ${this.schema.value}`:"")}}h.styles=(0,o.iv)(l||(l=i`:host{display:block}.label{font-weight:var(--ha-font-weight-medium)}`)),(0,s.__decorate)([(0,n.Cb)({attribute:!1})],h.prototype,"schema",void 0),(0,s.__decorate)([(0,n.Cb)()],h.prototype,"label",void 0),h=(0,s.__decorate)([(0,n.Mo)("ha-form-constant")],h)}}]);
//# sourceMappingURL=1303.586b15f0603e938e.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"1303.586b15f0603e938e.js","sources":["https://raw.githubusercontent.com/home-assistant/frontend/20250806.0/src/components/ha-form/ha-form-constant.ts"],"names":["HaFormConstant","LitElement","render","html","_t","_","this","label","schema","value","styles","css","_t2","attribute"],"mappings":"kPAMO,MAAMA,UAAuBC,EAAAA,GAKxBC,MAAAA,GACR,OAAOC,EAAAA,EAAAA,IAAIC,IAAAA,EAAAC,CAAA,uBAAuB,WAAvB,KAAuBC,KAAKC,MAAeD,KAAKE,OAAOC,MAC5D,KAAKH,KAAKE,OAAOC,QACjB,GACR,EATWT,EAWJU,QAASC,EAAAA,EAAAA,IAAGC,IAAAA,EAAAP,CAAA,0E,2BAVPQ,WAAW,K"}

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,29 @@
/**
* @license
* Copyright 2021 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @license
* Copyright 2021 Google LLC
* SPDX-License-Identifier: BSD-3-Clause
*/
/**
* @license
* Copyright 2022 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @license
* Copyright 2023 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @license
* Copyright 2024 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/

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 @@
"use strict";(self.webpackChunkhome_assistant_frontend=self.webpackChunkhome_assistant_frontend||[]).push([["1374"],{5786:function(t,e,n){n.r(e),n.d(e,{HaIconNext:function(){return c}});n(26847),n(27530);var o=n(73742),s=n(7616),a=n(88479),r=n(993);class c extends r.HaSvgIcon{constructor(...t){super(...t),this.path="rtl"===a.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"}}(0,o.__decorate)([(0,s.Cb)()],c.prototype,"path",void 0),c=(0,o.__decorate)([(0,s.Mo)("ha-icon-next")],c)}}]);
//# sourceMappingURL=1374.3202a7d8086556de.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"1374.3202a7d8086556de.js","sources":["https://raw.githubusercontent.com/home-assistant/frontend/20250806.0/src/components/ha-icon-next.ts"],"names":["HaIconNext","HaSvgIcon","args","path","mainWindow"],"mappings":"yPAMO,MAAMA,UAAmBC,EAAAA,U,kBAAzB,SAAAC,GAAA,KACuBC,KACE,QAA5BC,EAAAA,EAAAA,SAAAA,I"}

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