mirror of
https://github.com/home-assistant/supervisor.git
synced 2025-09-29 14:59:29 +00:00
Compare commits
168 Commits
2025.07.1
...
improve-ap
Author | SHA1 | Date | |
---|---|---|---|
![]() |
747b4152bd | ||
![]() |
3634f9f301 | ||
![]() |
e5e62aca46 | ||
![]() |
fabfe760fb | ||
![]() |
092013e457 | ||
![]() |
e13f216b2e | ||
![]() |
97c7686b95 | ||
![]() |
42f93d0176 | ||
![]() |
ed7155604c | ||
![]() |
595e33ac68 | ||
![]() |
ae70ffd1b2 | ||
![]() |
17cb18a371 | ||
![]() |
9f5bebd0eb | ||
![]() |
c712d3cc53 | ||
![]() |
46fc5c8aa1 | ||
![]() |
8b23383e26 | ||
![]() |
c1ccb00946 | ||
![]() |
5693a5be0d | ||
![]() |
01911a44cd | ||
![]() |
857dae7736 | ||
![]() |
d2ddd9579c | ||
![]() |
ac9947d599 | ||
![]() |
2e22e1e884 | ||
![]() |
e7f3573e32 | ||
![]() |
b26451a59a | ||
![]() |
4e882f7c76 | ||
![]() |
5fa50ccf05 | ||
![]() |
3891df5266 | ||
![]() |
5aad32c15b | ||
![]() |
4a40490af7 | ||
![]() |
0a46e030f5 | ||
![]() |
bd00f90304 | ||
![]() |
819f097f01 | ||
![]() |
4513592993 | ||
![]() |
7e526a26af | ||
![]() |
b3af22f048 | ||
![]() |
bbb9469c1c | ||
![]() |
859c32a706 | ||
![]() |
87fc84c65c | ||
![]() |
e38ca5acb4 | ||
![]() |
09cd8eede2 | ||
![]() |
d1c537b280 | ||
![]() |
e6785d6a89 | ||
![]() |
59e051ad93 | ||
![]() |
3397def8b9 | ||
![]() |
b832edc10d | ||
![]() |
f69071878c | ||
![]() |
e065ba6081 | ||
![]() |
38611ad12f | ||
![]() |
8beb66d46c | ||
![]() |
c277f3cad6 | ||
![]() |
236c39cbb0 | ||
![]() |
7ed83a15fe | ||
![]() |
a3a5f6ba98 | ||
![]() |
8d3ededf2f | ||
![]() |
3d62c9afb1 | ||
![]() |
ef313d1fb5 | ||
![]() |
cae31637ae | ||
![]() |
9392d10625 | ||
![]() |
5ce62f324f | ||
![]() |
f84d514958 | ||
![]() |
3c39f2f785 | ||
![]() |
30db72df78 | ||
![]() |
00a78f372b | ||
![]() |
b69546f2c1 | ||
![]() |
78be155b94 | ||
![]() |
9900dfc8ca | ||
![]() |
3a1ebc9d37 | ||
![]() |
580c3273dc | ||
![]() |
b889f94ca4 | ||
![]() |
2d12920b35 | ||
![]() |
8a95113ebd | ||
![]() |
3fc1abf661 | ||
![]() |
207b665e1d | ||
![]() |
1fb15772d7 | ||
![]() |
9740de7a83 | ||
![]() |
8e8d77d90c | ||
![]() |
dbce22bd08 | ||
![]() |
192d446888 | ||
![]() |
d95ca401ec | ||
![]() |
07d8fd006a | ||
![]() |
b49ce96df8 | ||
![]() |
4109c15a36 | ||
![]() |
d0e2778255 | ||
![]() |
014082eda8 | ||
![]() |
2324b70084 | ||
![]() |
43f20fe24f | ||
![]() |
8ef5eae22a | ||
![]() |
e5dd09ab6b | ||
![]() |
3f2db956cb | ||
![]() |
603df92618 | ||
![]() |
8a82b98e5b | ||
![]() |
07dd0b7394 | ||
![]() |
cf0a85a4b1 | ||
![]() |
9924165cd3 | ||
![]() |
91392a5443 | ||
![]() |
fd205ce2ef | ||
![]() |
9ec56d9266 | ||
![]() |
886b1bd281 | ||
![]() |
ee0474edf5 | ||
![]() |
f173489e69 | ||
![]() |
cee495bde3 | ||
![]() |
59104a4438 | ||
![]() |
e4eaeb91cd | ||
![]() |
e61d88779d | ||
![]() |
0513ea0438 | ||
![]() |
030927dc01 | ||
![]() |
cad14bf46e | ||
![]() |
5d851ad747 | ||
![]() |
528032fb36 | ||
![]() |
3b093200e3 | ||
![]() |
15ba1a3c94 | ||
![]() |
8e4a87c751 | ||
![]() |
fdde95d849 | ||
![]() |
65e5a36aa7 | ||
![]() |
bd62602cde | ||
![]() |
f9bcc273f8 | ||
![]() |
059b161f4f | ||
![]() |
f11eb6b35a | ||
![]() |
9bee58a8b1 | ||
![]() |
8a1e6b0895 | ||
![]() |
f150d1b287 | ||
![]() |
628a18c6b8 | ||
![]() |
74e43411e5 | ||
![]() |
e6b0d4144c | ||
![]() |
033896480d | ||
![]() |
478e00c0fe | ||
![]() |
6f2ba7d68c | ||
![]() |
22afa60f55 | ||
![]() |
9f2fda5dc7 | ||
![]() |
27b092aed0 | ||
![]() |
3af13cb7e2 | ||
![]() |
6871ea4b81 | ||
![]() |
cf77ab2290 | ||
![]() |
ceeffa3284 | ||
![]() |
31f2f70cd9 | ||
![]() |
deac85bddb | ||
![]() |
7dcf5ba631 | ||
![]() |
a004830131 | ||
![]() |
a8cc6c416d | ||
![]() |
74b26642b0 | ||
![]() |
5e26ab5f4a | ||
![]() |
a841cb8282 | ||
![]() |
3b1b03c8a7 | ||
![]() |
680428f304 | ||
![]() |
f34128c37e | ||
![]() |
2ed0682b34 | ||
![]() |
fbb0915ef8 | ||
![]() |
780ae1e15c | ||
![]() |
c617358855 | ||
![]() |
b679c4f4d8 | ||
![]() |
c946c421f2 | ||
![]() |
aeabf7ea25 | ||
![]() |
365b838abf | ||
![]() |
99c040520e | ||
![]() |
eefe2f2e06 | ||
![]() |
a366e36b37 | ||
![]() |
27a2fde9e1 | ||
![]() |
9a0f530a2f | ||
![]() |
baf9695cf7 | ||
![]() |
7873c457d5 | ||
![]() |
cbc48c381f | ||
![]() |
11e37011bd | ||
![]() |
cfda559a90 | ||
![]() |
806bd9f52c | ||
![]() |
953f7d01d7 | ||
![]() |
381e719a0e | ||
![]() |
296071067d |
6
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
6
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
@@ -8,7 +8,7 @@ body:
|
||||
|
||||
If you have a feature or enhancement request, please use the [feature request][fr] section of our [Community Forum][fr].
|
||||
|
||||
[fr]: https://community.home-assistant.io/c/feature-requests
|
||||
[fr]: https://github.com/orgs/home-assistant/discussions
|
||||
- type: textarea
|
||||
validations:
|
||||
required: true
|
||||
@@ -75,7 +75,7 @@ body:
|
||||
description: >
|
||||
The System information can be found in [Settings -> System -> Repairs -> (three dot menu) -> System Information](https://my.home-assistant.io/redirect/system_health/).
|
||||
Click the copy button at the bottom of the pop-up and paste it here.
|
||||
|
||||
|
||||
[](https://my.home-assistant.io/redirect/system_health/)
|
||||
- type: textarea
|
||||
attributes:
|
||||
@@ -85,7 +85,7 @@ body:
|
||||
Supervisor diagnostics can be found in [Settings -> Devices & services](https://my.home-assistant.io/redirect/integrations/).
|
||||
Find the card that says `Home Assistant Supervisor`, open it, and select the three dot menu of the Supervisor integration entry
|
||||
and select 'Download diagnostics'.
|
||||
|
||||
|
||||
**Please drag-and-drop the downloaded file into the textbox below. Do not copy and paste its contents.**
|
||||
- type: textarea
|
||||
attributes:
|
||||
|
53
.github/ISSUE_TEMPLATE/task.yml
vendored
Normal file
53
.github/ISSUE_TEMPLATE/task.yml
vendored
Normal file
@@ -0,0 +1,53 @@
|
||||
name: Task
|
||||
description: For staff only - Create a task
|
||||
type: Task
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
## ⚠️ RESTRICTED ACCESS
|
||||
|
||||
**This form is restricted to Open Home Foundation staff and authorized contributors only.**
|
||||
|
||||
If you are a community member wanting to contribute, please:
|
||||
- For bug reports: Use the [bug report form](https://github.com/home-assistant/supervisor/issues/new?template=bug_report.yml)
|
||||
- For feature requests: Submit to [Feature Requests](https://github.com/orgs/home-assistant/discussions)
|
||||
|
||||
---
|
||||
|
||||
### For authorized contributors
|
||||
|
||||
Use this form to create tasks for development work, improvements, or other actionable items that need to be tracked.
|
||||
- type: textarea
|
||||
id: description
|
||||
attributes:
|
||||
label: Description
|
||||
description: |
|
||||
Provide a clear and detailed description of the task that needs to be accomplished.
|
||||
|
||||
Be specific about what needs to be done, why it's important, and any constraints or requirements.
|
||||
placeholder: |
|
||||
Describe the task, including:
|
||||
- What needs to be done
|
||||
- Why this task is needed
|
||||
- Expected outcome
|
||||
- Any constraints or requirements
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: additional_context
|
||||
attributes:
|
||||
label: Additional context
|
||||
description: |
|
||||
Any additional information, links, research, or context that would be helpful.
|
||||
|
||||
Include links to related issues, research, prototypes, roadmap opportunities etc.
|
||||
placeholder: |
|
||||
- Roadmap opportunity: [link]
|
||||
- Epic: [link]
|
||||
- Feature request: [link]
|
||||
- Technical design documents: [link]
|
||||
- Prototype/mockup: [link]
|
||||
- Dependencies: [links]
|
||||
validations:
|
||||
required: false
|
5
.github/copilot-instructions.md
vendored
5
.github/copilot-instructions.md
vendored
@@ -251,8 +251,8 @@ async def backup_full(self, request: web.Request) -> dict[str, Any]:
|
||||
### Development Commands
|
||||
|
||||
```bash
|
||||
# Run tests with coverage
|
||||
pytest tests/ --cov=supervisor --cov-report=term-missing
|
||||
# Run tests, adjust paths as necessary
|
||||
pytest -qsx tests/
|
||||
|
||||
# Linting and formatting
|
||||
ruff check supervisor/
|
||||
@@ -275,6 +275,7 @@ Always run the pre-commit hooks at the end of code editing.
|
||||
- Use `self.sys_run_in_executor()` for blocking operations
|
||||
- Access Docker via `self.sys_docker` not direct Docker API
|
||||
- Use constants from `const.py` instead of hardcoding
|
||||
- Store types in (per-module) `const.py` (e.g. supervisor/store/const.py)
|
||||
|
||||
**❌ Avoid These Patterns**:
|
||||
- Direct Docker API usage - use Supervisor's Docker manager
|
||||
|
27
.github/workflows/builder.yml
vendored
27
.github/workflows/builder.yml
vendored
@@ -53,7 +53,7 @@ jobs:
|
||||
requirements: ${{ steps.requirements.outputs.changed }}
|
||||
steps:
|
||||
- name: Checkout the repository
|
||||
uses: actions/checkout@v4.2.2
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
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@v3.0.0
|
||||
uses: masesgroup/retrieve-changed-files@491e80760c0e28d36ca6240a27b1ccb8e1402c13 # 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@v4.2.2
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
@@ -104,9 +104,10 @@ 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.03.0
|
||||
uses: home-assistant/wheels@2025.09.0
|
||||
with:
|
||||
abi: cp313
|
||||
tag: musllinux_1_2
|
||||
@@ -125,15 +126,15 @@ jobs:
|
||||
|
||||
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
|
||||
if: needs.init.outputs.publish == 'true'
|
||||
uses: actions/setup-python@v5.6.0
|
||||
uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
|
||||
with:
|
||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||
|
||||
- name: Install Cosign
|
||||
if: needs.init.outputs.publish == 'true'
|
||||
uses: sigstore/cosign-installer@v3.9.1
|
||||
uses: sigstore/cosign-installer@d7543c93d881b35a8faa02e8e3605f69b7a1ce62 # v3.10.0
|
||||
with:
|
||||
cosign-release: "v2.4.3"
|
||||
cosign-release: "v2.5.3"
|
||||
|
||||
- name: Install dirhash and calc hash
|
||||
if: needs.init.outputs.publish == 'true'
|
||||
@@ -149,7 +150,7 @@ jobs:
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
if: needs.init.outputs.publish == 'true'
|
||||
uses: docker/login-action@v3.4.0
|
||||
uses: docker/login-action@184bdaa0721073962dff0199f1fb9940f07167d1 # v3.5.0
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
@@ -159,8 +160,9 @@ 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.03.0
|
||||
uses: home-assistant/builder@2025.09.0
|
||||
with:
|
||||
args: |
|
||||
$BUILD_ARGS \
|
||||
@@ -178,7 +180,7 @@ jobs:
|
||||
steps:
|
||||
- name: Checkout the repository
|
||||
if: needs.init.outputs.publish == 'true'
|
||||
uses: actions/checkout@v4.2.2
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
|
||||
- name: Initialize git
|
||||
if: needs.init.outputs.publish == 'true'
|
||||
@@ -203,11 +205,12 @@ jobs:
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- name: Checkout the repository
|
||||
uses: actions/checkout@v4.2.2
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
|
||||
# home-assistant/builder doesn't support sha pinning
|
||||
- name: Build the Supervisor
|
||||
if: needs.init.outputs.publish != 'true'
|
||||
uses: home-assistant/builder@2025.03.0
|
||||
uses: home-assistant/builder@2025.09.0
|
||||
with:
|
||||
args: |
|
||||
--test \
|
||||
|
83
.github/workflows/ci.yaml
vendored
83
.github/workflows/ci.yaml
vendored
@@ -26,15 +26,15 @@ jobs:
|
||||
name: Prepare Python dependencies
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@v4.2.2
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
- name: Set up Python
|
||||
id: python
|
||||
uses: actions/setup-python@v5.6.0
|
||||
uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
|
||||
with:
|
||||
python-version: ${{ env.DEFAULT_PYTHON }}
|
||||
- name: Restore Python virtual environment
|
||||
id: cache-venv
|
||||
uses: actions/cache@v4.2.3
|
||||
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
|
||||
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@v4.2.3
|
||||
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
|
||||
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@v4.2.2
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
- name: Set up Python ${{ needs.prepare.outputs.python-version }}
|
||||
uses: actions/setup-python@v5.6.0
|
||||
uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
|
||||
id: python
|
||||
with:
|
||||
python-version: ${{ needs.prepare.outputs.python-version }}
|
||||
- name: Restore Python virtual environment
|
||||
id: cache-venv
|
||||
uses: actions/cache@v4.2.3
|
||||
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
|
||||
with:
|
||||
path: venv
|
||||
key: |
|
||||
@@ -88,7 +88,7 @@ jobs:
|
||||
exit 1
|
||||
- name: Restore pre-commit environment from cache
|
||||
id: cache-precommit
|
||||
uses: actions/cache@v4.2.3
|
||||
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
|
||||
with:
|
||||
path: ${{ env.PRE_COMMIT_CACHE }}
|
||||
key: |
|
||||
@@ -111,15 +111,15 @@ jobs:
|
||||
needs: prepare
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@v4.2.2
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
- name: Set up Python ${{ needs.prepare.outputs.python-version }}
|
||||
uses: actions/setup-python@v5.6.0
|
||||
uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
|
||||
id: python
|
||||
with:
|
||||
python-version: ${{ needs.prepare.outputs.python-version }}
|
||||
- name: Restore Python virtual environment
|
||||
id: cache-venv
|
||||
uses: actions/cache@v4.2.3
|
||||
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
|
||||
with:
|
||||
path: venv
|
||||
key: |
|
||||
@@ -131,7 +131,7 @@ jobs:
|
||||
exit 1
|
||||
- name: Restore pre-commit environment from cache
|
||||
id: cache-precommit
|
||||
uses: actions/cache@v4.2.3
|
||||
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
|
||||
with:
|
||||
path: ${{ env.PRE_COMMIT_CACHE }}
|
||||
key: |
|
||||
@@ -154,7 +154,7 @@ jobs:
|
||||
needs: prepare
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@v4.2.2
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
- 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@v4.2.2
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
- name: Set up Python ${{ needs.prepare.outputs.python-version }}
|
||||
uses: actions/setup-python@v5.6.0
|
||||
uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
|
||||
id: python
|
||||
with:
|
||||
python-version: ${{ needs.prepare.outputs.python-version }}
|
||||
- name: Restore Python virtual environment
|
||||
id: cache-venv
|
||||
uses: actions/cache@v4.2.3
|
||||
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
|
||||
with:
|
||||
path: venv
|
||||
key: |
|
||||
@@ -189,7 +189,7 @@ jobs:
|
||||
exit 1
|
||||
- name: Restore pre-commit environment from cache
|
||||
id: cache-precommit
|
||||
uses: actions/cache@v4.2.3
|
||||
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
|
||||
with:
|
||||
path: ${{ env.PRE_COMMIT_CACHE }}
|
||||
key: |
|
||||
@@ -213,15 +213,15 @@ jobs:
|
||||
needs: prepare
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@v4.2.2
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
- name: Set up Python ${{ needs.prepare.outputs.python-version }}
|
||||
uses: actions/setup-python@v5.6.0
|
||||
uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
|
||||
id: python
|
||||
with:
|
||||
python-version: ${{ needs.prepare.outputs.python-version }}
|
||||
- name: Restore Python virtual environment
|
||||
id: cache-venv
|
||||
uses: actions/cache@v4.2.3
|
||||
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
|
||||
with:
|
||||
path: venv
|
||||
key: |
|
||||
@@ -233,7 +233,7 @@ jobs:
|
||||
exit 1
|
||||
- name: Restore pre-commit environment from cache
|
||||
id: cache-precommit
|
||||
uses: actions/cache@v4.2.3
|
||||
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
|
||||
with:
|
||||
path: ${{ env.PRE_COMMIT_CACHE }}
|
||||
key: |
|
||||
@@ -257,15 +257,15 @@ jobs:
|
||||
needs: prepare
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@v4.2.2
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
- name: Set up Python ${{ needs.prepare.outputs.python-version }}
|
||||
uses: actions/setup-python@v5.6.0
|
||||
uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
|
||||
id: python
|
||||
with:
|
||||
python-version: ${{ needs.prepare.outputs.python-version }}
|
||||
- name: Restore Python virtual environment
|
||||
id: cache-venv
|
||||
uses: actions/cache@v4.2.3
|
||||
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
|
||||
with:
|
||||
path: venv
|
||||
key: |
|
||||
@@ -293,9 +293,9 @@ jobs:
|
||||
needs: prepare
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@v4.2.2
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
- name: Set up Python ${{ needs.prepare.outputs.python-version }}
|
||||
uses: actions/setup-python@v5.6.0
|
||||
uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.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@v4.2.3
|
||||
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
|
||||
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@v4.2.3
|
||||
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
|
||||
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@v4.2.2
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
- name: Set up Python ${{ needs.prepare.outputs.python-version }}
|
||||
uses: actions/setup-python@v5.6.0
|
||||
uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
|
||||
id: python
|
||||
with:
|
||||
python-version: ${{ needs.prepare.outputs.python-version }}
|
||||
- name: Install Cosign
|
||||
uses: sigstore/cosign-installer@v3.9.1
|
||||
uses: sigstore/cosign-installer@d7543c93d881b35a8faa02e8e3605f69b7a1ce62 # v3.10.0
|
||||
with:
|
||||
cosign-release: "v2.4.3"
|
||||
cosign-release: "v2.5.3"
|
||||
- name: Restore Python virtual environment
|
||||
id: cache-venv
|
||||
uses: actions/cache@v4.2.3
|
||||
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
|
||||
with:
|
||||
path: venv
|
||||
key: |
|
||||
@@ -386,9 +386,9 @@ jobs:
|
||||
-o console_output_style=count \
|
||||
tests
|
||||
- name: Upload coverage artifact
|
||||
uses: actions/upload-artifact@v4.6.2
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
with:
|
||||
name: coverage-${{ matrix.python-version }}
|
||||
name: coverage
|
||||
path: .coverage
|
||||
include-hidden-files: true
|
||||
|
||||
@@ -398,15 +398,15 @@ jobs:
|
||||
needs: ["pytest", "prepare"]
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@v4.2.2
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
- name: Set up Python ${{ needs.prepare.outputs.python-version }}
|
||||
uses: actions/setup-python@v5.6.0
|
||||
uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
|
||||
id: python
|
||||
with:
|
||||
python-version: ${{ needs.prepare.outputs.python-version }}
|
||||
- name: Restore Python virtual environment
|
||||
id: cache-venv
|
||||
uses: actions/cache@v4.2.3
|
||||
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
|
||||
with:
|
||||
path: venv
|
||||
key: |
|
||||
@@ -417,7 +417,10 @@ jobs:
|
||||
echo "Failed to restore Python virtual environment from cache"
|
||||
exit 1
|
||||
- name: Download all coverage artifacts
|
||||
uses: actions/download-artifact@v4.3.0
|
||||
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0
|
||||
with:
|
||||
name: coverage
|
||||
path: coverage/
|
||||
- name: Combine coverage results
|
||||
run: |
|
||||
. venv/bin/activate
|
||||
@@ -425,4 +428,4 @@ jobs:
|
||||
coverage report
|
||||
coverage xml
|
||||
- name: Upload coverage to Codecov
|
||||
uses: codecov/codecov-action@v5.4.3
|
||||
uses: codecov/codecov-action@5a1091511ad55cbe89839c7260b706298ca349f7 # v5.5.1
|
||||
|
2
.github/workflows/lock.yml
vendored
2
.github/workflows/lock.yml
vendored
@@ -9,7 +9,7 @@ jobs:
|
||||
lock:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: dessant/lock-threads@v5.0.1
|
||||
- uses: dessant/lock-threads@1bf7ec25051fe7c00bdd17e6a7cf3d7bfb7dc771 # v5.0.1
|
||||
with:
|
||||
github-token: ${{ github.token }}
|
||||
issue-inactive-days: "30"
|
||||
|
4
.github/workflows/release-drafter.yml
vendored
4
.github/workflows/release-drafter.yml
vendored
@@ -11,7 +11,7 @@ jobs:
|
||||
name: Release Drafter
|
||||
steps:
|
||||
- name: Checkout the repository
|
||||
uses: actions/checkout@v4.2.2
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
@@ -36,7 +36,7 @@ jobs:
|
||||
echo "version=$datepre.$newpost" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Run Release Drafter
|
||||
uses: release-drafter/release-drafter@v6.1.0
|
||||
uses: release-drafter/release-drafter@b1476f6e6eb133afa41ed8589daba6dc69b4d3f5 # v6.1.0
|
||||
with:
|
||||
tag: ${{ steps.version.outputs.version }}
|
||||
name: ${{ steps.version.outputs.version }}
|
||||
|
58
.github/workflows/restrict-task-creation.yml
vendored
Normal file
58
.github/workflows/restrict-task-creation.yml
vendored
Normal file
@@ -0,0 +1,58 @@
|
||||
name: Restrict task creation
|
||||
|
||||
# yamllint disable-line rule:truthy
|
||||
on:
|
||||
issues:
|
||||
types: [opened]
|
||||
|
||||
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'
|
||||
steps:
|
||||
- name: Check if user is authorized
|
||||
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
|
||||
with:
|
||||
script: |
|
||||
const issueAuthor = context.payload.issue.user.login;
|
||||
|
||||
// Check if user is an organization member
|
||||
try {
|
||||
await github.rest.orgs.checkMembershipForUser({
|
||||
org: 'home-assistant',
|
||||
username: issueAuthor
|
||||
});
|
||||
console.log(`✅ ${issueAuthor} is an organization member`);
|
||||
return; // Authorized
|
||||
} catch (error) {
|
||||
console.log(`❌ ${issueAuthor} is not authorized to create Task issues`);
|
||||
}
|
||||
|
||||
// Close the issue with a comment
|
||||
await github.rest.issues.createComment({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: context.issue.number,
|
||||
body: `Hi @${issueAuthor}, thank you for your contribution!\n\n` +
|
||||
`Task issues are restricted to Open Home Foundation staff and authorized contributors.\n\n` +
|
||||
`If you would like to:\n` +
|
||||
`- Report a bug: Please use the [bug report form](https://github.com/home-assistant/supervisor/issues/new?template=bug_report.yml)\n` +
|
||||
`- Request a feature: Please submit to [Feature Requests](https://github.com/orgs/home-assistant/discussions)\n\n` +
|
||||
`If you believe you should have access to create Task issues, please contact the maintainers.`
|
||||
});
|
||||
|
||||
await github.rest.issues.update({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: context.issue.number,
|
||||
state: 'closed'
|
||||
});
|
||||
|
||||
// Add a label to indicate this was auto-closed
|
||||
await github.rest.issues.addLabels({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: context.issue.number,
|
||||
labels: ['auto-closed']
|
||||
});
|
4
.github/workflows/sentry.yaml
vendored
4
.github/workflows/sentry.yaml
vendored
@@ -10,9 +10,9 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@v4.2.2
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
- name: Sentry Release
|
||||
uses: getsentry/action-release@v3.2.0
|
||||
uses: getsentry/action-release@4f502acc1df792390abe36f2dcb03612ef144818 # v3.3.0
|
||||
env:
|
||||
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
|
||||
SENTRY_ORG: ${{ secrets.SENTRY_ORG }}
|
||||
|
2
.github/workflows/stale.yml
vendored
2
.github/workflows/stale.yml
vendored
@@ -9,7 +9,7 @@ jobs:
|
||||
stale:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/stale@v9.1.0
|
||||
- uses: actions/stale@3a9db7e6a41a89f618792c92c0e97cc736e1b13f # v10.0.0
|
||||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
days-before-stale: 30
|
||||
|
10
.github/workflows/update_frontend.yml
vendored
10
.github/workflows/update_frontend.yml
vendored
@@ -14,10 +14,10 @@ jobs:
|
||||
latest_version: ${{ steps.latest_frontend_version.outputs.latest_tag }}
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
- name: Get latest frontend release
|
||||
id: latest_frontend_version
|
||||
uses: abatilo/release-info-action@v1.3.3
|
||||
uses: abatilo/release-info-action@32cb932219f1cee3fc4f4a298fd65ead5d35b661 # 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@v4
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
- 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@v1
|
||||
uses: robinraju/release-downloader@daf26c55d821e836577a15f77d86ddc078948b05 # v1.12
|
||||
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@v7
|
||||
uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e # v7.0.8
|
||||
with:
|
||||
commit-message: "Update frontend to version ${{ needs.check-version.outputs.latest_version }}"
|
||||
branch: autoupdate-frontend
|
||||
|
3
.gitignore
vendored
3
.gitignore
vendored
@@ -100,3 +100,6 @@ ENV/
|
||||
# mypy
|
||||
/.mypy_cache/*
|
||||
/.dmypy.json
|
||||
|
||||
# Mac
|
||||
.DS_Store
|
@@ -1 +1 @@
|
||||
20250401.0
|
||||
20250811.0
|
||||
|
@@ -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.6.17
|
||||
&& pip3 install uv==0.8.9
|
||||
|
||||
# Install requirements
|
||||
COPY requirements.txt .
|
||||
|
12
build.yaml
12
build.yaml
@@ -1,10 +1,10 @@
|
||||
image: ghcr.io/home-assistant/{arch}-hassio-supervisor
|
||||
build_from:
|
||||
aarch64: ghcr.io/home-assistant/aarch64-base-python:3.13-alpine3.21
|
||||
armhf: ghcr.io/home-assistant/armhf-base-python:3.13-alpine3.21
|
||||
armv7: ghcr.io/home-assistant/armv7-base-python:3.13-alpine3.21
|
||||
amd64: ghcr.io/home-assistant/amd64-base-python:3.13-alpine3.21
|
||||
i386: ghcr.io/home-assistant/i386-base-python:3.13-alpine3.21
|
||||
aarch64: ghcr.io/home-assistant/aarch64-base-python:3.13-alpine3.22
|
||||
armhf: ghcr.io/home-assistant/armhf-base-python:3.13-alpine3.22
|
||||
armv7: ghcr.io/home-assistant/armv7-base-python:3.13-alpine3.22
|
||||
amd64: ghcr.io/home-assistant/amd64-base-python:3.13-alpine3.22
|
||||
i386: ghcr.io/home-assistant/i386-base-python:3.13-alpine3.22
|
||||
codenotary:
|
||||
signer: notary@home-assistant.io
|
||||
base_image: notary@home-assistant.io
|
||||
@@ -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.4.3
|
||||
COSIGN_VERSION: 2.5.3
|
||||
labels:
|
||||
io.hass.type: supervisor
|
||||
org.opencontainers.image.title: Home Assistant Supervisor
|
||||
|
@@ -1,30 +1,30 @@
|
||||
aiodns==3.5.0
|
||||
aiohttp==3.12.13
|
||||
aiohttp==3.12.15
|
||||
atomicwrites-homeassistant==1.4.1
|
||||
attrs==25.3.0
|
||||
awesomeversion==25.5.0
|
||||
blockbuster==1.5.24
|
||||
awesomeversion==25.8.0
|
||||
blockbuster==1.5.25
|
||||
brotli==1.1.0
|
||||
ciso8601==2.3.2
|
||||
ciso8601==2.3.3
|
||||
colorlog==6.9.0
|
||||
cpe==1.3.1
|
||||
cryptography==45.0.5
|
||||
debugpy==1.8.14
|
||||
cryptography==46.0.1
|
||||
debugpy==1.8.17
|
||||
deepmerge==2.0
|
||||
dirhash==0.5.0
|
||||
docker==7.1.0
|
||||
faust-cchardet==2.1.19
|
||||
gitpython==3.1.44
|
||||
gitpython==3.1.45
|
||||
jinja2==3.1.6
|
||||
log-rate-limit==1.4.2
|
||||
orjson==3.10.18
|
||||
orjson==3.11.3
|
||||
pulsectl==24.12.0
|
||||
pyudev==0.24.3
|
||||
PyYAML==6.0.2
|
||||
requests==2.32.4
|
||||
requests==2.32.5
|
||||
securetar==2025.2.1
|
||||
sentry-sdk==2.32.0
|
||||
sentry-sdk==2.38.0
|
||||
setuptools==80.9.0
|
||||
voluptuous==0.15.2
|
||||
dbus-fast==2.44.1
|
||||
dbus-fast==2.44.3
|
||||
zlib-fast==0.2.1
|
||||
|
@@ -1,16 +1,16 @@
|
||||
astroid==3.3.10
|
||||
coverage==7.9.2
|
||||
mypy==1.16.1
|
||||
pre-commit==4.2.0
|
||||
pylint==3.3.7
|
||||
astroid==3.3.11
|
||||
coverage==7.10.7
|
||||
mypy==1.18.2
|
||||
pre-commit==4.3.0
|
||||
pylint==3.3.8
|
||||
pytest-aiohttp==1.1.0
|
||||
pytest-asyncio==0.25.2
|
||||
pytest-cov==6.2.1
|
||||
pytest-cov==7.0.0
|
||||
pytest-timeout==2.4.0
|
||||
pytest==8.4.1
|
||||
ruff==0.12.2
|
||||
time-machine==2.16.0
|
||||
types-docker==7.1.0.20250705
|
||||
types-pyyaml==6.0.12.20250516
|
||||
types-requests==2.32.4.20250611
|
||||
pytest==8.4.2
|
||||
ruff==0.13.1
|
||||
time-machine==2.19.0
|
||||
types-docker==7.1.0.20250916
|
||||
types-pyyaml==6.0.12.20250915
|
||||
types-requests==2.32.4.20250913
|
||||
urllib3==2.5.0
|
||||
|
@@ -66,10 +66,23 @@ if __name__ == "__main__":
|
||||
_LOGGER.info("Setting up Supervisor")
|
||||
loop.run_until_complete(coresys.core.setup())
|
||||
|
||||
bootstrap.register_signal_handlers(loop, coresys)
|
||||
# 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)
|
||||
|
||||
try:
|
||||
loop.run_until_complete(coresys.core.start())
|
||||
loop.run_until_complete(startup_task)
|
||||
except asyncio.CancelledError:
|
||||
_LOGGER.warning("Supervisor startup cancelled")
|
||||
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
|
||||
|
@@ -67,17 +67,16 @@ from ..docker.monitor import DockerContainerStateEvent
|
||||
from ..docker.stats import DockerStats
|
||||
from ..exceptions import (
|
||||
AddonConfigurationError,
|
||||
AddonNotSupportedError,
|
||||
AddonsError,
|
||||
AddonsJobError,
|
||||
AddonsNotSupportedError,
|
||||
ConfigurationFileError,
|
||||
DockerError,
|
||||
HomeAssistantAPIError,
|
||||
HostAppArmorError,
|
||||
)
|
||||
from ..hardware.data import Device
|
||||
from ..homeassistant.const import WSEvent
|
||||
from ..jobs.const import JobExecutionLimit
|
||||
from ..jobs.const import JobConcurrency, JobThrottle
|
||||
from ..jobs.decorator import Job
|
||||
from ..resolution.const import ContextType, IssueType, UnhealthyReason
|
||||
from ..resolution.data import Issue
|
||||
@@ -733,8 +732,8 @@ class Addon(AddonModel):
|
||||
|
||||
@Job(
|
||||
name="addon_unload",
|
||||
limit=JobExecutionLimit.GROUP_ONCE,
|
||||
on_condition=AddonsJobError,
|
||||
concurrency=JobConcurrency.GROUP_REJECT,
|
||||
)
|
||||
async def unload(self) -> None:
|
||||
"""Unload add-on and remove data."""
|
||||
@@ -766,8 +765,8 @@ class Addon(AddonModel):
|
||||
|
||||
@Job(
|
||||
name="addon_install",
|
||||
limit=JobExecutionLimit.GROUP_ONCE,
|
||||
on_condition=AddonsJobError,
|
||||
concurrency=JobConcurrency.GROUP_REJECT,
|
||||
)
|
||||
async def install(self) -> None:
|
||||
"""Install and setup this addon."""
|
||||
@@ -807,8 +806,8 @@ class Addon(AddonModel):
|
||||
|
||||
@Job(
|
||||
name="addon_uninstall",
|
||||
limit=JobExecutionLimit.GROUP_ONCE,
|
||||
on_condition=AddonsJobError,
|
||||
concurrency=JobConcurrency.GROUP_REJECT,
|
||||
)
|
||||
async def uninstall(
|
||||
self, *, remove_config: bool, remove_image: bool = True
|
||||
@@ -842,8 +841,7 @@ class Addon(AddonModel):
|
||||
# Cleanup Ingress panel from sidebar
|
||||
if self.ingress_panel:
|
||||
self.ingress_panel = False
|
||||
with suppress(HomeAssistantAPIError):
|
||||
await self.sys_ingress.update_hass_panel(self)
|
||||
await self.sys_ingress.update_hass_panel(self)
|
||||
|
||||
# Cleanup Ingress dynamic port assignment
|
||||
need_ingress_token_cleanup = False
|
||||
@@ -873,8 +871,8 @@ class Addon(AddonModel):
|
||||
|
||||
@Job(
|
||||
name="addon_update",
|
||||
limit=JobExecutionLimit.GROUP_ONCE,
|
||||
on_condition=AddonsJobError,
|
||||
concurrency=JobConcurrency.GROUP_REJECT,
|
||||
)
|
||||
async def update(self) -> asyncio.Task | None:
|
||||
"""Update this addon to latest version.
|
||||
@@ -923,8 +921,8 @@ class Addon(AddonModel):
|
||||
|
||||
@Job(
|
||||
name="addon_rebuild",
|
||||
limit=JobExecutionLimit.GROUP_ONCE,
|
||||
on_condition=AddonsJobError,
|
||||
concurrency=JobConcurrency.GROUP_REJECT,
|
||||
)
|
||||
async def rebuild(self) -> asyncio.Task | None:
|
||||
"""Rebuild this addons container and image.
|
||||
@@ -1068,8 +1066,8 @@ class Addon(AddonModel):
|
||||
|
||||
@Job(
|
||||
name="addon_start",
|
||||
limit=JobExecutionLimit.GROUP_ONCE,
|
||||
on_condition=AddonsJobError,
|
||||
concurrency=JobConcurrency.GROUP_REJECT,
|
||||
)
|
||||
async def start(self) -> asyncio.Task:
|
||||
"""Set options and start add-on.
|
||||
@@ -1117,8 +1115,8 @@ class Addon(AddonModel):
|
||||
|
||||
@Job(
|
||||
name="addon_stop",
|
||||
limit=JobExecutionLimit.GROUP_ONCE,
|
||||
on_condition=AddonsJobError,
|
||||
concurrency=JobConcurrency.GROUP_REJECT,
|
||||
)
|
||||
async def stop(self) -> None:
|
||||
"""Stop add-on."""
|
||||
@@ -1131,8 +1129,8 @@ class Addon(AddonModel):
|
||||
|
||||
@Job(
|
||||
name="addon_restart",
|
||||
limit=JobExecutionLimit.GROUP_ONCE,
|
||||
on_condition=AddonsJobError,
|
||||
concurrency=JobConcurrency.GROUP_REJECT,
|
||||
)
|
||||
async def restart(self) -> asyncio.Task:
|
||||
"""Restart add-on.
|
||||
@@ -1166,13 +1164,13 @@ class Addon(AddonModel):
|
||||
|
||||
@Job(
|
||||
name="addon_write_stdin",
|
||||
limit=JobExecutionLimit.GROUP_ONCE,
|
||||
on_condition=AddonsJobError,
|
||||
concurrency=JobConcurrency.GROUP_REJECT,
|
||||
)
|
||||
async def write_stdin(self, data) -> None:
|
||||
"""Write data to add-on stdin."""
|
||||
if not self.with_stdin:
|
||||
raise AddonsNotSupportedError(
|
||||
raise AddonNotSupportedError(
|
||||
f"Add-on {self.slug} does not support writing to stdin!", _LOGGER.error
|
||||
)
|
||||
|
||||
@@ -1200,8 +1198,8 @@ class Addon(AddonModel):
|
||||
|
||||
@Job(
|
||||
name="addon_begin_backup",
|
||||
limit=JobExecutionLimit.GROUP_ONCE,
|
||||
on_condition=AddonsJobError,
|
||||
concurrency=JobConcurrency.GROUP_REJECT,
|
||||
)
|
||||
async def begin_backup(self) -> bool:
|
||||
"""Execute pre commands or stop addon if necessary.
|
||||
@@ -1222,8 +1220,8 @@ class Addon(AddonModel):
|
||||
|
||||
@Job(
|
||||
name="addon_end_backup",
|
||||
limit=JobExecutionLimit.GROUP_ONCE,
|
||||
on_condition=AddonsJobError,
|
||||
concurrency=JobConcurrency.GROUP_REJECT,
|
||||
)
|
||||
async def end_backup(self) -> asyncio.Task | None:
|
||||
"""Execute post commands or restart addon if necessary.
|
||||
@@ -1260,8 +1258,8 @@ class Addon(AddonModel):
|
||||
|
||||
@Job(
|
||||
name="addon_backup",
|
||||
limit=JobExecutionLimit.GROUP_ONCE,
|
||||
on_condition=AddonsJobError,
|
||||
concurrency=JobConcurrency.GROUP_REJECT,
|
||||
)
|
||||
async def backup(self, tar_file: tarfile.TarFile) -> asyncio.Task | None:
|
||||
"""Backup state of an add-on.
|
||||
@@ -1368,8 +1366,8 @@ class Addon(AddonModel):
|
||||
|
||||
@Job(
|
||||
name="addon_restore",
|
||||
limit=JobExecutionLimit.GROUP_ONCE,
|
||||
on_condition=AddonsJobError,
|
||||
concurrency=JobConcurrency.GROUP_REJECT,
|
||||
)
|
||||
async def restore(self, tar_file: tarfile.TarFile) -> asyncio.Task | None:
|
||||
"""Restore state of an add-on.
|
||||
@@ -1419,7 +1417,7 @@ class Addon(AddonModel):
|
||||
|
||||
# If available
|
||||
if not self._available(data[ATTR_SYSTEM]):
|
||||
raise AddonsNotSupportedError(
|
||||
raise AddonNotSupportedError(
|
||||
f"Add-on {self.slug} is not available for this platform",
|
||||
_LOGGER.error,
|
||||
)
|
||||
@@ -1521,10 +1519,10 @@ class Addon(AddonModel):
|
||||
|
||||
@Job(
|
||||
name="addon_restart_after_problem",
|
||||
limit=JobExecutionLimit.GROUP_THROTTLE_RATE_LIMIT,
|
||||
throttle_period=WATCHDOG_THROTTLE_PERIOD,
|
||||
throttle_max_calls=WATCHDOG_THROTTLE_MAX_CALLS,
|
||||
on_condition=AddonsJobError,
|
||||
throttle=JobThrottle.GROUP_RATE_LIMIT,
|
||||
)
|
||||
async def _restart_after_problem(self, state: ContainerState):
|
||||
"""Restart unhealthy or failed addon."""
|
||||
|
@@ -9,16 +9,17 @@ from typing import Self, Union
|
||||
|
||||
from attr import evolve
|
||||
|
||||
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,
|
||||
HomeAssistantAPIError,
|
||||
)
|
||||
from ..jobs.decorator import Job, JobCondition
|
||||
from ..resolution.const import ContextType, IssueType, SuggestionType
|
||||
@@ -180,8 +181,11 @@ class AddonManager(CoreSysAttributes):
|
||||
name="addon_manager_install",
|
||||
conditions=ADDON_UPDATE_CONDITIONS,
|
||||
on_condition=AddonsJobError,
|
||||
concurrency=JobConcurrency.QUEUE,
|
||||
)
|
||||
async def install(self, slug: str) -> None:
|
||||
async def install(
|
||||
self, slug: str, *, validation_complete: asyncio.Event | None = None
|
||||
) -> None:
|
||||
"""Install an add-on."""
|
||||
self.sys_jobs.current.reference = slug
|
||||
|
||||
@@ -194,6 +198,10 @@ 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)
|
||||
@@ -223,7 +231,11 @@ class AddonManager(CoreSysAttributes):
|
||||
on_condition=AddonsJobError,
|
||||
)
|
||||
async def update(
|
||||
self, slug: str, backup: bool | None = False
|
||||
self,
|
||||
slug: str,
|
||||
backup: bool | None = False,
|
||||
*,
|
||||
validation_complete: asyncio.Event | None = None,
|
||||
) -> asyncio.Task | None:
|
||||
"""Update add-on.
|
||||
|
||||
@@ -248,6 +260,10 @@ 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}",
|
||||
@@ -266,7 +282,7 @@ class AddonManager(CoreSysAttributes):
|
||||
],
|
||||
on_condition=AddonsJobError,
|
||||
)
|
||||
async def rebuild(self, slug: str) -> asyncio.Task | None:
|
||||
async def rebuild(self, slug: str, *, force: bool = False) -> asyncio.Task | None:
|
||||
"""Perform a rebuild of local build add-on.
|
||||
|
||||
Returns a Task that completes when addon has state 'started' (see addon.start)
|
||||
@@ -289,8 +305,8 @@ class AddonManager(CoreSysAttributes):
|
||||
raise AddonsError(
|
||||
"Version changed, use Update instead Rebuild", _LOGGER.error
|
||||
)
|
||||
if not addon.need_build:
|
||||
raise AddonsNotSupportedError(
|
||||
if not force and not addon.need_build:
|
||||
raise AddonNotSupportedError(
|
||||
"Can't rebuild a image based add-on", _LOGGER.error
|
||||
)
|
||||
|
||||
@@ -334,8 +350,7 @@ class AddonManager(CoreSysAttributes):
|
||||
# Update ingress
|
||||
if had_ingress != addon.ingress_panel:
|
||||
await self.sys_ingress.reload()
|
||||
with suppress(HomeAssistantAPIError):
|
||||
await self.sys_ingress.update_hass_panel(addon)
|
||||
await self.sys_ingress.update_hass_panel(addon)
|
||||
|
||||
return wait_for_start
|
||||
|
||||
|
@@ -89,7 +89,12 @@ from ..const import (
|
||||
)
|
||||
from ..coresys import CoreSys
|
||||
from ..docker.const import Capabilities
|
||||
from ..exceptions import AddonsNotSupportedError
|
||||
from ..exceptions import (
|
||||
AddonNotSupportedArchitectureError,
|
||||
AddonNotSupportedError,
|
||||
AddonNotSupportedHomeAssistantVersionError,
|
||||
AddonNotSupportedMachineTypeError,
|
||||
)
|
||||
from ..jobs.const import JOB_GROUP_ADDON
|
||||
from ..jobs.job_group import JobGroup
|
||||
from ..utils import version_is_new_enough
|
||||
@@ -645,7 +650,7 @@ class AddonModel(JobGroup, ABC):
|
||||
return None
|
||||
|
||||
# Return data
|
||||
return readme.read_text(encoding="utf-8")
|
||||
return readme.read_text(encoding="utf-8", errors="replace")
|
||||
|
||||
return await self.sys_run_in_executor(read_readme)
|
||||
|
||||
@@ -680,9 +685,8 @@ class AddonModel(JobGroup, ABC):
|
||||
"""Validate if addon is available for current system."""
|
||||
# Architecture
|
||||
if not self.sys_arch.is_supported(config[ATTR_ARCH]):
|
||||
raise AddonsNotSupportedError(
|
||||
f"Add-on {self.slug} not supported on this platform, supported architectures: {', '.join(config[ATTR_ARCH])}",
|
||||
logger,
|
||||
raise AddonNotSupportedArchitectureError(
|
||||
logger, slug=self.slug, architectures=config[ATTR_ARCH]
|
||||
)
|
||||
|
||||
# Machine / Hardware
|
||||
@@ -690,9 +694,8 @@ class AddonModel(JobGroup, ABC):
|
||||
if machine and (
|
||||
f"!{self.sys_machine}" in machine or self.sys_machine not in machine
|
||||
):
|
||||
raise AddonsNotSupportedError(
|
||||
f"Add-on {self.slug} not supported on this machine, supported machine types: {', '.join(machine)}",
|
||||
logger,
|
||||
raise AddonNotSupportedMachineTypeError(
|
||||
logger, slug=self.slug, machine_types=machine
|
||||
)
|
||||
|
||||
# Home Assistant
|
||||
@@ -701,16 +704,15 @@ class AddonModel(JobGroup, ABC):
|
||||
if version and not version_is_new_enough(
|
||||
self.sys_homeassistant.version, version
|
||||
):
|
||||
raise AddonsNotSupportedError(
|
||||
f"Add-on {self.slug} not supported on this system, requires Home Assistant version {version} or greater",
|
||||
logger,
|
||||
raise AddonNotSupportedHomeAssistantVersionError(
|
||||
logger, slug=self.slug, version=str(version)
|
||||
)
|
||||
|
||||
def _available(self, config) -> bool:
|
||||
"""Return True if this add-on is available on this platform."""
|
||||
try:
|
||||
self._validate_availability(config)
|
||||
except AddonsNotSupportedError:
|
||||
except AddonNotSupportedError:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
@@ -93,15 +93,7 @@ class AddonOptions(CoreSysAttributes):
|
||||
|
||||
typ = self.raw_schema[key]
|
||||
try:
|
||||
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)
|
||||
options[key] = self._validate_element(typ, value, key)
|
||||
except (IndexError, KeyError):
|
||||
raise vol.Invalid(
|
||||
f"Type error for option '{key}' in {self._name} ({self._slug})"
|
||||
@@ -111,7 +103,20 @@ class AddonOptions(CoreSysAttributes):
|
||||
return options
|
||||
|
||||
# pylint: disable=no-value-for-parameter
|
||||
def _single_validate(self, typ: str, value: Any, key: str):
|
||||
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:
|
||||
"""Validate a single element."""
|
||||
# if required argument
|
||||
if value is None:
|
||||
@@ -182,13 +187,15 @@ class AddonOptions(CoreSysAttributes):
|
||||
|
||||
# Device valid
|
||||
self.devices.add(device)
|
||||
return str(device.path)
|
||||
return str(value)
|
||||
|
||||
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):
|
||||
def _nested_validate_list(
|
||||
self, typ: Any, data_list: list[Any], key: str
|
||||
) -> list[Any]:
|
||||
"""Validate nested items."""
|
||||
options = []
|
||||
|
||||
@@ -201,17 +208,13 @@ class AddonOptions(CoreSysAttributes):
|
||||
# Process list
|
||||
for element in data_list:
|
||||
# Nested?
|
||||
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))
|
||||
options.append(self._validate_element(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 = {}
|
||||
|
||||
@@ -231,12 +234,7 @@ class AddonOptions(CoreSysAttributes):
|
||||
continue
|
||||
|
||||
# Nested?
|
||||
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)
|
||||
options[c_key] = self._validate_element(typ[c_key], c_value, c_key)
|
||||
|
||||
self._check_missing_options(typ, options, key)
|
||||
return options
|
||||
@@ -274,18 +272,28 @@ class UiOptions(CoreSysAttributes):
|
||||
|
||||
# read options
|
||||
for key, value in raw_schema.items():
|
||||
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)
|
||||
self._ui_schema_element(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]],
|
||||
@@ -377,10 +385,7 @@ class UiOptions(CoreSysAttributes):
|
||||
_LOGGER.error("Invalid schema %s", key)
|
||||
return
|
||||
|
||||
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)
|
||||
self._ui_schema_element(ui_schema, element, key, multiple=True)
|
||||
|
||||
def _nested_ui_dict(
|
||||
self,
|
||||
@@ -399,11 +404,7 @@ class UiOptions(CoreSysAttributes):
|
||||
|
||||
nested_schema: list[dict[str, Any]] = []
|
||||
for c_key, c_value in option_dict.items():
|
||||
# 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)
|
||||
self._ui_schema_element(nested_schema, c_value, c_key)
|
||||
|
||||
ui_node["schema"] = nested_schema
|
||||
ui_schema.append(ui_node)
|
||||
|
@@ -32,6 +32,7 @@ from ..const import (
|
||||
ATTR_DISCOVERY,
|
||||
ATTR_DOCKER_API,
|
||||
ATTR_ENVIRONMENT,
|
||||
ATTR_FIELDS,
|
||||
ATTR_FULL_ACCESS,
|
||||
ATTR_GPIO,
|
||||
ATTR_HASSIO_API,
|
||||
@@ -137,7 +138,19 @@ RE_DOCKER_IMAGE_BUILD = re.compile(
|
||||
r"^([a-zA-Z\-\.:\d{}]+/)*?([\-\w{}]+)/([\-\w{}]+)(:[\.\-\w{}]+)?$"
|
||||
)
|
||||
|
||||
SCHEMA_ELEMENT = vol.Match(RE_SCHEMA_ELEMENT)
|
||||
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},
|
||||
)
|
||||
)
|
||||
|
||||
RE_MACHINE = re.compile(
|
||||
r"^!?(?:"
|
||||
@@ -266,10 +279,23 @@ 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(
|
||||
{
|
||||
@@ -278,8 +304,8 @@ def _migrate_addon_config(protocol=False):
|
||||
}
|
||||
)
|
||||
|
||||
if volumes:
|
||||
config[ATTR_MAP] = volumes
|
||||
# Always update config to clear potentially malformed ones
|
||||
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):
|
||||
@@ -393,20 +419,7 @@ _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: vol.Any(
|
||||
SCHEMA_ELEMENT,
|
||||
[
|
||||
vol.Any(
|
||||
SCHEMA_ELEMENT,
|
||||
{str: vol.Any(SCHEMA_ELEMENT, [SCHEMA_ELEMENT])},
|
||||
)
|
||||
],
|
||||
vol.Schema({str: vol.Any(SCHEMA_ELEMENT, [SCHEMA_ELEMENT])}),
|
||||
)
|
||||
}
|
||||
),
|
||||
vol.Schema({str: SCHEMA_ELEMENT}),
|
||||
False,
|
||||
),
|
||||
vol.Optional(ATTR_IMAGE): docker_image,
|
||||
@@ -442,6 +455,7 @@ 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,
|
||||
)
|
||||
|
@@ -146,6 +146,14 @@ 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),
|
||||
@@ -198,6 +206,7 @@ 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),
|
||||
]
|
||||
)
|
||||
|
||||
@@ -439,6 +448,7 @@ 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(
|
||||
@@ -448,6 +458,10 @@ 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",
|
||||
@@ -560,6 +574,10 @@ 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",
|
||||
@@ -734,6 +752,10 @@ 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
|
||||
),
|
||||
|
@@ -36,6 +36,7 @@ from ..const import (
|
||||
ATTR_DNS,
|
||||
ATTR_DOCKER_API,
|
||||
ATTR_DOCUMENTATION,
|
||||
ATTR_FORCE,
|
||||
ATTR_FULL_ACCESS,
|
||||
ATTR_GPIO,
|
||||
ATTR_HASSIO_API,
|
||||
@@ -139,6 +140,8 @@ SCHEMA_SECURITY = vol.Schema({vol.Optional(ATTR_PROTECTED): vol.Boolean()})
|
||||
SCHEMA_UNINSTALL = vol.Schema(
|
||||
{vol.Optional(ATTR_REMOVE_CONFIG, default=False): vol.Boolean()}
|
||||
)
|
||||
|
||||
SCHEMA_REBUILD = vol.Schema({vol.Optional(ATTR_FORCE, default=False): vol.Boolean()})
|
||||
# pylint: enable=no-value-for-parameter
|
||||
|
||||
|
||||
@@ -303,7 +306,7 @@ class APIAddons(CoreSysAttributes):
|
||||
)
|
||||
|
||||
# Validate/Process Body
|
||||
body = await api_validate(addon_schema, request, origin=[ATTR_OPTIONS])
|
||||
body = await api_validate(addon_schema, request)
|
||||
if ATTR_OPTIONS in body:
|
||||
addon.options = body[ATTR_OPTIONS]
|
||||
if ATTR_BOOT in body:
|
||||
@@ -461,7 +464,11 @@ class APIAddons(CoreSysAttributes):
|
||||
async def rebuild(self, request: web.Request) -> None:
|
||||
"""Rebuild local build add-on."""
|
||||
addon = self.get_addon_for_request(request)
|
||||
if start_task := await asyncio.shield(self.sys_addons.rebuild(addon.slug)):
|
||||
body: dict[str, Any] = await api_validate(SCHEMA_REBUILD, request)
|
||||
|
||||
if start_task := await asyncio.shield(
|
||||
self.sys_addons.rebuild(addon.slug, force=body[ATTR_FORCE])
|
||||
):
|
||||
await start_task
|
||||
|
||||
@api_process
|
||||
|
@@ -92,13 +92,18 @@ class APIAuth(CoreSysAttributes):
|
||||
# Json
|
||||
if request.headers.get(CONTENT_TYPE) == CONTENT_TYPE_JSON:
|
||||
data = await request.json(loads=json_loads)
|
||||
return await self._process_dict(request, addon, data)
|
||||
if not await self._process_dict(request, addon, data):
|
||||
raise HTTPUnauthorized()
|
||||
return True
|
||||
|
||||
# URL encoded
|
||||
if request.headers.get(CONTENT_TYPE) == CONTENT_TYPE_URL:
|
||||
data = await request.post()
|
||||
return await self._process_dict(request, addon, data)
|
||||
if not await self._process_dict(request, addon, data):
|
||||
raise HTTPUnauthorized()
|
||||
return True
|
||||
|
||||
# Advertise Basic authentication by default
|
||||
raise HTTPUnauthorized(headers=REALM_HEADER)
|
||||
|
||||
@api_process
|
||||
|
@@ -3,7 +3,6 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
from collections.abc import Callable
|
||||
import errno
|
||||
from io import IOBase
|
||||
import logging
|
||||
@@ -46,12 +45,9 @@ 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 (
|
||||
@@ -61,7 +57,7 @@ from .const import (
|
||||
ATTR_LOCATIONS,
|
||||
CONTENT_TYPE_TAR,
|
||||
)
|
||||
from .utils import api_process, api_validate
|
||||
from .utils import api_process, api_validate, background_task
|
||||
|
||||
_LOGGER: logging.Logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -289,41 +285,6 @@ 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."""
|
||||
@@ -342,8 +303,8 @@ class APIBackups(CoreSysAttributes):
|
||||
body[ATTR_ADDITIONAL_LOCATIONS] = locations
|
||||
|
||||
background = body.pop(ATTR_BACKGROUND)
|
||||
backup_task, job_id = await self._background_backup_task(
|
||||
self.sys_backups.do_backup_full, **body
|
||||
backup_task, job_id = await background_task(
|
||||
self, self.sys_backups.do_backup_full, **body
|
||||
)
|
||||
|
||||
if background and not backup_task.done():
|
||||
@@ -378,8 +339,8 @@ class APIBackups(CoreSysAttributes):
|
||||
body[ATTR_ADDONS] = list(self.sys_addons.local)
|
||||
|
||||
background = body.pop(ATTR_BACKGROUND)
|
||||
backup_task, job_id = await self._background_backup_task(
|
||||
self.sys_backups.do_backup_partial, **body
|
||||
backup_task, job_id = await background_task(
|
||||
self, self.sys_backups.do_backup_partial, **body
|
||||
)
|
||||
|
||||
if background and not backup_task.done():
|
||||
@@ -402,8 +363,8 @@ class APIBackups(CoreSysAttributes):
|
||||
request, body.get(ATTR_LOCATION, backup.location)
|
||||
)
|
||||
background = body.pop(ATTR_BACKGROUND)
|
||||
restore_task, job_id = await self._background_backup_task(
|
||||
self.sys_backups.do_restore_full, backup, **body
|
||||
restore_task, job_id = await background_task(
|
||||
self, self.sys_backups.do_restore_full, backup, **body
|
||||
)
|
||||
|
||||
if background and not restore_task.done() or await restore_task:
|
||||
@@ -422,8 +383,8 @@ class APIBackups(CoreSysAttributes):
|
||||
request, body.get(ATTR_LOCATION, backup.location)
|
||||
)
|
||||
background = body.pop(ATTR_BACKGROUND)
|
||||
restore_task, job_id = await self._background_backup_task(
|
||||
self.sys_backups.do_restore_partial, backup, **body
|
||||
restore_task, job_id = await background_task(
|
||||
self, self.sys_backups.do_restore_partial, backup, **body
|
||||
)
|
||||
|
||||
if background and not restore_task.done() or await restore_task:
|
||||
|
@@ -49,6 +49,7 @@ 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"
|
||||
|
@@ -6,10 +6,13 @@ from typing import Any
|
||||
from aiohttp import web
|
||||
import voluptuous as vol
|
||||
|
||||
from supervisor.resolution.const import ContextType, IssueType, SuggestionType
|
||||
|
||||
from ..const import (
|
||||
ATTR_ENABLE_IPV6,
|
||||
ATTR_HOSTNAME,
|
||||
ATTR_LOGGING,
|
||||
ATTR_MTU,
|
||||
ATTR_PASSWORD,
|
||||
ATTR_REGISTRIES,
|
||||
ATTR_STORAGE,
|
||||
@@ -32,7 +35,12 @@ SCHEMA_DOCKER_REGISTRY = vol.Schema(
|
||||
)
|
||||
|
||||
# pylint: disable=no-value-for-parameter
|
||||
SCHEMA_OPTIONS = vol.Schema({vol.Optional(ATTR_ENABLE_IPV6): vol.Boolean()})
|
||||
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))),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
class APIDocker(CoreSysAttributes):
|
||||
@@ -49,6 +57,7 @@ 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,
|
||||
@@ -59,8 +68,28 @@ class APIDocker(CoreSysAttributes):
|
||||
"""Set docker options."""
|
||||
body = await api_validate(SCHEMA_OPTIONS, request)
|
||||
|
||||
if ATTR_ENABLE_IPV6 in body:
|
||||
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"
|
||||
)
|
||||
self.sys_resolution.create_issue(
|
||||
IssueType.REBOOT_REQUIRED,
|
||||
ContextType.SYSTEM,
|
||||
suggestions=[SuggestionType.EXECUTE_REBOOT],
|
||||
)
|
||||
|
||||
await self.sys_docker.config.save_data()
|
||||
|
||||
|
@@ -20,6 +20,7 @@ from ..const import (
|
||||
ATTR_CPU_PERCENT,
|
||||
ATTR_IMAGE,
|
||||
ATTR_IP_ADDRESS,
|
||||
ATTR_JOB_ID,
|
||||
ATTR_MACHINE,
|
||||
ATTR_MEMORY_LIMIT,
|
||||
ATTR_MEMORY_PERCENT,
|
||||
@@ -37,8 +38,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_FORCE, ATTR_SAFE_MODE
|
||||
from .utils import api_process, api_validate
|
||||
from .const import ATTR_BACKGROUND, ATTR_FORCE, ATTR_SAFE_MODE
|
||||
from .utils import api_process, api_validate, background_task
|
||||
|
||||
_LOGGER: logging.Logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -61,6 +62,7 @@ SCHEMA_UPDATE = vol.Schema(
|
||||
{
|
||||
vol.Optional(ATTR_VERSION): version_tag,
|
||||
vol.Optional(ATTR_BACKUP): bool,
|
||||
vol.Optional(ATTR_BACKGROUND, default=False): bool,
|
||||
}
|
||||
)
|
||||
|
||||
@@ -170,18 +172,24 @@ class APIHomeAssistant(CoreSysAttributes):
|
||||
}
|
||||
|
||||
@api_process
|
||||
async def update(self, request: web.Request) -> None:
|
||||
async def update(self, request: web.Request) -> dict[str, str] | None:
|
||||
"""Update Home Assistant."""
|
||||
body = await api_validate(SCHEMA_UPDATE, request)
|
||||
await self._check_offline_migration()
|
||||
|
||||
await asyncio.shield(
|
||||
self.sys_homeassistant.core.update(
|
||||
version=body.get(ATTR_VERSION, self.sys_homeassistant.latest_version),
|
||||
backup=body.get(ATTR_BACKUP),
|
||||
)
|
||||
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),
|
||||
)
|
||||
|
||||
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."""
|
||||
|
@@ -2,10 +2,17 @@
|
||||
|
||||
import asyncio
|
||||
from contextlib import suppress
|
||||
import json
|
||||
import logging
|
||||
from typing import Any
|
||||
|
||||
from aiohttp import ClientConnectionResetError, ClientPayloadError, web
|
||||
from aiohttp import (
|
||||
ClientConnectionResetError,
|
||||
ClientError,
|
||||
ClientPayloadError,
|
||||
ClientTimeout,
|
||||
web,
|
||||
)
|
||||
from aiohttp.hdrs import ACCEPT, RANGE
|
||||
import voluptuous as vol
|
||||
from voluptuous.error import CoerceInvalid
|
||||
@@ -51,6 +58,7 @@ from .const import (
|
||||
ATTR_FORCE,
|
||||
ATTR_IDENTIFIERS,
|
||||
ATTR_LLMNR_HOSTNAME,
|
||||
ATTR_MAX_DEPTH,
|
||||
ATTR_STARTUP_TIME,
|
||||
ATTR_USE_NTP,
|
||||
ATTR_VIRTUALIZATION,
|
||||
@@ -193,7 +201,11 @@ class APIHost(CoreSysAttributes):
|
||||
return possible_offset
|
||||
|
||||
async def advanced_logs_handler(
|
||||
self, request: web.Request, identifier: str | None = None, follow: bool = False
|
||||
self,
|
||||
request: web.Request,
|
||||
identifier: str | None = None,
|
||||
follow: bool = False,
|
||||
latest: bool = False,
|
||||
) -> web.StreamResponse:
|
||||
"""Return systemd-journald logs."""
|
||||
log_formatter = LogFormatter.PLAIN
|
||||
@@ -212,6 +224,20 @@ 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,
|
||||
@@ -240,6 +266,8 @@ 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:
|
||||
@@ -285,7 +313,81 @@ 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
|
||||
self,
|
||||
request: web.Request,
|
||||
identifier: str | None = None,
|
||||
follow: bool = False,
|
||||
latest: bool = False,
|
||||
) -> web.StreamResponse:
|
||||
"""Return systemd-journald logs. Wrapped as standard API handler."""
|
||||
return await self.advanced_logs_handler(request, identifier, follow)
|
||||
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
|
||||
|
@@ -199,21 +199,25 @@ class APIIngress(CoreSysAttributes):
|
||||
url = f"{url}?{request.query_string}"
|
||||
|
||||
# Start proxy
|
||||
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,
|
||||
)
|
||||
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)
|
||||
|
||||
return ws_server
|
||||
|
||||
@@ -286,6 +290,7 @@ class APIIngress(CoreSysAttributes):
|
||||
aiohttp.ClientError,
|
||||
aiohttp.ClientPayloadError,
|
||||
ConnectionResetError,
|
||||
ConnectionError,
|
||||
) as err:
|
||||
_LOGGER.error("Stream error with %s: %s", url, err)
|
||||
|
||||
@@ -309,9 +314,9 @@ class APIIngress(CoreSysAttributes):
|
||||
|
||||
def _init_header(
|
||||
request: web.Request, addon: Addon, session_data: IngressSessionData | None
|
||||
) -> CIMultiDict | dict[str, str]:
|
||||
) -> CIMultiDict[str]:
|
||||
"""Create initial header."""
|
||||
headers = {}
|
||||
headers = CIMultiDict[str]()
|
||||
|
||||
if session_data is not None:
|
||||
headers[HEADER_REMOTE_USER_ID] = session_data.user.id
|
||||
@@ -337,7 +342,7 @@ def _init_header(
|
||||
istr(HEADER_REMOTE_USER_DISPLAY_NAME),
|
||||
):
|
||||
continue
|
||||
headers[name] = value
|
||||
headers.add(name, value)
|
||||
|
||||
# Update X-Forwarded-For
|
||||
if request.transport:
|
||||
@@ -348,9 +353,9 @@ def _init_header(
|
||||
return headers
|
||||
|
||||
|
||||
def _response_header(response: aiohttp.ClientResponse) -> dict[str, str]:
|
||||
def _response_header(response: aiohttp.ClientResponse) -> CIMultiDict[str]:
|
||||
"""Create response header."""
|
||||
headers = {}
|
||||
headers = CIMultiDict[str]()
|
||||
|
||||
for name, value in response.headers.items():
|
||||
if name in (
|
||||
@@ -360,7 +365,7 @@ def _response_header(response: aiohttp.ClientResponse) -> dict[str, str]:
|
||||
hdrs.CONTENT_ENCODING,
|
||||
):
|
||||
continue
|
||||
headers[name] = value
|
||||
headers.add(name, value)
|
||||
|
||||
return headers
|
||||
|
||||
@@ -386,9 +391,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()
|
||||
await ws_to.ping(msg.data)
|
||||
elif msg.type == aiohttp.WSMsgType.PONG:
|
||||
await ws_to.pong()
|
||||
await ws_to.pong(msg.data)
|
||||
elif ws_to.closed:
|
||||
await ws_to.close(code=ws_to.close_code, message=msg.extra)
|
||||
except RuntimeError:
|
||||
|
@@ -26,7 +26,9 @@ from ..const import (
|
||||
ATTR_IP6_PRIVACY,
|
||||
ATTR_IPV4,
|
||||
ATTR_IPV6,
|
||||
ATTR_LLMNR,
|
||||
ATTR_MAC,
|
||||
ATTR_MDNS,
|
||||
ATTR_METHOD,
|
||||
ATTR_MODE,
|
||||
ATTR_NAMESERVERS,
|
||||
@@ -54,6 +56,7 @@ from ..host.configuration import (
|
||||
Ip6Setting,
|
||||
IpConfig,
|
||||
IpSetting,
|
||||
MulticastDnsMode,
|
||||
VlanConfig,
|
||||
WifiConfig,
|
||||
)
|
||||
@@ -97,6 +100,8 @@ 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),
|
||||
}
|
||||
)
|
||||
|
||||
@@ -160,6 +165,8 @@ 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,
|
||||
}
|
||||
|
||||
|
||||
@@ -260,6 +267,10 @@ 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))
|
||||
|
||||
@@ -300,6 +311,15 @@ 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(
|
||||
@@ -325,7 +345,7 @@ class APINetwork(CoreSysAttributes):
|
||||
)
|
||||
|
||||
vlan_interface = Interface(
|
||||
"",
|
||||
f"{interface.name}.{vlan}",
|
||||
"",
|
||||
"",
|
||||
True,
|
||||
@@ -338,5 +358,7 @@ class APINetwork(CoreSysAttributes):
|
||||
ipv6_setting,
|
||||
None,
|
||||
vlan_config,
|
||||
mdns=mdns_mode,
|
||||
llmnr=llmnr_mode,
|
||||
)
|
||||
await asyncio.shield(self.sys_host.network.apply_changes(vlan_interface))
|
||||
await asyncio.shield(self.sys_host.network.create_vlan(vlan_interface))
|
||||
|
@@ -1 +1 @@
|
||||
!function(){function d(d){var e=document.createElement("script");e.src=d,document.body.appendChild(e)}if(/Edge?\/(12[4-9]|1[3-9]\d|[2-9]\d{2}|\d{4,})\.\d+(\.\d+|)|Firefox\/(12[5-9]|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\/(17\.([5-9]|\d{2,})|(1[89]|[2-9]\d|\d{3,})\.\d+)([,.]\d+|)( \(\w+\)|)( Mobile\/\w+|) Safari\/|Chrome.+OPR\/(1{2}\d|1[2-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[4-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[5-9]|1[3-9]\d|[2-9]\d{2}|\d{4,})\.\d+(\.\d+|)|Android.+Chrom(ium|e)\/(12[4-9]|1[3-9]\d|[2-9]\d{2}|\d{4,})\.\d+(\.\d+|)|SamsungBrowser\/(2[5-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.35399ae87c70acf8.js')")()}catch(e){d("/api/hassio/app/frontend_es5/entrypoint.476bfed22da63267.js")}else d("/api/hassio/app/frontend_es5/entrypoint.476bfed22da63267.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.13b942f66af14ae7.js')")()}catch(e){d("/api/hassio/app/frontend_es5/entrypoint.4fccc51701dd6142.js")}else d("/api/hassio/app/frontend_es5/entrypoint.4fccc51701dd6142.js")}()
|
Binary file not shown.
Binary file not shown.
10
supervisor/api/panel/frontend_es5/1028.6e5f21856b867af6.js
Normal file
10
supervisor/api/panel/frontend_es5/1028.6e5f21856b867af6.js
Normal file
File diff suppressed because one or more lines are too long
BIN
supervisor/api/panel/frontend_es5/1028.6e5f21856b867af6.js.br
Normal file
BIN
supervisor/api/panel/frontend_es5/1028.6e5f21856b867af6.js.br
Normal file
Binary file not shown.
BIN
supervisor/api/panel/frontend_es5/1028.6e5f21856b867af6.js.gz
Normal file
BIN
supervisor/api/panel/frontend_es5/1028.6e5f21856b867af6.js.gz
Normal file
Binary file not shown.
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
BIN
supervisor/api/panel/frontend_es5/1066.2d3485fe168fe07f.js.br
Normal file
BIN
supervisor/api/panel/frontend_es5/1066.2d3485fe168fe07f.js.br
Normal file
Binary file not shown.
BIN
supervisor/api/panel/frontend_es5/1066.2d3485fe168fe07f.js.gz
Normal file
BIN
supervisor/api/panel/frontend_es5/1066.2d3485fe168fe07f.js.gz
Normal file
Binary file not shown.
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
@@ -1 +0,0 @@
|
||||
{"version":3,"file":"1081.91949d686e61cc12.js","sources":["https://raw.githubusercontent.com/home-assistant/frontend/20250401.0/src/components/ha-button-toggle-group.ts","https://raw.githubusercontent.com/home-assistant/frontend/20250401.0/src/components/ha-selector/ha-selector-button-toggle.ts"],"names":["_decorate","customElement","_initialize","_LitElement","F","constructor","args","d","kind","decorators","property","attribute","key","value","type","Boolean","queryAll","html","_t","_","this","buttons","map","button","iconPath","_t2","label","active","_handleClick","_t3","styleMap","width","fullWidth","length","dense","_this$_buttons","_buttons","forEach","async","updateComplete","shadowRoot","querySelector","style","margin","ev","currentTarget","fireEvent","static","css","_t4","LitElement","HaButtonToggleSelector","_this$selector$button","_this$selector$button2","_this$selector$button3","options","selector","button_toggle","option","translationKey","translation_key","localizeValue","localizedLabel","sort","a","b","caseInsensitiveStringCompare","hass","locale","language","toggleButtons","item","_valueChanged","_ev$detail","_this$value","stopPropagation","detail","target","disabled","undefined"],"mappings":"qXAWgCA,EAAAA,EAAAA,GAAA,EAD/BC,EAAAA,EAAAA,IAAc,4BAAyB,SAAAC,EAAAC,GAkIvC,OAAAC,EAlID,cACgCD,EAAoBE,WAAAA,IAAAC,GAAA,SAAAA,GAAAJ,EAAA,QAApBK,EAAA,EAAAC,KAAA,QAAAC,WAAA,EAC7BC,EAAAA,EAAAA,IAAS,CAAEC,WAAW,KAAQC,IAAA,UAAAC,WAAA,IAAAL,KAAA,QAAAC,WAAA,EAE9BC,EAAAA,EAAAA,OAAUE,IAAA,SAAAC,WAAA,IAAAL,KAAA,QAAAC,WAAA,EAEVC,EAAAA,EAAAA,IAAS,CAAEC,UAAW,aAAcG,KAAMC,WAAUH,IAAA,YAAAC,KAAAA,GAAA,OAClC,CAAK,IAAAL,KAAA,QAAAC,WAAA,EAEvBC,EAAAA,EAAAA,IAAS,CAAEI,KAAMC,WAAUH,IAAA,QAAAC,KAAAA,GAAA,OAAgB,CAAK,IAAAL,KAAA,QAAAC,WAAA,EAEhDO,EAAAA,EAAAA,IAAS,eAAaJ,IAAA,WAAAC,WAAA,IAAAL,KAAA,SAAAI,IAAA,SAAAC,MAEvB,WACE,OAAOI,EAAAA,EAAAA,IAAIC,IAAAA,EAAAC,CAAA,uBAELC,KAAKC,QAAQC,KAAKC,GAClBA,EAAOC,UACHP,EAAAA,EAAAA,IAAIQ,IAAAA,EAAAN,CAAA,2GACOI,EAAOG,MACRH,EAAOC,SACND,EAAOV,MACNO,KAAKO,SAAWJ,EAAOV,MACxBO,KAAKQ,eAEhBX,EAAAA,EAAAA,IAAIY,IAAAA,EAAAV,CAAA,iHACMW,EAAAA,EAAAA,GAAS,CACfC,MAAOX,KAAKY,UACL,IAAMZ,KAAKC,QAAQY,OAAtB,IACA,YAGGb,KAAKc,MACLX,EAAOV,MACNO,KAAKO,SAAWJ,EAAOV,MACxBO,KAAKQ,aACXL,EAAOG,SAKxB,GAAC,CAAAlB,KAAA,SAAAI,IAAA,UAAAC,MAED,WAAoB,IAAAsB,EAEL,QAAbA,EAAAf,KAAKgB,gBAAQ,IAAAD,GAAbA,EAAeE,SAAQC,gBACff,EAAOgB,eAEXhB,EAAOiB,WAAYC,cAAc,UACjCC,MAAMC,OAAS,GAAG,GAExB,GAAC,CAAAnC,KAAA,SAAAI,IAAA,eAAAC,MAED,SAAqB+B,GACnBxB,KAAKO,OAASiB,EAAGC,cAAchC,OAC/BiC,EAAAA,EAAAA,GAAU1B,KAAM,gBAAiB,CAAEP,MAAOO,KAAKO,QACjD,GAAC,CAAAnB,KAAA,QAAAuC,QAAA,EAAAnC,IAAA,SAAAC,KAAAA,GAAA,OAEemC,EAAAA,EAAAA,IAAGC,IAAAA,EAAA9B,CAAA,u0CAzDoB+B,EAAAA,I,MCD5BC,GAAsBnD,EAAAA,EAAAA,GAAA,EADlCC,EAAAA,EAAAA,IAAc,+BAA4B,SAAAC,EAAAC,GA4F1C,OAAAC,EA5FD,cACmCD,EAAoBE,WAAAA,IAAAC,GAAA,SAAAA,GAAAJ,EAAA,QAApBK,EAAA,EAAAC,KAAA,QAAAC,WAAA,EAChCC,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,CAAEC,WAAW,KAAQC,IAAA,gBAAAC,WAAA,IAAAL,KAAA,QAAAC,WAAA,EAG9BC,EAAAA,EAAAA,IAAS,CAAEI,KAAMC,WAAUH,IAAA,WAAAC,KAAAA,GAAA,OAAmB,CAAK,IAAAL,KAAA,QAAAC,WAAA,EAEnDC,EAAAA,EAAAA,IAAS,CAAEI,KAAMC,WAAUH,IAAA,WAAAC,KAAAA,GAAA,OAAmB,CAAI,IAAAL,KAAA,SAAAI,IAAA,SAAAC,MAEnD,WAAmB,IAAAuC,EAAAC,EAAAC,EACjB,MAAMC,GACuB,QAA3BH,EAAAhC,KAAKoC,SAASC,qBAAa,IAAAL,GAAS,QAATA,EAA3BA,EAA6BG,eAAO,IAAAH,OAAA,EAApCA,EAAsC9B,KAAKoC,GACvB,iBAAXA,EACFA,EACA,CAAE7C,MAAO6C,EAAQhC,MAAOgC,OAC1B,GAEDC,EAA4C,QAA9BN,EAAGjC,KAAKoC,SAASC,qBAAa,IAAAJ,OAAA,EAA3BA,EAA6BO,gBAEhDxC,KAAKyC,eAAiBF,GACxBJ,EAAQlB,SAASqB,IACf,MAAMI,EAAiB1C,KAAKyC,cAC1B,GAAGF,aAA0BD,EAAO7C,SAElCiD,IACFJ,EAAOhC,MAAQoC,EACjB,IAI2B,QAA/BR,EAAIlC,KAAKoC,SAASC,qBAAa,IAAAH,GAA3BA,EAA6BS,MAC/BR,EAAQQ,MAAK,CAACC,EAAGC,KACfC,EAAAA,EAAAA,IACEF,EAAEtC,MACFuC,EAAEvC,MACFN,KAAK+C,KAAKC,OAAOC,YAKvB,MAAMC,EAAgCf,EAAQjC,KAAKiD,IAAkB,CACnE7C,MAAO6C,EAAK7C,MACZb,MAAO0D,EAAK1D,UAGd,OAAOI,EAAAA,EAAAA,IAAIC,IAAAA,EAAAC,CAAA,iHACPC,KAAKM,MAEM4C,EACDlD,KAAKP,MACEO,KAAKoD,cAG5B,GAAC,CAAAhE,KAAA,SAAAI,IAAA,gBAAAC,MAED,SAAsB+B,GAAI,IAAA6B,EAAAC,EACxB9B,EAAG+B,kBAEH,MAAM9D,GAAiB,QAAT4D,EAAA7B,EAAGgC,cAAM,IAAAH,OAAA,EAATA,EAAW5D,QAAS+B,EAAGiC,OAAOhE,MACxCO,KAAK0D,eAAsBC,IAAVlE,GAAuBA,KAAqB,QAAhB6D,EAAMtD,KAAKP,aAAK,IAAA6D,EAAAA,EAAI,MAGrE5B,EAAAA,EAAAA,GAAU1B,KAAM,gBAAiB,CAC/BP,MAAOA,GAEX,GAAC,CAAAL,KAAA,QAAAuC,QAAA,EAAAnC,IAAA,SAAAC,KAAAA,GAAA,OAEemC,EAAAA,EAAAA,IAAGvB,IAAAA,EAAAN,CAAA,wLA5EuB+B,EAAAA,G"}
|
@@ -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
|
BIN
supervisor/api/panel/frontend_es5/113.13cc15fa81bd492f.js.br
Normal file
BIN
supervisor/api/panel/frontend_es5/113.13cc15fa81bd492f.js.br
Normal file
Binary file not shown.
BIN
supervisor/api/panel/frontend_es5/113.13cc15fa81bd492f.js.gz
Normal file
BIN
supervisor/api/panel/frontend_es5/113.13cc15fa81bd492f.js.gz
Normal file
Binary file not shown.
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"113.13cc15fa81bd492f.js","sources":["https://raw.githubusercontent.com/home-assistant/frontend/20250811.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
BIN
supervisor/api/panel/frontend_es5/1175.06400a4b8d4b589f.js.br
Normal file
BIN
supervisor/api/panel/frontend_es5/1175.06400a4b8d4b589f.js.br
Normal file
Binary file not shown.
BIN
supervisor/api/panel/frontend_es5/1175.06400a4b8d4b589f.js.gz
Normal file
BIN
supervisor/api/panel/frontend_es5/1175.06400a4b8d4b589f.js.gz
Normal file
Binary file not shown.
File diff suppressed because one or more lines are too long
@@ -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:()=>c});var d=t(73577),r=(t(71695),t(47021),t(57243)),n=t(50778),l=t(36522),o=t(63297),s=e([o]);o=(s.then?(await s)():s)[0];let u,h=e=>e,c=(0,d.Z)([(0,n.Mo)("ha-selector-navigation")],(function(e,a){return{F:class extends a{constructor(...a){super(...a),e(this)}},d:[{kind:"field",decorators:[(0,n.Cb)({attribute:!1})],key:"hass",value:void 0},{kind:"field",decorators:[(0,n.Cb)({attribute:!1})],key:"selector",value:void 0},{kind:"field",decorators:[(0,n.Cb)()],key:"value",value:void 0},{kind:"field",decorators:[(0,n.Cb)()],key:"label",value:void 0},{kind:"field",decorators:[(0,n.Cb)()],key:"helper",value:void 0},{kind:"field",decorators:[(0,n.Cb)({type:Boolean,reflect:!0})],key:"disabled",value(){return!1}},{kind:"field",decorators:[(0,n.Cb)({type:Boolean})],key:"required",value(){return!0}},{kind:"method",key:"render",value:function(){return(0,r.dy)(u||(u=h` <ha-navigation-picker .hass="${0}" .label="${0}" .value="${0}" .required="${0}" .disabled="${0}" .helper="${0}" @value-changed="${0}"></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,l.B)(this,"value-changed",{value:e.detail.value})}}]}}),r.oi);i()}catch(u){i(u)}}))}}]);
|
||||
//# sourceMappingURL=12.ffa1bdc0a98802fa.js.map
|
Binary file not shown.
Binary file not shown.
@@ -1 +0,0 @@
|
||||
{"version":3,"file":"12.ffa1bdc0a98802fa.js","sources":["https://raw.githubusercontent.com/home-assistant/frontend/20250401.0/src/components/ha-selector/ha-selector-navigation.ts"],"names":["HaNavigationSelector","_decorate","customElement","_initialize","_LitElement","F","constructor","args","d","kind","decorators","property","attribute","key","value","type","Boolean","reflect","html","_t","_","this","hass","label","required","disabled","helper","_valueChanged","ev","fireEvent","detail","LitElement"],"mappings":"mVAQaA,GAAoBC,EAAAA,EAAAA,GAAA,EADhCC,EAAAA,EAAAA,IAAc,4BAAyB,SAAAC,EAAAC,GAiCvC,OAAAC,EAjCD,cACiCD,EAAoBE,WAAAA,IAAAC,GAAA,SAAAA,GAAAJ,EAAA,QAApBK,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,KAAAA,GAAA,OAAmB,CAAK,IAAAL,KAAA,QAAAC,WAAA,EAElEC,EAAAA,EAAAA,IAAS,CAAEI,KAAMC,WAAUH,IAAA,WAAAC,KAAAA,GAAA,OAAmB,CAAI,IAAAL,KAAA,SAAAI,IAAA,SAAAC,MAEnD,WACE,OAAOI,EAAAA,EAAAA,IAAIC,IAAAA,EAAAC,CAAA,mKAECC,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"}
|
19
supervisor/api/panel/frontend_es5/1217.beeab73da0328333.js
Normal file
19
supervisor/api/panel/frontend_es5/1217.beeab73da0328333.js
Normal file
File diff suppressed because one or more lines are too long
BIN
supervisor/api/panel/frontend_es5/1217.beeab73da0328333.js.br
Normal file
BIN
supervisor/api/panel/frontend_es5/1217.beeab73da0328333.js.br
Normal file
Binary file not shown.
BIN
supervisor/api/panel/frontend_es5/1217.beeab73da0328333.js.gz
Normal file
BIN
supervisor/api/panel/frontend_es5/1217.beeab73da0328333.js.gz
Normal file
Binary file not shown.
File diff suppressed because one or more lines are too long
@@ -1,2 +0,0 @@
|
||||
(self.webpackChunkhome_assistant_frontend=self.webpackChunkhome_assistant_frontend||[]).push([["1236"],{4121:function(){Intl.PluralRules&&"function"==typeof Intl.PluralRules.__addLocaleData&&Intl.PluralRules.__addLocaleData({data:{categories:{cardinal:["one","other"],ordinal:["one","two","few","other"]},fn:function(e,n){var t=String(e).split("."),a=!t[1],l=Number(t[0])==e,o=l&&t[0].slice(-1),r=l&&t[0].slice(-2);return n?1==o&&11!=r?"one":2==o&&12!=r?"two":3==o&&13!=r?"few":"other":1==e&&a?"one":"other"}},locale:"en"})}}]);
|
||||
//# sourceMappingURL=1236.64ca65d0ea4d76d4.js.map
|
Binary file not shown.
Binary file not shown.
@@ -1 +0,0 @@
|
||||
{"version":3,"file":"1236.64ca65d0ea4d76d4.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":"wHAEIA,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
Binary file not shown.
Binary file not shown.
File diff suppressed because one or more lines are too long
@@ -1 +0,0 @@
|
||||
"use strict";(self.webpackChunkhome_assistant_frontend=self.webpackChunkhome_assistant_frontend||[]).push([["1295"],{21393:function(s,n,e){e.r(n)}}]);
|
Binary file not shown.
Binary file not shown.
@@ -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
|
BIN
supervisor/api/panel/frontend_es5/1303.586b15f0603e938e.js.br
Normal file
BIN
supervisor/api/panel/frontend_es5/1303.586b15f0603e938e.js.br
Normal file
Binary file not shown.
BIN
supervisor/api/panel/frontend_es5/1303.586b15f0603e938e.js.gz
Normal file
BIN
supervisor/api/panel/frontend_es5/1303.586b15f0603e938e.js.gz
Normal file
Binary file not shown.
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"1303.586b15f0603e938e.js","sources":["https://raw.githubusercontent.com/home-assistant/frontend/20250811.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
Binary file not shown.
Binary file not shown.
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
BIN
supervisor/api/panel/frontend_es5/1351.14a42c606e2b25af.js.br
Normal file
BIN
supervisor/api/panel/frontend_es5/1351.14a42c606e2b25af.js.br
Normal file
Binary file not shown.
BIN
supervisor/api/panel/frontend_es5/1351.14a42c606e2b25af.js.gz
Normal file
BIN
supervisor/api/panel/frontend_es5/1351.14a42c606e2b25af.js.gz
Normal file
Binary file not shown.
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
File diff suppressed because one or more lines are too long
@@ -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
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user