mirror of
https://github.com/home-assistant/frontend.git
synced 2025-08-12 02:39:26 +00:00
Compare commits
249 Commits
multiple-a
...
20210225.0
Author | SHA1 | Date | |
---|---|---|---|
![]() |
12a8a1531d | ||
![]() |
f44d867d3a | ||
![]() |
6f636187f7 | ||
![]() |
9414f89e50 | ||
![]() |
60bf1a5451 | ||
![]() |
32ba8f4731 | ||
![]() |
81f96de2bd | ||
![]() |
0c417755ed | ||
![]() |
93e5bde797 | ||
![]() |
c85f69c9ee | ||
![]() |
b6eaf0a7c5 | ||
![]() |
5f1851bade | ||
![]() |
5c66a02711 | ||
![]() |
bde925a0e3 | ||
![]() |
0f574a765b | ||
![]() |
782b941531 | ||
![]() |
f42c0a0717 | ||
![]() |
13ac14d449 | ||
![]() |
db9cea81db | ||
![]() |
7c1fd542da | ||
![]() |
54a2b2534a | ||
![]() |
f5fb6c1e03 | ||
![]() |
781c0701fc | ||
![]() |
742f1f85dc | ||
![]() |
a648e9be49 | ||
![]() |
fd9441dde2 | ||
![]() |
b5ec59c396 | ||
![]() |
60e4594abd | ||
![]() |
79692ef58a | ||
![]() |
ace7ee5622 | ||
![]() |
741ac679a0 | ||
![]() |
216526e391 | ||
![]() |
d76af2cb61 | ||
![]() |
b7d4c40736 | ||
![]() |
6092af8de6 | ||
![]() |
627424b8b9 | ||
![]() |
e33aff7cf3 | ||
![]() |
ef0bfb237a | ||
![]() |
c042c5568b | ||
![]() |
d84a7ee358 | ||
![]() |
8bfc8ece9d | ||
![]() |
2d3cf7d84d | ||
![]() |
520ef8f1df | ||
![]() |
f251d4267f | ||
![]() |
2052a5351c | ||
![]() |
9807d0aede | ||
![]() |
a41afcd714 | ||
![]() |
d93d2b5945 | ||
![]() |
d54a129605 | ||
![]() |
77911980cb | ||
![]() |
d51fd1e2f9 | ||
![]() |
fe54f8eb16 | ||
![]() |
fc7c4af27a | ||
![]() |
09e7600d86 | ||
![]() |
17410874e3 | ||
![]() |
03d4174163 | ||
![]() |
99eff73b0d | ||
![]() |
acefa39796 | ||
![]() |
c01c0528a6 | ||
![]() |
0ec58007c9 | ||
![]() |
e8daf88729 | ||
![]() |
ab74c7f7eb | ||
![]() |
6b673c7f44 | ||
![]() |
53510a3cb9 | ||
![]() |
d4d38a880d | ||
![]() |
18783d5e3b | ||
![]() |
eb235cb552 | ||
![]() |
435a6b6d53 | ||
![]() |
8d13745c6b | ||
![]() |
14c7cfc64c | ||
![]() |
c7821b9cee | ||
![]() |
a1d66aef0c | ||
![]() |
e275f1f4b9 | ||
![]() |
48de8b0739 | ||
![]() |
b75dc0efe0 | ||
![]() |
1d498349c5 | ||
![]() |
311e1cfb00 | ||
![]() |
5cdcec699b | ||
![]() |
cd72287d99 | ||
![]() |
c8717bfa32 | ||
![]() |
83de75b689 | ||
![]() |
e5ea762cbc | ||
![]() |
01df01cd66 | ||
![]() |
2c07a2c825 | ||
![]() |
c3f50ba0fb | ||
![]() |
c04419fd09 | ||
![]() |
9c7af0dfce | ||
![]() |
b66d14e980 | ||
![]() |
6a553e9554 | ||
![]() |
4273b72d71 | ||
![]() |
9ccfa79199 | ||
![]() |
fe3d22d4f8 | ||
![]() |
e06642e892 | ||
![]() |
5199e946a1 | ||
![]() |
17aff2f9b8 | ||
![]() |
f7c7ac44f7 | ||
![]() |
62dd0a561e | ||
![]() |
858eacddea | ||
![]() |
471bb5169c | ||
![]() |
9d89aa329c | ||
![]() |
4e4d8bdc5e | ||
![]() |
a30ec32ac1 | ||
![]() |
a9192ae2e1 | ||
![]() |
3d4a0b02e5 | ||
![]() |
3659cf7c87 | ||
![]() |
fbcf35414c | ||
![]() |
d79e5dd8fb | ||
![]() |
92b116c0da | ||
![]() |
da3f911deb | ||
![]() |
9d82ce8ab4 | ||
![]() |
db9597d2e7 | ||
![]() |
1523558f4c | ||
![]() |
bd9f4fe41c | ||
![]() |
6b938b2597 | ||
![]() |
5a8009e46e | ||
![]() |
c92eeb6bb7 | ||
![]() |
8ea6baaf5d | ||
![]() |
1ed03842c0 | ||
![]() |
0e9984413c | ||
![]() |
362b419814 | ||
![]() |
bffcccc1fe | ||
![]() |
b8e9a4ce9f | ||
![]() |
bdff3fd452 | ||
![]() |
1fc51f0087 | ||
![]() |
226013d999 | ||
![]() |
86847263b8 | ||
![]() |
b798523d53 | ||
![]() |
bd59c4fccf | ||
![]() |
566ffe24a4 | ||
![]() |
0e5c1b2041 | ||
![]() |
9a088a21da | ||
![]() |
1160d27004 | ||
![]() |
b4e5740050 | ||
![]() |
12bb3f5796 | ||
![]() |
ff62fdb69d | ||
![]() |
4ebf32cb1f | ||
![]() |
5afb8a77a9 | ||
![]() |
48ed33af95 | ||
![]() |
4a64cd4464 | ||
![]() |
8ae1a1b558 | ||
![]() |
ef1dd8b761 | ||
![]() |
3766f44787 | ||
![]() |
101067d018 | ||
![]() |
448d19bfbb | ||
![]() |
c1caad6d43 | ||
![]() |
a653bf5b0d | ||
![]() |
afdb369e04 | ||
![]() |
f02841409c | ||
![]() |
d4000cf662 | ||
![]() |
e1cf5919fa | ||
![]() |
d33e8d77c2 | ||
![]() |
3a522215a9 | ||
![]() |
7de6ea0879 | ||
![]() |
5ee0250ba5 | ||
![]() |
69d0a22091 | ||
![]() |
4d6c11ce31 | ||
![]() |
178605664e | ||
![]() |
12acb5473b | ||
![]() |
0cf8004b8d | ||
![]() |
6b0a8eae74 | ||
![]() |
00412c7216 | ||
![]() |
6483f23558 | ||
![]() |
4234d51a29 | ||
![]() |
9243d300cc | ||
![]() |
2ce70206c6 | ||
![]() |
b22455d2a5 | ||
![]() |
4f0bb9f6c3 | ||
![]() |
7dfa1b0942 | ||
![]() |
093e23c006 | ||
![]() |
5f003ccbe2 | ||
![]() |
8d0608610f | ||
![]() |
8bfe583a20 | ||
![]() |
0a3172dfdb | ||
![]() |
5c0e151bc2 | ||
![]() |
e69f36047d | ||
![]() |
ed368ddd9d | ||
![]() |
8400e90e34 | ||
![]() |
4cd95b724b | ||
![]() |
46b3836fbd | ||
![]() |
9988227d93 | ||
![]() |
9d289bfa34 | ||
![]() |
048de6b388 | ||
![]() |
9b7d8934da | ||
![]() |
0dd9b21c2d | ||
![]() |
417184525f | ||
![]() |
0a09ec706f | ||
![]() |
cf43b26e14 | ||
![]() |
a8b27e224f | ||
![]() |
dc5d14834d | ||
![]() |
12c935f647 | ||
![]() |
7710cb245c | ||
![]() |
a6b77c0457 | ||
![]() |
748a05f355 | ||
![]() |
831b9da0cf | ||
![]() |
a3339c9d5f | ||
![]() |
d228f38471 | ||
![]() |
fe13853b8b | ||
![]() |
6f4dbdc959 | ||
![]() |
870f0bcbb1 | ||
![]() |
599dd81e3c | ||
![]() |
9e99d158fd | ||
![]() |
136ebb5a07 | ||
![]() |
707338b1aa | ||
![]() |
7e06bd53b6 | ||
![]() |
08c1b864fc | ||
![]() |
16e7a16d12 | ||
![]() |
45200da32f | ||
![]() |
84a9ca59ef | ||
![]() |
40f4c35b42 | ||
![]() |
bb77d34017 | ||
![]() |
9659ebe59b | ||
![]() |
16c914b139 | ||
![]() |
142f26add1 | ||
![]() |
ef7d2aea8d | ||
![]() |
1aa40cb6df | ||
![]() |
cd06b931a9 | ||
![]() |
fd2df92000 | ||
![]() |
b565a8b8f7 | ||
![]() |
1aab656705 | ||
![]() |
6919d0cde6 | ||
![]() |
95fd8de1bc | ||
![]() |
28ca1a5193 | ||
![]() |
a1d07e5a00 | ||
![]() |
131a7f3782 | ||
![]() |
c28a7d6c10 | ||
![]() |
6b20bb967b | ||
![]() |
75f228418d | ||
![]() |
41f8b0d19b | ||
![]() |
cea3b8b010 | ||
![]() |
aba0e1f026 | ||
![]() |
f42587af22 | ||
![]() |
edcb7e87bb | ||
![]() |
96f0ceeb8c | ||
![]() |
a9baa7f1c1 | ||
![]() |
b1483287dc | ||
![]() |
a4657541fc | ||
![]() |
56d88b4c56 | ||
![]() |
02313e4be8 | ||
![]() |
2a14a3a4dc | ||
![]() |
d02a2e8c2e | ||
![]() |
0d281f8437 | ||
![]() |
2b0f43f334 | ||
![]() |
f9d28fc124 | ||
![]() |
da07173471 | ||
![]() |
4deeff7029 | ||
![]() |
a9d926e80a | ||
![]() |
c41369c89c | ||
![]() |
656bef3da9 | ||
![]() |
3d7ee6a4df |
@@ -4,7 +4,7 @@
|
||||
"dockerfile": "Dockerfile",
|
||||
"context": ".."
|
||||
},
|
||||
"appPort": 8123,
|
||||
"appPort": "8124:8123",
|
||||
"context": "..",
|
||||
"postCreateCommand": "script/bootstrap",
|
||||
"extensions": [
|
||||
@@ -26,6 +26,9 @@
|
||||
"[typescript]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
"[javascript]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
"files.trimTrailingWhitespace": true
|
||||
}
|
||||
}
|
||||
|
@@ -77,8 +77,8 @@ DO NOT DELETE ANY TEXT from this template! Otherwise, your issue may be closed w
|
||||
## Problem-relevant frontend configuration
|
||||
|
||||
<!--
|
||||
An example configuration that caused the problem for you, e.g. the YAML configuration
|
||||
of the used cards. Fill this out even if it seems unimportant to you. Please be sure
|
||||
An example configuration that caused the problem for you, e.g. the YAML configuration
|
||||
of the used cards. Fill this out even if it seems unimportant to you. Please be sure
|
||||
to remove personal information like passwords, private URLs and other credentials.
|
||||
-->
|
||||
|
138
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
Normal file
138
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
Normal file
@@ -0,0 +1,138 @@
|
||||
name: Report a bug with the UI, Frontend or Lovelace
|
||||
about: Report an issue related to the Home Assistant frontend.
|
||||
labels: bug
|
||||
title: ""
|
||||
issue_body: true
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Make sure you are running the [latest version of Home Assistant][releases] before reporting an issue.
|
||||
|
||||
If you have a feature or enhancement request for the frontend, please [start an discussion][fr] instead of creating an issue.
|
||||
|
||||
**Please not not report issues for custom Lovelace cards.**
|
||||
|
||||
[fr]: https://github.com/home-assistant/frontend/discussions
|
||||
[releases]: https://github.com/home-assistant/home-assistant/releases
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
label: Checklist
|
||||
description: Please verify that you've followed these steps
|
||||
options:
|
||||
- label: I have updated to the latest available Home Assistant version.
|
||||
required: true
|
||||
- label: I have cleared the cache of my browser.
|
||||
required: true
|
||||
- label: I have tried a different browser to see if it is related to my browser.
|
||||
required: true
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
## The problem
|
||||
- type: textarea
|
||||
validations:
|
||||
required: true
|
||||
attributes:
|
||||
label: Describe the issue you are experiencing
|
||||
description: Provide a clear and concise description of what the bug is.
|
||||
- type: textarea
|
||||
validations:
|
||||
required: true
|
||||
attributes:
|
||||
label: Describe the behavior you expected
|
||||
description: Describe what you expected to happen or it should look/behave.
|
||||
- type: textarea
|
||||
validations:
|
||||
required: true
|
||||
attributes:
|
||||
label: Steps to reproduce the issue
|
||||
description: |
|
||||
Please tell us exactly how to reproduce your issue.
|
||||
Provide clear and concise step by step instructions and add code snippets if needed.
|
||||
value: |
|
||||
1.
|
||||
2.
|
||||
3.
|
||||
...
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
## Environment
|
||||
- type: input
|
||||
validations:
|
||||
required: true
|
||||
attributes:
|
||||
label: What version of Home Assistant Core has the issue?
|
||||
placeholder: core-
|
||||
description: >
|
||||
Can be found in the Configuration panel -> Info.
|
||||
- type: input
|
||||
attributes:
|
||||
label: What was the last working version of Home Assistant Core?
|
||||
placeholder: core-
|
||||
description: >
|
||||
If known, otherwise leave blank.
|
||||
- type: input
|
||||
attributes:
|
||||
label: In which browser are you experiencing the issue with?
|
||||
placeholder: Google Chrome 88.0.4324.150
|
||||
description: >
|
||||
Provide the full name and don't forget to add the version!
|
||||
- type: input
|
||||
attributes:
|
||||
label: Which operating system are you using to run this browser?
|
||||
placeholder: macOS Big Sur (1.11)
|
||||
description: >
|
||||
Don't forget to add the version!
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
# Details
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: State of relevant entities
|
||||
description: >
|
||||
If your issue is about how an entity is shown in the UI, please add the
|
||||
state and attributes for all situations. You can find this information
|
||||
at Developer Tools -> States.
|
||||
value: |
|
||||
```yaml
|
||||
# Paste your state here.
|
||||
|
||||
```
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Problem-relevant frontend configuration
|
||||
description: >
|
||||
An example configuration that caused the problem for you, e.g., the YAML
|
||||
configuration of the used cards. Fill this out even if it seems
|
||||
unimportant to you. Please be sure to remove personal information like
|
||||
passwords, private URLs and other credentials.
|
||||
value: |
|
||||
```yaml
|
||||
# Paste your YAML here.
|
||||
|
||||
```
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Javascript errors shown in your browser console/inspector
|
||||
description: >
|
||||
If you come across any Javascript or other error logs, e.g., in your
|
||||
browser console/inspector please provide them.
|
||||
value: |
|
||||
```txt
|
||||
# Paste your logs here.
|
||||
|
||||
```
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
## Additional information
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
If you have any additional information for us, use the field below.
|
||||
Please note, you can attach screenshots or screen recordings here,
|
||||
by dragging and dropping files in the field below.
|
19
.github/workflows/netflify.yml
vendored
Normal file
19
.github/workflows/netflify.yml
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
name: Netlify
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: "0 0 * * *"
|
||||
|
||||
jobs:
|
||||
trigger_builds:
|
||||
name: Trigger netlify build preview
|
||||
runs-on: "ubuntu-latest"
|
||||
steps:
|
||||
- name: Trigger Cast build
|
||||
run: curl -X POST -d {} https://api.netlify.com/build_hooks/${{ secrets.NETLIFY_CAST_DEV_BUILD_HOOK }}
|
||||
|
||||
- name: Trigger Demo build
|
||||
run: curl -X POST -d {} https://api.netlify.com/build_hooks/${{ secrets.NETLIFY_DEMO_DEV_BUILD_HOOK }}
|
||||
|
||||
- name: Trigger Gallery build
|
||||
run: curl -X POST -d {} https://api.netlify.com/build_hooks/${{ secrets.NETLIFY_GALLERY_DEV_BUILD_HOOK }}
|
81
.github/workflows/release.yaml
vendored
Normal file
81
.github/workflows/release.yaml
vendored
Normal file
@@ -0,0 +1,81 @@
|
||||
name: Release
|
||||
|
||||
on:
|
||||
release:
|
||||
types:
|
||||
- published
|
||||
|
||||
env:
|
||||
WHEELS_TAG: 3.7-alpine3.11
|
||||
PYTHON_VERSION: 3.7
|
||||
NODE_VERSION: 12.1
|
||||
|
||||
jobs:
|
||||
release:
|
||||
name: Release
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout the repository
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Verify version
|
||||
uses: home-assistant/actions/helpers/verify-version@master
|
||||
|
||||
- name: Set up Python ${{ env.PYTHON_VERSION }}
|
||||
uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: ${{ env.PYTHON_VERSION }}
|
||||
|
||||
- name: Set up Node ${{ env.NODE_VERSION }}
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
|
||||
- name: Build and release package
|
||||
run: |
|
||||
python3 -m pip install twine
|
||||
export TWINE_USERNAME="__token__"
|
||||
export TWINE_PASSWORD="${{ secrets.TWINE_TOKEN }}"
|
||||
|
||||
script/release
|
||||
|
||||
wheels-init:
|
||||
name: Init wheels build
|
||||
needs: release
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Generate requirements.txt
|
||||
run: |
|
||||
# Sleep to give pypi time to populate the new version across mirrors
|
||||
sleep 240
|
||||
version=$(echo "${{ github.ref }}" | awk -F"/" '{print $NF}' )
|
||||
echo "home-assistant-frontend==$version" > ./requirements.txt
|
||||
|
||||
- name: Upload requirements.txt
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: requirements
|
||||
path: ./requirements.txt
|
||||
|
||||
build-wheels:
|
||||
name: Build wheels for ${{ matrix.arch }}
|
||||
needs: wheels-init
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
arch: ["aarch64", "armhf", "armv7", "amd64", "i386"]
|
||||
steps:
|
||||
- name: Download requirements.txt
|
||||
uses: actions/download-artifact@v2
|
||||
with:
|
||||
name: requirements
|
||||
|
||||
- name: Build wheels
|
||||
uses: home-assistant/wheels@master
|
||||
with:
|
||||
tag: ${{ env.WHEELS_TAG }}
|
||||
arch: ${{ matrix.arch }}
|
||||
wheels-host: ${{ secrets.WHEELS_HOST }}
|
||||
wheels-key: ${{ secrets.WHEELS_KEY }}
|
||||
wheels-user: wheels
|
||||
requirements: "requirements.txt"
|
65
.github/workflows/translations.yaml
vendored
Normal file
65
.github/workflows/translations.yaml
vendored
Normal file
@@ -0,0 +1,65 @@
|
||||
name: Translations
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: "30 0 * * *"
|
||||
push:
|
||||
branches:
|
||||
- dev
|
||||
paths:
|
||||
- translations/en.json
|
||||
|
||||
env:
|
||||
NODE_VERSION: 12
|
||||
|
||||
jobs:
|
||||
upload:
|
||||
name: Upload
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout the repository
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Set up Node ${{ env.NODE_VERSION }}
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
|
||||
- name: Upload Translations
|
||||
run: |
|
||||
export LOKALISE_TOKEN="${{ secrets.LOKALISE_TOKEN }}"
|
||||
|
||||
./script/translations_upload_base
|
||||
|
||||
download:
|
||||
name: Download
|
||||
needs: upload
|
||||
if: github.event_name == 'schedule'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout the repository
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Set up Node ${{ env.NODE_VERSION }}
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
|
||||
- name: Download Translations
|
||||
run: |
|
||||
export LOKALISE_TOKEN="${{ secrets.LOKALISE_TOKEN }}"
|
||||
|
||||
npm install
|
||||
./script/translations_download
|
||||
|
||||
- name: Initialize git
|
||||
uses: home-assistant/actions/helpers/git-init@master
|
||||
with:
|
||||
name: GitHub Action
|
||||
email: github-action@users.noreply.github.com
|
||||
|
||||
- name: Update translation
|
||||
run: |
|
||||
git add translations
|
||||
git commit -am "Translation update"
|
||||
git push
|
@@ -1,30 +0,0 @@
|
||||
# https://dev.azure.com/home-assistant
|
||||
|
||||
trigger: none
|
||||
pr: none
|
||||
schedules:
|
||||
- cron: "0 0 * * *"
|
||||
displayName: "build preview"
|
||||
branches:
|
||||
include:
|
||||
- dev
|
||||
always: true
|
||||
variables:
|
||||
- group: netlify
|
||||
|
||||
jobs:
|
||||
|
||||
- job: 'Netlify_preview'
|
||||
pool:
|
||||
vmImage: 'ubuntu-latest'
|
||||
steps:
|
||||
- script: |
|
||||
# Cast
|
||||
curl -X POST -d {} https://api.netlify.com/build_hooks/${NETLIFY_CAST}
|
||||
|
||||
# Demo
|
||||
curl -X POST -d {} https://api.netlify.com/build_hooks/${NETLIFY_DEMO}
|
||||
|
||||
# Gallery
|
||||
curl -X POST -d {} https://api.netlify.com/build_hooks/${NETLIFY_GALLERY}
|
||||
displayName: 'Trigger netlify build preview'
|
@@ -1,59 +0,0 @@
|
||||
# https://dev.azure.com/home-assistant
|
||||
|
||||
trigger:
|
||||
batch: true
|
||||
tags:
|
||||
include:
|
||||
- "*"
|
||||
pr: none
|
||||
variables:
|
||||
- name: versionWheels
|
||||
value: '1.10.1-3.7-alpine3.11'
|
||||
- name: versionNode
|
||||
value: '12.1'
|
||||
- group: twine
|
||||
resources:
|
||||
repositories:
|
||||
- repository: azure
|
||||
type: github
|
||||
name: 'home-assistant/ci-azure'
|
||||
endpoint: 'home-assistant'
|
||||
|
||||
|
||||
stages:
|
||||
- stage: "Validate"
|
||||
jobs:
|
||||
- template: templates/azp-job-version.yaml@azure
|
||||
|
||||
- stage: "Build"
|
||||
jobs:
|
||||
- job: "ReleasePython"
|
||||
pool:
|
||||
vmImage: "ubuntu-latest"
|
||||
steps:
|
||||
- task: UsePythonVersion@0
|
||||
displayName: "Use Python 3.7"
|
||||
inputs:
|
||||
versionSpec: "3.7"
|
||||
- task: NodeTool@0
|
||||
displayName: "Use Node $(versionNode)"
|
||||
inputs:
|
||||
versionSpec: "$(versionNode)"
|
||||
- script: pip install twine wheel
|
||||
displayName: "Install tools"
|
||||
- script: |
|
||||
export TWINE_USERNAME="$(twineUser)"
|
||||
export TWINE_PASSWORD="$(twinePassword)"
|
||||
|
||||
script/release
|
||||
displayName: "Build and release package"
|
||||
- stage: "Wheels"
|
||||
jobs:
|
||||
- template: templates/azp-job-wheels.yaml@azure
|
||||
parameters:
|
||||
builderVersion: '$(versionWheels)'
|
||||
wheelsRequirement: 'requirement.txt'
|
||||
preBuild:
|
||||
- script: |
|
||||
sleep 240
|
||||
echo "home-assistant-frontend==$(Build.SourceBranchName)" > requirement.txt
|
@@ -1,70 +0,0 @@
|
||||
# https://dev.azure.com/home-assistant
|
||||
|
||||
trigger:
|
||||
batch: true
|
||||
branches:
|
||||
include:
|
||||
- dev
|
||||
paths:
|
||||
include:
|
||||
- translations/en.json
|
||||
pr: none
|
||||
schedules:
|
||||
- cron: "30 0 * * *"
|
||||
displayName: "frontend translation update"
|
||||
branches:
|
||||
include:
|
||||
- dev
|
||||
always: true
|
||||
variables:
|
||||
- group: translation
|
||||
resources:
|
||||
repositories:
|
||||
- repository: azure
|
||||
type: github
|
||||
name: 'home-assistant/ci-azure'
|
||||
endpoint: 'home-assistant'
|
||||
|
||||
|
||||
jobs:
|
||||
|
||||
- job: 'Upload'
|
||||
pool:
|
||||
vmImage: 'ubuntu-latest'
|
||||
steps:
|
||||
- task: NodeTool@0
|
||||
displayName: 'Use Node 12.x'
|
||||
inputs:
|
||||
versionSpec: '12.x'
|
||||
- script: |
|
||||
export LOKALISE_TOKEN="$(lokaliseToken)"
|
||||
export AZURE_BRANCH="$(Build.SourceBranchName)"
|
||||
|
||||
./script/translations_upload_base
|
||||
displayName: 'Upload Translation'
|
||||
|
||||
- job: 'Download'
|
||||
dependsOn:
|
||||
- 'Upload'
|
||||
condition: or(eq(variables['Build.Reason'], 'Schedule'), eq(variables['Build.Reason'], 'Manual'))
|
||||
pool:
|
||||
vmImage: 'ubuntu-latest'
|
||||
steps:
|
||||
- task: NodeTool@0
|
||||
displayName: 'Use Node 12.x'
|
||||
inputs:
|
||||
versionSpec: '12.x'
|
||||
- template: templates/azp-step-git-init.yaml@azure
|
||||
- script: |
|
||||
export LOKALISE_TOKEN="$(lokaliseToken)"
|
||||
export AZURE_BRANCH="$(Build.SourceBranchName)"
|
||||
|
||||
npm install
|
||||
./script/translations_download
|
||||
displayName: 'Download Translation'
|
||||
- script: |
|
||||
git checkout dev
|
||||
git add translation
|
||||
git commit -am "[ci skip] Translation update"
|
||||
git push
|
||||
displayName: 'Update translation'
|
@@ -1,7 +1,7 @@
|
||||
const webpack = require("webpack");
|
||||
const path = require("path");
|
||||
const TerserPlugin = require("terser-webpack-plugin");
|
||||
const ManifestPlugin = require("webpack-manifest-plugin");
|
||||
const { WebpackManifestPlugin } = require("webpack-manifest-plugin");
|
||||
const paths = require("./paths.js");
|
||||
const bundle = require("./bundle");
|
||||
const log = require("fancy-log");
|
||||
@@ -68,7 +68,7 @@ const createWebpackConfig = ({
|
||||
],
|
||||
},
|
||||
plugins: [
|
||||
new ManifestPlugin({
|
||||
new WebpackManifestPlugin({
|
||||
// Only include the JS of entrypoints
|
||||
filter: (file) => file.isInitial && !file.name.endsWith(".map"),
|
||||
}),
|
||||
|
@@ -48,7 +48,7 @@ class HcCast extends LitElement {
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (this.lovelaceConfig === undefined) {
|
||||
return html` <hass-loading-screen no-toolbar></hass-loading-screen>> `;
|
||||
return html`<hass-loading-screen no-toolbar></hass-loading-screen>`;
|
||||
}
|
||||
|
||||
const error =
|
||||
|
@@ -98,8 +98,12 @@ class HcLayout extends LitElement {
|
||||
line-height: 32px;
|
||||
padding: 24px 16px 16px;
|
||||
display: block;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.hero {
|
||||
border-radius: 4px 4px 0 0;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
font-size: 14px;
|
||||
color: var(--secondary-text-color);
|
||||
|
@@ -1,8 +1,8 @@
|
||||
import {
|
||||
customElement,
|
||||
html,
|
||||
property,
|
||||
internalProperty,
|
||||
property,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import { mockHistory } from "../../../../demo/src/stubs/history";
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import "web-animations-js/web-animations-next-lite.min";
|
||||
import "../../../src/resources/roboto";
|
||||
import "../../../src/resources/ha-style";
|
||||
import "../../../src/resources/roboto";
|
||||
import "./layout/hc-lovelace";
|
||||
|
@@ -54,6 +54,8 @@ export const demoEntitiesArsaboo: DemoConfig["entities"] = (localize) =>
|
||||
state: "21",
|
||||
attributes: {
|
||||
friendly_name: "Living room temperature",
|
||||
device_class: "temperature",
|
||||
unit_of_measurement: "°C",
|
||||
},
|
||||
},
|
||||
"sensor.study_temp_rounded": {
|
||||
@@ -61,6 +63,8 @@ export const demoEntitiesArsaboo: DemoConfig["entities"] = (localize) =>
|
||||
state: "23",
|
||||
attributes: {
|
||||
friendly_name: "Study temperature",
|
||||
device_class: "temperature",
|
||||
unit_of_measurement: "°C",
|
||||
},
|
||||
},
|
||||
"sensor.living_room": {
|
||||
@@ -261,7 +265,7 @@ export const demoEntitiesArsaboo: DemoConfig["entities"] = (localize) =>
|
||||
entity_id: "light.kitchen_lights",
|
||||
state: "off",
|
||||
attributes: {
|
||||
friendly_name: "Kitchen lights",
|
||||
friendly_name: "Kitchen Lights",
|
||||
supported_features: 1,
|
||||
},
|
||||
},
|
||||
@@ -484,7 +488,7 @@ export const demoEntitiesArsaboo: DemoConfig["entities"] = (localize) =>
|
||||
attributes: {
|
||||
min_mireds: 111,
|
||||
max_mireds: 400,
|
||||
friendly_name: "Garage lights",
|
||||
friendly_name: "Garage Lights",
|
||||
supported_features: 55,
|
||||
},
|
||||
},
|
||||
|
@@ -12,6 +12,7 @@ export const demoLovelaceArsaboo: DemoConfig["lovelace"] = (localize) => ({
|
||||
{
|
||||
type: "entities",
|
||||
title: localize("ui.panel.page-demo.config.arsaboo.labels.lights"),
|
||||
state_color: true,
|
||||
entities: [
|
||||
{
|
||||
entity: "light.kitchen_lights",
|
||||
|
@@ -653,7 +653,7 @@ export const demoEntitiesJimpower: DemoConfig["entities"] = () =>
|
||||
entity_id: "binary_sensor.smoke_sensor_158d0001b8ddc7",
|
||||
state: "off",
|
||||
attributes: {
|
||||
Density: 0,
|
||||
density: 0,
|
||||
battery_level: 59,
|
||||
friendly_name: "Downstairs Smoke Detector",
|
||||
device_class: "smoke",
|
||||
@@ -663,7 +663,7 @@ export const demoEntitiesJimpower: DemoConfig["entities"] = () =>
|
||||
entity_id: "binary_sensor.smoke_sensor_158d0001b8deba",
|
||||
state: "off",
|
||||
attributes: {
|
||||
Density: 0,
|
||||
density: 0,
|
||||
battery_level: 65,
|
||||
friendly_name: "Upstairs Smoke Detector",
|
||||
device_class: "smoke",
|
||||
|
@@ -3,49 +3,7 @@ import { DemoConfig } from "../types";
|
||||
|
||||
export const demoLovelaceJimpower: DemoConfig["lovelace"] = () => ({
|
||||
name: "Kingia Castle",
|
||||
resources: [
|
||||
// {
|
||||
// url: "/local/custom_ui/dark-sky-weather-card.js?v=4",
|
||||
// type: "js",
|
||||
// },
|
||||
// {
|
||||
// url: "/local/custom_ui/mini-media-player-bundle.js?v=0.9.8",
|
||||
// type: "module",
|
||||
// },
|
||||
// {
|
||||
// url: "/local/custom_ui/tracker-card.js?v=0.1.5",
|
||||
// type: "js",
|
||||
// },
|
||||
// {
|
||||
// url: "/local/custom_ui/surveillance-card.js?v=0.0.1",
|
||||
// type: "module",
|
||||
// },
|
||||
// {
|
||||
// url: "/local/custom_ui/mini-graph-card-bundle.js?v=0.1.0",
|
||||
// type: "module",
|
||||
// },
|
||||
// {
|
||||
// url: "/local/custom_ui/slider-entity-row.js?v=d6da75",
|
||||
// type: "js",
|
||||
// },
|
||||
// {
|
||||
// url:
|
||||
// "/local/custom_ui/compact-custom-header/compact-custom-header.js?v=0.2.7",
|
||||
// type: "js",
|
||||
// },
|
||||
// {
|
||||
// url: "/local/custom_ui/waze-card.js?v=1.1.1",
|
||||
// type: "js",
|
||||
// },
|
||||
// {
|
||||
// url: "/local/custom_ui/circle-sensor-card.js?v=1.2.0",
|
||||
// type: "module",
|
||||
// },
|
||||
// {
|
||||
// url: "/local/custom_ui/monster-card.js?v=0.2.3",
|
||||
// type: "js",
|
||||
// },
|
||||
],
|
||||
resources: [],
|
||||
views: [
|
||||
{
|
||||
cards: [
|
||||
@@ -603,89 +561,6 @@ export const demoLovelaceJimpower: DemoConfig["lovelace"] = () => ({
|
||||
},
|
||||
{
|
||||
cards: [
|
||||
// {
|
||||
// style: {
|
||||
// "background-image": 'url("/assets/jimpower/cardbackK.png")',
|
||||
// "background-size": "100% 400px",
|
||||
// "box-shadow": "3px 3px rgba(0,0,0,0.4)",
|
||||
// "background-repeat": "no-repeat",
|
||||
// color: "#999999",
|
||||
// "border-radius": "20px",
|
||||
// border: "solid 1px rgba(100,100,100,0.3)",
|
||||
// "background-color": "rgba(50,50,50,0.3)",
|
||||
// },
|
||||
// type: "custom:card-modder",
|
||||
// card: {
|
||||
// entity_visibility: "sensor.dark_sky_visibility",
|
||||
// entity_sun: "sun.sun",
|
||||
// entity_daily_summary:
|
||||
// "sensor.bom_gc_forecast_detailed_summary_0",
|
||||
// entity_temperature: "sensor.bom_temp",
|
||||
// entity_forecast_high_temp_3:
|
||||
// "sensor.bom_gc_forecast_max_temp_c_3",
|
||||
// entity_forecast_high_temp_2:
|
||||
// "sensor.bom_gc_forecast_max_temp_c_2",
|
||||
// entity_forecast_high_temp_5:
|
||||
// "sensor.bom_gc_forecast_max_temp_c_5",
|
||||
// entity_forecast_high_temp_4:
|
||||
// "sensor.bom_gc_forecast_max_temp_c_4",
|
||||
// entity_wind_speed: "sensor.bom_wind_sp",
|
||||
// entity_forecast_icon_4: "sensor.dark_sky_icon_4",
|
||||
// entity_forecast_icon_5: "sensor.dark_sky_icon_5",
|
||||
// entity_forecast_icon_2: "sensor.dark_sky_icon_2",
|
||||
// entity_forecast_icon_3: "sensor.dark_sky_icon_3",
|
||||
// entity_forecast_icon_1: "sensor.dark_sky_icon_1",
|
||||
// entity_forecast_high_temp_1:
|
||||
// "sensor.bom_gc_forecast_max_temp_c_1",
|
||||
// entity_wind_bearing: "sensor.bom_wind_bear",
|
||||
// entity_forecast_low_temp_2:
|
||||
// "sensor.bom_gc_forecast_min_temp_c_2",
|
||||
// entity_forecast_low_temp_3:
|
||||
// "sensor.bom_gc_forecast_min_temp_c_3",
|
||||
// entity_pressure: "sensor.bom_pres",
|
||||
// entity_forecast_low_temp_1:
|
||||
// "sensor.bom_gc_forecast_min_temp_c_1",
|
||||
// entity_forecast_low_temp_4:
|
||||
// "sensor.bom_gc_forecast_min_temp_c_4",
|
||||
// entity_forecast_low_temp_5:
|
||||
// "sensor.bom_gc_forecast_min_temp_c_5",
|
||||
// entity_humidity: "sensor.bom_humd",
|
||||
// type: "custom:dark-sky-weather-card",
|
||||
// entity_current_conditions: "sensor.dark_sky_icon",
|
||||
// },
|
||||
// },
|
||||
// {
|
||||
// style: {
|
||||
// "background-image": 'url("/assets/jimpower/home/waze_5.png")',
|
||||
// "background-size": "100% 400px",
|
||||
// "box-shadow": "3px 3px rgba(0,0,0,0.4)",
|
||||
// "background-repeat": "no-repeat",
|
||||
// "border-radius": "20px",
|
||||
// border: "solid 1px rgba(100,100,100,0.3)",
|
||||
// "background-color": "rgba(50,50,50,0.3)",
|
||||
// },
|
||||
// type: "custom:card-modder",
|
||||
// card: {
|
||||
// entities: [
|
||||
// {
|
||||
// name: "James",
|
||||
// zone: "zone.home",
|
||||
// entity: "sensor.james_to_home",
|
||||
// },
|
||||
// {
|
||||
// name: "Tina",
|
||||
// zone: "zone.home",
|
||||
// entity: "sensor.tina_to_home",
|
||||
// },
|
||||
// {
|
||||
// name: "Work",
|
||||
// zone: "zone.powertec",
|
||||
// entity: "sensor.commute_to_work",
|
||||
// },
|
||||
// ],
|
||||
// type: "custom:waze-card",
|
||||
// },
|
||||
// },
|
||||
{
|
||||
style: {
|
||||
"border-radius": "20px",
|
||||
@@ -722,46 +597,8 @@ export const demoLovelaceJimpower: DemoConfig["lovelace"] = () => ({
|
||||
],
|
||||
type: "vertical-stack",
|
||||
},
|
||||
// {
|
||||
// cards: [
|
||||
// {
|
||||
// style: {
|
||||
// "border-radius": "20px",
|
||||
// color: "#999999",
|
||||
// "box-shadow": "3px 3px rgba(0,0,0,0.4)",
|
||||
// border: "solid 1px rgba(100,100,100,0.3)",
|
||||
// },
|
||||
// type: "custom:card-modder",
|
||||
// card: {
|
||||
// type: "picture-entity",
|
||||
// entity: "camera.bom_radar",
|
||||
// },
|
||||
// },
|
||||
// // {
|
||||
// // style: {
|
||||
// // "background-image": 'url("/assets/jimpower/cardbackK.png")',
|
||||
// // "background-size": "100% 525px",
|
||||
// // "box-shadow": "3px 3px rgba(0,0,0,0.4)",
|
||||
// // "background-repeat": "no-repeat",
|
||||
// // color: "#999999",
|
||||
// // "border-radius": "20px",
|
||||
// // border: "solid 1px rgba(100,100,100,0.3)",
|
||||
// // "background-color": "rgba(50,50,50,0.3)",
|
||||
// // },
|
||||
// // type: "custom:card-modder",
|
||||
// // card: {
|
||||
// // title: null,
|
||||
// // type: "custom:tracker-card",
|
||||
// // trackers: [
|
||||
// // "sensor.custom_card_tracker",
|
||||
// // "sensor.custom_component_tracker",
|
||||
// // ],
|
||||
// // },
|
||||
// // },
|
||||
// ],
|
||||
// type: "vertical-stack",
|
||||
// },
|
||||
],
|
||||
path: "home",
|
||||
icon: "mdi:castle",
|
||||
name: "Home",
|
||||
background:
|
||||
@@ -881,26 +718,13 @@ export const demoLovelaceJimpower: DemoConfig["lovelace"] = () => ({
|
||||
card: {
|
||||
image: "/assets/jimpower/security/air_8.jpg",
|
||||
elements: [
|
||||
{
|
||||
image:
|
||||
"https://www.airvisual.com/assets/aqi/ic-face-1-green.svg",
|
||||
type: "image",
|
||||
style: {
|
||||
width: "80px",
|
||||
top: "30%",
|
||||
left: "12%",
|
||||
transform: "none",
|
||||
height: "80px",
|
||||
},
|
||||
entity: "sensor.us_air_pollution_level_2",
|
||||
},
|
||||
{
|
||||
style: {
|
||||
color: "hsl(120, 41%, 39%)",
|
||||
top: "50%",
|
||||
"font-weight": 600,
|
||||
"font-size": "20px",
|
||||
left: "44%",
|
||||
"font-size": "50px",
|
||||
left: "30%",
|
||||
},
|
||||
type: "state-label",
|
||||
entity: "sensor.us_air_pollution_level_2",
|
||||
@@ -920,7 +744,7 @@ export const demoLovelaceJimpower: DemoConfig["lovelace"] = () => ({
|
||||
style: {
|
||||
color: "white",
|
||||
top: "80%",
|
||||
left: "52%",
|
||||
left: "48%",
|
||||
},
|
||||
type: "state-icon",
|
||||
entity: "sensor.us_main_pollutant_2",
|
||||
@@ -1411,6 +1235,7 @@ export const demoLovelaceJimpower: DemoConfig["lovelace"] = () => ({
|
||||
type: "vertical-stack",
|
||||
},
|
||||
],
|
||||
path: "security",
|
||||
icon: "hass:shield-home",
|
||||
name: "Security",
|
||||
background:
|
||||
|
@@ -101,7 +101,12 @@ export const demoEntitiesKernehed: DemoConfig["entities"] = () =>
|
||||
"sensor.zwave_battery_front_door": {
|
||||
entity_id: "sensor.zwave_battery_front_door",
|
||||
state: "63",
|
||||
attributes: { friendly_name: "Battery", icon: "mdi:battery-60" },
|
||||
attributes: {
|
||||
friendly_name: "Battery",
|
||||
icon: "mdi:battery-60",
|
||||
unit_of_measurement: "%",
|
||||
device_class: "battery",
|
||||
},
|
||||
},
|
||||
"sensor.oskar_devices": {
|
||||
entity_id: "sensor.oskar_devices",
|
||||
@@ -164,7 +169,7 @@ export const demoEntitiesKernehed: DemoConfig["entities"] = () =>
|
||||
},
|
||||
"input_select.christmas_pattern": {
|
||||
entity_id: "input_select.christmas_pattern",
|
||||
state: "None",
|
||||
state: "Rainbow",
|
||||
attributes: {
|
||||
options: [
|
||||
"None",
|
||||
@@ -186,7 +191,7 @@ export const demoEntitiesKernehed: DemoConfig["entities"] = () =>
|
||||
},
|
||||
"input_select.christmas_palette": {
|
||||
entity_id: "input_select.christmas_palette",
|
||||
state: "None",
|
||||
state: "Party",
|
||||
attributes: {
|
||||
options: [
|
||||
"None",
|
||||
@@ -457,7 +462,7 @@ export const demoEntitiesKernehed: DemoConfig["entities"] = () =>
|
||||
state: "0.0",
|
||||
attributes: {
|
||||
unit_of_measurement: "kB/s",
|
||||
friendly_name: "Nedladdning",
|
||||
friendly_name: "Downloading",
|
||||
icon: "mdi:file-download",
|
||||
},
|
||||
},
|
||||
@@ -471,7 +476,7 @@ export const demoEntitiesKernehed: DemoConfig["entities"] = () =>
|
||||
state: "0.0",
|
||||
attributes: {
|
||||
unit_of_measurement: "kB/s",
|
||||
friendly_name: "Uppladdning",
|
||||
friendly_name: "Uploading",
|
||||
icon: "mdi:file-upload",
|
||||
},
|
||||
},
|
||||
|
@@ -2,44 +2,7 @@ import { DemoConfig } from "../types";
|
||||
|
||||
export const demoLovelaceKernehed: DemoConfig["lovelace"] = () => ({
|
||||
name: "Hem",
|
||||
resources: [
|
||||
// {
|
||||
// url: "/local/custom-lovelace/monster-card.js",
|
||||
// type: "js",
|
||||
// },
|
||||
// {
|
||||
// url: "/local/custom-lovelace/mini-media-player-bundle.js?v=0.9.8",
|
||||
// type: "module",
|
||||
// },
|
||||
// {
|
||||
// url: "/local/custom-lovelace/slideshow-card.js?=1.1.0",
|
||||
// type: "js",
|
||||
// },
|
||||
// {
|
||||
// url: "/local/custom-lovelace/fold-entity-row.js?v=3ae2c4",
|
||||
// type: "js",
|
||||
// },
|
||||
// {
|
||||
// url: "/local/custom-lovelace/swipe-card/swipe-card.js?v=2.0.0",
|
||||
// type: "module",
|
||||
// },
|
||||
// {
|
||||
// url: "/local/custom-lovelace/upcoming-media-card/upcoming-media-card.js",
|
||||
// type: "js",
|
||||
// },
|
||||
// {
|
||||
// url: "/local/custom-lovelace/tracker-card.js?v=0.1.5",
|
||||
// type: "js",
|
||||
// },
|
||||
// {
|
||||
// url: "/local/custom-lovelace/card-tools.js?v=6ce5d0",
|
||||
// type: "js",
|
||||
// },
|
||||
// {
|
||||
// url: "/local/custom-lovelace/krisinfo.js?=0.0.1",
|
||||
// type: "js",
|
||||
// },
|
||||
],
|
||||
resources: [],
|
||||
views: [
|
||||
{
|
||||
cards: [
|
||||
@@ -64,7 +27,7 @@ export const demoLovelaceKernehed: DemoConfig["lovelace"] = () => ({
|
||||
style: {
|
||||
color: "white",
|
||||
top: "93%",
|
||||
left: "90%",
|
||||
left: "85%",
|
||||
},
|
||||
type: "state-label",
|
||||
entity: "sensor.battery_oskar",
|
||||
@@ -87,7 +50,7 @@ export const demoLovelaceKernehed: DemoConfig["lovelace"] = () => ({
|
||||
{
|
||||
style: {
|
||||
color: "white",
|
||||
top: "92%",
|
||||
top: "93%",
|
||||
left: "20%",
|
||||
},
|
||||
type: "state-label",
|
||||
@@ -96,8 +59,8 @@ export const demoLovelaceKernehed: DemoConfig["lovelace"] = () => ({
|
||||
{
|
||||
style: {
|
||||
color: "white",
|
||||
top: "92%",
|
||||
left: "90%",
|
||||
top: "93%",
|
||||
left: "85%",
|
||||
},
|
||||
type: "state-label",
|
||||
entity: "sensor.battery_bella",
|
||||
@@ -105,7 +68,7 @@ export const demoLovelaceKernehed: DemoConfig["lovelace"] = () => ({
|
||||
{
|
||||
style: {
|
||||
color: "white",
|
||||
top: "92%",
|
||||
top: "93%",
|
||||
left: "55%",
|
||||
},
|
||||
type: "state-label",
|
||||
@@ -131,78 +94,6 @@ export const demoLovelaceKernehed: DemoConfig["lovelace"] = () => ({
|
||||
type: "entities",
|
||||
title: "Lock",
|
||||
},
|
||||
// {
|
||||
// filter: {
|
||||
// exclude: [
|
||||
// {
|
||||
// state: "not_home",
|
||||
// },
|
||||
// ],
|
||||
// include: [
|
||||
// {
|
||||
// entity_id: "device_tracker.annasiphone",
|
||||
// },
|
||||
// {
|
||||
// entity_id: "device_tracker.iphone_2",
|
||||
// },
|
||||
// ],
|
||||
// },
|
||||
// type: "custom:monster-card",
|
||||
// card: {
|
||||
// show_header_toggle: false,
|
||||
// type: "entities",
|
||||
// title: "G\u00e4ster",
|
||||
// },
|
||||
// show_empty: false,
|
||||
// },
|
||||
// {
|
||||
// filter: {
|
||||
// exclude: [
|
||||
// {
|
||||
// state: "Inget",
|
||||
// },
|
||||
// {
|
||||
// state: "i.u.",
|
||||
// },
|
||||
// ],
|
||||
// include: [
|
||||
// {
|
||||
// entity_id: "sensor.pollen_al",
|
||||
// },
|
||||
// {
|
||||
// entity_id: "sensor.pollen_alm",
|
||||
// },
|
||||
// {
|
||||
// entity_id: "sensor.pollen_salg_vide",
|
||||
// },
|
||||
// {
|
||||
// entity_id: "sensor.pollen_bjork",
|
||||
// },
|
||||
// {
|
||||
// entity_id: "sensor.pollen_bok",
|
||||
// },
|
||||
// {
|
||||
// entity_id: "sensor.pollen_ek",
|
||||
// },
|
||||
// {
|
||||
// entity_id: "sensor.pollen_grabo",
|
||||
// },
|
||||
// {
|
||||
// entity_id: "sensor.pollen_gras",
|
||||
// },
|
||||
// {
|
||||
// entity_id: "sensor.pollen_hassel",
|
||||
// },
|
||||
// ],
|
||||
// },
|
||||
// type: "custom:monster-card",
|
||||
// card: {
|
||||
// show_header_toggle: false,
|
||||
// type: "entities",
|
||||
// title: "Pollenniv\u00e5er",
|
||||
// },
|
||||
// show_empty: false,
|
||||
// },
|
||||
{
|
||||
cards: [
|
||||
{
|
||||
@@ -226,10 +117,6 @@ export const demoLovelaceKernehed: DemoConfig["lovelace"] = () => ({
|
||||
],
|
||||
type: "vertical-stack",
|
||||
},
|
||||
// {
|
||||
// url: "https://embed.windy.com/embed2.html",
|
||||
// type: "iframe",
|
||||
// },
|
||||
{
|
||||
entities: [
|
||||
{
|
||||
@@ -263,6 +150,7 @@ export const demoLovelaceKernehed: DemoConfig["lovelace"] = () => ({
|
||||
],
|
||||
type: "glance",
|
||||
show_state: false,
|
||||
columns: 4,
|
||||
},
|
||||
{
|
||||
entities: ["sensor.oskar_bluetooth"],
|
||||
@@ -270,32 +158,6 @@ export const demoLovelaceKernehed: DemoConfig["lovelace"] = () => ({
|
||||
type: "entities",
|
||||
title: "Occupancy",
|
||||
},
|
||||
// {
|
||||
// filter: {
|
||||
// exclude: [
|
||||
// {
|
||||
// state: false,
|
||||
// },
|
||||
// ],
|
||||
// include: [
|
||||
// {
|
||||
// entity_id:
|
||||
// "binary_sensor.fibaro_system_unknown_type0c02_id1003_sensor_2",
|
||||
// },
|
||||
// {
|
||||
// entity_id:
|
||||
// "binary_sensor.fibaro_system_unknown_type0c02_id1003_sensor_3",
|
||||
// },
|
||||
// ],
|
||||
// },
|
||||
// type: "custom:monster-card",
|
||||
// card: {
|
||||
// show_header_toggle: false,
|
||||
// type: "entities",
|
||||
// title: "Brandvarnare",
|
||||
// },
|
||||
// show_empty: false,
|
||||
// },
|
||||
{
|
||||
type: "weather-forecast",
|
||||
entity: "weather.smhi_vader",
|
||||
@@ -378,41 +240,9 @@ export const demoLovelaceKernehed: DemoConfig["lovelace"] = () => ({
|
||||
"binary_sensor.windows_server",
|
||||
"binary_sensor.teamspeak",
|
||||
"binary_sensor.harmony_hub",
|
||||
// {
|
||||
// style: {
|
||||
// height: "1px",
|
||||
// width: "85%",
|
||||
// "margin-left": "auto",
|
||||
// background: "#62717b",
|
||||
// "margin-right": "auto",
|
||||
// },
|
||||
// type: "divider",
|
||||
// },
|
||||
// {
|
||||
// items: ["sensor.uptime_router", "sensor.installerad_routeros"],
|
||||
// head: {
|
||||
// entity: "binary_sensor.router",
|
||||
// },
|
||||
// type: "custom:fold-entity-row",
|
||||
// group_config: {
|
||||
// icon: "mdi:router",
|
||||
// },
|
||||
// },
|
||||
// {
|
||||
// items: [
|
||||
// "sensor.uptime_router_server",
|
||||
// "sensor.installerad_routeros_server",
|
||||
// ],
|
||||
// head: {
|
||||
// entity: "binary_sensor.router_server",
|
||||
// },
|
||||
// type: "custom:fold-entity-row",
|
||||
// group_config: {
|
||||
// icon: "mdi:router",
|
||||
// },
|
||||
// },
|
||||
],
|
||||
show_header_toggle: false,
|
||||
state_color: true,
|
||||
type: "entities",
|
||||
title: "Network",
|
||||
},
|
||||
@@ -422,29 +252,10 @@ export const demoLovelaceKernehed: DemoConfig["lovelace"] = () => ({
|
||||
"binary_sensor.ubiquiti_switch",
|
||||
"binary_sensor.ubiquiti_nvr",
|
||||
"binary_sensor.entre_kamera",
|
||||
// {
|
||||
// items: ["sensor.uptime_ap_1"],
|
||||
// head: {
|
||||
// entity: "binary_sensor.accesspunkt_1",
|
||||
// },
|
||||
// type: "custom:fold-entity-row",
|
||||
// group_config: {
|
||||
// icon: "router-wireless",
|
||||
// },
|
||||
// },
|
||||
// {
|
||||
// items: ["sensor.uptime_ap_2"],
|
||||
// head: {
|
||||
// entity: "binary_sensor.accesspunkt_2",
|
||||
// },
|
||||
// type: "custom:fold-entity-row",
|
||||
// group_config: {
|
||||
// icon: "router-wireless",
|
||||
// },
|
||||
// },
|
||||
"sensor.total_clients_wireless",
|
||||
],
|
||||
show_header_toggle: false,
|
||||
state_color: true,
|
||||
type: "entities",
|
||||
title: "Ubiquiti",
|
||||
},
|
||||
|
@@ -215,6 +215,7 @@ export const demoLovelaceTeachingbirds: DemoConfig["lovelace"] = () => ({
|
||||
card: {
|
||||
type: "glance",
|
||||
show_state: false,
|
||||
columns: 4,
|
||||
},
|
||||
state_filter: ["on"],
|
||||
},
|
||||
@@ -808,67 +809,6 @@ export const demoLovelaceTeachingbirds: DemoConfig["lovelace"] = () => ({
|
||||
],
|
||||
type: "vertical-stack",
|
||||
},
|
||||
// {
|
||||
// cards: [
|
||||
// {
|
||||
// entities: [
|
||||
// {
|
||||
// hide_when_off: true,
|
||||
// toggle: true,
|
||||
// type: "custom:slider-entity-row",
|
||||
// name: "Bedside",
|
||||
// entity: "light.bedside_lamp",
|
||||
// },
|
||||
// {
|
||||
// hide_when_off: true,
|
||||
// toggle: true,
|
||||
// type: "custom:slider-entity-row",
|
||||
// name: "Bedroom",
|
||||
// entity: "light.bedroom_ceiling_light",
|
||||
// },
|
||||
// {
|
||||
// hide_when_off: true,
|
||||
// toggle: true,
|
||||
// type: "custom:slider-entity-row",
|
||||
// name: "Isa",
|
||||
// entity: "light.isa_ceiling_light",
|
||||
// },
|
||||
// {
|
||||
// hide_when_off: true,
|
||||
// toggle: true,
|
||||
// type: "custom:slider-entity-row",
|
||||
// name: "Upstairs hallway",
|
||||
// entity: "light.upstairs_hallway_ceiling_light_level",
|
||||
// },
|
||||
// {
|
||||
// hide_when_off: true,
|
||||
// toggle: true,
|
||||
// type: "custom:slider-entity-row",
|
||||
// name: "Nightlight",
|
||||
// entity: "light.gateway_light_34ce008bfc4b",
|
||||
// },
|
||||
// {
|
||||
// hide_when_off: true,
|
||||
// toggle: true,
|
||||
// type: "custom:slider-entity-row",
|
||||
// name: "Walk in closet",
|
||||
// entity: "light.walk_in_closet_lights",
|
||||
// },
|
||||
// {
|
||||
// hide_when_off: true,
|
||||
// toggle: false,
|
||||
// type: "custom:slider-entity-row",
|
||||
// name: "Stefan",
|
||||
// entity: "light.stefan_lightstrip",
|
||||
// },
|
||||
// ],
|
||||
// show_header_toggle: false,
|
||||
// type: "entities",
|
||||
// title: "Upstairs",
|
||||
// },
|
||||
// ],
|
||||
// type: "vertical-stack",
|
||||
// },
|
||||
],
|
||||
path: "lights",
|
||||
title: "Lights",
|
||||
@@ -918,10 +858,6 @@ export const demoLovelaceTeachingbirds: DemoConfig["lovelace"] = () => ({
|
||||
name: "Dafang",
|
||||
icon: "mdi:webcam",
|
||||
},
|
||||
{
|
||||
name: "IR Hallway",
|
||||
entity: "sensor.system_ir_blaster",
|
||||
},
|
||||
{
|
||||
name: "IR Bedroom",
|
||||
entity: "sensor.system_ir_blaster_bedroom",
|
||||
@@ -940,7 +876,7 @@ export const demoLovelaceTeachingbirds: DemoConfig["lovelace"] = () => ({
|
||||
"sensor.system_ring_chime",
|
||||
],
|
||||
type: "glance",
|
||||
columns: 5,
|
||||
columns: 4,
|
||||
show_state: false,
|
||||
},
|
||||
{
|
||||
|
@@ -3,8 +3,8 @@ import {
|
||||
CSSResult,
|
||||
customElement,
|
||||
html,
|
||||
LitElement,
|
||||
internalProperty,
|
||||
LitElement,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import { CastManager } from "../../../src/cast/cast_manager";
|
||||
|
@@ -3,9 +3,9 @@ import {
|
||||
css,
|
||||
CSSResult,
|
||||
html,
|
||||
internalProperty,
|
||||
LitElement,
|
||||
property,
|
||||
internalProperty,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import { until } from "lit-html/directives/until";
|
||||
|
@@ -1,8 +1,8 @@
|
||||
import "../../src/resources/safari-14-attachshadow-patch";
|
||||
import "@polymer/polymer/lib/elements/dom-if";
|
||||
import "@polymer/polymer/lib/elements/dom-repeat";
|
||||
import "../../src/resources/ha-style";
|
||||
import "../../src/resources/roboto";
|
||||
import "../../src/resources/safari-14-attachshadow-patch";
|
||||
import "./ha-demo";
|
||||
|
||||
/* polyfill for paper-dropdown */
|
||||
|
@@ -1,3 +1,4 @@
|
||||
// Compat needs to be first import
|
||||
import "../../src/resources/compatibility";
|
||||
import { isNavigationClick } from "../../src/common/dom/is-navigation-click";
|
||||
import { navigate } from "../../src/common/navigate";
|
||||
|
@@ -2,10 +2,10 @@ import "@polymer/app-layout/app-toolbar/app-toolbar";
|
||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||
/* eslint-plugin-disable lit */
|
||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||
import "../../../src/components/ha-switch";
|
||||
import "../../../src/components/ha-formfield";
|
||||
import "./demo-card";
|
||||
import { applyThemesOnElement } from "../../../src/common/dom/apply_themes_on_element";
|
||||
import "../../../src/components/ha-formfield";
|
||||
import "../../../src/components/ha-switch";
|
||||
import "./demo-card";
|
||||
|
||||
class DemoCards extends PolymerElement {
|
||||
static get template() {
|
||||
|
@@ -2,8 +2,8 @@ import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||
/* eslint-plugin-disable lit */
|
||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||
import "../../../src/components/ha-card";
|
||||
import "../../../src/state-summary/state-card-content";
|
||||
import "../../../src/dialogs/more-info/more-info-content";
|
||||
import "../../../src/state-summary/state-card-content";
|
||||
|
||||
class DemoMoreInfo extends PolymerElement {
|
||||
static get template() {
|
||||
|
@@ -2,10 +2,10 @@ import "@polymer/app-layout/app-toolbar/app-toolbar";
|
||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||
/* eslint-plugin-disable lit */
|
||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||
import "../../../src/components/ha-switch";
|
||||
import "../../../src/components/ha-formfield";
|
||||
import "./demo-more-info";
|
||||
import { applyThemesOnElement } from "../../../src/common/dom/apply_themes_on_element";
|
||||
import "../../../src/components/ha-formfield";
|
||||
import "../../../src/components/ha-switch";
|
||||
import "./demo-more-info";
|
||||
|
||||
class DemoMoreInfos extends PolymerElement {
|
||||
static get template() {
|
||||
|
@@ -1,7 +1,7 @@
|
||||
import {
|
||||
customElement,
|
||||
html,
|
||||
LitElement,
|
||||
customElement,
|
||||
PropertyValues,
|
||||
query,
|
||||
TemplateResult,
|
||||
|
@@ -1,7 +1,7 @@
|
||||
import {
|
||||
customElement,
|
||||
html,
|
||||
LitElement,
|
||||
customElement,
|
||||
PropertyValues,
|
||||
query,
|
||||
TemplateResult,
|
||||
|
@@ -1,7 +1,7 @@
|
||||
import {
|
||||
customElement,
|
||||
html,
|
||||
LitElement,
|
||||
customElement,
|
||||
PropertyValues,
|
||||
query,
|
||||
TemplateResult,
|
||||
|
@@ -1,7 +1,7 @@
|
||||
import {
|
||||
customElement,
|
||||
html,
|
||||
LitElement,
|
||||
customElement,
|
||||
PropertyValues,
|
||||
query,
|
||||
TemplateResult,
|
||||
@@ -53,7 +53,7 @@ const CONFIGS = [
|
||||
config: `
|
||||
- type: button
|
||||
entity: light.bed_light
|
||||
tap_action:
|
||||
tap_action:
|
||||
action: toggle
|
||||
`,
|
||||
},
|
||||
|
@@ -1,7 +1,7 @@
|
||||
import {
|
||||
customElement,
|
||||
html,
|
||||
LitElement,
|
||||
customElement,
|
||||
PropertyValues,
|
||||
query,
|
||||
TemplateResult,
|
||||
@@ -48,7 +48,7 @@ const ENTITIES = [
|
||||
|
||||
const CONFIGS = [
|
||||
{
|
||||
heading: "Controller",
|
||||
heading: "Unfiltered controller",
|
||||
config: `
|
||||
- type: entities
|
||||
entities:
|
||||
@@ -58,7 +58,7 @@ const CONFIGS = [
|
||||
`,
|
||||
},
|
||||
{
|
||||
heading: "Basic",
|
||||
heading: "Filtered entities card",
|
||||
config: `
|
||||
- type: entity-filter
|
||||
entities:
|
||||
@@ -74,7 +74,27 @@ const CONFIGS = [
|
||||
`,
|
||||
},
|
||||
{
|
||||
heading: "With card config",
|
||||
heading: 'With "entities" card config',
|
||||
config: `
|
||||
- type: entity-filter
|
||||
entities:
|
||||
- device_tracker.demo_anne_therese
|
||||
- device_tracker.demo_home_boy
|
||||
- device_tracker.demo_paulus
|
||||
- light.bed_light
|
||||
- light.ceiling_lights
|
||||
- light.kitchen_lights
|
||||
state_filter:
|
||||
- "on"
|
||||
- not_home
|
||||
card:
|
||||
type: entities
|
||||
title: Custom Title
|
||||
show_header_toggle: false
|
||||
`,
|
||||
},
|
||||
{
|
||||
heading: 'With "glance" card config',
|
||||
config: `
|
||||
- type: entity-filter
|
||||
entities:
|
||||
@@ -89,7 +109,8 @@ const CONFIGS = [
|
||||
- not_home
|
||||
card:
|
||||
type: glance
|
||||
show_state: false
|
||||
show_state: true
|
||||
title: Custom Title
|
||||
`,
|
||||
},
|
||||
];
|
||||
|
@@ -1,7 +1,7 @@
|
||||
import {
|
||||
customElement,
|
||||
html,
|
||||
LitElement,
|
||||
customElement,
|
||||
PropertyValues,
|
||||
query,
|
||||
TemplateResult,
|
||||
|
@@ -1,7 +1,7 @@
|
||||
import {
|
||||
customElement,
|
||||
html,
|
||||
LitElement,
|
||||
customElement,
|
||||
PropertyValues,
|
||||
query,
|
||||
TemplateResult,
|
||||
@@ -82,7 +82,8 @@ const CONFIGS = [
|
||||
heading: "With title",
|
||||
config: `
|
||||
- type: glance
|
||||
title: This is glance
|
||||
title: Custom title
|
||||
columns: 4
|
||||
entities:
|
||||
- device_tracker.demo_paulus
|
||||
- media_player.living_room
|
||||
@@ -109,9 +110,10 @@ const CONFIGS = [
|
||||
`,
|
||||
},
|
||||
{
|
||||
heading: "No name",
|
||||
heading: "No entity names",
|
||||
config: `
|
||||
- type: glance
|
||||
columns: 4
|
||||
show_name: false
|
||||
entities:
|
||||
- device_tracker.demo_paulus
|
||||
@@ -124,9 +126,10 @@ const CONFIGS = [
|
||||
`,
|
||||
},
|
||||
{
|
||||
heading: "No state",
|
||||
heading: "No state labels",
|
||||
config: `
|
||||
- type: glance
|
||||
columns: 4
|
||||
show_state: false
|
||||
entities:
|
||||
- device_tracker.demo_paulus
|
||||
@@ -139,9 +142,10 @@ const CONFIGS = [
|
||||
`,
|
||||
},
|
||||
{
|
||||
heading: "No name and no state",
|
||||
heading: "No names and no state labels",
|
||||
config: `
|
||||
- type: glance
|
||||
columns: 4
|
||||
show_name: false
|
||||
show_state: false
|
||||
entities:
|
||||
@@ -155,47 +159,24 @@ const CONFIGS = [
|
||||
`,
|
||||
},
|
||||
{
|
||||
heading: "Custom name, custom icon",
|
||||
heading: "Custom name + custom icon",
|
||||
config: `
|
||||
- type: glance
|
||||
columns: 4
|
||||
entities:
|
||||
- entity: device_tracker.demo_paulus
|
||||
name: ¯\\_(ツ)_/¯
|
||||
icon: mdi:home-assistant
|
||||
- media_player.living_room
|
||||
- sun.sun
|
||||
- cover.kitchen_window
|
||||
- entity: light.kitchen_lights
|
||||
icon: mdi:alarm-light
|
||||
- lock.kitchen_door
|
||||
- light.ceiling_lights
|
||||
`,
|
||||
},
|
||||
{
|
||||
heading: "Custom tap action",
|
||||
config: `
|
||||
- type: glance
|
||||
entities:
|
||||
- entity: lock.kitchen_door
|
||||
tap_action:
|
||||
type: toggle
|
||||
- entity: light.ceiling_lights
|
||||
tap_action:
|
||||
action: call-service
|
||||
service: light.turn_on
|
||||
service_data:
|
||||
entity_id: light.ceiling_lights
|
||||
- device_tracker.demo_paulus
|
||||
- media_player.living_room
|
||||
- sun.sun
|
||||
- cover.kitchen_window
|
||||
- light.kitchen_lights
|
||||
- entity: media_player.living_room
|
||||
name: ¯\\_(ツ)_/¯
|
||||
icon: mdi:home-assistant
|
||||
`,
|
||||
},
|
||||
{
|
||||
heading: "Selectively hidden name",
|
||||
config: `
|
||||
- type: glance
|
||||
columns: 4
|
||||
entities:
|
||||
- device_tracker.demo_paulus
|
||||
- entity: media_player.living_room
|
||||
@@ -204,21 +185,32 @@ const CONFIGS = [
|
||||
- entity: cover.kitchen_window
|
||||
name:
|
||||
- light.kitchen_lights
|
||||
- entity: lock.kitchen_door
|
||||
name:
|
||||
- light.ceiling_lights
|
||||
`,
|
||||
},
|
||||
{
|
||||
heading: "Primary theme",
|
||||
heading: "Custom tap action",
|
||||
config: `
|
||||
- type: glance
|
||||
theming: primary
|
||||
columns: 4
|
||||
entities:
|
||||
- device_tracker.demo_paulus
|
||||
- media_player.living_room
|
||||
- sun.sun
|
||||
- cover.kitchen_window
|
||||
- light.kitchen_lights
|
||||
- lock.kitchen_door
|
||||
- light.ceiling_lights
|
||||
- entity: lock.kitchen_door
|
||||
name: Custom
|
||||
tap_action:
|
||||
type: toggle
|
||||
- entity: light.ceiling_lights
|
||||
name: Custom
|
||||
tap_action:
|
||||
action: call-service
|
||||
service: light.turn_on
|
||||
service_data:
|
||||
entity_id: light.ceiling_lights
|
||||
- entity: sun.sun
|
||||
name: Regular
|
||||
- entity: light.kitchen_lights
|
||||
name: Regular
|
||||
`,
|
||||
},
|
||||
];
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import { html, LitElement, customElement, TemplateResult } from "lit-element";
|
||||
import { customElement, html, LitElement, TemplateResult } from "lit-element";
|
||||
import "../components/demo-cards";
|
||||
|
||||
const CONFIGS = [
|
||||
|
@@ -1,7 +1,7 @@
|
||||
import {
|
||||
customElement,
|
||||
html,
|
||||
LitElement,
|
||||
customElement,
|
||||
PropertyValues,
|
||||
query,
|
||||
TemplateResult,
|
||||
|
@@ -1,7 +1,7 @@
|
||||
import {
|
||||
customElement,
|
||||
html,
|
||||
LitElement,
|
||||
customElement,
|
||||
PropertyValues,
|
||||
query,
|
||||
TemplateResult,
|
||||
|
@@ -1,7 +1,7 @@
|
||||
import {
|
||||
customElement,
|
||||
html,
|
||||
LitElement,
|
||||
customElement,
|
||||
PropertyValues,
|
||||
query,
|
||||
TemplateResult,
|
||||
|
@@ -1,7 +1,7 @@
|
||||
import {
|
||||
customElement,
|
||||
html,
|
||||
LitElement,
|
||||
customElement,
|
||||
PropertyValues,
|
||||
query,
|
||||
TemplateResult,
|
||||
|
@@ -1,7 +1,7 @@
|
||||
import {
|
||||
customElement,
|
||||
html,
|
||||
LitElement,
|
||||
customElement,
|
||||
PropertyValues,
|
||||
query,
|
||||
TemplateResult,
|
||||
@@ -31,9 +31,9 @@ const CONFIGS = [
|
||||
- entity: media_player.android_cast
|
||||
name: Screen casting
|
||||
- entity: media_player.image_display
|
||||
name: Digital Picture Frame
|
||||
name: Digital Picture Frame
|
||||
- entity: media_player.sonos_idle
|
||||
name: Sonos Idle
|
||||
name: Sonos Idle
|
||||
- entity: media_player.idle_browse_media
|
||||
name: Idle waiting for Browse Media
|
||||
- entity: media_player.theater_off
|
||||
@@ -43,7 +43,7 @@ const CONFIGS = [
|
||||
- entity: media_player.theater_off_static
|
||||
name: Player Off (cannot be switched on)
|
||||
- entity: media_player.theater_on_static
|
||||
name: Player On (cannot be switched off)
|
||||
name: Player On (cannot be switched off)
|
||||
- entity: media_player.idle
|
||||
name: Player Idle
|
||||
- entity: media_player.playing
|
||||
|
@@ -1,7 +1,7 @@
|
||||
import {
|
||||
customElement,
|
||||
html,
|
||||
LitElement,
|
||||
customElement,
|
||||
PropertyValues,
|
||||
query,
|
||||
TemplateResult,
|
||||
|
@@ -1,7 +1,7 @@
|
||||
import {
|
||||
customElement,
|
||||
html,
|
||||
LitElement,
|
||||
customElement,
|
||||
PropertyValues,
|
||||
query,
|
||||
TemplateResult,
|
||||
|
@@ -1,7 +1,7 @@
|
||||
import {
|
||||
customElement,
|
||||
html,
|
||||
LitElement,
|
||||
customElement,
|
||||
PropertyValues,
|
||||
query,
|
||||
TemplateResult,
|
||||
|
@@ -1,7 +1,7 @@
|
||||
import {
|
||||
customElement,
|
||||
html,
|
||||
LitElement,
|
||||
customElement,
|
||||
PropertyValues,
|
||||
query,
|
||||
TemplateResult,
|
||||
|
@@ -1,7 +1,7 @@
|
||||
import {
|
||||
customElement,
|
||||
html,
|
||||
LitElement,
|
||||
customElement,
|
||||
PropertyValues,
|
||||
query,
|
||||
TemplateResult,
|
||||
|
@@ -1,7 +1,7 @@
|
||||
import {
|
||||
customElement,
|
||||
html,
|
||||
LitElement,
|
||||
customElement,
|
||||
PropertyValues,
|
||||
query,
|
||||
TemplateResult,
|
||||
|
@@ -1,7 +1,7 @@
|
||||
import {
|
||||
customElement,
|
||||
html,
|
||||
LitElement,
|
||||
customElement,
|
||||
PropertyValues,
|
||||
query,
|
||||
TemplateResult,
|
||||
|
@@ -1,7 +1,7 @@
|
||||
import {
|
||||
customElement,
|
||||
html,
|
||||
LitElement,
|
||||
customElement,
|
||||
property,
|
||||
PropertyValues,
|
||||
query,
|
||||
@@ -10,20 +10,20 @@ import {
|
||||
import "../../../src/components/ha-card";
|
||||
import {
|
||||
SUPPORT_BRIGHTNESS,
|
||||
SUPPORT_COLOR,
|
||||
SUPPORT_COLOR_TEMP,
|
||||
SUPPORT_EFFECT,
|
||||
SUPPORT_FLASH,
|
||||
SUPPORT_COLOR,
|
||||
SUPPORT_TRANSITION,
|
||||
SUPPORT_WHITE_VALUE,
|
||||
} from "../../../src/data/light";
|
||||
import "../../../src/dialogs/more-info/more-info-content";
|
||||
import { getEntity } from "../../../src/fake_data/entity";
|
||||
import {
|
||||
provideHass,
|
||||
MockHomeAssistant,
|
||||
provideHass,
|
||||
} from "../../../src/fake_data/provide_hass";
|
||||
import "../components/demo-more-infos";
|
||||
import "../../../src/dialogs/more-info/more-info-content";
|
||||
|
||||
const ENTITIES = [
|
||||
getEntity("light", "bed_light", "on", {
|
||||
|
@@ -11,17 +11,18 @@ import {
|
||||
PropertyValues,
|
||||
} from "lit-element";
|
||||
import { html, TemplateResult } from "lit-html";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { atLeastVersion } from "../../../src/common/config/version";
|
||||
import { fireEvent } from "../../../src/common/dom/fire_event";
|
||||
import "../../../src/common/search/search-input";
|
||||
import "../../../src/components/ha-button-menu";
|
||||
import "../../../src/components/ha-svg-icon";
|
||||
import {
|
||||
fetchHassioAddonsInfo,
|
||||
HassioAddonInfo,
|
||||
HassioAddonRepository,
|
||||
reloadHassioAddons,
|
||||
} from "../../../src/data/hassio/addon";
|
||||
import { extractApiErrorMessage } from "../../../src/data/hassio/common";
|
||||
import { Supervisor } from "../../../src/data/supervisor/supervisor";
|
||||
import "../../../src/layouts/hass-loading-screen";
|
||||
import "../../../src/layouts/hass-tabs-subpage";
|
||||
import { HomeAssistant, Route } from "../../../src/types";
|
||||
@@ -49,46 +50,27 @@ const sortRepos = (a: HassioAddonRepository, b: HassioAddonRepository) => {
|
||||
class HassioAddonStore extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ attribute: false }) public supervisor!: Supervisor;
|
||||
|
||||
@property({ type: Boolean }) public narrow!: boolean;
|
||||
|
||||
@property({ attribute: false }) public route!: Route;
|
||||
|
||||
@property({ attribute: false }) private _addons?: HassioAddonInfo[];
|
||||
|
||||
@property({ attribute: false }) private _repos?: HassioAddonRepository[];
|
||||
|
||||
@internalProperty() private _filter?: string;
|
||||
|
||||
public async refreshData() {
|
||||
this._repos = undefined;
|
||||
this._addons = undefined;
|
||||
this._filter = undefined;
|
||||
await reloadHassioAddons(this.hass);
|
||||
await this._loadData();
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
const repos: TemplateResult[] = [];
|
||||
let repos: TemplateResult[] = [];
|
||||
|
||||
if (this._repos) {
|
||||
for (const repo of this._repos) {
|
||||
const addons = this._addons!.filter(
|
||||
(addon) => addon.repository === repo.slug
|
||||
);
|
||||
|
||||
if (addons.length === 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
repos.push(html`
|
||||
<hassio-addon-repository
|
||||
.hass=${this.hass}
|
||||
.repo=${repo}
|
||||
.addons=${addons}
|
||||
.filter=${this._filter!}
|
||||
></hassio-addon-repository>
|
||||
`);
|
||||
}
|
||||
if (this.supervisor.addon.repositories) {
|
||||
repos = this.addonRepositories(
|
||||
this.supervisor.addon.repositories,
|
||||
this.supervisor.addon.addons
|
||||
);
|
||||
}
|
||||
|
||||
return html`
|
||||
@@ -157,6 +139,27 @@ class HassioAddonStore extends LitElement {
|
||||
this._loadData();
|
||||
}
|
||||
|
||||
private addonRepositories = memoizeOne(
|
||||
(repositories: HassioAddonRepository[], addons: HassioAddonInfo[]) => {
|
||||
return repositories.sort(sortRepos).map((repo) => {
|
||||
const filteredAddons = addons.filter(
|
||||
(addon) => addon.repository === repo.slug
|
||||
);
|
||||
|
||||
return filteredAddons.length !== 0
|
||||
? html`
|
||||
<hassio-addon-repository
|
||||
.hass=${this.hass}
|
||||
.repo=${repo}
|
||||
.addons=${filteredAddons}
|
||||
.filter=${this._filter!}
|
||||
></hassio-addon-repository>
|
||||
`
|
||||
: html``;
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
private _handleAction(ev: CustomEvent<ActionDetail>) {
|
||||
switch (ev.detail.index) {
|
||||
case 0:
|
||||
@@ -179,7 +182,7 @@ class HassioAddonStore extends LitElement {
|
||||
|
||||
private async _manageRepositories() {
|
||||
showRepositoriesDialog(this, {
|
||||
repos: this._repos!,
|
||||
repos: this.supervisor.addon.repositories,
|
||||
loadData: () => this._loadData(),
|
||||
});
|
||||
}
|
||||
@@ -189,14 +192,8 @@ class HassioAddonStore extends LitElement {
|
||||
}
|
||||
|
||||
private async _loadData() {
|
||||
try {
|
||||
const addonsInfo = await fetchHassioAddonsInfo(this.hass);
|
||||
this._repos = addonsInfo.repositories;
|
||||
this._repos.sort(sortRepos);
|
||||
this._addons = addonsInfo.addons;
|
||||
} catch (err) {
|
||||
alert(extractApiErrorMessage(err));
|
||||
}
|
||||
fireEvent(this, "supervisor-store-refresh", { store: "addon" });
|
||||
fireEvent(this, "supervisor-store-refresh", { store: "supervisor" });
|
||||
}
|
||||
|
||||
private async _filterChanged(e) {
|
||||
|
@@ -7,13 +7,14 @@ import {
|
||||
CSSResult,
|
||||
customElement,
|
||||
html,
|
||||
internalProperty,
|
||||
LitElement,
|
||||
property,
|
||||
internalProperty,
|
||||
PropertyValues,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import "web-animations-js/web-animations-next-lite.min";
|
||||
import "../../../../src/components/buttons/ha-progress-button";
|
||||
import "../../../../src/components/ha-card";
|
||||
import {
|
||||
HassioAddonDetails,
|
||||
@@ -28,7 +29,6 @@ import { haStyle } from "../../../../src/resources/styles";
|
||||
import { HomeAssistant } from "../../../../src/types";
|
||||
import { suggestAddonRestart } from "../../dialogs/suggestAddonRestart";
|
||||
import { hassioStyle } from "../../resources/hassio-style";
|
||||
import "../../../../src/components/buttons/ha-progress-button";
|
||||
|
||||
@customElement("hassio-addon-audio")
|
||||
class HassioAddonAudio extends LitElement {
|
||||
|
@@ -7,11 +7,11 @@ import {
|
||||
property,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import "../../../../src/components/ha-circular-progress";
|
||||
import { HassioAddonDetails } from "../../../../src/data/hassio/addon";
|
||||
import { haStyle } from "../../../../src/resources/styles";
|
||||
import { HomeAssistant } from "../../../../src/types";
|
||||
import { hassioStyle } from "../../resources/hassio-style";
|
||||
import "../../../../src/components/ha-circular-progress";
|
||||
import "./hassio-addon-audio";
|
||||
import "./hassio-addon-config";
|
||||
import "./hassio-addon-network";
|
||||
@@ -26,28 +26,41 @@ class HassioAddonConfigDashboard extends LitElement {
|
||||
if (!this.addon) {
|
||||
return html`<ha-circular-progress active></ha-circular-progress>`;
|
||||
}
|
||||
const hasOptions =
|
||||
this.addon.options && Object.keys(this.addon.options).length;
|
||||
const hasSchema =
|
||||
hasOptions && this.addon.schema && Object.keys(this.addon.schema).length;
|
||||
|
||||
return html`
|
||||
<div class="content">
|
||||
<hassio-addon-config
|
||||
.hass=${this.hass}
|
||||
.addon=${this.addon}
|
||||
></hassio-addon-config>
|
||||
${this.addon.network
|
||||
${hasOptions || hasSchema || this.addon.network || this.addon.audio
|
||||
? html`
|
||||
<hassio-addon-network
|
||||
.hass=${this.hass}
|
||||
.addon=${this.addon}
|
||||
></hassio-addon-network>
|
||||
${hasOptions || hasSchema
|
||||
? html`
|
||||
<hassio-addon-config
|
||||
.hass=${this.hass}
|
||||
.addon=${this.addon}
|
||||
></hassio-addon-config>
|
||||
`
|
||||
: ""}
|
||||
${this.addon.network
|
||||
? html`
|
||||
<hassio-addon-network
|
||||
.hass=${this.hass}
|
||||
.addon=${this.addon}
|
||||
></hassio-addon-network>
|
||||
`
|
||||
: ""}
|
||||
${this.addon.audio
|
||||
? html`
|
||||
<hassio-addon-audio
|
||||
.hass=${this.hass}
|
||||
.addon=${this.addon}
|
||||
></hassio-addon-audio>
|
||||
`
|
||||
: ""}
|
||||
`
|
||||
: ""}
|
||||
${this.addon.audio
|
||||
? html`
|
||||
<hassio-addon-audio
|
||||
.hass=${this.hass}
|
||||
.addon=${this.addon}
|
||||
></hassio-addon-audio>
|
||||
`
|
||||
: ""}
|
||||
: "This add-on does not expose configuration for you to mess with.... 👋"}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
@@ -1,4 +1,7 @@
|
||||
import "@material/mwc-button";
|
||||
import { ActionDetail } from "@material/mwc-list";
|
||||
import "@material/mwc-list/mwc-list-item";
|
||||
import { mdiDotsVertical } from "@mdi/js";
|
||||
import "@polymer/iron-autogrow-textarea/iron-autogrow-textarea";
|
||||
import {
|
||||
css,
|
||||
@@ -14,7 +17,9 @@ import {
|
||||
} from "lit-element";
|
||||
import { fireEvent } from "../../../../src/common/dom/fire_event";
|
||||
import "../../../../src/components/buttons/ha-progress-button";
|
||||
import "../../../../src/components/ha-button-menu";
|
||||
import "../../../../src/components/ha-card";
|
||||
import "../../../../src/components/ha-form/ha-form";
|
||||
import "../../../../src/components/ha-yaml-editor";
|
||||
import type { HaYamlEditor } from "../../../../src/components/ha-yaml-editor";
|
||||
import {
|
||||
@@ -29,35 +34,67 @@ import type { HomeAssistant } from "../../../../src/types";
|
||||
import { suggestAddonRestart } from "../../dialogs/suggestAddonRestart";
|
||||
import { hassioStyle } from "../../resources/hassio-style";
|
||||
|
||||
const SUPPORTED_UI_TYPES = ["string", "select", "boolean", "integer", "float"];
|
||||
|
||||
@customElement("hassio-addon-config")
|
||||
class HassioAddonConfig extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ attribute: false }) public addon!: HassioAddonDetails;
|
||||
|
||||
@internalProperty() private _error?: string;
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ type: Boolean }) private _configHasChanged = false;
|
||||
|
||||
@property({ type: Boolean }) private _valid = true;
|
||||
|
||||
@query("ha-yaml-editor", true) private _editor!: HaYamlEditor;
|
||||
@internalProperty() private _canShowSchema = false;
|
||||
|
||||
@internalProperty() private _error?: string;
|
||||
|
||||
@internalProperty() private _options?: Record<string, unknown>;
|
||||
|
||||
@internalProperty() private _yamlMode = false;
|
||||
|
||||
@query("ha-yaml-editor") private _editor?: HaYamlEditor;
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
<h1>${this.addon.name}</h1>
|
||||
<ha-card header="Configuration">
|
||||
<div class="card-content">
|
||||
<ha-yaml-editor
|
||||
@value-changed=${this._configChanged}
|
||||
></ha-yaml-editor>
|
||||
${this._error ? html` <div class="errors">${this._error}</div> ` : ""}
|
||||
${this._valid ? "" : html` <div class="errors">Invalid YAML</div> `}
|
||||
<ha-card>
|
||||
<div class="header">
|
||||
<h2>Configuration</h2>
|
||||
<div class="card-menu">
|
||||
<ha-button-menu corner="BOTTOM_START" @action=${this._handleAction}>
|
||||
<mwc-icon-button slot="trigger">
|
||||
<ha-svg-icon .path=${mdiDotsVertical}></ha-svg-icon>
|
||||
</mwc-icon-button>
|
||||
<mwc-list-item .disabled=${!this._canShowSchema}>
|
||||
${this._yamlMode ? "Edit in UI" : "Edit in YAML"}
|
||||
</mwc-list-item>
|
||||
<mwc-list-item class="warning">
|
||||
Reset to defaults
|
||||
</mwc-list-item>
|
||||
</ha-button-menu>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-actions">
|
||||
<ha-progress-button class="warning" @click=${this._resetTapped}>
|
||||
Reset to defaults
|
||||
</ha-progress-button>
|
||||
|
||||
<div class="card-content">
|
||||
${!this._yamlMode && this._canShowSchema && this.addon.schema
|
||||
? html`<ha-form
|
||||
.data=${this._options!}
|
||||
@value-changed=${this._configChanged}
|
||||
.schema=${this.addon.schema}
|
||||
></ha-form>`
|
||||
: html` <ha-yaml-editor
|
||||
@value-changed=${this._configChanged}
|
||||
></ha-yaml-editor>`}
|
||||
${this._error ? html` <div class="errors">${this._error}</div> ` : ""}
|
||||
${!this._yamlMode ||
|
||||
(this._canShowSchema && this.addon.schema) ||
|
||||
this._valid
|
||||
? ""
|
||||
: html` <div class="errors">Invalid YAML</div> `}
|
||||
</div>
|
||||
<div class="card-actions right">
|
||||
<ha-progress-button
|
||||
@click=${this._saveTapped}
|
||||
.disabled=${!this._configHasChanged || !this._valid}
|
||||
@@ -69,16 +106,55 @@ class HassioAddonConfig extends LitElement {
|
||||
`;
|
||||
}
|
||||
|
||||
protected firstUpdated(changedProps) {
|
||||
super.firstUpdated(changedProps);
|
||||
this._canShowSchema =
|
||||
Object.keys(this.addon.options).length !== 0 &&
|
||||
!this.addon.schema!.find(
|
||||
// @ts-ignore
|
||||
(entry) => !SUPPORTED_UI_TYPES.includes(entry.type) || entry.multiple
|
||||
);
|
||||
this._yamlMode = !this._canShowSchema;
|
||||
}
|
||||
|
||||
protected updated(changedProperties: PropertyValues): void {
|
||||
super.updated(changedProperties);
|
||||
if (changedProperties.has("addon")) {
|
||||
this._editor.setValue(this.addon.options);
|
||||
this._options = { ...this.addon.options };
|
||||
}
|
||||
super.updated(changedProperties);
|
||||
if (
|
||||
changedProperties.has("_yamlMode") ||
|
||||
changedProperties.has("_options")
|
||||
) {
|
||||
if (this._yamlMode) {
|
||||
const editor = this._editor;
|
||||
if (editor) {
|
||||
editor.setValue(this._options!);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private _handleAction(ev: CustomEvent<ActionDetail>) {
|
||||
switch (ev.detail.index) {
|
||||
case 0:
|
||||
this._yamlMode = !this._yamlMode;
|
||||
break;
|
||||
case 1:
|
||||
this._resetTapped(ev);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private _configChanged(ev): void {
|
||||
this._configHasChanged = true;
|
||||
this._valid = ev.detail.isValid;
|
||||
if (this.addon.schema && this._canShowSchema && !this._yamlMode) {
|
||||
this._valid = true;
|
||||
this._configHasChanged = true;
|
||||
this._options! = ev.detail.value;
|
||||
} else {
|
||||
this._configHasChanged = true;
|
||||
this._valid = ev.detail.isValid;
|
||||
}
|
||||
}
|
||||
|
||||
private async _resetTapped(ev: CustomEvent): Promise<void> {
|
||||
@@ -122,18 +198,13 @@ class HassioAddonConfig extends LitElement {
|
||||
const button = ev.currentTarget as any;
|
||||
button.progress = true;
|
||||
|
||||
let data: HassioAddonSetOptionParams;
|
||||
this._error = undefined;
|
||||
|
||||
try {
|
||||
data = {
|
||||
options: this._editor.value,
|
||||
};
|
||||
} catch (err) {
|
||||
this._error = err;
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await setHassioAddonOption(this.hass, this.addon.slug, data);
|
||||
await setHassioAddonOption(this.hass, this.addon.slug, {
|
||||
options: this._yamlMode ? this._editor?.value : this._options,
|
||||
});
|
||||
|
||||
this._configHasChanged = false;
|
||||
const eventdata = {
|
||||
success: true,
|
||||
@@ -178,6 +249,32 @@ class HassioAddonConfig extends LitElement {
|
||||
.syntaxerror {
|
||||
color: var(--error-color);
|
||||
}
|
||||
.card-menu {
|
||||
float: right;
|
||||
z-index: 3;
|
||||
--mdc-theme-text-primary-on-background: var(--primary-text-color);
|
||||
}
|
||||
mwc-list-item[disabled] {
|
||||
--mdc-theme-text-primary-on-background: var(--disabled-text-color);
|
||||
}
|
||||
.header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
.header h2 {
|
||||
color: var(--ha-card-header-color, --primary-text-color);
|
||||
font-family: var(--ha-card-header-font-family, inherit);
|
||||
font-size: var(--ha-card-header-font-size, 24px);
|
||||
letter-spacing: -0.012em;
|
||||
line-height: 48px;
|
||||
padding: 12px 16px 16px;
|
||||
display: block;
|
||||
margin-block: 0px;
|
||||
font-weight: normal;
|
||||
}
|
||||
.card-actions.right {
|
||||
justify-content: flex-end;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
@@ -9,17 +9,25 @@ import {
|
||||
CSSResult,
|
||||
customElement,
|
||||
html,
|
||||
internalProperty,
|
||||
LitElement,
|
||||
property,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { fireEvent } from "../../../src/common/dom/fire_event";
|
||||
import { navigate } from "../../../src/common/navigate";
|
||||
import { extractSearchParam } from "../../../src/common/url/search-params";
|
||||
import "../../../src/components/ha-circular-progress";
|
||||
import {
|
||||
fetchHassioAddonInfo,
|
||||
HassioAddonDetails,
|
||||
} from "../../../src/data/hassio/addon";
|
||||
import { extractApiErrorMessage } from "../../../src/data/hassio/common";
|
||||
import { Supervisor } from "../../../src/data/supervisor/supervisor";
|
||||
import "../../../src/layouts/hass-error-screen";
|
||||
import "../../../src/layouts/hass-loading-screen";
|
||||
import "../../../src/layouts/hass-tabs-subpage";
|
||||
import "../../../src/components/ha-circular-progress";
|
||||
import type { PageNavigation } from "../../../src/layouts/hass-tabs-subpage";
|
||||
import { haStyle } from "../../../src/resources/styles";
|
||||
import { HomeAssistant, Route } from "../../../src/types";
|
||||
@@ -35,12 +43,16 @@ import "./log/hassio-addon-logs";
|
||||
class HassioAddonDashboard extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ attribute: false }) public supervisor!: Supervisor;
|
||||
|
||||
@property({ attribute: false }) public route!: Route;
|
||||
|
||||
@property({ attribute: false }) public addon?: HassioAddonDetails;
|
||||
|
||||
@property({ type: Boolean }) public narrow!: boolean;
|
||||
|
||||
@internalProperty() _error?: string;
|
||||
|
||||
private _computeTail = memoizeOne((route: Route) => {
|
||||
const dividerPos = route.path.indexOf("/", 1);
|
||||
return dividerPos === -1
|
||||
@@ -55,8 +67,14 @@ class HassioAddonDashboard extends LitElement {
|
||||
});
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (this._error) {
|
||||
return html`<hass-error-screen
|
||||
.error=${this._error}
|
||||
></hass-error-screen>`;
|
||||
}
|
||||
|
||||
if (!this.addon) {
|
||||
return html`<ha-circular-progress active></ha-circular-progress>`;
|
||||
return html`<hass-loading-screen></hass-loading-screen>`;
|
||||
}
|
||||
|
||||
const addonTabs: PageNavigation[] = [
|
||||
@@ -106,6 +124,7 @@ class HassioAddonDashboard extends LitElement {
|
||||
.route=${route}
|
||||
.narrow=${this.narrow}
|
||||
.hass=${this.hass}
|
||||
.supervisor=${this.supervisor}
|
||||
.addon=${this.addon}
|
||||
></hassio-addon-router>
|
||||
</hass-tabs-subpage>
|
||||
@@ -152,30 +171,51 @@ class HassioAddonDashboard extends LitElement {
|
||||
}
|
||||
|
||||
protected async firstUpdated(): Promise<void> {
|
||||
await this._routeDataChanged(this.route);
|
||||
if (this.route.path === "") {
|
||||
const addon = extractSearchParam("addon");
|
||||
if (addon) {
|
||||
navigate(this, `/hassio/addon/${addon}`, true);
|
||||
}
|
||||
}
|
||||
this.addEventListener("hass-api-called", (ev) => this._apiCalled(ev));
|
||||
}
|
||||
|
||||
private async _apiCalled(ev): Promise<void> {
|
||||
const path: string = ev.detail.path;
|
||||
const pathSplit: string[] = ev.detail.path?.split("/");
|
||||
|
||||
if (!path) {
|
||||
if (!pathSplit || pathSplit.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const path: string = pathSplit[pathSplit.length - 1];
|
||||
|
||||
if (["uninstall", "install", "update", "start", "stop"].includes(path)) {
|
||||
fireEvent(this, "supervisor-store-refresh", { store: "supervisor" });
|
||||
}
|
||||
|
||||
if (path === "uninstall") {
|
||||
history.back();
|
||||
window.history.back();
|
||||
} else {
|
||||
await this._routeDataChanged(this.route);
|
||||
await this._routeDataChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private async _routeDataChanged(routeData: Route): Promise<void> {
|
||||
const addon = routeData.path.split("/")[1];
|
||||
protected updated(changedProperties) {
|
||||
if (changedProperties.has("route") && !this.addon) {
|
||||
this._routeDataChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private async _routeDataChanged(): Promise<void> {
|
||||
const addon = this.route.path.split("/")[1];
|
||||
if (!addon) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const addoninfo = await fetchHassioAddonInfo(this.hass, addon);
|
||||
this.addon = addoninfo;
|
||||
} catch {
|
||||
} catch (err) {
|
||||
this._error = `Error fetching addon info: ${extractApiErrorMessage(err)}`;
|
||||
this.addon = undefined;
|
||||
}
|
||||
}
|
||||
|
@@ -1,5 +1,6 @@
|
||||
import { customElement, property } from "lit-element";
|
||||
import { HassioAddonDetails } from "../../../src/data/hassio/addon";
|
||||
import { Supervisor } from "../../../src/data/supervisor/supervisor";
|
||||
import {
|
||||
HassRouterPage,
|
||||
RouterOptions,
|
||||
@@ -17,6 +18,8 @@ class HassioAddonRouter extends HassRouterPage {
|
||||
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ attribute: false }) public supervisor!: Supervisor;
|
||||
|
||||
@property({ attribute: false }) public addon!: HassioAddonDetails;
|
||||
|
||||
protected routerOptions: RouterOptions = {
|
||||
@@ -41,6 +44,7 @@ class HassioAddonRouter extends HassRouterPage {
|
||||
protected updatePageEl(el) {
|
||||
el.route = this.routeTail;
|
||||
el.hass = this.hass;
|
||||
el.supervisor = this.supervisor;
|
||||
el.addon = this.addon;
|
||||
el.narrow = this.narrow;
|
||||
}
|
||||
|
@@ -7,8 +7,9 @@ import {
|
||||
property,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import { HassioAddonDetails } from "../../../../src/data/hassio/addon";
|
||||
import "../../../../src/components/ha-circular-progress";
|
||||
import { HassioAddonDetails } from "../../../../src/data/hassio/addon";
|
||||
import { Supervisor } from "../../../../src/data/supervisor/supervisor";
|
||||
import { haStyle } from "../../../../src/resources/styles";
|
||||
import { HomeAssistant } from "../../../../src/types";
|
||||
import { hassioStyle } from "../../resources/hassio-style";
|
||||
@@ -20,6 +21,8 @@ class HassioAddonInfoDashboard extends LitElement {
|
||||
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ attribute: false }) public supervisor!: Supervisor;
|
||||
|
||||
@property({ attribute: false }) public addon?: HassioAddonDetails;
|
||||
|
||||
protected render(): TemplateResult {
|
||||
@@ -32,6 +35,7 @@ class HassioAddonInfoDashboard extends LitElement {
|
||||
<hassio-addon-info
|
||||
.narrow=${this.narrow}
|
||||
.hass=${this.hass}
|
||||
.supervisor=${this.supervisor}
|
||||
.addon=${this.addon}
|
||||
></hassio-addon-info>
|
||||
</div>
|
||||
|
@@ -43,22 +43,33 @@ import {
|
||||
HassioAddonSetOptionParams,
|
||||
HassioAddonSetSecurityParams,
|
||||
installHassioAddon,
|
||||
restartHassioAddon,
|
||||
setHassioAddonOption,
|
||||
setHassioAddonSecurity,
|
||||
startHassioAddon,
|
||||
stopHassioAddon,
|
||||
uninstallHassioAddon,
|
||||
updateHassioAddon,
|
||||
validateHassioAddonOption,
|
||||
} from "../../../../src/data/hassio/addon";
|
||||
import { extractApiErrorMessage } from "../../../../src/data/hassio/common";
|
||||
import {
|
||||
extractApiErrorMessage,
|
||||
fetchHassioStats,
|
||||
HassioStats,
|
||||
} from "../../../../src/data/hassio/common";
|
||||
import { Supervisor } from "../../../../src/data/supervisor/supervisor";
|
||||
import {
|
||||
showAlertDialog,
|
||||
showConfirmationDialog,
|
||||
} from "../../../../src/dialogs/generic/show-dialog-box";
|
||||
import { haStyle } from "../../../../src/resources/styles";
|
||||
import { HomeAssistant } from "../../../../src/types";
|
||||
import { bytesToString } from "../../../../src/util/bytes-to-string";
|
||||
import "../../components/hassio-card-content";
|
||||
import "../../components/supervisor-metric";
|
||||
import { showHassioMarkdownDialog } from "../../dialogs/markdown/show-dialog-hassio-markdown";
|
||||
import { hassioStyle } from "../../resources/hassio-style";
|
||||
import { addonArchIsSupported } from "../../util/addon";
|
||||
|
||||
const STAGE_ICON = {
|
||||
stable: mdiCheckCircle,
|
||||
@@ -131,9 +142,26 @@ class HassioAddonInfo extends LitElement {
|
||||
|
||||
@property({ attribute: false }) public addon!: HassioAddonDetails;
|
||||
|
||||
@property({ attribute: false }) public supervisor!: Supervisor;
|
||||
|
||||
@internalProperty() private _metrics?: HassioStats;
|
||||
|
||||
@internalProperty() private _error?: string;
|
||||
|
||||
protected render(): TemplateResult {
|
||||
const metrics = [
|
||||
{
|
||||
description: "Add-on CPU Usage",
|
||||
value: this._metrics?.cpu_percent,
|
||||
},
|
||||
{
|
||||
description: "Add-on RAM Usage",
|
||||
value: this._metrics?.memory_percent,
|
||||
tooltip: `${bytesToString(this._metrics?.memory_usage)}/${bytesToString(
|
||||
this._metrics?.memory_limit
|
||||
)}`,
|
||||
},
|
||||
];
|
||||
return html`
|
||||
${this.addon.update_available
|
||||
? html`
|
||||
@@ -149,21 +177,31 @@ class HassioAddonInfo extends LitElement {
|
||||
iconClass="update"
|
||||
></hassio-card-content>
|
||||
${!this.addon.available
|
||||
? html`
|
||||
<p>
|
||||
This update is no longer compatible with your system.
|
||||
</p>
|
||||
`
|
||||
? !addonArchIsSupported(
|
||||
this.supervisor.info.supported_arch,
|
||||
this.addon.arch
|
||||
)
|
||||
? html`
|
||||
<p>
|
||||
This add-on is not compatible with the processor of
|
||||
your device or the operating system you have installed
|
||||
on your device.
|
||||
</p>
|
||||
`
|
||||
: html`
|
||||
<p>
|
||||
You are running Home Assistant
|
||||
${this.supervisor.core.version}, to update to this
|
||||
version of the add-on you need at least version
|
||||
${this.addon.homeassistant} of Home Assistant
|
||||
</p>
|
||||
`
|
||||
: ""}
|
||||
</div>
|
||||
<div class="card-actions">
|
||||
<ha-call-api-button
|
||||
.hass=${this.hass}
|
||||
.disabled=${!this.addon.available}
|
||||
path="hassio/addons/${this.addon.slug}/update"
|
||||
>
|
||||
<ha-progress-button @click=${this._updateClicked}>
|
||||
Update
|
||||
</ha-call-api-button>
|
||||
</ha-progress-button>
|
||||
${this.addon.changelog
|
||||
? html`
|
||||
<mwc-button @click=${this._openChangelog}>
|
||||
@@ -237,331 +275,378 @@ class HassioAddonInfo extends LitElement {
|
||||
>
|
||||
for details.
|
||||
</div>
|
||||
${this.addon.logo
|
||||
? html`
|
||||
<img
|
||||
class="logo"
|
||||
src="/api/hassio/addons/${this.addon.slug}/logo"
|
||||
/>
|
||||
`
|
||||
: ""}
|
||||
<div class="security">
|
||||
${this.addon.stage !== "stable"
|
||||
? html` <ha-label-badge
|
||||
<div class="addon-container">
|
||||
<div>
|
||||
${this.addon.logo
|
||||
? html`
|
||||
<img
|
||||
class="logo"
|
||||
src="/api/hassio/addons/${this.addon.slug}/logo"
|
||||
/>
|
||||
`
|
||||
: ""}
|
||||
<div class="security">
|
||||
${this.addon.stage !== "stable"
|
||||
? html` <ha-label-badge
|
||||
class=${classMap({
|
||||
yellow: this.addon.stage === "experimental",
|
||||
red: this.addon.stage === "deprecated",
|
||||
})}
|
||||
@click=${this._showMoreInfo}
|
||||
id="stage"
|
||||
label="stage"
|
||||
description=""
|
||||
>
|
||||
<ha-svg-icon
|
||||
.path=${STAGE_ICON[this.addon.stage]}
|
||||
></ha-svg-icon>
|
||||
</ha-label-badge>`
|
||||
: ""}
|
||||
|
||||
<ha-label-badge
|
||||
class=${classMap({
|
||||
yellow: this.addon.stage === "experimental",
|
||||
red: this.addon.stage === "deprecated",
|
||||
green: [5, 6].includes(Number(this.addon.rating)),
|
||||
yellow: [3, 4].includes(Number(this.addon.rating)),
|
||||
red: [1, 2].includes(Number(this.addon.rating)),
|
||||
})}
|
||||
@click=${this._showMoreInfo}
|
||||
id="stage"
|
||||
label="stage"
|
||||
id="rating"
|
||||
.value=${this.addon.rating}
|
||||
label="rating"
|
||||
description=""
|
||||
>
|
||||
<ha-svg-icon
|
||||
.path=${STAGE_ICON[this.addon.stage]}
|
||||
></ha-svg-icon>
|
||||
</ha-label-badge>`
|
||||
: ""}
|
||||
></ha-label-badge>
|
||||
${this.addon.host_network
|
||||
? html`
|
||||
<ha-label-badge
|
||||
@click=${this._showMoreInfo}
|
||||
id="host_network"
|
||||
label="host"
|
||||
description=""
|
||||
>
|
||||
<ha-svg-icon .path=${mdiNetwork}></ha-svg-icon>
|
||||
</ha-label-badge>
|
||||
`
|
||||
: ""}
|
||||
${this.addon.full_access
|
||||
? html`
|
||||
<ha-label-badge
|
||||
@click=${this._showMoreInfo}
|
||||
id="full_access"
|
||||
label="hardware"
|
||||
description=""
|
||||
>
|
||||
<ha-svg-icon .path=${mdiChip}></ha-svg-icon>
|
||||
</ha-label-badge>
|
||||
`
|
||||
: ""}
|
||||
${this.addon.homeassistant_api
|
||||
? html`
|
||||
<ha-label-badge
|
||||
@click=${this._showMoreInfo}
|
||||
id="homeassistant_api"
|
||||
label="hass"
|
||||
description=""
|
||||
>
|
||||
<ha-svg-icon .path=${mdiHomeAssistant}></ha-svg-icon>
|
||||
</ha-label-badge>
|
||||
`
|
||||
: ""}
|
||||
${this._computeHassioApi
|
||||
? html`
|
||||
<ha-label-badge
|
||||
@click=${this._showMoreInfo}
|
||||
id="hassio_api"
|
||||
label="hassio"
|
||||
.description=${this.addon.hassio_role}
|
||||
>
|
||||
<ha-svg-icon .path=${mdiHomeAssistant}></ha-svg-icon>
|
||||
</ha-label-badge>
|
||||
`
|
||||
: ""}
|
||||
${this.addon.docker_api
|
||||
? html`
|
||||
<ha-label-badge
|
||||
@click=${this._showMoreInfo}
|
||||
id="docker_api"
|
||||
label="docker"
|
||||
description=""
|
||||
>
|
||||
<ha-svg-icon .path=${mdiDocker}></ha-svg-icon>
|
||||
</ha-label-badge>
|
||||
`
|
||||
: ""}
|
||||
${this.addon.host_pid
|
||||
? html`
|
||||
<ha-label-badge
|
||||
@click=${this._showMoreInfo}
|
||||
id="host_pid"
|
||||
label="host pid"
|
||||
description=""
|
||||
>
|
||||
<ha-svg-icon .path=${mdiPound}></ha-svg-icon>
|
||||
</ha-label-badge>
|
||||
`
|
||||
: ""}
|
||||
${this.addon.apparmor
|
||||
? html`
|
||||
<ha-label-badge
|
||||
@click=${this._showMoreInfo}
|
||||
class=${this._computeApparmorClassName}
|
||||
id="apparmor"
|
||||
label="apparmor"
|
||||
description=""
|
||||
>
|
||||
<ha-svg-icon .path=${mdiShield}></ha-svg-icon>
|
||||
</ha-label-badge>
|
||||
`
|
||||
: ""}
|
||||
${this.addon.auth_api
|
||||
? html`
|
||||
<ha-label-badge
|
||||
@click=${this._showMoreInfo}
|
||||
id="auth_api"
|
||||
label="auth"
|
||||
description=""
|
||||
>
|
||||
<ha-svg-icon .path=${mdiKey}></ha-svg-icon>
|
||||
</ha-label-badge>
|
||||
`
|
||||
: ""}
|
||||
${this.addon.ingress
|
||||
? html`
|
||||
<ha-label-badge
|
||||
@click=${this._showMoreInfo}
|
||||
id="ingress"
|
||||
label="ingress"
|
||||
description=""
|
||||
>
|
||||
<ha-svg-icon
|
||||
.path=${mdiCursorDefaultClickOutline}
|
||||
></ha-svg-icon>
|
||||
</ha-label-badge>
|
||||
`
|
||||
: ""}
|
||||
</div>
|
||||
|
||||
<ha-label-badge
|
||||
class=${classMap({
|
||||
green: [5, 6].includes(Number(this.addon.rating)),
|
||||
yellow: [3, 4].includes(Number(this.addon.rating)),
|
||||
red: [1, 2].includes(Number(this.addon.rating)),
|
||||
})}
|
||||
@click=${this._showMoreInfo}
|
||||
id="rating"
|
||||
.value=${this.addon.rating}
|
||||
label="rating"
|
||||
description=""
|
||||
></ha-label-badge>
|
||||
${this.addon.host_network
|
||||
? html`
|
||||
<ha-label-badge
|
||||
@click=${this._showMoreInfo}
|
||||
id="host_network"
|
||||
label="host"
|
||||
description=""
|
||||
>
|
||||
<ha-svg-icon .path=${mdiNetwork}></ha-svg-icon>
|
||||
</ha-label-badge>
|
||||
`
|
||||
: ""}
|
||||
${this.addon.full_access
|
||||
? html`
|
||||
<ha-label-badge
|
||||
@click=${this._showMoreInfo}
|
||||
id="full_access"
|
||||
label="hardware"
|
||||
description=""
|
||||
>
|
||||
<ha-svg-icon .path=${mdiChip}></ha-svg-icon>
|
||||
</ha-label-badge>
|
||||
`
|
||||
: ""}
|
||||
${this.addon.homeassistant_api
|
||||
? html`
|
||||
<ha-label-badge
|
||||
@click=${this._showMoreInfo}
|
||||
id="homeassistant_api"
|
||||
label="hass"
|
||||
description=""
|
||||
>
|
||||
<ha-svg-icon .path=${mdiHomeAssistant}></ha-svg-icon>
|
||||
</ha-label-badge>
|
||||
`
|
||||
: ""}
|
||||
${this._computeHassioApi
|
||||
? html`
|
||||
<ha-label-badge
|
||||
@click=${this._showMoreInfo}
|
||||
id="hassio_api"
|
||||
label="hassio"
|
||||
.description=${this.addon.hassio_role}
|
||||
>
|
||||
<ha-svg-icon .path=${mdiHomeAssistant}></ha-svg-icon>
|
||||
</ha-label-badge>
|
||||
`
|
||||
: ""}
|
||||
${this.addon.docker_api
|
||||
? html`
|
||||
<ha-label-badge
|
||||
@click=${this._showMoreInfo}
|
||||
id="docker_api"
|
||||
label="docker"
|
||||
description=""
|
||||
>
|
||||
<ha-svg-icon .path=${mdiDocker}></ha-svg-icon>
|
||||
</ha-label-badge>
|
||||
`
|
||||
: ""}
|
||||
${this.addon.host_pid
|
||||
? html`
|
||||
<ha-label-badge
|
||||
@click=${this._showMoreInfo}
|
||||
id="host_pid"
|
||||
label="host pid"
|
||||
description=""
|
||||
>
|
||||
<ha-svg-icon .path=${mdiPound}></ha-svg-icon>
|
||||
</ha-label-badge>
|
||||
`
|
||||
: ""}
|
||||
${this.addon.apparmor
|
||||
? html`
|
||||
<ha-label-badge
|
||||
@click=${this._showMoreInfo}
|
||||
class=${this._computeApparmorClassName}
|
||||
id="apparmor"
|
||||
label="apparmor"
|
||||
description=""
|
||||
>
|
||||
<ha-svg-icon .path=${mdiShield}></ha-svg-icon>
|
||||
</ha-label-badge>
|
||||
`
|
||||
: ""}
|
||||
${this.addon.auth_api
|
||||
? html`
|
||||
<ha-label-badge
|
||||
@click=${this._showMoreInfo}
|
||||
id="auth_api"
|
||||
label="auth"
|
||||
description=""
|
||||
>
|
||||
<ha-svg-icon .path=${mdiKey}></ha-svg-icon>
|
||||
</ha-label-badge>
|
||||
`
|
||||
: ""}
|
||||
${this.addon.ingress
|
||||
? html`
|
||||
<ha-label-badge
|
||||
@click=${this._showMoreInfo}
|
||||
id="ingress"
|
||||
label="ingress"
|
||||
description=""
|
||||
>
|
||||
<ha-svg-icon
|
||||
.path=${mdiCursorDefaultClickOutline}
|
||||
></ha-svg-icon>
|
||||
</ha-label-badge>
|
||||
`
|
||||
: ""}
|
||||
${this.addon.version
|
||||
? html`
|
||||
<div
|
||||
class="${classMap({
|
||||
"addon-options": true,
|
||||
started: this.addon.state === "started",
|
||||
})}"
|
||||
>
|
||||
<ha-settings-row ?three-line=${this.narrow}>
|
||||
<span slot="heading">
|
||||
Start on boot
|
||||
</span>
|
||||
<span slot="description">
|
||||
Make the add-on start during a system boot
|
||||
</span>
|
||||
<ha-switch
|
||||
@change=${this._startOnBootToggled}
|
||||
.checked=${this.addon.boot === "auto"}
|
||||
haptic
|
||||
></ha-switch>
|
||||
</ha-settings-row>
|
||||
|
||||
${this.addon.startup !== "once"
|
||||
? html`
|
||||
<ha-settings-row ?three-line=${this.narrow}>
|
||||
<span slot="heading">
|
||||
Watchdog
|
||||
</span>
|
||||
<span slot="description">
|
||||
This will start the add-on if it crashes
|
||||
</span>
|
||||
<ha-switch
|
||||
@change=${this._watchdogToggled}
|
||||
.checked=${this.addon.watchdog}
|
||||
haptic
|
||||
></ha-switch>
|
||||
</ha-settings-row>
|
||||
`
|
||||
: ""}
|
||||
${this.addon.auto_update ||
|
||||
this.hass.userData?.showAdvanced
|
||||
? html`
|
||||
<ha-settings-row ?three-line=${this.narrow}>
|
||||
<span slot="heading">
|
||||
Auto update
|
||||
</span>
|
||||
<span slot="description">
|
||||
Auto update the add-on when there is a new
|
||||
version available
|
||||
</span>
|
||||
<ha-switch
|
||||
@change=${this._autoUpdateToggled}
|
||||
.checked=${this.addon.auto_update}
|
||||
haptic
|
||||
></ha-switch>
|
||||
</ha-settings-row>
|
||||
`
|
||||
: ""}
|
||||
${this.addon.ingress
|
||||
? html`
|
||||
<ha-settings-row ?three-line=${this.narrow}>
|
||||
<span slot="heading">
|
||||
Show in sidebar
|
||||
</span>
|
||||
<span slot="description">
|
||||
${this._computeCannotIngressSidebar
|
||||
? "This option requires Home Assistant 0.92 or later."
|
||||
: "Add this add-on to your sidebar"}
|
||||
</span>
|
||||
<ha-switch
|
||||
@change=${this._panelToggled}
|
||||
.checked=${this.addon.ingress_panel}
|
||||
.disabled=${this._computeCannotIngressSidebar}
|
||||
haptic
|
||||
></ha-switch>
|
||||
</ha-settings-row>
|
||||
`
|
||||
: ""}
|
||||
${this._computeUsesProtectedOptions
|
||||
? html`
|
||||
<ha-settings-row ?three-line=${this.narrow}>
|
||||
<span slot="heading">
|
||||
Protection mode
|
||||
</span>
|
||||
<span slot="description">
|
||||
Blocks elevated system access from the add-on
|
||||
</span>
|
||||
<ha-switch
|
||||
@change=${this._protectionToggled}
|
||||
.checked=${this.addon.protected}
|
||||
haptic
|
||||
></ha-switch>
|
||||
</ha-settings-row>
|
||||
`
|
||||
: ""}
|
||||
</div>
|
||||
`
|
||||
: ""}
|
||||
</div>
|
||||
<div>
|
||||
${this.addon.state === "started"
|
||||
? html`<ha-settings-row ?three-line=${this.narrow}>
|
||||
<span slot="heading">
|
||||
Hostname
|
||||
</span>
|
||||
<code slot="description">
|
||||
${this.addon.hostname}
|
||||
</code>
|
||||
</ha-settings-row>
|
||||
${metrics.map(
|
||||
(metric) =>
|
||||
html`
|
||||
<supervisor-metric
|
||||
.description=${metric.description}
|
||||
.value=${metric.value ?? 0}
|
||||
.tooltip=${metric.tooltip}
|
||||
></supervisor-metric>
|
||||
`
|
||||
)}`
|
||||
: ""}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
${this.addon.version
|
||||
? html`
|
||||
<div class="addon-options">
|
||||
<ha-settings-row ?three-line=${this.narrow}>
|
||||
<span slot="heading">
|
||||
Start on boot
|
||||
</span>
|
||||
<span slot="description">
|
||||
Make the add-on start during a system boot
|
||||
</span>
|
||||
<ha-switch
|
||||
@change=${this._startOnBootToggled}
|
||||
.checked=${this.addon.boot === "auto"}
|
||||
haptic
|
||||
></ha-switch>
|
||||
</ha-settings-row>
|
||||
|
||||
${this.addon.startup !== "once"
|
||||
? html`
|
||||
<ha-settings-row ?three-line=${this.narrow}>
|
||||
<span slot="heading">
|
||||
Watchdog
|
||||
</span>
|
||||
<span slot="description">
|
||||
This will start the add-on if it crashes
|
||||
</span>
|
||||
<ha-switch
|
||||
@change=${this._watchdogToggled}
|
||||
.checked=${this.addon.watchdog}
|
||||
haptic
|
||||
></ha-switch>
|
||||
</ha-settings-row>
|
||||
`
|
||||
: ""}
|
||||
${this.addon.auto_update || this.hass.userData?.showAdvanced
|
||||
? html`
|
||||
<ha-settings-row ?three-line=${this.narrow}>
|
||||
<span slot="heading">
|
||||
Auto update
|
||||
</span>
|
||||
<span slot="description">
|
||||
Auto update the add-on when there is a new version
|
||||
available
|
||||
</span>
|
||||
<ha-switch
|
||||
@change=${this._autoUpdateToggled}
|
||||
.checked=${this.addon.auto_update}
|
||||
haptic
|
||||
></ha-switch>
|
||||
</ha-settings-row>
|
||||
`
|
||||
: ""}
|
||||
${this.addon.ingress
|
||||
? html`
|
||||
<ha-settings-row ?three-line=${this.narrow}>
|
||||
<span slot="heading">
|
||||
Show in sidebar
|
||||
</span>
|
||||
<span slot="description">
|
||||
${this._computeCannotIngressSidebar
|
||||
? "This option requires Home Assistant 0.92 or later."
|
||||
: "Add this add-on to your sidebar"}
|
||||
</span>
|
||||
<ha-switch
|
||||
@change=${this._panelToggled}
|
||||
.checked=${this.addon.ingress_panel}
|
||||
.disabled=${this._computeCannotIngressSidebar}
|
||||
haptic
|
||||
></ha-switch>
|
||||
</ha-settings-row>
|
||||
`
|
||||
: ""}
|
||||
${this._computeUsesProtectedOptions
|
||||
? html`
|
||||
<ha-settings-row ?three-line=${this.narrow}>
|
||||
<span slot="heading">
|
||||
Protection mode
|
||||
</span>
|
||||
<span slot="description">
|
||||
Blocks elevated system access from the add-on
|
||||
</span>
|
||||
<ha-switch
|
||||
@change=${this._protectionToggled}
|
||||
.checked=${this.addon.protected}
|
||||
haptic
|
||||
></ha-switch>
|
||||
</ha-settings-row>
|
||||
`
|
||||
: ""}
|
||||
</div>
|
||||
`
|
||||
: ""}
|
||||
${this._error ? html` <div class="errors">${this._error}</div> ` : ""}
|
||||
${!this.addon.available
|
||||
? !addonArchIsSupported(
|
||||
this.supervisor.info.supported_arch,
|
||||
this.addon.arch
|
||||
)
|
||||
? html`
|
||||
<p class="warning">
|
||||
This add-on is not compatible with the processor of your
|
||||
device or the operating system you have installed on your
|
||||
device.
|
||||
</p>
|
||||
`
|
||||
: html`
|
||||
<p class="warning">
|
||||
You are running Home Assistant
|
||||
${this.supervisor.core.version}, to install this add-on you
|
||||
need at least version ${this.addon.homeassistant} of Home
|
||||
Assistant
|
||||
</p>
|
||||
`
|
||||
: ""}
|
||||
</div>
|
||||
<div class="card-actions">
|
||||
${this.addon.version
|
||||
? html`
|
||||
${this._computeIsRunning
|
||||
? html`
|
||||
<ha-call-api-button
|
||||
class="warning"
|
||||
.hass=${this.hass}
|
||||
.path="hassio/addons/${this.addon.slug}/stop"
|
||||
>
|
||||
Stop
|
||||
</ha-call-api-button>
|
||||
<ha-call-api-button
|
||||
class="warning"
|
||||
.hass=${this.hass}
|
||||
.path="hassio/addons/${this.addon.slug}/restart"
|
||||
>
|
||||
Restart
|
||||
</ha-call-api-button>
|
||||
`
|
||||
: html`
|
||||
<ha-progress-button @click=${this._startClicked}>
|
||||
Start
|
||||
</ha-progress-button>
|
||||
`}
|
||||
${this._computeShowWebUI
|
||||
? html`
|
||||
<a
|
||||
href=${this._pathWebui!}
|
||||
tabindex="-1"
|
||||
target="_blank"
|
||||
class="right"
|
||||
rel="noopener"
|
||||
>
|
||||
<mwc-button>
|
||||
<div>
|
||||
${this.addon.version
|
||||
? this._computeIsRunning
|
||||
? html`
|
||||
<ha-progress-button
|
||||
class="warning"
|
||||
@click=${this._stopClicked}
|
||||
>
|
||||
Stop
|
||||
</ha-progress-button>
|
||||
<ha-progress-button
|
||||
class="warning"
|
||||
@click=${this._restartClicked}
|
||||
>
|
||||
Restart
|
||||
</ha-progress-button>
|
||||
`
|
||||
: html`
|
||||
<ha-progress-button @click=${this._startClicked}>
|
||||
Start
|
||||
</ha-progress-button>
|
||||
`
|
||||
: html`
|
||||
<ha-progress-button
|
||||
.disabled=${!this.addon.available}
|
||||
@click=${this._installClicked}
|
||||
>
|
||||
Install
|
||||
</ha-progress-button>
|
||||
`}
|
||||
</div>
|
||||
<div>
|
||||
${this.addon.version
|
||||
? html` ${this._computeShowWebUI
|
||||
? html`
|
||||
<a
|
||||
href=${this._pathWebui!}
|
||||
tabindex="-1"
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
>
|
||||
<mwc-button>
|
||||
Open web UI
|
||||
</mwc-button>
|
||||
</a>
|
||||
`
|
||||
: ""}
|
||||
${this._computeShowIngressUI
|
||||
? html`
|
||||
<mwc-button @click=${this._openIngress}>
|
||||
Open web UI
|
||||
</mwc-button>
|
||||
</a>
|
||||
`
|
||||
: ""}
|
||||
${this._computeShowIngressUI
|
||||
? html`
|
||||
<mwc-button class="right" @click=${this._openIngress}>
|
||||
Open web UI
|
||||
</mwc-button>
|
||||
`
|
||||
: ""}
|
||||
<ha-progress-button
|
||||
class=" right warning"
|
||||
@click=${this._uninstallClicked}
|
||||
>
|
||||
Uninstall
|
||||
</ha-progress-button>
|
||||
${this.addon.build
|
||||
? html`
|
||||
<ha-call-api-button
|
||||
class="warning right"
|
||||
.hass=${this.hass}
|
||||
.path="hassio/addons/${this.addon.slug}/rebuild"
|
||||
>
|
||||
Rebuild
|
||||
</ha-call-api-button>
|
||||
`
|
||||
: ""}
|
||||
`
|
||||
: html`
|
||||
${!this.addon.available
|
||||
? html`
|
||||
<p class="warning">
|
||||
This add-on is not available on your system.
|
||||
</p>
|
||||
`
|
||||
: ""}
|
||||
<ha-progress-button
|
||||
.disabled=${!this.addon.available}
|
||||
@click=${this._installClicked}
|
||||
>
|
||||
Install
|
||||
</ha-progress-button>
|
||||
`}
|
||||
`
|
||||
: ""}
|
||||
<ha-progress-button
|
||||
class="warning"
|
||||
@click=${this._uninstallClicked}
|
||||
>
|
||||
Uninstall
|
||||
</ha-progress-button>
|
||||
${this.addon.build
|
||||
? html`
|
||||
<ha-call-api-button
|
||||
class="warning"
|
||||
.hass=${this.hass}
|
||||
.path="hassio/addons/${this.addon.slug}/rebuild"
|
||||
>
|
||||
Rebuild
|
||||
</ha-call-api-button>
|
||||
`
|
||||
: ""}`
|
||||
: ""}
|
||||
</div>
|
||||
</div>
|
||||
</ha-card>
|
||||
|
||||
@@ -579,6 +664,22 @@ class HassioAddonInfo extends LitElement {
|
||||
`;
|
||||
}
|
||||
|
||||
protected updated(changedProps) {
|
||||
super.updated(changedProps);
|
||||
if (changedProps.has("addon")) {
|
||||
this._loadData();
|
||||
}
|
||||
}
|
||||
|
||||
private async _loadData(): Promise<void> {
|
||||
if (this.addon.state === "started") {
|
||||
this._metrics = await fetchHassioStats(
|
||||
this.hass,
|
||||
`addons/${this.addon.slug}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private get _computeHassioApi(): boolean {
|
||||
return (
|
||||
this.addon.hassio_api &&
|
||||
@@ -779,6 +880,82 @@ class HassioAddonInfo extends LitElement {
|
||||
button.progress = false;
|
||||
}
|
||||
|
||||
private async _stopClicked(ev: CustomEvent): Promise<void> {
|
||||
const button = ev.currentTarget as any;
|
||||
button.progress = true;
|
||||
|
||||
try {
|
||||
await stopHassioAddon(this.hass, this.addon.slug);
|
||||
const eventdata = {
|
||||
success: true,
|
||||
response: undefined,
|
||||
path: "stop",
|
||||
};
|
||||
fireEvent(this, "hass-api-called", eventdata);
|
||||
} catch (err) {
|
||||
showAlertDialog(this, {
|
||||
title: "Failed to stop addon",
|
||||
text: extractApiErrorMessage(err),
|
||||
});
|
||||
}
|
||||
button.progress = false;
|
||||
}
|
||||
|
||||
private async _restartClicked(ev: CustomEvent): Promise<void> {
|
||||
const button = ev.currentTarget as any;
|
||||
button.progress = true;
|
||||
|
||||
try {
|
||||
await restartHassioAddon(this.hass, this.addon.slug);
|
||||
const eventdata = {
|
||||
success: true,
|
||||
response: undefined,
|
||||
path: "stop",
|
||||
};
|
||||
fireEvent(this, "hass-api-called", eventdata);
|
||||
} catch (err) {
|
||||
showAlertDialog(this, {
|
||||
title: "Failed to restart addon",
|
||||
text: extractApiErrorMessage(err),
|
||||
});
|
||||
}
|
||||
button.progress = false;
|
||||
}
|
||||
|
||||
private async _updateClicked(ev: CustomEvent): Promise<void> {
|
||||
const button = ev.currentTarget as any;
|
||||
button.progress = true;
|
||||
|
||||
const confirmed = await showConfirmationDialog(this, {
|
||||
title: this.addon.name,
|
||||
text: "Are you sure you want to update this add-on?",
|
||||
confirmText: "update add-on",
|
||||
dismissText: "no",
|
||||
});
|
||||
|
||||
if (!confirmed) {
|
||||
button.progress = false;
|
||||
return;
|
||||
}
|
||||
|
||||
this._error = undefined;
|
||||
try {
|
||||
await updateHassioAddon(this.hass, this.addon.slug);
|
||||
const eventdata = {
|
||||
success: true,
|
||||
response: undefined,
|
||||
path: "update",
|
||||
};
|
||||
fireEvent(this, "hass-api-called", eventdata);
|
||||
} catch (err) {
|
||||
showAlertDialog(this, {
|
||||
title: "Failed to update addon",
|
||||
text: extractApiErrorMessage(err),
|
||||
});
|
||||
}
|
||||
button.progress = false;
|
||||
}
|
||||
|
||||
private async _startClicked(ev: CustomEvent): Promise<void> {
|
||||
const button = ev.currentTarget as any;
|
||||
button.progress = true;
|
||||
@@ -787,10 +964,10 @@ class HassioAddonInfo extends LitElement {
|
||||
this.hass,
|
||||
this.addon.slug
|
||||
);
|
||||
if (!validate.data.valid) {
|
||||
if (!validate.valid) {
|
||||
await showConfirmationDialog(this, {
|
||||
title: "Failed to start addon - configuration validation failed!",
|
||||
text: validate.data.message.split(" Got ")[0],
|
||||
text: validate.message.split(" Got ")[0],
|
||||
confirm: () => this._openConfiguration(),
|
||||
confirmText: "Go to configuration",
|
||||
dismissText: "Cancel",
|
||||
@@ -810,6 +987,12 @@ class HassioAddonInfo extends LitElement {
|
||||
try {
|
||||
await startHassioAddon(this.hass, this.addon.slug);
|
||||
this.addon = await fetchHassioAddonInfo(this.hass, this.addon.slug);
|
||||
const eventdata = {
|
||||
success: true,
|
||||
response: undefined,
|
||||
path: "start",
|
||||
};
|
||||
fireEvent(this, "hass-api-called", eventdata);
|
||||
} catch (err) {
|
||||
showAlertDialog(this, {
|
||||
title: "Failed to start addon",
|
||||
@@ -925,9 +1108,6 @@ class HassioAddonInfo extends LitElement {
|
||||
font-weight: 500;
|
||||
color: var(--primary-color);
|
||||
}
|
||||
.right {
|
||||
float: right;
|
||||
}
|
||||
protection-enable mwc-button {
|
||||
--mdc-theme-primary: white;
|
||||
}
|
||||
@@ -950,7 +1130,8 @@ class HassioAddonInfo extends LitElement {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.card-actions {
|
||||
display: flow-root;
|
||||
justify-content: space-between;
|
||||
display: flex;
|
||||
}
|
||||
.security h3 {
|
||||
margin-bottom: 8px;
|
||||
@@ -986,12 +1167,26 @@ class HassioAddonInfo extends LitElement {
|
||||
}
|
||||
|
||||
.addon-options {
|
||||
max-width: 50%;
|
||||
max-width: 90%;
|
||||
}
|
||||
|
||||
.addon-container {
|
||||
display: grid;
|
||||
grid-auto-flow: column;
|
||||
grid-template-columns: 60% 40%;
|
||||
}
|
||||
|
||||
.addon-container > div:last-of-type {
|
||||
align-self: end;
|
||||
}
|
||||
|
||||
@media (max-width: 720px) {
|
||||
.addon-options {
|
||||
max-width: 100%;
|
||||
}
|
||||
.addon-container {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
@@ -7,8 +7,8 @@ import {
|
||||
property,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import { HassioAddonDetails } from "../../../../src/data/hassio/addon";
|
||||
import "../../../../src/components/ha-circular-progress";
|
||||
import { HassioAddonDetails } from "../../../../src/data/hassio/addon";
|
||||
import { haStyle } from "../../../../src/resources/styles";
|
||||
import { HomeAssistant } from "../../../../src/types";
|
||||
import { hassioStyle } from "../../resources/hassio-style";
|
||||
|
87
hassio/src/components/supervisor-metric.ts
Normal file
87
hassio/src/components/supervisor-metric.ts
Normal file
@@ -0,0 +1,87 @@
|
||||
import {
|
||||
css,
|
||||
CSSResult,
|
||||
customElement,
|
||||
html,
|
||||
LitElement,
|
||||
property,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import { classMap } from "lit-html/directives/class-map";
|
||||
import "../../../src/components/ha-bar";
|
||||
import "../../../src/components/ha-settings-row";
|
||||
import { roundWithOneDecimal } from "../../../src/util/calculate";
|
||||
|
||||
@customElement("supervisor-metric")
|
||||
class SupervisorMetric extends LitElement {
|
||||
@property({ type: Number }) public value!: number;
|
||||
|
||||
@property({ type: String }) public description!: string;
|
||||
|
||||
@property({ type: String }) public tooltip?: string;
|
||||
|
||||
protected render(): TemplateResult {
|
||||
const roundedValue = roundWithOneDecimal(this.value);
|
||||
return html`<ha-settings-row>
|
||||
<span slot="heading">
|
||||
${this.description}
|
||||
</span>
|
||||
<div slot="description" title="${this.tooltip ?? ""}">
|
||||
<span class="value">
|
||||
${roundedValue}%
|
||||
</span>
|
||||
<ha-bar
|
||||
class="${classMap({
|
||||
"target-warning": roundedValue > 50,
|
||||
"target-critical": roundedValue > 85,
|
||||
})}"
|
||||
.value=${this.value}
|
||||
></ha-bar>
|
||||
</div>
|
||||
</ha-settings-row>`;
|
||||
}
|
||||
|
||||
static get styles(): CSSResult {
|
||||
return css`
|
||||
ha-settings-row {
|
||||
padding: 0;
|
||||
height: 54px;
|
||||
width: 100%;
|
||||
}
|
||||
ha-settings-row > div[slot="description"] {
|
||||
white-space: normal;
|
||||
color: var(--secondary-text-color);
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
ha-bar {
|
||||
--ha-bar-primary-color: var(
|
||||
--hassio-bar-ok-color,
|
||||
var(--success-color)
|
||||
);
|
||||
}
|
||||
.target-warning {
|
||||
--ha-bar-primary-color: var(
|
||||
--hassio-bar-warning-color,
|
||||
var(--warning-color)
|
||||
);
|
||||
}
|
||||
.target-critical {
|
||||
--ha-bar-primary-color: var(
|
||||
--hassio-bar-critical-color,
|
||||
var(--error-color)
|
||||
);
|
||||
}
|
||||
.value {
|
||||
width: 42px;
|
||||
padding-right: 4px;
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"supervisor-metric": SupervisorMetric;
|
||||
}
|
||||
}
|
@@ -10,6 +10,7 @@ import {
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { fireEvent } from "../../../src/common/dom/fire_event";
|
||||
import "../../../src/components/buttons/ha-progress-button";
|
||||
import "../../../src/components/ha-card";
|
||||
import "../../../src/components/ha-svg-icon";
|
||||
@@ -64,6 +65,7 @@ export class HassioUpdate extends LitElement {
|
||||
<div class="card-group">
|
||||
${this._renderUpdateCard(
|
||||
"Home Assistant Core",
|
||||
"core",
|
||||
this.supervisor.core,
|
||||
"hassio/homeassistant/update",
|
||||
`https://${
|
||||
@@ -72,6 +74,7 @@ export class HassioUpdate extends LitElement {
|
||||
)}
|
||||
${this._renderUpdateCard(
|
||||
"Supervisor",
|
||||
"supervisor",
|
||||
this.supervisor.supervisor,
|
||||
"hassio/supervisor/update",
|
||||
`https://github.com//home-assistant/hassio/releases/tag/${this.supervisor.supervisor.version_latest}`
|
||||
@@ -79,6 +82,7 @@ export class HassioUpdate extends LitElement {
|
||||
${this.supervisor.host.features.includes("hassos")
|
||||
? this._renderUpdateCard(
|
||||
"Operating System",
|
||||
"os",
|
||||
this.supervisor.os,
|
||||
"hassio/os/update",
|
||||
`https://github.com//home-assistant/hassos/releases/tag/${this.supervisor.os.version_latest}`
|
||||
@@ -91,6 +95,7 @@ export class HassioUpdate extends LitElement {
|
||||
|
||||
private _renderUpdateCard(
|
||||
name: string,
|
||||
key: string,
|
||||
object: HassioHomeAssistantInfo | HassioSupervisorInfo | HassioHassOSInfo,
|
||||
apiPath: string,
|
||||
releaseNotesUrl: string
|
||||
@@ -116,6 +121,7 @@ export class HassioUpdate extends LitElement {
|
||||
<ha-progress-button
|
||||
.apiPath=${apiPath}
|
||||
.name=${name}
|
||||
.key=${key}
|
||||
.version=${object.version_latest}
|
||||
@click=${this._confirmUpdate}
|
||||
>
|
||||
@@ -142,6 +148,7 @@ export class HassioUpdate extends LitElement {
|
||||
}
|
||||
try {
|
||||
await this.hass.callApi<HassioResponse<void>>("POST", item.apiPath);
|
||||
fireEvent(this, "supervisor-store-refresh", { store: item.key });
|
||||
} catch (err) {
|
||||
// Only show an error if the status code was not expected (user behind proxy)
|
||||
// or no status at all(connection terminated)
|
||||
|
@@ -3,9 +3,9 @@ import {
|
||||
CSSResult,
|
||||
customElement,
|
||||
html,
|
||||
internalProperty,
|
||||
LitElement,
|
||||
property,
|
||||
internalProperty,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import { createCloseHeading } from "../../../../src/components/ha-dialog";
|
||||
|
@@ -22,7 +22,11 @@ import {
|
||||
fetchHassioSnapshotInfo,
|
||||
HassioSnapshotDetail,
|
||||
} from "../../../../src/data/hassio/snapshot";
|
||||
import { showConfirmationDialog } from "../../../../src/dialogs/generic/show-dialog-box";
|
||||
import { Supervisor } from "../../../../src/data/supervisor/supervisor";
|
||||
import {
|
||||
showAlertDialog,
|
||||
showConfirmationDialog,
|
||||
} from "../../../../src/dialogs/generic/show-dialog-box";
|
||||
import { PolymerChangedEvent } from "../../../../src/polymer-types";
|
||||
import { haStyle, haStyleDialog } from "../../../../src/resources/styles";
|
||||
import { HomeAssistant } from "../../../../src/types";
|
||||
@@ -75,6 +79,8 @@ interface FolderItem {
|
||||
class HassioSnapshotDialog extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ attribute: false }) public supervisor?: Supervisor;
|
||||
|
||||
@internalProperty() private _error?: string;
|
||||
|
||||
@internalProperty() private _onboarding = false;
|
||||
@@ -102,6 +108,7 @@ class HassioSnapshotDialog extends LitElement {
|
||||
|
||||
this._dialogParams = params;
|
||||
this._onboarding = params.onboarding ?? false;
|
||||
this.supervisor = params.supervisor;
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
@@ -298,6 +305,16 @@ class HassioSnapshotDialog extends LitElement {
|
||||
}
|
||||
|
||||
private async _partialRestoreClicked() {
|
||||
if (
|
||||
this.supervisor !== undefined &&
|
||||
this.supervisor.info.state !== "running"
|
||||
) {
|
||||
await showAlertDialog(this, {
|
||||
title: "Could not restore snapshot",
|
||||
text: `Restoring a snapshot is not possible right now because the system is in ${this.supervisor.info.state} state.`,
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (
|
||||
!(await showConfirmationDialog(this, {
|
||||
title: "Are you sure you want partially to restore this snapshot?",
|
||||
@@ -359,6 +376,16 @@ class HassioSnapshotDialog extends LitElement {
|
||||
}
|
||||
|
||||
private async _fullRestoreClicked() {
|
||||
if (
|
||||
this.supervisor !== undefined &&
|
||||
this.supervisor.info.state !== "running"
|
||||
) {
|
||||
await showAlertDialog(this, {
|
||||
title: "Could not restore snapshot",
|
||||
text: `Restoring a snapshot is not possible right now because the system is in ${this.supervisor.info.state} state.`,
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (
|
||||
!(await showConfirmationDialog(this, {
|
||||
title:
|
||||
|
@@ -1,9 +1,11 @@
|
||||
import { fireEvent } from "../../../../src/common/dom/fire_event";
|
||||
import { Supervisor } from "../../../../src/data/supervisor/supervisor";
|
||||
|
||||
export interface HassioSnapshotDialogParams {
|
||||
slug: string;
|
||||
onDelete?: () => void;
|
||||
onboarding?: boolean;
|
||||
supervisor?: Supervisor;
|
||||
}
|
||||
|
||||
export const showHassioSnapshotDialog = (
|
||||
|
@@ -1,6 +1,7 @@
|
||||
// Compat needs to be first import
|
||||
import "../../src/resources/compatibility";
|
||||
import "../../src/resources/safari-14-attachshadow-patch";
|
||||
import "../../src/resources/roboto";
|
||||
import "../../src/resources/safari-14-attachshadow-patch";
|
||||
import "./hassio-main";
|
||||
|
||||
const styleEl = document.createElement("style");
|
||||
|
@@ -1,11 +1,13 @@
|
||||
import { html, PropertyValues, customElement, property } from "lit-element";
|
||||
import "./hassio-router";
|
||||
import { HomeAssistant, Route } from "../../src/types";
|
||||
import { HassioPanelInfo } from "../../src/data/hassio/supervisor";
|
||||
import { customElement, html, property, PropertyValues } from "lit-element";
|
||||
import { atLeastVersion } from "../../src/common/config/version";
|
||||
import { applyThemesOnElement } from "../../src/common/dom/apply_themes_on_element";
|
||||
import { fireEvent } from "../../src/common/dom/fire_event";
|
||||
import { HassioPanelInfo } from "../../src/data/hassio/supervisor";
|
||||
import { supervisorStore } from "../../src/data/supervisor/supervisor";
|
||||
import { makeDialogManager } from "../../src/dialogs/make-dialog-manager";
|
||||
import { atLeastVersion } from "../../src/common/config/version";
|
||||
import "../../src/layouts/hass-loading-screen";
|
||||
import { HomeAssistant, Route } from "../../src/types";
|
||||
import "./hassio-router";
|
||||
import { SupervisorBaseElement } from "./supervisor-base-element";
|
||||
|
||||
@customElement("hassio-main")
|
||||
@@ -71,8 +73,15 @@ export class HassioMain extends SupervisorBaseElement {
|
||||
|
||||
protected render() {
|
||||
if (!this.supervisor || !this.hass) {
|
||||
return html``;
|
||||
return html`<hass-loading-screen></hass-loading-screen>`;
|
||||
}
|
||||
|
||||
if (
|
||||
Object.keys(supervisorStore).some((store) => !this.supervisor![store])
|
||||
) {
|
||||
return html`<hass-loading-screen></hass-loading-screen>`;
|
||||
}
|
||||
|
||||
return html`
|
||||
<hassio-router
|
||||
.hass=${this.hass}
|
||||
|
128
hassio/src/hassio-my-redirect.ts
Normal file
128
hassio/src/hassio-my-redirect.ts
Normal file
@@ -0,0 +1,128 @@
|
||||
import {
|
||||
customElement,
|
||||
html,
|
||||
internalProperty,
|
||||
LitElement,
|
||||
property,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import { sanitizeUrl } from "@braintree/sanitize-url";
|
||||
import {
|
||||
createSearchParam,
|
||||
extractSearchParamsObject,
|
||||
} from "../../src/common/url/search-params";
|
||||
import "../../src/layouts/hass-error-screen";
|
||||
import {
|
||||
ParamType,
|
||||
Redirect,
|
||||
Redirects,
|
||||
} from "../../src/panels/my/ha-panel-my";
|
||||
import { navigate } from "../../src/common/navigate";
|
||||
import { HomeAssistant, Route } from "../../src/types";
|
||||
|
||||
const REDIRECTS: Redirects = {
|
||||
supervisor_logs: {
|
||||
redirect: "/hassio/system",
|
||||
},
|
||||
supervisor_info: {
|
||||
redirect: "/hassio/system",
|
||||
},
|
||||
supervisor_snapshots: {
|
||||
redirect: "/hassio/snapshots",
|
||||
},
|
||||
supervisor_store: {
|
||||
redirect: "/hassio/store",
|
||||
},
|
||||
supervisor: {
|
||||
redirect: "/hassio/dashboard",
|
||||
},
|
||||
supervisor_addon: {
|
||||
redirect: "/hassio/addon",
|
||||
params: {
|
||||
addon: "string",
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@customElement("hassio-my-redirect")
|
||||
class HassioMyRedirect extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property() public route!: Route;
|
||||
|
||||
@internalProperty() public _error?: TemplateResult | string;
|
||||
|
||||
connectedCallback() {
|
||||
super.connectedCallback();
|
||||
const path = this.route.path.substr(1);
|
||||
const redirect = REDIRECTS[path];
|
||||
|
||||
if (!redirect) {
|
||||
this._error = html`This redirect is not supported by your Home Assistant
|
||||
instance. Check the
|
||||
<a
|
||||
target="_blank"
|
||||
rel="noreferrer noopener"
|
||||
href="https://my.home-assistant.io/faq.html#supported-pages"
|
||||
>My Home Assistant FAQ</a
|
||||
>
|
||||
for the supported redirects and the version they where introduced.`;
|
||||
return;
|
||||
}
|
||||
|
||||
let url: string;
|
||||
try {
|
||||
url = this._createRedirectUrl(redirect);
|
||||
} catch (err) {
|
||||
this._error = "An unknown error occured";
|
||||
return;
|
||||
}
|
||||
|
||||
navigate(this, url, true);
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (this._error) {
|
||||
return html`<hass-error-screen
|
||||
.error=${this._error}
|
||||
></hass-error-screen>`;
|
||||
}
|
||||
return html``;
|
||||
}
|
||||
|
||||
private _createRedirectUrl(redirect: Redirect): string {
|
||||
const params = this._createRedirectParams(redirect);
|
||||
return `${redirect.redirect}${params}`;
|
||||
}
|
||||
|
||||
private _createRedirectParams(redirect: Redirect): string {
|
||||
const params = extractSearchParamsObject();
|
||||
if (!redirect.params && !Object.keys(params).length) {
|
||||
return "";
|
||||
}
|
||||
const resultParams = {};
|
||||
Object.entries(redirect.params || {}).forEach(([key, type]) => {
|
||||
if (!params[key] || !this._checkParamType(type, params[key])) {
|
||||
throw Error();
|
||||
}
|
||||
resultParams[key] = params[key];
|
||||
});
|
||||
return `?${createSearchParam(resultParams)}`;
|
||||
}
|
||||
|
||||
private _checkParamType(type: ParamType, value: string) {
|
||||
if (type === "string") {
|
||||
return true;
|
||||
}
|
||||
if (type === "url") {
|
||||
return value && value === sanitizeUrl(value);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"hassio-my-redirect": HassioMyRedirect;
|
||||
}
|
||||
}
|
@@ -23,7 +23,7 @@ class HassioRouter extends HassRouterPage {
|
||||
protected routerOptions: RouterOptions = {
|
||||
// Hass.io has a page with tabs, so we route all non-matching routes to it.
|
||||
defaultPage: "dashboard",
|
||||
initialLoad: () => this._fetchData(),
|
||||
initialLoad: () => this._redirectIngress(),
|
||||
showLoading: true,
|
||||
routes: {
|
||||
dashboard: {
|
||||
@@ -41,32 +41,42 @@ class HassioRouter extends HassRouterPage {
|
||||
tag: "hassio-ingress-view",
|
||||
load: () => import("./ingress-view/hassio-ingress-view"),
|
||||
},
|
||||
_my_redirect: {
|
||||
tag: "hassio-my-redirect",
|
||||
load: () => import("./hassio-my-redirect"),
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
protected updatePageEl(el) {
|
||||
// the tabs page does its own routing so needs full route.
|
||||
const route = el.nodeName === "HASSIO-PANEL" ? this.route : this.routeTail;
|
||||
const hassioPanel = el.nodeName === "HASSIO-PANEL";
|
||||
const route = hassioPanel ? this.route : this.routeTail;
|
||||
|
||||
if (hassioPanel && this.panel.config?.ingress) {
|
||||
this._redirectIngress();
|
||||
return;
|
||||
}
|
||||
|
||||
el.hass = this.hass;
|
||||
el.supervisor = this.supervisor;
|
||||
el.narrow = this.narrow;
|
||||
el.route = route;
|
||||
|
||||
if (el.localName === "hassio-ingress-view") {
|
||||
el.ingressPanel = this.panel.config && this.panel.config.ingress;
|
||||
} else {
|
||||
el.supervisor = this.supervisor;
|
||||
}
|
||||
}
|
||||
|
||||
private async _fetchData() {
|
||||
private async _redirectIngress() {
|
||||
if (this.panel.config && this.panel.config.ingress) {
|
||||
this._redirectIngress(this.panel.config.ingress);
|
||||
this.route = {
|
||||
prefix: "/hassio",
|
||||
path: `/ingress/${this.panel.config.ingress}`,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private _redirectIngress(addonSlug: string) {
|
||||
this.route = { prefix: "/hassio", path: `/ingress/${addonSlug}` };
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
|
@@ -1,14 +1,17 @@
|
||||
import { mdiMenu } from "@mdi/js";
|
||||
import {
|
||||
css,
|
||||
CSSResult,
|
||||
customElement,
|
||||
html,
|
||||
internalProperty,
|
||||
LitElement,
|
||||
property,
|
||||
internalProperty,
|
||||
PropertyValues,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import { fireEvent } from "../../../src/common/dom/fire_event";
|
||||
import { navigate } from "../../../src/common/navigate";
|
||||
import {
|
||||
fetchHassioAddonInfo,
|
||||
HassioAddonDetails,
|
||||
@@ -17,13 +20,10 @@ import {
|
||||
createHassioSession,
|
||||
validateHassioSession,
|
||||
} from "../../../src/data/hassio/ingress";
|
||||
import { showAlertDialog } from "../../../src/dialogs/generic/show-dialog-box";
|
||||
import "../../../src/layouts/hass-loading-screen";
|
||||
import "../../../src/layouts/hass-subpage";
|
||||
import { HomeAssistant, Route } from "../../../src/types";
|
||||
import { showAlertDialog } from "../../../src/dialogs/generic/show-dialog-box";
|
||||
import { navigate } from "../../../src/common/navigate";
|
||||
import { mdiMenu } from "@mdi/js";
|
||||
import { fireEvent } from "../../../src/common/dom/fire_event";
|
||||
|
||||
@customElement("hassio-ingress-view")
|
||||
class HassioIngressView extends LitElement {
|
||||
|
@@ -41,6 +41,7 @@ import {
|
||||
reloadHassioSnapshots,
|
||||
} from "../../../src/data/hassio/snapshot";
|
||||
import { Supervisor } from "../../../src/data/supervisor/supervisor";
|
||||
import { showAlertDialog } from "../../../src/dialogs/generic/show-dialog-box";
|
||||
import "../../../src/layouts/hass-tabs-subpage";
|
||||
import { PolymerChangedEvent } from "../../../src/polymer-types";
|
||||
import { haStyle } from "../../../src/resources/styles";
|
||||
@@ -211,7 +212,13 @@ class HassioSnapshots extends LitElement {
|
||||
: undefined}
|
||||
</div>
|
||||
<div class="card-actions">
|
||||
<ha-progress-button @click=${this._createSnapshot}>
|
||||
<ha-progress-button
|
||||
@click=${this._createSnapshot}
|
||||
title="${this.supervisor.info.state !== "running"
|
||||
? `Creating a snapshot is not possible right now because the system is in ${this.supervisor.info.state} state.`
|
||||
: ""}"
|
||||
.disabled=${this.supervisor.info.state !== "running"}
|
||||
>
|
||||
Create
|
||||
</ha-progress-button>
|
||||
</div>
|
||||
@@ -264,7 +271,7 @@ class HassioSnapshots extends LitElement {
|
||||
}
|
||||
|
||||
protected updated(changedProps: PropertyValues) {
|
||||
if (changedProps.has("supervisorInfo")) {
|
||||
if (changedProps.has("supervisor")) {
|
||||
this._addonList = this.supervisor.supervisor.addons
|
||||
.map((addon) => ({
|
||||
slug: addon.slug,
|
||||
@@ -325,6 +332,12 @@ class HassioSnapshots extends LitElement {
|
||||
}
|
||||
|
||||
private async _createSnapshot(ev: CustomEvent): Promise<void> {
|
||||
if (this.supervisor.info.state !== "running") {
|
||||
await showAlertDialog(this, {
|
||||
title: "Could not create snapshot",
|
||||
text: `Creating a snapshot is not possible right now because the system is in ${this.supervisor.info.state} state.`,
|
||||
});
|
||||
}
|
||||
const button = ev.currentTarget as any;
|
||||
button.progress = true;
|
||||
|
||||
@@ -386,6 +399,7 @@ class HassioSnapshots extends LitElement {
|
||||
private _snapshotClicked(ev) {
|
||||
showHassioSnapshotDialog(this, {
|
||||
slug: ev.currentTarget!.snapshot.slug,
|
||||
supervisor: this.supervisor,
|
||||
onDelete: () => this._updateSnapshots(),
|
||||
});
|
||||
}
|
||||
@@ -395,6 +409,7 @@ class HassioSnapshots extends LitElement {
|
||||
showSnapshot: (slug: string) =>
|
||||
showHassioSnapshotDialog(this, {
|
||||
slug,
|
||||
supervisor: this.supervisor,
|
||||
onDelete: () => this._updateSnapshots(),
|
||||
}),
|
||||
reloadSnapshot: () => this.refreshData(),
|
||||
|
@@ -1,4 +1,13 @@
|
||||
import { LitElement, property, PropertyValues } from "lit-element";
|
||||
import { Collection, UnsubscribeFunc } from "home-assistant-js-websocket";
|
||||
import {
|
||||
internalProperty,
|
||||
LitElement,
|
||||
property,
|
||||
PropertyValues,
|
||||
} from "lit-element";
|
||||
import { atLeastVersion } from "../../src/common/config/version";
|
||||
import { fetchHassioAddonsInfo } from "../../src/data/hassio/addon";
|
||||
import { HassioResponse } from "../../src/data/hassio/common";
|
||||
import {
|
||||
fetchHassioHassOsInfo,
|
||||
fetchHassioHostInfo,
|
||||
@@ -10,13 +19,20 @@ import {
|
||||
fetchHassioInfo,
|
||||
fetchHassioSupervisorInfo,
|
||||
} from "../../src/data/hassio/supervisor";
|
||||
import { Supervisor } from "../../src/data/supervisor/supervisor";
|
||||
import {
|
||||
getSupervisorEventCollection,
|
||||
subscribeSupervisorEvents,
|
||||
Supervisor,
|
||||
SupervisorObject,
|
||||
supervisorStore,
|
||||
} from "../../src/data/supervisor/supervisor";
|
||||
import { ProvideHassLitMixin } from "../../src/mixins/provide-hass-lit-mixin";
|
||||
import { urlSyncMixin } from "../../src/state/url-sync-mixin";
|
||||
|
||||
declare global {
|
||||
interface HASSDomEvents {
|
||||
"supervisor-update": Partial<Supervisor>;
|
||||
"supervisor-store-refresh": { store: SupervisorObject };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,6 +41,20 @@ export class SupervisorBaseElement extends urlSyncMixin(
|
||||
) {
|
||||
@property({ attribute: false }) public supervisor?: Supervisor;
|
||||
|
||||
@internalProperty() private _unsubs: Record<string, UnsubscribeFunc> = {};
|
||||
|
||||
@internalProperty() private _collections: Record<
|
||||
string,
|
||||
Collection<unknown>
|
||||
> = {};
|
||||
|
||||
public disconnectedCallback() {
|
||||
super.disconnectedCallback();
|
||||
Object.keys(this._unsubs).forEach((unsub) => {
|
||||
this._unsubs[unsub]();
|
||||
});
|
||||
}
|
||||
|
||||
protected _updateSupervisor(obj: Partial<Supervisor>): void {
|
||||
this.supervisor = { ...this.supervisor!, ...obj };
|
||||
}
|
||||
@@ -32,13 +62,59 @@ export class SupervisorBaseElement extends urlSyncMixin(
|
||||
protected firstUpdated(changedProps: PropertyValues): void {
|
||||
super.firstUpdated(changedProps);
|
||||
this._initSupervisor();
|
||||
this.addEventListener("supervisor-update", (ev) =>
|
||||
this._updateSupervisor(ev.detail)
|
||||
}
|
||||
|
||||
private async _handleSupervisorStoreRefreshEvent(ev) {
|
||||
const store = ev.detail.store;
|
||||
if (atLeastVersion(this.hass.config.version, 2021, 2, 4)) {
|
||||
this._collections[store].refresh();
|
||||
return;
|
||||
}
|
||||
|
||||
const response = await this.hass.callApi<HassioResponse<any>>(
|
||||
"GET",
|
||||
`hassio${supervisorStore[store]}`
|
||||
);
|
||||
this._updateSupervisor({ [store]: response.data });
|
||||
}
|
||||
|
||||
private async _initSupervisor(): Promise<void> {
|
||||
this.addEventListener(
|
||||
"supervisor-store-refresh",
|
||||
this._handleSupervisorStoreRefreshEvent
|
||||
);
|
||||
|
||||
if (atLeastVersion(this.hass.config.version, 2021, 2, 4)) {
|
||||
Object.keys(supervisorStore).forEach((store) => {
|
||||
this._unsubs[store] = subscribeSupervisorEvents(
|
||||
this.hass,
|
||||
(data) => this._updateSupervisor({ [store]: data }),
|
||||
store,
|
||||
supervisorStore[store]
|
||||
);
|
||||
if (this._collections[store]) {
|
||||
this._collections[store].refresh();
|
||||
} else {
|
||||
this._collections[store] = getSupervisorEventCollection(
|
||||
this.hass.connection,
|
||||
store,
|
||||
supervisorStore[store]
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
if (this.supervisor === undefined) {
|
||||
Object.keys(this._collections).forEach((collection) =>
|
||||
this._updateSupervisor({
|
||||
[collection]: this._collections[collection].state,
|
||||
})
|
||||
);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const [
|
||||
addon,
|
||||
supervisor,
|
||||
host,
|
||||
core,
|
||||
@@ -47,6 +123,7 @@ export class SupervisorBaseElement extends urlSyncMixin(
|
||||
network,
|
||||
resolution,
|
||||
] = await Promise.all([
|
||||
fetchHassioAddonsInfo(this.hass),
|
||||
fetchHassioSupervisorInfo(this.hass),
|
||||
fetchHassioHostInfo(this.hass),
|
||||
fetchHassioHomeAssistantInfo(this.hass),
|
||||
@@ -57,6 +134,7 @@ export class SupervisorBaseElement extends urlSyncMixin(
|
||||
]);
|
||||
|
||||
this.supervisor = {
|
||||
addon,
|
||||
supervisor,
|
||||
host,
|
||||
core,
|
||||
@@ -65,5 +143,9 @@ export class SupervisorBaseElement extends urlSyncMixin(
|
||||
network,
|
||||
resolution,
|
||||
};
|
||||
|
||||
this.addEventListener("supervisor-update", (ev) =>
|
||||
this._updateSupervisor(ev.detail)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
248
hassio/src/system/hassio-core-info.ts
Normal file
248
hassio/src/system/hassio-core-info.ts
Normal file
@@ -0,0 +1,248 @@
|
||||
import "@material/mwc-button";
|
||||
import "@material/mwc-list/mwc-list-item";
|
||||
import {
|
||||
css,
|
||||
CSSResult,
|
||||
customElement,
|
||||
html,
|
||||
internalProperty,
|
||||
LitElement,
|
||||
property,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import { fireEvent } from "../../../src/common/dom/fire_event";
|
||||
import "../../../src/components/buttons/ha-progress-button";
|
||||
import "../../../src/components/ha-button-menu";
|
||||
import "../../../src/components/ha-card";
|
||||
import "../../../src/components/ha-settings-row";
|
||||
import {
|
||||
extractApiErrorMessage,
|
||||
fetchHassioStats,
|
||||
HassioStats,
|
||||
} from "../../../src/data/hassio/common";
|
||||
import { restartCore, updateCore } from "../../../src/data/supervisor/core";
|
||||
import { Supervisor } from "../../../src/data/supervisor/supervisor";
|
||||
import {
|
||||
showAlertDialog,
|
||||
showConfirmationDialog,
|
||||
} from "../../../src/dialogs/generic/show-dialog-box";
|
||||
import { haStyle } from "../../../src/resources/styles";
|
||||
import { HomeAssistant } from "../../../src/types";
|
||||
import { bytesToString } from "../../../src/util/bytes-to-string";
|
||||
import "../components/supervisor-metric";
|
||||
import { hassioStyle } from "../resources/hassio-style";
|
||||
|
||||
@customElement("hassio-core-info")
|
||||
class HassioCoreInfo extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ attribute: false }) public supervisor!: Supervisor;
|
||||
|
||||
@internalProperty() private _metrics?: HassioStats;
|
||||
|
||||
protected render(): TemplateResult | void {
|
||||
const metrics = [
|
||||
{
|
||||
description: "Core CPU Usage",
|
||||
value: this._metrics?.cpu_percent,
|
||||
},
|
||||
{
|
||||
description: "Core RAM Usage",
|
||||
value: this._metrics?.memory_percent,
|
||||
tooltip: `${bytesToString(this._metrics?.memory_usage)}/${bytesToString(
|
||||
this._metrics?.memory_limit
|
||||
)}`,
|
||||
},
|
||||
];
|
||||
|
||||
return html`
|
||||
<ha-card header="Core">
|
||||
<div class="card-content">
|
||||
<div>
|
||||
<ha-settings-row>
|
||||
<span slot="heading">
|
||||
Version
|
||||
</span>
|
||||
<span slot="description">
|
||||
core-${this.supervisor.core.version}
|
||||
</span>
|
||||
</ha-settings-row>
|
||||
<ha-settings-row>
|
||||
<span slot="heading">
|
||||
Newest Version
|
||||
</span>
|
||||
<span slot="description">
|
||||
core-${this.supervisor.core.version_latest}
|
||||
</span>
|
||||
${this.supervisor.core.update_available
|
||||
? html`
|
||||
<ha-progress-button
|
||||
title="Update the core"
|
||||
@click=${this._coreUpdate}
|
||||
>
|
||||
Update
|
||||
</ha-progress-button>
|
||||
`
|
||||
: ""}
|
||||
</ha-settings-row>
|
||||
</div>
|
||||
<div>
|
||||
${metrics.map(
|
||||
(metric) =>
|
||||
html`
|
||||
<supervisor-metric
|
||||
.description=${metric.description}
|
||||
.value=${metric.value ?? 0}
|
||||
.tooltip=${metric.tooltip}
|
||||
></supervisor-metric>
|
||||
`
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-actions">
|
||||
<ha-progress-button
|
||||
slot="primaryAction"
|
||||
class="warning"
|
||||
@click=${this._coreRestart}
|
||||
title="Restart Home Assistant Core"
|
||||
>
|
||||
Restart Core
|
||||
</ha-progress-button>
|
||||
</div>
|
||||
</ha-card>
|
||||
`;
|
||||
}
|
||||
|
||||
protected firstUpdated(): void {
|
||||
this._loadData();
|
||||
}
|
||||
|
||||
private async _loadData(): Promise<void> {
|
||||
this._metrics = await fetchHassioStats(this.hass, "core");
|
||||
}
|
||||
|
||||
private async _coreRestart(ev: CustomEvent): Promise<void> {
|
||||
const button = ev.currentTarget as any;
|
||||
button.progress = true;
|
||||
|
||||
const confirmed = await showConfirmationDialog(this, {
|
||||
title: "Restart Home Assistant Core",
|
||||
text: "Are you sure you want to restart Home Assistant Core",
|
||||
confirmText: "restart",
|
||||
dismissText: "cancel",
|
||||
});
|
||||
|
||||
if (!confirmed) {
|
||||
button.progress = false;
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await restartCore(this.hass);
|
||||
} catch (err) {
|
||||
showAlertDialog(this, {
|
||||
title: "Failed to restart Home Assistant Core",
|
||||
text: extractApiErrorMessage(err),
|
||||
});
|
||||
} finally {
|
||||
button.progress = false;
|
||||
}
|
||||
}
|
||||
|
||||
private async _coreUpdate(ev: CustomEvent): Promise<void> {
|
||||
const button = ev.currentTarget as any;
|
||||
button.progress = true;
|
||||
|
||||
const confirmed = await showConfirmationDialog(this, {
|
||||
title: "Update Home Assistant Core",
|
||||
text: `Are you sure you want to update Home Assistant Core to version ${this.supervisor.core.version_latest}?`,
|
||||
confirmText: "update",
|
||||
dismissText: "cancel",
|
||||
});
|
||||
|
||||
if (!confirmed) {
|
||||
button.progress = false;
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await updateCore(this.hass);
|
||||
fireEvent(this, "supervisor-store-refresh", { store: "core" });
|
||||
} catch (err) {
|
||||
showAlertDialog(this, {
|
||||
title: "Failed to update Home Assistant Core",
|
||||
text: extractApiErrorMessage(err),
|
||||
});
|
||||
} finally {
|
||||
button.progress = false;
|
||||
}
|
||||
}
|
||||
|
||||
static get styles(): CSSResult[] {
|
||||
return [
|
||||
haStyle,
|
||||
hassioStyle,
|
||||
css`
|
||||
ha-card {
|
||||
height: 100%;
|
||||
justify-content: space-between;
|
||||
flex-direction: column;
|
||||
display: flex;
|
||||
}
|
||||
.card-actions {
|
||||
height: 48px;
|
||||
border-top: none;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
}
|
||||
.card-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: calc(100% - 124px);
|
||||
justify-content: space-between;
|
||||
}
|
||||
ha-settings-row {
|
||||
padding: 0;
|
||||
height: 54px;
|
||||
width: 100%;
|
||||
}
|
||||
ha-settings-row[three-line] {
|
||||
height: 74px;
|
||||
}
|
||||
ha-settings-row > span[slot="description"] {
|
||||
white-space: normal;
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
|
||||
.warning {
|
||||
--mdc-theme-primary: var(--error-color);
|
||||
}
|
||||
|
||||
ha-button-menu {
|
||||
color: var(--secondary-text-color);
|
||||
--mdc-menu-min-width: 200px;
|
||||
}
|
||||
@media (min-width: 563px) {
|
||||
paper-listbox {
|
||||
max-height: 150px;
|
||||
overflow: auto;
|
||||
}
|
||||
}
|
||||
paper-item {
|
||||
cursor: pointer;
|
||||
min-height: 35px;
|
||||
}
|
||||
mwc-list-item ha-svg-icon {
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"hassio-core-info": HassioCoreInfo;
|
||||
}
|
||||
}
|
@@ -13,6 +13,7 @@ import {
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { atLeastVersion } from "../../../src/common/config/version";
|
||||
import { fireEvent } from "../../../src/common/dom/fire_event";
|
||||
import "../../../src/components/buttons/ha-progress-button";
|
||||
import "../../../src/components/ha-button-menu";
|
||||
@@ -26,7 +27,6 @@ import { fetchHassioHardwareInfo } from "../../../src/data/hassio/hardware";
|
||||
import {
|
||||
changeHostOptions,
|
||||
configSyncOS,
|
||||
fetchHassioHostInfo,
|
||||
rebootHost,
|
||||
shutdownHost,
|
||||
updateOS,
|
||||
@@ -43,6 +43,11 @@ import {
|
||||
} from "../../../src/dialogs/generic/show-dialog-box";
|
||||
import { haStyle } from "../../../src/resources/styles";
|
||||
import { HomeAssistant } from "../../../src/types";
|
||||
import {
|
||||
getValueInPercentage,
|
||||
roundWithOneDecimal,
|
||||
} from "../../../src/util/calculate";
|
||||
import "../components/supervisor-metric";
|
||||
import { showHassioMarkdownDialog } from "../dialogs/markdown/show-dialog-hassio-markdown";
|
||||
import { showNetworkDialog } from "../dialogs/network/show-dialog-network";
|
||||
import { hassioStyle } from "../resources/hassio-style";
|
||||
@@ -57,80 +62,117 @@ class HassioHostInfo extends LitElement {
|
||||
const primaryIpAddress = this.supervisor.host.features.includes("network")
|
||||
? this._primaryIpAddress(this.supervisor.network!)
|
||||
: "";
|
||||
return html`
|
||||
<ha-card header="Host System">
|
||||
<div class="card-content">
|
||||
${this.supervisor.host.features.includes("hostname")
|
||||
? html`<ha-settings-row>
|
||||
<span slot="heading">
|
||||
Hostname
|
||||
</span>
|
||||
<span slot="description">
|
||||
${this.supervisor.host.hostname}
|
||||
</span>
|
||||
<mwc-button
|
||||
title="Change the hostname"
|
||||
label="Change"
|
||||
@click=${this._changeHostnameClicked}
|
||||
>
|
||||
</mwc-button>
|
||||
</ha-settings-row>`
|
||||
: ""}
|
||||
${this.supervisor.host.features.includes("network")
|
||||
? html` <ha-settings-row>
|
||||
<span slot="heading">
|
||||
IP Address
|
||||
</span>
|
||||
<span slot="description">
|
||||
${primaryIpAddress}
|
||||
</span>
|
||||
<mwc-button
|
||||
title="Change the network"
|
||||
label="Change"
|
||||
@click=${this._changeNetworkClicked}
|
||||
>
|
||||
</mwc-button>
|
||||
</ha-settings-row>`
|
||||
: ""}
|
||||
|
||||
<ha-settings-row>
|
||||
<span slot="heading">
|
||||
Operating System
|
||||
</span>
|
||||
<span slot="description">
|
||||
${this.supervisor.host.operating_system}
|
||||
</span>
|
||||
${this.supervisor.os.update_available
|
||||
? html`
|
||||
<ha-progress-button
|
||||
title="Update the host OS"
|
||||
@click=${this._osUpdate}
|
||||
const metrics = [
|
||||
{
|
||||
description: "Used Space",
|
||||
value: this._getUsedSpace(
|
||||
this.supervisor.host.disk_used,
|
||||
this.supervisor.host.disk_total
|
||||
),
|
||||
tooltip: `${this.supervisor.host.disk_used} GB/${this.supervisor.host.disk_total} GB`,
|
||||
},
|
||||
];
|
||||
return html`
|
||||
<ha-card header="Host">
|
||||
<div class="card-content">
|
||||
<div>
|
||||
${this.supervisor.host.features.includes("hostname")
|
||||
? html`<ha-settings-row>
|
||||
<span slot="heading">
|
||||
Hostname
|
||||
</span>
|
||||
<span slot="description">
|
||||
${this.supervisor.host.hostname}
|
||||
</span>
|
||||
<mwc-button
|
||||
title="Change the hostname"
|
||||
label="Change"
|
||||
@click=${this._changeHostnameClicked}
|
||||
>
|
||||
Update
|
||||
</ha-progress-button>
|
||||
`
|
||||
</mwc-button>
|
||||
</ha-settings-row>`
|
||||
: ""}
|
||||
</ha-settings-row>
|
||||
${!this.supervisor.host.features.includes("hassos")
|
||||
? html`<ha-settings-row>
|
||||
<span slot="heading">
|
||||
Docker version
|
||||
</span>
|
||||
<span slot="description">
|
||||
${this.supervisor.info.docker}
|
||||
</span>
|
||||
</ha-settings-row>`
|
||||
: ""}
|
||||
${this.supervisor.host.deployment
|
||||
? html`<ha-settings-row>
|
||||
<span slot="heading">
|
||||
Deployment
|
||||
</span>
|
||||
<span slot="description">
|
||||
${this.supervisor.host.deployment}
|
||||
</span>
|
||||
</ha-settings-row>`
|
||||
: ""}
|
||||
${this.supervisor.host.features.includes("network")
|
||||
? html` <ha-settings-row>
|
||||
<span slot="heading">
|
||||
IP Address
|
||||
</span>
|
||||
<span slot="description">
|
||||
${primaryIpAddress}
|
||||
</span>
|
||||
<mwc-button
|
||||
title="Change the network"
|
||||
label="Change"
|
||||
@click=${this._changeNetworkClicked}
|
||||
>
|
||||
</mwc-button>
|
||||
</ha-settings-row>`
|
||||
: ""}
|
||||
|
||||
<ha-settings-row>
|
||||
<span slot="heading">
|
||||
Operating System
|
||||
</span>
|
||||
<span slot="description">
|
||||
${this.supervisor.host.operating_system}
|
||||
</span>
|
||||
${this.supervisor.os.update_available
|
||||
? html`
|
||||
<ha-progress-button
|
||||
title="Update the host OS"
|
||||
@click=${this._osUpdate}
|
||||
>
|
||||
Update
|
||||
</ha-progress-button>
|
||||
`
|
||||
: ""}
|
||||
</ha-settings-row>
|
||||
${!this.supervisor.host.features.includes("hassos")
|
||||
? html`<ha-settings-row>
|
||||
<span slot="heading">
|
||||
Docker version
|
||||
</span>
|
||||
<span slot="description">
|
||||
${this.supervisor.info.docker}
|
||||
</span>
|
||||
</ha-settings-row>`
|
||||
: ""}
|
||||
${this.supervisor.host.deployment
|
||||
? html`<ha-settings-row>
|
||||
<span slot="heading">
|
||||
Deployment
|
||||
</span>
|
||||
<span slot="description">
|
||||
${this.supervisor.host.deployment}
|
||||
</span>
|
||||
</ha-settings-row>`
|
||||
: ""}
|
||||
</div>
|
||||
<div>
|
||||
${this.supervisor.host.disk_life_time !== "" &&
|
||||
this.supervisor.host.disk_life_time >= 10
|
||||
? html` <ha-settings-row>
|
||||
<span slot="heading">
|
||||
eMMC Lifetime Used
|
||||
</span>
|
||||
<span slot="description">
|
||||
${this.supervisor.host.disk_life_time - 10}% -
|
||||
${this.supervisor.host.disk_life_time}%
|
||||
</span>
|
||||
</ha-settings-row>`
|
||||
: ""}
|
||||
${metrics.map(
|
||||
(metric) =>
|
||||
html`
|
||||
<supervisor-metric
|
||||
.description=${metric.description}
|
||||
.value=${metric.value ?? 0}
|
||||
.tooltip=${metric.tooltip}
|
||||
></supervisor-metric>
|
||||
`
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-actions">
|
||||
${this.supervisor.host.features.includes("reboot")
|
||||
@@ -140,7 +182,7 @@ class HassioHostInfo extends LitElement {
|
||||
class="warning"
|
||||
@click=${this._hostReboot}
|
||||
>
|
||||
Reboot
|
||||
Reboot Host
|
||||
</ha-progress-button>
|
||||
`
|
||||
: ""}
|
||||
@@ -151,7 +193,7 @@ class HassioHostInfo extends LitElement {
|
||||
class="warning"
|
||||
@click=${this._hostShutdown}
|
||||
>
|
||||
Shutdown
|
||||
Shutdown Host
|
||||
</ha-progress-button>
|
||||
`
|
||||
: ""}
|
||||
@@ -183,6 +225,10 @@ class HassioHostInfo extends LitElement {
|
||||
this._loadData();
|
||||
}
|
||||
|
||||
private _getUsedSpace = memoizeOne((used: number, total: number) =>
|
||||
roundWithOneDecimal(getValueInPercentage(used, 0, total))
|
||||
);
|
||||
|
||||
private _primaryIpAddress = memoizeOne((network_info: NetworkInfo) => {
|
||||
if (!network_info || !network_info.interfaces) {
|
||||
return "";
|
||||
@@ -294,6 +340,7 @@ class HassioHostInfo extends LitElement {
|
||||
|
||||
try {
|
||||
await updateOS(this.hass);
|
||||
fireEvent(this, "supervisor-store-refresh", { store: "os" });
|
||||
} catch (err) {
|
||||
showAlertDialog(this, {
|
||||
title: "Failed to update",
|
||||
@@ -322,8 +369,7 @@ class HassioHostInfo extends LitElement {
|
||||
if (hostname && hostname !== curHostname) {
|
||||
try {
|
||||
await changeHostOptions(this.hass, { hostname });
|
||||
const host = await fetchHassioHostInfo(this.hass);
|
||||
fireEvent(this, "supervisor-update", { host });
|
||||
fireEvent(this, "supervisor-store-refresh", { store: "host" });
|
||||
} catch (err) {
|
||||
showAlertDialog(this, {
|
||||
title: "Setting hostname failed",
|
||||
@@ -336,8 +382,7 @@ class HassioHostInfo extends LitElement {
|
||||
private async _importFromUSB(): Promise<void> {
|
||||
try {
|
||||
await configSyncOS(this.hass);
|
||||
const host = await fetchHassioHostInfo(this.hass);
|
||||
fireEvent(this, "supervisor-update", { host });
|
||||
fireEvent(this, "supervisor-store-refresh", { store: "host" });
|
||||
} catch (err) {
|
||||
showAlertDialog(this, {
|
||||
title: "Failed to import from USB",
|
||||
@@ -347,8 +392,12 @@ class HassioHostInfo extends LitElement {
|
||||
}
|
||||
|
||||
private async _loadData(): Promise<void> {
|
||||
const network = await fetchNetworkInfo(this.hass);
|
||||
fireEvent(this, "supervisor-update", { network });
|
||||
if (atLeastVersion(this.hass.config.version, 2021, 2, 4)) {
|
||||
fireEvent(this, "supervisor-store-refresh", { store: "network" });
|
||||
} else {
|
||||
const network = await fetchNetworkInfo(this.hass);
|
||||
fireEvent(this, "supervisor-update", { network });
|
||||
}
|
||||
}
|
||||
|
||||
static get styles(): CSSResult[] {
|
||||
@@ -369,6 +418,12 @@ class HassioHostInfo extends LitElement {
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
.card-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: calc(100% - 124px);
|
||||
justify-content: space-between;
|
||||
}
|
||||
ha-settings-row {
|
||||
padding: 0;
|
||||
height: 54px;
|
||||
|
@@ -3,6 +3,7 @@ import {
|
||||
CSSResult,
|
||||
customElement,
|
||||
html,
|
||||
internalProperty,
|
||||
LitElement,
|
||||
property,
|
||||
TemplateResult,
|
||||
@@ -12,9 +13,12 @@ import "../../../src/components/buttons/ha-progress-button";
|
||||
import "../../../src/components/ha-card";
|
||||
import "../../../src/components/ha-settings-row";
|
||||
import "../../../src/components/ha-switch";
|
||||
import { extractApiErrorMessage } from "../../../src/data/hassio/common";
|
||||
import {
|
||||
fetchHassioSupervisorInfo,
|
||||
extractApiErrorMessage,
|
||||
fetchHassioStats,
|
||||
HassioStats,
|
||||
} from "../../../src/data/hassio/common";
|
||||
import {
|
||||
reloadSupervisor,
|
||||
restartSupervisor,
|
||||
setSupervisorOption,
|
||||
@@ -28,7 +32,9 @@ import {
|
||||
} from "../../../src/dialogs/generic/show-dialog-box";
|
||||
import { haStyle } from "../../../src/resources/styles";
|
||||
import { HomeAssistant } from "../../../src/types";
|
||||
import { bytesToString } from "../../../src/util/bytes-to-string";
|
||||
import { documentationUrl } from "../../../src/util/documentation-url";
|
||||
import "../components/supervisor-metric";
|
||||
import { hassioStyle } from "../resources/hassio-style";
|
||||
|
||||
const UNSUPPORTED_REASON = {
|
||||
@@ -87,127 +93,164 @@ class HassioSupervisorInfo extends LitElement {
|
||||
|
||||
@property({ attribute: false }) public supervisor!: Supervisor;
|
||||
|
||||
@internalProperty() private _metrics?: HassioStats;
|
||||
|
||||
protected render(): TemplateResult | void {
|
||||
const metrics = [
|
||||
{
|
||||
description: "Supervisor CPU Usage",
|
||||
value: this._metrics?.cpu_percent,
|
||||
},
|
||||
{
|
||||
description: "Supervisor RAM Usage",
|
||||
value: this._metrics?.memory_percent,
|
||||
tooltip: `${bytesToString(this._metrics?.memory_usage)}/${bytesToString(
|
||||
this._metrics?.memory_limit
|
||||
)}`,
|
||||
},
|
||||
];
|
||||
return html`
|
||||
<ha-card header="Supervisor">
|
||||
<div class="card-content">
|
||||
<ha-settings-row>
|
||||
<span slot="heading">
|
||||
Version
|
||||
</span>
|
||||
<span slot="description">
|
||||
${this.supervisor.supervisor.version}
|
||||
</span>
|
||||
</ha-settings-row>
|
||||
<ha-settings-row>
|
||||
<span slot="heading">
|
||||
Newest Version
|
||||
</span>
|
||||
<span slot="description">
|
||||
${this.supervisor.supervisor.version_latest}
|
||||
</span>
|
||||
${this.supervisor.supervisor.update_available
|
||||
? html`
|
||||
<ha-progress-button
|
||||
title="Update the supervisor"
|
||||
@click=${this._supervisorUpdate}
|
||||
>
|
||||
Update
|
||||
</ha-progress-button>
|
||||
`
|
||||
: ""}
|
||||
</ha-settings-row>
|
||||
<ha-settings-row>
|
||||
<span slot="heading">
|
||||
Channel
|
||||
</span>
|
||||
<span slot="description">
|
||||
${this.supervisor.supervisor.channel}
|
||||
</span>
|
||||
${this.supervisor.supervisor.channel === "beta"
|
||||
? html`
|
||||
<ha-progress-button
|
||||
@click=${this._toggleBeta}
|
||||
title="Get stable updates for Home Assistant, supervisor and host"
|
||||
>
|
||||
Leave beta channel
|
||||
</ha-progress-button>
|
||||
`
|
||||
: this.supervisor.supervisor.channel === "stable"
|
||||
? html`
|
||||
<ha-progress-button
|
||||
@click=${this._toggleBeta}
|
||||
title="Get beta updates for Home Assistant (RCs), supervisor and host"
|
||||
>
|
||||
Join beta channel
|
||||
</ha-progress-button>
|
||||
`
|
||||
: ""}
|
||||
</ha-settings-row>
|
||||
<div>
|
||||
<ha-settings-row>
|
||||
<span slot="heading">
|
||||
Version
|
||||
</span>
|
||||
<span slot="description">
|
||||
supervisor-${this.supervisor.supervisor.version}
|
||||
</span>
|
||||
</ha-settings-row>
|
||||
<ha-settings-row>
|
||||
<span slot="heading">
|
||||
Newest Version
|
||||
</span>
|
||||
<span slot="description">
|
||||
supervisor-${this.supervisor.supervisor.version_latest}
|
||||
</span>
|
||||
${this.supervisor.supervisor.update_available
|
||||
? html`
|
||||
<ha-progress-button
|
||||
title="Update the supervisor"
|
||||
@click=${this._supervisorUpdate}
|
||||
>
|
||||
Update
|
||||
</ha-progress-button>
|
||||
`
|
||||
: ""}
|
||||
</ha-settings-row>
|
||||
<ha-settings-row>
|
||||
<span slot="heading">
|
||||
Channel
|
||||
</span>
|
||||
<span slot="description">
|
||||
${this.supervisor.supervisor.channel}
|
||||
</span>
|
||||
${this.supervisor.supervisor.channel === "beta"
|
||||
? html`
|
||||
<ha-progress-button
|
||||
@click=${this._toggleBeta}
|
||||
title="Get stable updates for Home Assistant, supervisor and host"
|
||||
>
|
||||
Leave beta channel
|
||||
</ha-progress-button>
|
||||
`
|
||||
: this.supervisor.supervisor.channel === "stable"
|
||||
? html`
|
||||
<ha-progress-button
|
||||
@click=${this._toggleBeta}
|
||||
title="Get beta updates for Home Assistant (RCs), supervisor and host"
|
||||
>
|
||||
Join beta channel
|
||||
</ha-progress-button>
|
||||
`
|
||||
: ""}
|
||||
</ha-settings-row>
|
||||
|
||||
${this.supervisor.supervisor.supported
|
||||
? html` <ha-settings-row three-line>
|
||||
<span slot="heading">
|
||||
Share Diagnostics
|
||||
</span>
|
||||
<div slot="description" class="diagnostics-description">
|
||||
Share crash reports and diagnostic information.
|
||||
${this.supervisor.supervisor.supported
|
||||
? html` <ha-settings-row three-line>
|
||||
<span slot="heading">
|
||||
Share Diagnostics
|
||||
</span>
|
||||
<div slot="description" class="diagnostics-description">
|
||||
Share crash reports and diagnostic information.
|
||||
<button
|
||||
class="link"
|
||||
title="Show more information about this"
|
||||
@click=${this._diagnosticsInformationDialog}
|
||||
>
|
||||
Learn more
|
||||
</button>
|
||||
</div>
|
||||
<ha-switch
|
||||
haptic
|
||||
.checked=${this.supervisor.supervisor.diagnostics}
|
||||
@change=${this._toggleDiagnostics}
|
||||
></ha-switch>
|
||||
</ha-settings-row>`
|
||||
: html`<div class="error">
|
||||
You are running an unsupported installation.
|
||||
<button
|
||||
class="link"
|
||||
title="Show more information about this"
|
||||
@click=${this._diagnosticsInformationDialog}
|
||||
title="Learn more about how you can make your system compliant"
|
||||
@click=${this._unsupportedDialog}
|
||||
>
|
||||
Learn more
|
||||
</button>
|
||||
</div>
|
||||
<ha-switch
|
||||
haptic
|
||||
.checked=${this.supervisor.supervisor.diagnostics}
|
||||
@change=${this._toggleDiagnostics}
|
||||
></ha-switch>
|
||||
</ha-settings-row>`
|
||||
: html`<div class="error">
|
||||
You are running an unsupported installation.
|
||||
<button
|
||||
class="link"
|
||||
title="Learn more about how you can make your system compliant"
|
||||
@click=${this._unsupportedDialog}
|
||||
>
|
||||
Learn more
|
||||
</button>
|
||||
</div>`}
|
||||
${!this.supervisor.supervisor.healthy
|
||||
? html`<div class="error">
|
||||
Your installation is running in an unhealthy state.
|
||||
<button
|
||||
class="link"
|
||||
title="Learn more about why your system is marked as unhealthy"
|
||||
@click=${this._unhealthyDialog}
|
||||
>
|
||||
Learn more
|
||||
</button>
|
||||
</div>`
|
||||
: ""}
|
||||
</div>`}
|
||||
${!this.supervisor.supervisor.healthy
|
||||
? html`<div class="error">
|
||||
Your installation is running in an unhealthy state.
|
||||
<button
|
||||
class="link"
|
||||
title="Learn more about why your system is marked as unhealthy"
|
||||
@click=${this._unhealthyDialog}
|
||||
>
|
||||
Learn more
|
||||
</button>
|
||||
</div>`
|
||||
: ""}
|
||||
</div>
|
||||
<div class="metrics-block">
|
||||
${metrics.map(
|
||||
(metric) =>
|
||||
html`
|
||||
<supervisor-metric
|
||||
.description=${metric.description}
|
||||
.value=${metric.value ?? 0}
|
||||
.tooltip=${metric.tooltip}
|
||||
></supervisor-metric>
|
||||
`
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-actions">
|
||||
<ha-progress-button
|
||||
@click=${this._supervisorReload}
|
||||
title="Reload parts of the Supervisor"
|
||||
>
|
||||
Reload
|
||||
Reload Supervisor
|
||||
</ha-progress-button>
|
||||
<ha-progress-button
|
||||
class="warning"
|
||||
@click=${this._supervisorRestart}
|
||||
title="Restart the Supervisor"
|
||||
>
|
||||
Restart
|
||||
Restart Supervisor
|
||||
</ha-progress-button>
|
||||
</div>
|
||||
</ha-card>
|
||||
`;
|
||||
}
|
||||
|
||||
protected firstUpdated(): void {
|
||||
this._loadData();
|
||||
}
|
||||
|
||||
private async _loadData(): Promise<void> {
|
||||
this._metrics = await fetchHassioStats(this.hass, "supervisor");
|
||||
}
|
||||
|
||||
private async _toggleBeta(ev: CustomEvent): Promise<void> {
|
||||
const button = ev.currentTarget as any;
|
||||
button.progress = true;
|
||||
@@ -274,14 +317,25 @@ class HassioSupervisorInfo extends LitElement {
|
||||
|
||||
private async _reloadSupervisor(): Promise<void> {
|
||||
await reloadSupervisor(this.hass);
|
||||
const supervisor = await fetchHassioSupervisorInfo(this.hass);
|
||||
fireEvent(this, "supervisor-update", { supervisor });
|
||||
fireEvent(this, "supervisor-store-refresh", { store: "supervisor" });
|
||||
}
|
||||
|
||||
private async _supervisorRestart(ev: CustomEvent): Promise<void> {
|
||||
const button = ev.currentTarget as any;
|
||||
button.progress = true;
|
||||
|
||||
const confirmed = await showConfirmationDialog(this, {
|
||||
title: "Restart the Supervisor",
|
||||
text: "Are you sure you want to restart the Supervisor",
|
||||
confirmText: "restart",
|
||||
dismissText: "cancel",
|
||||
});
|
||||
|
||||
if (!confirmed) {
|
||||
button.progress = false;
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await restartSupervisor(this.hass);
|
||||
} catch (err) {
|
||||
@@ -312,6 +366,7 @@ class HassioSupervisorInfo extends LitElement {
|
||||
|
||||
try {
|
||||
await updateSupervisor(this.hass);
|
||||
fireEvent(this, "supervisor-store-refresh", { store: "supervisor" });
|
||||
} catch (err) {
|
||||
showAlertDialog(this, {
|
||||
title: "Failed to update the supervisor",
|
||||
@@ -426,6 +481,15 @@ class HassioSupervisorInfo extends LitElement {
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
.card-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: calc(100% - 124px);
|
||||
justify-content: space-between;
|
||||
}
|
||||
.metrics-block {
|
||||
margin-top: 16px;
|
||||
}
|
||||
button.link {
|
||||
color: var(--primary-color);
|
||||
}
|
||||
|
@@ -1,185 +0,0 @@
|
||||
import "@material/mwc-button";
|
||||
import "@material/mwc-list/mwc-list-item";
|
||||
import {
|
||||
css,
|
||||
CSSResult,
|
||||
customElement,
|
||||
html,
|
||||
internalProperty,
|
||||
LitElement,
|
||||
property,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import { classMap } from "lit-html/directives/class-map";
|
||||
import memoizeOne from "memoize-one";
|
||||
import "../../../src/components/buttons/ha-progress-button";
|
||||
import "../../../src/components/ha-bar";
|
||||
import "../../../src/components/ha-button-menu";
|
||||
import "../../../src/components/ha-card";
|
||||
import "../../../src/components/ha-settings-row";
|
||||
import { fetchHassioStats, HassioStats } from "../../../src/data/hassio/common";
|
||||
import { HassioHostInfo } from "../../../src/data/hassio/host";
|
||||
import { Supervisor } from "../../../src/data/supervisor/supervisor";
|
||||
import { haStyle } from "../../../src/resources/styles";
|
||||
import { HomeAssistant } from "../../../src/types";
|
||||
import { bytesToString } from "../../../src/util/bytes-to-string";
|
||||
import {
|
||||
getValueInPercentage,
|
||||
roundWithOneDecimal,
|
||||
} from "../../../src/util/calculate";
|
||||
import { hassioStyle } from "../resources/hassio-style";
|
||||
|
||||
@customElement("hassio-system-metrics")
|
||||
class HassioSystemMetrics extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ attribute: false }) public supervisor!: Supervisor;
|
||||
|
||||
@internalProperty() private _supervisorMetrics?: HassioStats;
|
||||
|
||||
@internalProperty() private _coreMetrics?: HassioStats;
|
||||
|
||||
protected render(): TemplateResult | void {
|
||||
const metrics = [
|
||||
{
|
||||
description: "Core CPU Usage",
|
||||
value: this._coreMetrics?.cpu_percent,
|
||||
},
|
||||
{
|
||||
description: "Core RAM Usage",
|
||||
value: this._coreMetrics?.memory_percent,
|
||||
tooltip: `${bytesToString(
|
||||
this._coreMetrics?.memory_usage
|
||||
)}/${bytesToString(this._coreMetrics?.memory_limit)}`,
|
||||
},
|
||||
{
|
||||
description: "Supervisor CPU Usage",
|
||||
value: this._supervisorMetrics?.cpu_percent,
|
||||
},
|
||||
{
|
||||
description: "Supervisor RAM Usage",
|
||||
value: this._supervisorMetrics?.memory_percent,
|
||||
tooltip: `${bytesToString(
|
||||
this._supervisorMetrics?.memory_usage
|
||||
)}/${bytesToString(this._supervisorMetrics?.memory_limit)}`,
|
||||
},
|
||||
{
|
||||
description: "Used Space",
|
||||
value: this._getUsedSpace(this.supervisor.host),
|
||||
tooltip: `${this.supervisor.host.disk_used} GB/${this.supervisor.host.disk_total} GB`,
|
||||
},
|
||||
];
|
||||
|
||||
return html`
|
||||
<ha-card header="System Metrics">
|
||||
<div class="card-content">
|
||||
${metrics.map((metric) =>
|
||||
this._renderMetric(
|
||||
metric.description,
|
||||
metric.value ?? 0,
|
||||
metric.tooltip
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
</ha-card>
|
||||
`;
|
||||
}
|
||||
|
||||
protected firstUpdated(): void {
|
||||
this._loadData();
|
||||
}
|
||||
|
||||
private _renderMetric(
|
||||
description: string,
|
||||
value: number,
|
||||
tooltip?: string
|
||||
): TemplateResult {
|
||||
const roundedValue = roundWithOneDecimal(value);
|
||||
return html`<ha-settings-row>
|
||||
<span slot="heading">
|
||||
${description}
|
||||
</span>
|
||||
<div slot="description" title="${tooltip ?? ""}">
|
||||
<span class="value">
|
||||
${roundedValue}%
|
||||
</span>
|
||||
<ha-bar
|
||||
class="${classMap({
|
||||
"target-warning": roundedValue > 50,
|
||||
"target-critical": roundedValue > 85,
|
||||
})}"
|
||||
.value=${value}
|
||||
></ha-bar>
|
||||
</div>
|
||||
</ha-settings-row>`;
|
||||
}
|
||||
|
||||
private _getUsedSpace = memoizeOne((hostInfo: HassioHostInfo) =>
|
||||
roundWithOneDecimal(
|
||||
getValueInPercentage(hostInfo.disk_used, 0, hostInfo.disk_total)
|
||||
)
|
||||
);
|
||||
|
||||
private async _loadData(): Promise<void> {
|
||||
const [supervisor, core] = await Promise.all([
|
||||
fetchHassioStats(this.hass, "supervisor"),
|
||||
fetchHassioStats(this.hass, "core"),
|
||||
]);
|
||||
this._supervisorMetrics = supervisor;
|
||||
this._coreMetrics = core;
|
||||
}
|
||||
|
||||
static get styles(): CSSResult[] {
|
||||
return [
|
||||
haStyle,
|
||||
hassioStyle,
|
||||
css`
|
||||
ha-card {
|
||||
height: 100%;
|
||||
justify-content: space-between;
|
||||
flex-direction: column;
|
||||
display: flex;
|
||||
}
|
||||
ha-settings-row {
|
||||
padding: 0;
|
||||
height: 54px;
|
||||
width: 100%;
|
||||
}
|
||||
ha-settings-row > div[slot="description"] {
|
||||
white-space: normal;
|
||||
color: var(--secondary-text-color);
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
ha-bar {
|
||||
--ha-bar-primary-color: var(
|
||||
--hassio-bar-ok-color,
|
||||
var(--success-color)
|
||||
);
|
||||
}
|
||||
.target-warning {
|
||||
--ha-bar-primary-color: var(
|
||||
--hassio-bar-warning-color,
|
||||
var(--warning-color)
|
||||
);
|
||||
}
|
||||
.target-critical {
|
||||
--ha-bar-primary-color: var(
|
||||
--hassio-bar-critical-color,
|
||||
var(--error-color)
|
||||
);
|
||||
}
|
||||
.value {
|
||||
width: 42px;
|
||||
padding-right: 4px;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"hassio-system-metrics": HassioSystemMetrics;
|
||||
}
|
||||
}
|
@@ -13,10 +13,10 @@ import { haStyle } from "../../../src/resources/styles";
|
||||
import { HomeAssistant, Route } from "../../../src/types";
|
||||
import { supervisorTabs } from "../hassio-tabs";
|
||||
import { hassioStyle } from "../resources/hassio-style";
|
||||
import "./hassio-core-info";
|
||||
import "./hassio-host-info";
|
||||
import "./hassio-supervisor-info";
|
||||
import "./hassio-supervisor-log";
|
||||
import "./hassio-system-metrics";
|
||||
|
||||
@customElement("hassio-system")
|
||||
class HassioSystem extends LitElement {
|
||||
@@ -41,6 +41,10 @@ class HassioSystem extends LitElement {
|
||||
<span slot="header">System</span>
|
||||
<div class="content">
|
||||
<div class="card-group">
|
||||
<hassio-core-info
|
||||
.hass=${this.hass}
|
||||
.supervisor=${this.supervisor}
|
||||
></hassio-core-info>
|
||||
<hassio-supervisor-info
|
||||
.hass=${this.hass}
|
||||
.supervisor=${this.supervisor}
|
||||
@@ -49,10 +53,6 @@ class HassioSystem extends LitElement {
|
||||
.hass=${this.hass}
|
||||
.supervisor=${this.supervisor}
|
||||
></hassio-host-info>
|
||||
<hassio-system-metrics
|
||||
.hass=${this.hass}
|
||||
.supervisor=${this.supervisor}
|
||||
></hassio-system-metrics>
|
||||
</div>
|
||||
<hassio-supervisor-log .hass=${this.hass}></hassio-supervisor-log>
|
||||
</div>
|
||||
|
7
hassio/src/util/addon.ts
Normal file
7
hassio/src/util/addon.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import memoizeOne from "memoize-one";
|
||||
import { SupervisorArch } from "../../../src/data/supervisor/supervisor";
|
||||
|
||||
export const addonArchIsSupported = memoizeOne(
|
||||
(supported_archs: SupervisorArch[], addon_archs: SupervisorArch[]) =>
|
||||
addon_archs.some((arch) => supported_archs.includes(arch))
|
||||
);
|
29
package.json
29
package.json
@@ -22,6 +22,15 @@
|
||||
"author": "Paulus Schoutsen <Paulus@PaulusSchoutsen.nl> (http://paulusschoutsen.nl)",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@braintree/sanitize-url": "^5.0.0",
|
||||
"@codemirror/commands": "^0.17.2",
|
||||
"@codemirror/gutter": "^0.17.2",
|
||||
"@codemirror/highlight": "^0.17.2",
|
||||
"@codemirror/legacy-modes": "^0.17.1",
|
||||
"@codemirror/state": "^0.17.1",
|
||||
"@codemirror/stream-parser": "^0.17.1",
|
||||
"@codemirror/text": "^0.17.2",
|
||||
"@codemirror/view": "^0.17.7",
|
||||
"@formatjs/intl-getcanonicallocales": "^1.4.6",
|
||||
"@formatjs/intl-pluralrules": "^3.4.10",
|
||||
"@fullcalendar/common": "5.1.0",
|
||||
@@ -100,7 +109,7 @@
|
||||
"fuse.js": "^6.0.0",
|
||||
"google-timezones-json": "^1.0.2",
|
||||
"hls.js": "^0.13.2",
|
||||
"home-assistant-js-websocket": "^5.4.1",
|
||||
"home-assistant-js-websocket": "^5.9.0",
|
||||
"idb-keyval": "^3.2.0",
|
||||
"intl-messageformat": "^8.3.9",
|
||||
"js-yaml": "^3.13.1",
|
||||
@@ -109,7 +118,7 @@
|
||||
"lit-element": "^2.4.0",
|
||||
"lit-html": "^1.3.0",
|
||||
"lit-virtualizer": "^0.4.2",
|
||||
"marked": "^1.1.1",
|
||||
"marked": "2.0.0",
|
||||
"mdn-polyfills": "^5.16.0",
|
||||
"memoize-one": "^5.0.2",
|
||||
"node-vibrant": "3.2.1-alpha.1",
|
||||
@@ -120,7 +129,7 @@
|
||||
"resize-observer-polyfill": "^1.5.1",
|
||||
"roboto-fontface": "^0.10.0",
|
||||
"sortablejs": "^1.10.2",
|
||||
"superstruct": "^0.10.12",
|
||||
"superstruct": "^0.10.13",
|
||||
"tinykeys": "^1.1.1",
|
||||
"unfetch": "^4.1.0",
|
||||
"vis-data": "^7.1.1",
|
||||
@@ -160,7 +169,7 @@
|
||||
"@types/js-yaml": "^3.12.1",
|
||||
"@types/leaflet": "^1.4.3",
|
||||
"@types/leaflet-draw": "^1.0.1",
|
||||
"@types/marked": "^1.1.0",
|
||||
"@types/marked": "^1.2.2",
|
||||
"@types/memoize-one": "4.1.0",
|
||||
"@types/mocha": "^7.0.2",
|
||||
"@types/resize-observer-browser": "^0.1.3",
|
||||
@@ -176,7 +185,7 @@
|
||||
"eslint": "^6.8.0",
|
||||
"eslint-config-airbnb-typescript": "^7.2.1",
|
||||
"eslint-config-prettier": "^6.10.1",
|
||||
"eslint-import-resolver-webpack": "^0.12.2",
|
||||
"eslint-import-resolver-webpack": "^0.13.0",
|
||||
"eslint-plugin-disable": "^2.0.1",
|
||||
"eslint-plugin-import": "^2.20.2",
|
||||
"eslint-plugin-lit": "^1.2.0",
|
||||
@@ -212,16 +221,16 @@
|
||||
"sinon": "^7.3.1",
|
||||
"source-map-url": "^0.4.0",
|
||||
"systemjs": "^6.3.2",
|
||||
"terser-webpack-plugin": "^5.0.0",
|
||||
"terser-webpack-plugin": "^5.1.1",
|
||||
"ts-lit-plugin": "^1.2.1",
|
||||
"ts-mocha": "^7.0.0",
|
||||
"typescript": "^4.0.3",
|
||||
"vinyl-buffer": "^1.0.1",
|
||||
"vinyl-source-stream": "^2.0.0",
|
||||
"webpack": "5.1.3",
|
||||
"webpack-cli": "4.1.0",
|
||||
"webpack-dev-server": "^3.11.0",
|
||||
"webpack-manifest-plugin": "3.0.0-rc.0",
|
||||
"webpack": "^5.24.1",
|
||||
"webpack-cli": "^4.5.0",
|
||||
"webpack-dev-server": "^3.11.2",
|
||||
"webpack-manifest-plugin": "^3.0.0",
|
||||
"workbox-build": "^5.1.3"
|
||||
},
|
||||
"_comment": "Polymer fixed to 3.1 because 3.2 throws on logbook page",
|
||||
|
2
setup.py
2
setup.py
@@ -2,7 +2,7 @@ from setuptools import setup, find_packages
|
||||
|
||||
setup(
|
||||
name="home-assistant-frontend",
|
||||
version="20201229.0",
|
||||
version="20210225.0",
|
||||
description="The Home Assistant frontend",
|
||||
url="https://github.com/home-assistant/home-assistant-polymer",
|
||||
author="The Home Assistant Authors",
|
||||
|
@@ -3,9 +3,9 @@ import {
|
||||
css,
|
||||
CSSResult,
|
||||
html,
|
||||
internalProperty,
|
||||
LitElement,
|
||||
property,
|
||||
internalProperty,
|
||||
PropertyValues,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
|
@@ -2,21 +2,25 @@ import {
|
||||
css,
|
||||
CSSResult,
|
||||
html,
|
||||
internalProperty,
|
||||
LitElement,
|
||||
property,
|
||||
internalProperty,
|
||||
PropertyValues,
|
||||
} from "lit-element";
|
||||
import punycode from "punycode";
|
||||
import { extractSearchParamsObject } from "../common/url/search-params";
|
||||
import {
|
||||
AuthProvider,
|
||||
fetchAuthProviders,
|
||||
AuthUrlSearchParams,
|
||||
fetchAuthProviders,
|
||||
} from "../data/auth";
|
||||
import {
|
||||
DiscoveryInformation,
|
||||
fetchDiscoveryInformation,
|
||||
} from "../data/discovery";
|
||||
import { litLocalizeLiteMixin } from "../mixins/lit-localize-lite-mixin";
|
||||
import { registerServiceWorker } from "../util/register-service-worker";
|
||||
import "./ha-auth-flow";
|
||||
import { extractSearchParamsObject } from "../common/url/search-params";
|
||||
import punycode from "punycode";
|
||||
|
||||
import("./ha-pick-auth-provider");
|
||||
|
||||
@@ -31,6 +35,8 @@ class HaAuthorize extends litLocalizeLiteMixin(LitElement) {
|
||||
|
||||
@internalProperty() private _authProviders?: AuthProvider[];
|
||||
|
||||
@internalProperty() private _discovery?: DiscoveryInformation;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.translationFragment = "page-authorize";
|
||||
@@ -58,14 +64,17 @@ class HaAuthorize extends litLocalizeLiteMixin(LitElement) {
|
||||
// the name with a bold tag.
|
||||
const loggingInWith = document.createElement("div");
|
||||
loggingInWith.innerText = this.localize(
|
||||
"ui.panel.page-authorize.logging_in_with",
|
||||
this._discovery?.location_name
|
||||
? "ui.panel.page-authorize.logging_in_to_with"
|
||||
: "ui.panel.page-authorize.logging_in_with",
|
||||
"locationName",
|
||||
"LOCATION",
|
||||
"authProviderName",
|
||||
"NAME"
|
||||
);
|
||||
loggingInWith.innerHTML = loggingInWith.innerHTML.replace(
|
||||
"**NAME**",
|
||||
`<b>${this._authProvider!.name}</b>`
|
||||
);
|
||||
loggingInWith.innerHTML = loggingInWith.innerHTML
|
||||
.replace("**LOCATION**", `<b>${this._discovery?.location_name}</b>`)
|
||||
.replace("**NAME**", `<b>${this._authProvider!.name}</b>`);
|
||||
|
||||
const inactiveProviders = this._authProviders.filter(
|
||||
(prv) => prv !== this._authProvider
|
||||
@@ -105,6 +114,7 @@ class HaAuthorize extends litLocalizeLiteMixin(LitElement) {
|
||||
protected firstUpdated(changedProps: PropertyValues) {
|
||||
super.firstUpdated(changedProps);
|
||||
this._fetchAuthProviders();
|
||||
this._fetchDiscoveryInfo();
|
||||
|
||||
if (!this.redirectUri) {
|
||||
return;
|
||||
@@ -126,6 +136,10 @@ class HaAuthorize extends litLocalizeLiteMixin(LitElement) {
|
||||
}
|
||||
}
|
||||
|
||||
private async _fetchDiscoveryInfo() {
|
||||
this._discovery = await fetchDiscoveryInformation();
|
||||
}
|
||||
|
||||
private async _fetchAuthProviders() {
|
||||
// Fetch auth providers
|
||||
try {
|
||||
|
@@ -1,6 +1,6 @@
|
||||
import { isComponentLoaded } from "./is_component_loaded";
|
||||
import { PageNavigation } from "../../layouts/hass-tabs-subpage";
|
||||
import { HomeAssistant } from "../../types";
|
||||
import { isComponentLoaded } from "./is_component_loaded";
|
||||
|
||||
export const canShowPage = (hass: HomeAssistant, page: PageNavigation) => {
|
||||
return (
|
||||
|
@@ -4,4 +4,4 @@ import { HomeAssistant } from "../../types";
|
||||
export const isComponentLoaded = (
|
||||
hass: HomeAssistant,
|
||||
component: string
|
||||
): boolean => hass && hass.config.components.indexOf(component) !== -1;
|
||||
): boolean => hass && hass.config.components.includes(component);
|
||||
|
@@ -1,11 +1,19 @@
|
||||
export const atLeastVersion = (
|
||||
version: string,
|
||||
major: number,
|
||||
minor: number
|
||||
minor: number,
|
||||
patch?: number
|
||||
): boolean => {
|
||||
const [haMajor, haMinor] = version.split(".", 2);
|
||||
const [haMajor, haMinor, haPatch] = version.split(".", 3);
|
||||
|
||||
return (
|
||||
Number(haMajor) > major ||
|
||||
(Number(haMajor) === major && Number(haMinor) >= minor)
|
||||
(Number(haMajor) === major && (patch === undefined
|
||||
? Number(haMinor) >= minor
|
||||
: Number(haMinor) > minor)) ||
|
||||
(patch !== undefined &&
|
||||
Number(haMajor) === major &&
|
||||
Number(haMinor) === minor &&
|
||||
Number(haPatch) >= patch)
|
||||
);
|
||||
};
|
||||
|
@@ -34,6 +34,7 @@ export const FIXED_DOMAIN_ICONS = {
|
||||
light: "hass:lightbulb",
|
||||
mailbox: "hass:mailbox",
|
||||
notify: "hass:comment-alert",
|
||||
number: "hass:ray-vertex",
|
||||
persistent_notification: "hass:bell",
|
||||
person: "hass:account",
|
||||
plant: "hass:flower",
|
||||
@@ -77,6 +78,7 @@ export const DOMAINS_WITH_CARD = [
|
||||
"input_text",
|
||||
"lock",
|
||||
"media_player",
|
||||
"number",
|
||||
"scene",
|
||||
"script",
|
||||
"timer",
|
||||
@@ -114,6 +116,7 @@ export const DOMAINS_HIDE_MORE_INFO = [
|
||||
"input_number",
|
||||
"input_select",
|
||||
"input_text",
|
||||
"number",
|
||||
"scene",
|
||||
];
|
||||
|
||||
@@ -138,6 +141,9 @@ export const DOMAINS_TOGGLE = new Set([
|
||||
"humidifier",
|
||||
]);
|
||||
|
||||
/** Domains that have a dynamic entity image / picture. */
|
||||
export const DOMAINS_WITH_DYNAMIC_PICTURE = new Set(["camera", "media_player"]);
|
||||
|
||||
/** Temperature units. */
|
||||
export const UNIT_C = "°C";
|
||||
export const UNIT_F = "°F";
|
||||
|
7
src/common/datetime/check_valid_date.ts
Normal file
7
src/common/datetime/check_valid_date.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
export default function checkValidDate(date?: Date): boolean {
|
||||
if (!date) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return date instanceof Date && !isNaN(date.valueOf());
|
||||
}
|
@@ -9,3 +9,12 @@ export const formatDate = toLocaleDateStringSupportsOptions
|
||||
day: "numeric",
|
||||
})
|
||||
: (dateObj: Date) => format(dateObj, "longDate");
|
||||
|
||||
export const formatDateWeekday = toLocaleDateStringSupportsOptions
|
||||
? (dateObj: Date, locales: string) =>
|
||||
dateObj.toLocaleDateString(locales, {
|
||||
weekday: "long",
|
||||
month: "short",
|
||||
day: "numeric",
|
||||
})
|
||||
: (dateObj: Date) => format(dateObj, "dddd, MMM D");
|
||||
|
@@ -17,3 +17,12 @@ export const formatTimeWithSeconds = toLocaleTimeStringSupportsOptions
|
||||
second: "2-digit",
|
||||
})
|
||||
: (dateObj: Date) => format(dateObj, "mediumTime");
|
||||
|
||||
export const formatTimeWeekday = toLocaleTimeStringSupportsOptions
|
||||
? (dateObj: Date, locales: string) =>
|
||||
dateObj.toLocaleTimeString(locales, {
|
||||
weekday: "long",
|
||||
hour: "numeric",
|
||||
minute: "2-digit",
|
||||
})
|
||||
: (dateObj: Date) => format(dateObj, "dddd, HH:mm");
|
||||
|
@@ -8,12 +8,19 @@ export const batteryIcon = (
|
||||
const battery = Number(batteryState.state);
|
||||
const battery_charging =
|
||||
batteryChargingState && batteryChargingState.state === "on";
|
||||
let icon = "hass:battery";
|
||||
|
||||
if (isNaN(battery)) {
|
||||
return "hass:battery-unknown";
|
||||
if (batteryState.state === "off") {
|
||||
icon += "-full";
|
||||
} else if (batteryState.state === "on") {
|
||||
icon += "-alert";
|
||||
} else {
|
||||
icon += "-unknown";
|
||||
}
|
||||
return icon;
|
||||
}
|
||||
|
||||
let icon = "hass:battery";
|
||||
const batteryRound = Math.round(battery / 10) * 10;
|
||||
if (battery_charging && battery > 10) {
|
||||
icon += `-charging-${batteryRound}`;
|
||||
|
@@ -3,9 +3,9 @@ import { UNAVAILABLE, UNKNOWN } from "../../data/entity";
|
||||
import { formatDate } from "../datetime/format_date";
|
||||
import { formatDateTime } from "../datetime/format_date_time";
|
||||
import { formatTime } from "../datetime/format_time";
|
||||
import { formatNumber } from "../string/format_number";
|
||||
import { LocalizeFunc } from "../translations/localize";
|
||||
import { computeStateDomain } from "./compute_state_domain";
|
||||
import { formatNumber } from "../string/format_number";
|
||||
|
||||
export const computeStateDisplay = (
|
||||
localize: LocalizeFunc,
|
||||
@@ -63,7 +63,7 @@ export const computeStateDisplay = (
|
||||
|
||||
if (domain === "humidifier") {
|
||||
if (compareState === "on" && stateObj.attributes.humidity) {
|
||||
return `${stateObj.attributes.humidity}%`;
|
||||
return `${stateObj.attributes.humidity} %`;
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -1,9 +1,9 @@
|
||||
import { HassEntity } from "home-assistant-js-websocket";
|
||||
import { UNAVAILABLE } from "../../data/entity";
|
||||
import { HomeAssistant } from "../../types";
|
||||
import { DOMAINS_WITH_CARD } from "../const";
|
||||
import { canToggleState } from "./can_toggle_state";
|
||||
import { computeStateDomain } from "./compute_state_domain";
|
||||
import { UNAVAILABLE } from "../../data/entity";
|
||||
|
||||
export const stateCardType = (hass: HomeAssistant, stateObj: HassEntity) => {
|
||||
if (stateObj.state === UNAVAILABLE) {
|
||||
|
@@ -1,7 +1,7 @@
|
||||
import Vibrant from "node-vibrant/lib/browser";
|
||||
import MMCQ from "@vibrant/quantizer-mmcq";
|
||||
import { BasicPipeline } from "@vibrant/core/lib/pipeline";
|
||||
import { Swatch, Vec3 } from "@vibrant/color";
|
||||
// We import the minified bundle because the unminified bundle
|
||||
// has some quirks that break wds. See #7784 for unminified version.
|
||||
import Vibrant from "node-vibrant/dist/vibrant";
|
||||
import type { Swatch, Vec3 } from "@vibrant/color";
|
||||
import { getRGBContrastRatio } from "../color/rgb";
|
||||
|
||||
const CONTRAST_RATIO = 4.5;
|
||||
@@ -104,23 +104,15 @@ const customGenerator = (colors: Swatch[]) => {
|
||||
}
|
||||
|
||||
return {
|
||||
foreground: new Swatch(foregroundColor, 0),
|
||||
// We can't import Swatch constructor from the minified bundle, take it from background color.
|
||||
// @ts-expect-error
|
||||
foreground: new backgroundColor.constructor(foregroundColor, 0),
|
||||
background: backgroundColor,
|
||||
};
|
||||
};
|
||||
|
||||
Vibrant.use(
|
||||
new BasicPipeline().filter
|
||||
.register(
|
||||
"default",
|
||||
(r: number, g: number, b: number, a: number) =>
|
||||
a >= 125 && !(r > 250 && g > 250 && b > 250)
|
||||
)
|
||||
.quantizer.register("mmcq", MMCQ)
|
||||
// Our generator has different output
|
||||
// @ts-expect-error
|
||||
.generator.register("default", customGenerator)
|
||||
);
|
||||
// Set our custom generator as the default.
|
||||
Vibrant._pipeline.generator.register("default", customGenerator);
|
||||
|
||||
export const extractColors = (url: string, downsampleColors = 16) =>
|
||||
new Vibrant(url, {
|
||||
|
@@ -1,3 +1,5 @@
|
||||
import "@material/mwc-icon-button/mwc-icon-button";
|
||||
import { mdiClose, mdiMagnify } from "@mdi/js";
|
||||
import "@polymer/paper-input/paper-input";
|
||||
import {
|
||||
css,
|
||||
@@ -10,8 +12,6 @@ import { html, TemplateResult } from "lit-html";
|
||||
import { classMap } from "lit-html/directives/class-map";
|
||||
import "../../components/ha-svg-icon";
|
||||
import { fireEvent } from "../dom/fire_event";
|
||||
import { mdiMagnify, mdiClose } from "@mdi/js";
|
||||
import "@material/mwc-icon-button/mwc-icon-button";
|
||||
|
||||
@customElement("search-input")
|
||||
class SearchInput extends LitElement {
|
||||
|
45
src/common/structs/handle-errors.ts
Normal file
45
src/common/structs/handle-errors.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
import { StructError } from "superstruct";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
|
||||
export const handleStructError = (
|
||||
hass: HomeAssistant,
|
||||
err: Error
|
||||
): { warnings: string[]; errors?: string[] } => {
|
||||
if (!(err instanceof StructError)) {
|
||||
return { warnings: [err.message], errors: undefined };
|
||||
}
|
||||
const errors: string[] = [];
|
||||
const warnings: string[] = [];
|
||||
for (const failure of err.failures()) {
|
||||
if (failure.value === undefined) {
|
||||
errors.push(
|
||||
hass.localize(
|
||||
"ui.errors.config.key_missing",
|
||||
"key",
|
||||
failure.path.join(".")
|
||||
)
|
||||
);
|
||||
} else if (failure.type === "never") {
|
||||
warnings.push(
|
||||
hass.localize(
|
||||
"ui.errors.config.key_not_expected",
|
||||
"key",
|
||||
failure.path.join(".")
|
||||
)
|
||||
);
|
||||
} else {
|
||||
warnings.push(
|
||||
hass.localize(
|
||||
"ui.errors.config.key_wrong_type",
|
||||
"key",
|
||||
failure.path.join("."),
|
||||
"type_correct",
|
||||
failure.type,
|
||||
"type_wrong",
|
||||
JSON.stringify(failure.value)
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
return { warnings, errors };
|
||||
};
|
@@ -1,4 +1,4 @@
|
||||
import { StructResult, StructContext, struct } from "superstruct";
|
||||
import { struct, StructContext, StructResult } from "superstruct";
|
||||
|
||||
const isEntityId = (value: unknown, context: StructContext): StructResult => {
|
||||
if (typeof value !== "string") {
|
@@ -1,4 +1,4 @@
|
||||
import { StructContext, StructResult, struct } from "superstruct";
|
||||
import { struct, StructContext, StructResult } from "superstruct";
|
||||
|
||||
const isIcon = (value: unknown, context: StructContext): StructResult => {
|
||||
if (typeof value !== "string") {
|
@@ -15,7 +15,7 @@ export const iconColorCSS = css`
|
||||
ha-icon[data-domain="media_player"][data-state="on"],
|
||||
ha-icon[data-domain="media_player"][data-state="paused"],
|
||||
ha-icon[data-domain="media_player"][data-state="playing"],
|
||||
ha-icon[data-domain="script"][data-state="running"],
|
||||
ha-icon[data-domain="script"][data-state="on"],
|
||||
ha-icon[data-domain="sun"][data-state="above_horizon"],
|
||||
ha-icon[data-domain="switch"][data-state="on"],
|
||||
ha-icon[data-domain="timer"][data-state="active"],
|
||||
|
@@ -1,5 +1,5 @@
|
||||
import IntlMessageFormat from "intl-messageformat";
|
||||
import { shouldPolyfill } from "@formatjs/intl-pluralrules/should-polyfill";
|
||||
import IntlMessageFormat from "intl-messageformat";
|
||||
import { Resources } from "../../types";
|
||||
|
||||
export type LocalizeFunc = (key: string, ...args: any[]) => string;
|
||||
|
@@ -6,3 +6,16 @@ export const extractSearchParamsObject = (): Record<string, string> => {
|
||||
}
|
||||
return query;
|
||||
};
|
||||
|
||||
export const extractSearchParam = (param: string): string | null => {
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
return urlParams.get(param);
|
||||
};
|
||||
|
||||
export const createSearchParam = (params: Record<string, string>): string => {
|
||||
const urlParams = new URLSearchParams();
|
||||
Object.entries(params).forEach(([key, value]) => {
|
||||
urlParams.append(key, value);
|
||||
});
|
||||
return urlParams.toString();
|
||||
};
|
||||
|
@@ -7,10 +7,9 @@ import {
|
||||
html,
|
||||
LitElement,
|
||||
property,
|
||||
TemplateResult,
|
||||
query,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
|
||||
import "../ha-circular-progress";
|
||||
|
||||
@customElement("ha-progress-button")
|
||||
|
@@ -1,5 +1,4 @@
|
||||
import { wrap } from "comlink";
|
||||
|
||||
import type { api } from "./sort_filter_worker";
|
||||
|
||||
type FilterDataType = api["filterData"];
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user