mirror of
https://github.com/home-assistant/frontend.git
synced 2025-09-18 01:19:49 +00:00
Compare commits
357 Commits
dashboard_
...
20240103.0
Author | SHA1 | Date | |
---|---|---|---|
![]() |
0bc69fb9b3 | ||
![]() |
6e7366bf69 | ||
![]() |
8ee4aa9e63 | ||
![]() |
386c3ea1ca | ||
![]() |
c2f3e43ee5 | ||
![]() |
7354988ec9 | ||
![]() |
53dedc6c65 | ||
![]() |
de3b9a5bb2 | ||
![]() |
8d496e1511 | ||
![]() |
01bd88ce10 | ||
![]() |
18b5fd59a6 | ||
![]() |
750c1d5013 | ||
![]() |
f2226cdec2 | ||
![]() |
c125ec087a | ||
![]() |
f099f66065 | ||
![]() |
4fcf99faa7 | ||
![]() |
2add88ccc2 | ||
![]() |
aa94ec7949 | ||
![]() |
d0c1481f76 | ||
![]() |
6cc9a99b77 | ||
![]() |
d25f49b694 | ||
![]() |
b92d25e28a | ||
![]() |
bf5c5bc46f | ||
![]() |
e7cc842be4 | ||
![]() |
cb29d35949 | ||
![]() |
fe18f70e51 | ||
![]() |
ee57f26415 | ||
![]() |
721ec8e559 | ||
![]() |
c584f83071 | ||
![]() |
3c2fed5041 | ||
![]() |
7e6d974438 | ||
![]() |
c95a30c837 | ||
![]() |
cb568d005e | ||
![]() |
442cce1574 | ||
![]() |
c25baf25ad | ||
![]() |
5e279405c7 | ||
![]() |
7b4ecfd30a | ||
![]() |
e5707b423f | ||
![]() |
7983556f98 | ||
![]() |
1916dff57b | ||
![]() |
b11563d618 | ||
![]() |
b180a587bf | ||
![]() |
5b11e0ce29 | ||
![]() |
d6684c5806 | ||
![]() |
9d9e789f4b | ||
![]() |
1a0e3890f4 | ||
![]() |
caece9d6ad | ||
![]() |
953a3793c4 | ||
![]() |
8bfae3b4cf | ||
![]() |
6911685bd0 | ||
![]() |
71025eaf4d | ||
![]() |
3aa612b766 | ||
![]() |
a2ffd0ae83 | ||
![]() |
c399da586e | ||
![]() |
d7826e4e6c | ||
![]() |
f5d13c9079 | ||
![]() |
01a142790f | ||
![]() |
df54687de1 | ||
![]() |
bded31b311 | ||
![]() |
67e573aff7 | ||
![]() |
6295c4ac76 | ||
![]() |
469811847f | ||
![]() |
8a1aefefca | ||
![]() |
bea16028a1 | ||
![]() |
bedb7d1d9e | ||
![]() |
1a3a20f478 | ||
![]() |
68ecb7c219 | ||
![]() |
61b04a882b | ||
![]() |
f8e621c5b9 | ||
![]() |
2f2209682e | ||
![]() |
f4f361b51a | ||
![]() |
55e59f8cb0 | ||
![]() |
6d20ed0a22 | ||
![]() |
874f604295 | ||
![]() |
30d36a11c1 | ||
![]() |
09dcc29175 | ||
![]() |
8f07e6f141 | ||
![]() |
7b6b5724e1 | ||
![]() |
521c0b58c8 | ||
![]() |
53839ab7b1 | ||
![]() |
dcfe9617b3 | ||
![]() |
58eebf2dbd | ||
![]() |
af9b64c6f0 | ||
![]() |
2b18ac8d4e | ||
![]() |
2306234063 | ||
![]() |
883a9e422e | ||
![]() |
d77ce721e3 | ||
![]() |
325ad6f721 | ||
![]() |
d762a9365f | ||
![]() |
ce983f043e | ||
![]() |
45b7ebbe46 | ||
![]() |
3e7fa66790 | ||
![]() |
ad543dbffb | ||
![]() |
cdd2c7be9a | ||
![]() |
ddf6945190 | ||
![]() |
f2745747ba | ||
![]() |
ff9d179c13 | ||
![]() |
e813108c66 | ||
![]() |
62d8cdfcf9 | ||
![]() |
5b9d46e350 | ||
![]() |
e5db95b2d2 | ||
![]() |
a8f7c7c999 | ||
![]() |
8a423fb775 | ||
![]() |
90c3d69af6 | ||
![]() |
bcb2d73c5c | ||
![]() |
8a6cea12e1 | ||
![]() |
0056de146a | ||
![]() |
0bcdbef11e | ||
![]() |
1b208cb531 | ||
![]() |
6a975e260c | ||
![]() |
02d2bde269 | ||
![]() |
70cfa7f48a | ||
![]() |
b7d4b9c21b | ||
![]() |
673c947c11 | ||
![]() |
2682a55148 | ||
![]() |
616f6ddf5f | ||
![]() |
29769813bc | ||
![]() |
221c46344a | ||
![]() |
a33eb25c92 | ||
![]() |
1dc61320a6 | ||
![]() |
ad556a43f9 | ||
![]() |
12e6701ffa | ||
![]() |
7167b66719 | ||
![]() |
a52ba5fbd1 | ||
![]() |
22cf903656 | ||
![]() |
d77b657036 | ||
![]() |
4a3038c12c | ||
![]() |
3ac7cd5d4a | ||
![]() |
58eddd2b42 | ||
![]() |
d808da68bd | ||
![]() |
2ed4d1efa0 | ||
![]() |
6ce613acd2 | ||
![]() |
fcb9e13a84 | ||
![]() |
3ada2f3279 | ||
![]() |
8d2d45ae4e | ||
![]() |
c9e6963387 | ||
![]() |
6d36b0e28c | ||
![]() |
aa38e2d409 | ||
![]() |
61117bb34f | ||
![]() |
86a3c32844 | ||
![]() |
b855b3e103 | ||
![]() |
80edeebab9 | ||
![]() |
f366e287b1 | ||
![]() |
9ce8684aba | ||
![]() |
caa6ea531c | ||
![]() |
cca1183ee3 | ||
![]() |
0f9c97aea0 | ||
![]() |
8d08aa8c79 | ||
![]() |
eebcab435d | ||
![]() |
76d3c6e237 | ||
![]() |
a820ca1e90 | ||
![]() |
fce4e5e382 | ||
![]() |
39260d172f | ||
![]() |
e646528b86 | ||
![]() |
86726102fb | ||
![]() |
1330558819 | ||
![]() |
b4ab0fc10b | ||
![]() |
15becf9ef6 | ||
![]() |
ef3785ce9f | ||
![]() |
134e13005d | ||
![]() |
eb5e7ba3f3 | ||
![]() |
dccd9b2541 | ||
![]() |
de83ad7a7a | ||
![]() |
7e55b9e6b8 | ||
![]() |
1b74ca47bf | ||
![]() |
c3d4be9ceb | ||
![]() |
b8d0c7f7c7 | ||
![]() |
92bce4078f | ||
![]() |
ff8f0697c2 | ||
![]() |
dddba7af00 | ||
![]() |
14a49d6664 | ||
![]() |
22da402d56 | ||
![]() |
aa4acc6572 | ||
![]() |
4d432fba57 | ||
![]() |
97b71c785b | ||
![]() |
8a93284bb3 | ||
![]() |
bb2abe4efc | ||
![]() |
ccada33caf | ||
![]() |
c5f15ee6ba | ||
![]() |
6f240ec681 | ||
![]() |
fc6aef138d | ||
![]() |
ae2e8e7402 | ||
![]() |
6d0eb05954 | ||
![]() |
96fbbc55e1 | ||
![]() |
7e74502ba3 | ||
![]() |
b273b31b03 | ||
![]() |
cf00ac74c6 | ||
![]() |
cc702f0fb3 | ||
![]() |
cc6eb5789d | ||
![]() |
669b80a9e1 | ||
![]() |
4c4fddee94 | ||
![]() |
b28a4e6b50 | ||
![]() |
bdd6a55a84 | ||
![]() |
d1ebc06994 | ||
![]() |
2a150788b4 | ||
![]() |
b854d23431 | ||
![]() |
72caf72e80 | ||
![]() |
a6ff1899df | ||
![]() |
6a999597df | ||
![]() |
219fc9e53a | ||
![]() |
861959ed2d | ||
![]() |
f7f50294e7 | ||
![]() |
a226333c1e | ||
![]() |
75f18aa69b | ||
![]() |
60ff4fdc1f | ||
![]() |
8d14cb0ab7 | ||
![]() |
606c809880 | ||
![]() |
f1692038f8 | ||
![]() |
79f5ec0cd5 | ||
![]() |
416a2a080d | ||
![]() |
92dc8b1561 | ||
![]() |
07b807adfc | ||
![]() |
843430ef41 | ||
![]() |
b5b2392dd2 | ||
![]() |
ef735d65cf | ||
![]() |
bf24740c7b | ||
![]() |
4ec11c7cf6 | ||
![]() |
2803e6aa95 | ||
![]() |
8ff36d0ccf | ||
![]() |
e7e465df69 | ||
![]() |
0c042079ed | ||
![]() |
a5a9bcafa7 | ||
![]() |
867f625b0a | ||
![]() |
3876c67588 | ||
![]() |
93158bb3af | ||
![]() |
5c47d8652d | ||
![]() |
eda0d12867 | ||
![]() |
4a53de4466 | ||
![]() |
e730649a8d | ||
![]() |
3a94deef69 | ||
![]() |
c1c186d279 | ||
![]() |
7356db919a | ||
![]() |
44157a5df3 | ||
![]() |
2acd1ff5e8 | ||
![]() |
150a5571cf | ||
![]() |
1eb25f4829 | ||
![]() |
d8d4ecf79f | ||
![]() |
081bd98e98 | ||
![]() |
6134f415e9 | ||
![]() |
b6a7581eca | ||
![]() |
7727f34e8f | ||
![]() |
9b20e1cf56 | ||
![]() |
b901ecacca | ||
![]() |
7691f7eb05 | ||
![]() |
8c0c56b954 | ||
![]() |
d968a20862 | ||
![]() |
9f1cd80a31 | ||
![]() |
5a9ccc5ae7 | ||
![]() |
6d7b0c5626 | ||
![]() |
400b8034e1 | ||
![]() |
05a22e3271 | ||
![]() |
c1f76e0565 | ||
![]() |
4582c3ba0a | ||
![]() |
f4ee734ea3 | ||
![]() |
2087028c47 | ||
![]() |
37a56b250a | ||
![]() |
71a41be20a | ||
![]() |
ffb19b31a5 | ||
![]() |
db68c7faa9 | ||
![]() |
07ae958eb0 | ||
![]() |
b62b5f6575 | ||
![]() |
2e1fb9df66 | ||
![]() |
31c91cea9a | ||
![]() |
9dc844ca28 | ||
![]() |
02ebc028c8 | ||
![]() |
5d35605b82 | ||
![]() |
22e86af5b3 | ||
![]() |
8c39ed46a8 | ||
![]() |
5965c3fdaa | ||
![]() |
15a5d2bc38 | ||
![]() |
0432cc95fc | ||
![]() |
6dafaac021 | ||
![]() |
d8929c9252 | ||
![]() |
5fa3a720b3 | ||
![]() |
6d3b838b6a | ||
![]() |
58e0179321 | ||
![]() |
eaf7e29b8a | ||
![]() |
260f1df1b9 | ||
![]() |
a88a4ef82c | ||
![]() |
075cca5991 | ||
![]() |
1d2dc37f75 | ||
![]() |
3da7025d78 | ||
![]() |
15b1d8ee14 | ||
![]() |
239d8fa948 | ||
![]() |
1de2e6161d | ||
![]() |
3e7f008277 | ||
![]() |
31a93d360d | ||
![]() |
2afd2788e2 | ||
![]() |
322fa99147 | ||
![]() |
c43ad52ff1 | ||
![]() |
6aa9737481 | ||
![]() |
7d9d6b99d9 | ||
![]() |
9c69e6f87c | ||
![]() |
0c90021895 | ||
![]() |
9163b9c124 | ||
![]() |
61717e1529 | ||
![]() |
168d418c27 | ||
![]() |
c787c920fc | ||
![]() |
1526209f82 | ||
![]() |
cf355c419d | ||
![]() |
eef024587b | ||
![]() |
270d463d02 | ||
![]() |
0735705dd3 | ||
![]() |
0a1dbe6d9d | ||
![]() |
15395275ba | ||
![]() |
6e66ba202f | ||
![]() |
5ebe1e0369 | ||
![]() |
860ab7a3ba | ||
![]() |
d499640304 | ||
![]() |
4f3f516767 | ||
![]() |
0996dbf2a1 | ||
![]() |
bc59e52cea | ||
![]() |
b8d4e957e2 | ||
![]() |
8002ec75bc | ||
![]() |
a45eefa742 | ||
![]() |
a1236924aa | ||
![]() |
9320e8c1a3 | ||
![]() |
addcd3a983 | ||
![]() |
e1dc73e992 | ||
![]() |
ba8849ed4d | ||
![]() |
36bca04341 | ||
![]() |
fb95de1f73 | ||
![]() |
3addfc3548 | ||
![]() |
e2e80d1f49 | ||
![]() |
a96eba2637 | ||
![]() |
274810b954 | ||
![]() |
842df983c3 | ||
![]() |
0eeadcd31a | ||
![]() |
8d37c5612b | ||
![]() |
e94461f7fe | ||
![]() |
f5edee1e91 | ||
![]() |
98cdbb4559 | ||
![]() |
f3f63d95b5 | ||
![]() |
244340f211 | ||
![]() |
75626431b1 | ||
![]() |
244868348b | ||
![]() |
a909c6d905 | ||
![]() |
f2efff9d71 | ||
![]() |
08a7a10e1c | ||
![]() |
b35e5abd83 | ||
![]() |
291e9fc0a2 | ||
![]() |
18b5215ce6 | ||
![]() |
00a311bd63 | ||
![]() |
749c2ab8ac | ||
![]() |
d06fdeb265 | ||
![]() |
06d76be2c2 | ||
![]() |
6728e8d107 | ||
![]() |
533e2bed8a | ||
![]() |
310a08f3e6 | ||
![]() |
0566889a1e | ||
![]() |
ddca7584ef | ||
![]() |
e6cfe74cac | ||
![]() |
3b553a3a4b | ||
![]() |
3d674cf237 | ||
![]() |
4e6e924a40 | ||
![]() |
073ead5828 | ||
![]() |
fdaefadd18 |
3
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
3
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
@@ -9,7 +9,7 @@ body:
|
||||
|
||||
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 cards.**
|
||||
**Please do not report issues for custom cards.**
|
||||
|
||||
[fr]: https://github.com/home-assistant/frontend/discussions
|
||||
[releases]: https://github.com/home-assistant/home-assistant/releases
|
||||
@@ -24,6 +24,7 @@ body:
|
||||
required: true
|
||||
- label: I have tried a different browser to see if it is related to my browser.
|
||||
required: true
|
||||
- label: I have tried reproducing the issue in [safe mode](https://www.home-assistant.io/blog/2023/11/01/release-202311/#restarting-into-safe-mode) to rule out problems with unsupported custom resources.
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
|
50
.github/labeler.yml
vendored
50
.github/labeler.yml
vendored
@@ -1,31 +1,45 @@
|
||||
Build:
|
||||
- build-scripts/**
|
||||
- .browserslistrc
|
||||
- gulpfile.js
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- build-scripts/**
|
||||
- .browserslistrc
|
||||
- gulpfile.js
|
||||
|
||||
Cast:
|
||||
- cast/src/**
|
||||
- src/cast/**
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- cast/src/**
|
||||
- src/cast/**
|
||||
|
||||
Demo:
|
||||
- demo/src/**
|
||||
- src/fake_data/**
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- demo/src/**
|
||||
- src/fake_data/**
|
||||
|
||||
Design:
|
||||
- gallery/src/**
|
||||
- src/fake_data/**
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- gallery/src/**
|
||||
- src/fake_data/**
|
||||
|
||||
Dependencies:
|
||||
- package.json
|
||||
- renovate.json
|
||||
- yarn.lock
|
||||
- .yarn/**
|
||||
- .yarnrc.yml
|
||||
- .nvmrc
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- package.json
|
||||
- renovate.json
|
||||
- yarn.lock
|
||||
- .yarn/**
|
||||
- .yarnrc.yml
|
||||
- .nvmrc
|
||||
|
||||
GitHub Actions:
|
||||
- .github/workflows/**
|
||||
- .github/*.yml
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- .github/workflows/**
|
||||
- .github/*.yml
|
||||
|
||||
Supervisor:
|
||||
- hassio/src/**
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- hassio/src/**
|
||||
|
4
.github/workflows/cast_deployment.yaml
vendored
4
.github/workflows/cast_deployment.yaml
vendored
@@ -26,7 +26,7 @@ jobs:
|
||||
ref: dev
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v4.0.0
|
||||
uses: actions/setup-node@v4.0.1
|
||||
with:
|
||||
node-version-file: ".nvmrc"
|
||||
cache: yarn
|
||||
@@ -62,7 +62,7 @@ jobs:
|
||||
ref: master
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v4.0.0
|
||||
uses: actions/setup-node@v4.0.1
|
||||
with:
|
||||
node-version-file: ".nvmrc"
|
||||
cache: yarn
|
||||
|
10
.github/workflows/ci.yaml
vendored
10
.github/workflows/ci.yaml
vendored
@@ -26,7 +26,7 @@ jobs:
|
||||
- name: Check out files from GitHub
|
||||
uses: actions/checkout@v4.1.1
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v4.0.0
|
||||
uses: actions/setup-node@v4.0.1
|
||||
with:
|
||||
node-version-file: ".nvmrc"
|
||||
cache: yarn
|
||||
@@ -57,14 +57,14 @@ jobs:
|
||||
- name: Check out files from GitHub
|
||||
uses: actions/checkout@v4.1.1
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v4.0.0
|
||||
uses: actions/setup-node@v4.0.1
|
||||
with:
|
||||
node-version-file: ".nvmrc"
|
||||
cache: yarn
|
||||
- name: Install dependencies
|
||||
run: yarn install --immutable
|
||||
- name: Build resources
|
||||
run: ./node_modules/.bin/gulp build-translations build-locale-data
|
||||
run: ./node_modules/.bin/gulp gen-icons-json build-translations build-locale-data
|
||||
- name: Run Tests
|
||||
run: yarn run test
|
||||
build:
|
||||
@@ -75,7 +75,7 @@ jobs:
|
||||
- name: Check out files from GitHub
|
||||
uses: actions/checkout@v4.1.1
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v4.0.0
|
||||
uses: actions/setup-node@v4.0.1
|
||||
with:
|
||||
node-version-file: ".nvmrc"
|
||||
cache: yarn
|
||||
@@ -99,7 +99,7 @@ jobs:
|
||||
- name: Check out files from GitHub
|
||||
uses: actions/checkout@v4.1.1
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v4.0.0
|
||||
uses: actions/setup-node@v4.0.1
|
||||
with:
|
||||
node-version-file: ".nvmrc"
|
||||
cache: yarn
|
||||
|
6
.github/workflows/codeql-analysis.yml
vendored
6
.github/workflows/codeql-analysis.yml
vendored
@@ -36,14 +36,14 @@ jobs:
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v2
|
||||
uses: github/codeql-action/init@v3
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
|
||||
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
||||
# If this step fails, then you should remove it and run the build manually (see below)
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@v2
|
||||
uses: github/codeql-action/autobuild@v3
|
||||
|
||||
# ℹ️ Command-line programs to run using the OS shell.
|
||||
# 📚 https://git.io/JvXDl
|
||||
@@ -57,4 +57,4 @@ jobs:
|
||||
# make release
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v2
|
||||
uses: github/codeql-action/analyze@v3
|
||||
|
4
.github/workflows/demo_deployment.yaml
vendored
4
.github/workflows/demo_deployment.yaml
vendored
@@ -27,7 +27,7 @@ jobs:
|
||||
ref: dev
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v4.0.0
|
||||
uses: actions/setup-node@v4.0.1
|
||||
with:
|
||||
node-version-file: ".nvmrc"
|
||||
cache: yarn
|
||||
@@ -63,7 +63,7 @@ jobs:
|
||||
ref: master
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v4.0.0
|
||||
uses: actions/setup-node@v4.0.1
|
||||
with:
|
||||
node-version-file: ".nvmrc"
|
||||
cache: yarn
|
||||
|
2
.github/workflows/design_deployment.yaml
vendored
2
.github/workflows/design_deployment.yaml
vendored
@@ -19,7 +19,7 @@ jobs:
|
||||
uses: actions/checkout@v4.1.1
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v4.0.0
|
||||
uses: actions/setup-node@v4.0.1
|
||||
with:
|
||||
node-version-file: ".nvmrc"
|
||||
cache: yarn
|
||||
|
2
.github/workflows/design_preview.yaml
vendored
2
.github/workflows/design_preview.yaml
vendored
@@ -24,7 +24,7 @@ jobs:
|
||||
uses: actions/checkout@v4.1.1
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v4.0.0
|
||||
uses: actions/setup-node@v4.0.1
|
||||
with:
|
||||
node-version-file: ".nvmrc"
|
||||
cache: yarn
|
||||
|
2
.github/workflows/labeler.yaml
vendored
2
.github/workflows/labeler.yaml
vendored
@@ -10,6 +10,6 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Apply labels
|
||||
uses: actions/labeler@v4.3.0
|
||||
uses: actions/labeler@v5.0.0
|
||||
with:
|
||||
sync-labels: true
|
||||
|
3
.github/workflows/lock.yml
vendored
3
.github/workflows/lock.yml
vendored
@@ -9,9 +9,10 @@ jobs:
|
||||
lock:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: dessant/lock-threads@v4.0.1
|
||||
- uses: dessant/lock-threads@v5.0.1
|
||||
with:
|
||||
github-token: ${{ github.token }}
|
||||
process-only: "issues, prs"
|
||||
issue-lock-inactive-days: "30"
|
||||
issue-exclude-created-before: "2020-10-01T00:00:00Z"
|
||||
issue-lock-reason: ""
|
||||
|
4
.github/workflows/nightly.yaml
vendored
4
.github/workflows/nightly.yaml
vendored
@@ -23,12 +23,12 @@ jobs:
|
||||
uses: actions/checkout@v4.1.1
|
||||
|
||||
- name: Set up Python ${{ env.PYTHON_VERSION }}
|
||||
uses: actions/setup-python@v4
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: ${{ env.PYTHON_VERSION }}
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v4.0.0
|
||||
uses: actions/setup-node@v4.0.1
|
||||
with:
|
||||
node-version-file: ".nvmrc"
|
||||
cache: yarn
|
||||
|
4
.github/workflows/release.yaml
vendored
4
.github/workflows/release.yaml
vendored
@@ -29,12 +29,12 @@ jobs:
|
||||
uses: home-assistant/actions/helpers/verify-version@master
|
||||
|
||||
- name: Set up Python ${{ env.PYTHON_VERSION }}
|
||||
uses: actions/setup-python@v4
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: ${{ env.PYTHON_VERSION }}
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v4.0.0
|
||||
uses: actions/setup-node@v4.0.1
|
||||
with:
|
||||
node-version-file: ".nvmrc"
|
||||
cache: yarn
|
||||
|
2
.github/workflows/stale.yml
vendored
2
.github/workflows/stale.yml
vendored
@@ -10,7 +10,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: 90 days stale policy
|
||||
uses: actions/stale@v8.0.0
|
||||
uses: actions/stale@v9.0.0
|
||||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
days-before-stale: 90
|
||||
|
File diff suppressed because one or more lines are too long
@@ -6,4 +6,4 @@ enableGlobalCache: false
|
||||
|
||||
nodeLinker: node-modules
|
||||
|
||||
yarnPath: .yarn/releases/yarn-4.0.1.cjs
|
||||
yarnPath: .yarn/releases/yarn-4.0.2.cjs
|
||||
|
56
build-scripts/babel-plugins/custom-polyfill-plugin.js
Normal file
56
build-scripts/babel-plugins/custom-polyfill-plugin.js
Normal file
@@ -0,0 +1,56 @@
|
||||
import defineProvider from "@babel/helper-define-polyfill-provider";
|
||||
|
||||
// List of polyfill keys with supported browser targets for the functionality
|
||||
const PolyfillSupport = {
|
||||
fetch: {
|
||||
android: 42,
|
||||
chrome: 42,
|
||||
edge: 14,
|
||||
firefox: 39,
|
||||
ios: 10.3,
|
||||
opera: 29,
|
||||
opera_mobile: 29,
|
||||
safari: 10.1,
|
||||
samsung: 4.0,
|
||||
},
|
||||
proxy: {
|
||||
android: 49,
|
||||
chrome: 49,
|
||||
edge: 12,
|
||||
firefox: 18,
|
||||
ios: 10.0,
|
||||
opera: 36,
|
||||
opera_mobile: 36,
|
||||
safari: 10.0,
|
||||
samsung: 5.0,
|
||||
},
|
||||
};
|
||||
|
||||
// Map of global variables and/or instance and static properties to the
|
||||
// corresponding polyfill key and actual module to import
|
||||
const polyfillMap = {
|
||||
global: {
|
||||
Proxy: { key: "proxy", module: "proxy-polyfill" },
|
||||
fetch: { key: "fetch", module: "unfetch/polyfill" },
|
||||
},
|
||||
instance: {},
|
||||
static: {},
|
||||
};
|
||||
|
||||
// Create plugin using the same factory as for CoreJS
|
||||
export default defineProvider(
|
||||
({ createMetaResolver, debug, shouldInjectPolyfill }) => {
|
||||
const resolvePolyfill = createMetaResolver(polyfillMap);
|
||||
return {
|
||||
name: "HA Custom",
|
||||
polyfills: PolyfillSupport,
|
||||
usageGlobal(meta, utils) {
|
||||
const polyfill = resolvePolyfill(meta);
|
||||
if (polyfill && shouldInjectPolyfill(polyfill.desc.key)) {
|
||||
debug(polyfill.desc.key);
|
||||
utils.injectGlobalImport(polyfill.desc.module);
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
);
|
@@ -1,6 +1,7 @@
|
||||
const path = require("path");
|
||||
const env = require("./env.cjs");
|
||||
const paths = require("./paths.cjs");
|
||||
const { dependencies } = require("../package.json");
|
||||
|
||||
// GitHub base URL to use for production source maps
|
||||
// Nightly builds use the commit SHA, otherwise assumes there is a tag that matches the version
|
||||
@@ -31,8 +32,6 @@ module.exports.emptyPackages = ({ latestBuild, isHassioBuild }) =>
|
||||
require.resolve(
|
||||
path.resolve(paths.polymer_dir, "src/resources/compatibility.ts")
|
||||
),
|
||||
// This polyfill is loaded in workers to support ES5, filter it out.
|
||||
latestBuild && require.resolve("proxy-polyfill/src/index.js"),
|
||||
// Icons in supervisor conflict with icons in HA so we don't load.
|
||||
isHassioBuild &&
|
||||
require.resolve(
|
||||
@@ -87,14 +86,12 @@ module.exports.babelOptions = ({ latestBuild, isProdBuild, isTestBuild }) => ({
|
||||
setSpreadProperties: true,
|
||||
},
|
||||
browserslistEnv: latestBuild ? "modern" : "legacy",
|
||||
// Must be unambiguous because some dependencies are CommonJS only
|
||||
sourceType: "unambiguous",
|
||||
presets: [
|
||||
[
|
||||
"@babel/preset-env",
|
||||
{
|
||||
useBuiltIns: latestBuild ? false : "entry",
|
||||
corejs: latestBuild ? false : { version: "3.33", proposals: true },
|
||||
useBuiltIns: latestBuild ? false : "usage",
|
||||
corejs: latestBuild ? false : dependencies["core-js"],
|
||||
bugfixes: true,
|
||||
shippedProposals: true,
|
||||
},
|
||||
@@ -112,27 +109,39 @@ module.exports.babelOptions = ({ latestBuild, isProdBuild, isTestBuild }) => ({
|
||||
ignoreModuleNotFound: true,
|
||||
},
|
||||
],
|
||||
[
|
||||
path.resolve(
|
||||
paths.polymer_dir,
|
||||
"build-scripts/babel-plugins/custom-polyfill-plugin.js"
|
||||
),
|
||||
{ method: "usage-global" },
|
||||
],
|
||||
// Minify template literals for production
|
||||
isProdBuild && [
|
||||
"template-html-minifier",
|
||||
{
|
||||
modules: {
|
||||
lit: [
|
||||
"html",
|
||||
{ name: "svg", encapsulation: "svg" },
|
||||
{ name: "css", encapsulation: "style" },
|
||||
],
|
||||
"@polymer/polymer/lib/utils/html-tag": ["html"],
|
||||
...Object.fromEntries(
|
||||
["lit", "lit-element", "lit-html"].map((m) => [
|
||||
m,
|
||||
[
|
||||
"html",
|
||||
{ name: "svg", encapsulation: "svg" },
|
||||
{ name: "css", encapsulation: "style" },
|
||||
],
|
||||
])
|
||||
),
|
||||
"@polymer/polymer/lib/utils/html-tag.js": ["html"],
|
||||
},
|
||||
strictCSS: true,
|
||||
htmlMinifier: module.exports.htmlMinifierOptions,
|
||||
failOnError: true, // we can turn this off in case of false positives
|
||||
failOnError: false, // we can turn this off in case of false positives
|
||||
},
|
||||
],
|
||||
// Import helpers and regenerator from runtime package
|
||||
[
|
||||
"@babel/plugin-transform-runtime",
|
||||
{ version: require("../package.json").dependencies["@babel/runtime"] },
|
||||
{ version: dependencies["@babel/runtime"] },
|
||||
],
|
||||
// Support some proposals still in TC39 process
|
||||
["@babel/plugin-proposal-decorators", { decoratorsBeforeExport: true }],
|
||||
@@ -143,6 +152,18 @@ module.exports.babelOptions = ({ latestBuild, isProdBuild, isTestBuild }) => ({
|
||||
/node_modules[\\/]webpack[\\/]buildin/,
|
||||
],
|
||||
sourceMaps: !isTestBuild,
|
||||
overrides: [
|
||||
{
|
||||
// Use unambiguous for dependencies so that require() is correctly injected into CommonJS files
|
||||
// Exclusions are needed in some cases where ES modules have no static imports or exports, such as polyfills
|
||||
sourceType: "unambiguous",
|
||||
include: /\/node_modules\//,
|
||||
exclude: [
|
||||
"element-internals-polyfill",
|
||||
"@?lit(?:-labs|-element|-html)?",
|
||||
].map((p) => new RegExp(`/node_modules/${p}/`)),
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const nameSuffix = (latestBuild) => (latestBuild ? "-modern" : "-legacy");
|
||||
|
@@ -30,8 +30,8 @@ gulp.task(
|
||||
env.useWDS()
|
||||
? "wds-watch-app"
|
||||
: env.useRollup()
|
||||
? "rollup-watch-app"
|
||||
: "webpack-watch-app"
|
||||
? "rollup-watch-app"
|
||||
: "webpack-watch-app"
|
||||
)
|
||||
);
|
||||
|
||||
|
@@ -161,6 +161,10 @@ gulp.task("fetch-lokalise", async function () {
|
||||
})
|
||||
);
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(err);
|
||||
throw err;
|
||||
})
|
||||
)
|
||||
);
|
||||
});
|
||||
|
@@ -51,8 +51,8 @@ const createWebpackConfig = ({
|
||||
devtool: isTestBuild
|
||||
? false
|
||||
: isProdBuild
|
||||
? "nosources-source-map"
|
||||
: "eval-cheap-module-source-map",
|
||||
? "nosources-source-map"
|
||||
: "eval-cheap-module-source-map",
|
||||
entry,
|
||||
node: false,
|
||||
module: {
|
||||
@@ -191,11 +191,11 @@ const createWebpackConfig = ({
|
||||
filename: ({ chunk }) =>
|
||||
!isProdBuild || isStatsBuild || dontHash.has(chunk.name)
|
||||
? "[name].js"
|
||||
: "[name]-[contenthash].js",
|
||||
: "[name].[contenthash].js",
|
||||
chunkFilename:
|
||||
isProdBuild && !isStatsBuild ? "[id]-[contenthash].js" : "[name].js",
|
||||
isProdBuild && !isStatsBuild ? "[name].[contenthash].js" : "[name].js",
|
||||
assetModuleFilename:
|
||||
isProdBuild && !isStatsBuild ? "[id]-[contenthash][ext]" : "[id][ext]",
|
||||
isProdBuild && !isStatsBuild ? "[id].[contenthash][ext]" : "[id][ext]",
|
||||
crossOriginLoading: "use-credentials",
|
||||
hashFunction: "xxhash64",
|
||||
hashDigest: "base64url",
|
||||
|
@@ -73,44 +73,44 @@ class HcCast extends LitElement {
|
||||
${error
|
||||
? html` <div class="card-content">${error}</div> `
|
||||
: !this.castManager.status
|
||||
? html`
|
||||
<p class="center-item">
|
||||
<mwc-button raised @click=${this._handleLaunch}>
|
||||
<ha-svg-icon .path=${mdiCast}></ha-svg-icon>
|
||||
Start Casting
|
||||
</mwc-button>
|
||||
</p>
|
||||
`
|
||||
: html`
|
||||
<div class="section-header">PICK A VIEW</div>
|
||||
<paper-listbox
|
||||
attr-for-selected="data-path"
|
||||
.selected=${this.castManager.status.lovelacePath || ""}
|
||||
>
|
||||
${(
|
||||
this.lovelaceViews ?? [
|
||||
generateDefaultViewConfig({}, {}, {}, {}, () => ""),
|
||||
]
|
||||
).map(
|
||||
(view, idx) => html`
|
||||
<paper-icon-item
|
||||
@click=${this._handlePickView}
|
||||
data-path=${view.path || idx}
|
||||
>
|
||||
${view.icon
|
||||
? html`
|
||||
<ha-icon
|
||||
.icon=${view.icon}
|
||||
slot="item-icon"
|
||||
></ha-icon>
|
||||
`
|
||||
: ""}
|
||||
${view.title || view.path}
|
||||
</paper-icon-item>
|
||||
`
|
||||
)}
|
||||
</paper-listbox>
|
||||
`}
|
||||
? html`
|
||||
<p class="center-item">
|
||||
<mwc-button raised @click=${this._handleLaunch}>
|
||||
<ha-svg-icon .path=${mdiCast}></ha-svg-icon>
|
||||
Start Casting
|
||||
</mwc-button>
|
||||
</p>
|
||||
`
|
||||
: html`
|
||||
<div class="section-header">PICK A VIEW</div>
|
||||
<paper-listbox
|
||||
attr-for-selected="data-path"
|
||||
.selected=${this.castManager.status.lovelacePath || ""}
|
||||
>
|
||||
${(
|
||||
this.lovelaceViews ?? [
|
||||
generateDefaultViewConfig({}, {}, {}, {}, () => ""),
|
||||
]
|
||||
).map(
|
||||
(view, idx) => html`
|
||||
<paper-icon-item
|
||||
@click=${this._handlePickView}
|
||||
data-path=${view.path || idx}
|
||||
>
|
||||
${view.icon
|
||||
? html`
|
||||
<ha-icon
|
||||
.icon=${view.icon}
|
||||
slot="item-icon"
|
||||
></ha-icon>
|
||||
`
|
||||
: ""}
|
||||
${view.title || view.path}
|
||||
</paper-icon-item>
|
||||
`
|
||||
)}
|
||||
</paper-listbox>
|
||||
`}
|
||||
<div class="card-actions">
|
||||
${this.castManager.status
|
||||
? html`
|
||||
|
@@ -1,6 +1,5 @@
|
||||
import "@material/mwc-button";
|
||||
import { mdiCastConnected, mdiCast } from "@mdi/js";
|
||||
import "@polymer/paper-input/paper-input";
|
||||
import {
|
||||
Auth,
|
||||
Connection,
|
||||
@@ -24,6 +23,7 @@ import "../../../../src/components/ha-svg-icon";
|
||||
import "../../../../src/layouts/hass-loading-screen";
|
||||
import { registerServiceWorker } from "../../../../src/util/register-service-worker";
|
||||
import "./hc-layout";
|
||||
import "../../../../src/components/ha-textfield";
|
||||
|
||||
const seeFAQ = (qid) => html`
|
||||
See <a href="./faq.html${qid ? `#${qid}` : ""}">the FAQ</a> for more
|
||||
@@ -33,13 +33,13 @@ const translateErr = (err) =>
|
||||
err === ERR_CANNOT_CONNECT
|
||||
? "Unable to connect"
|
||||
: err === ERR_HASS_HOST_REQUIRED
|
||||
? "Please enter a Home Assistant URL."
|
||||
: err === ERR_INVALID_HTTPS_TO_HTTP
|
||||
? html`
|
||||
Cannot connect to Home Assistant instances over "http://".
|
||||
${seeFAQ("https")}
|
||||
`
|
||||
: `Unknown error (${err}).`;
|
||||
? "Please enter a Home Assistant URL."
|
||||
: err === ERR_INVALID_HTTPS_TO_HTTP
|
||||
? html`
|
||||
Cannot connect to Home Assistant instances over "http://".
|
||||
${seeFAQ("https")}
|
||||
`
|
||||
: `Unknown error (${err}).`;
|
||||
|
||||
const INTRO = html`
|
||||
<p>
|
||||
@@ -116,13 +116,11 @@ export class HcConnect extends LitElement {
|
||||
To get started, enter your Home Assistant URL and click authorize.
|
||||
If you want a preview instead, click the show demo button.
|
||||
</p>
|
||||
<p>
|
||||
<paper-input
|
||||
label="Home Assistant URL"
|
||||
placeholder="https://abcdefghijklmnop.ui.nabu.casa"
|
||||
@keydown=${this._handleInputKeyDown}
|
||||
></paper-input>
|
||||
</p>
|
||||
<ha-textfield
|
||||
label="Home Assistant URL"
|
||||
placeholder="https://abcdefghijklmnop.ui.nabu.casa"
|
||||
@keydown=${this._handleInputKeyDown}
|
||||
></ha-textfield>
|
||||
${this.error ? html` <p class="error">${this.error}</p> ` : ""}
|
||||
</div>
|
||||
<div class="card-actions">
|
||||
@@ -196,7 +194,7 @@ export class HcConnect extends LitElement {
|
||||
}
|
||||
|
||||
private async _handleConnect() {
|
||||
const inputEl = this.shadowRoot!.querySelector("paper-input")!;
|
||||
const inputEl = this.shadowRoot!.querySelector("ha-textfield")!;
|
||||
const value = inputEl.value || "";
|
||||
this.error = undefined;
|
||||
|
||||
@@ -315,6 +313,10 @@ export class HcConnect extends LitElement {
|
||||
.spacer {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
ha-textfield {
|
||||
width: 100%;
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
@@ -39,7 +39,9 @@ export class HADemoCard extends LitElement implements LovelaceCard {
|
||||
<div class="picker">
|
||||
<div class="label">
|
||||
${this._switching
|
||||
? html`<ha-circular-progress active></ha-circular-progress>`
|
||||
? html`<ha-circular-progress
|
||||
indeterminate
|
||||
></ha-circular-progress>`
|
||||
: until(
|
||||
selectedDemoConfig.then(
|
||||
(conf) => html`
|
||||
@@ -48,8 +50,7 @@ export class HADemoCard extends LitElement implements LovelaceCard {
|
||||
<a target="_blank" href=${conf.authorUrl}>
|
||||
${this.hass.localize(
|
||||
"ui.panel.page-demo.cards.demo.demo_by",
|
||||
"name",
|
||||
conf.authorName
|
||||
{ name: conf.authorName }
|
||||
)}
|
||||
</a>
|
||||
</small>
|
||||
|
@@ -43,8 +43,8 @@ const generateMeanStatistics = (
|
||||
period === "day"
|
||||
? addDays(currentDate, 1)
|
||||
: period === "month"
|
||||
? addMonths(currentDate, 1)
|
||||
: addHours(currentDate, 1);
|
||||
? addMonths(currentDate, 1)
|
||||
: addHours(currentDate, 1);
|
||||
}
|
||||
return statistics;
|
||||
};
|
||||
@@ -80,8 +80,8 @@ const generateSumStatistics = (
|
||||
period === "day"
|
||||
? addDays(currentDate, 1)
|
||||
: period === "month"
|
||||
? addMonths(currentDate, 1)
|
||||
: addHours(currentDate, 1);
|
||||
? addMonths(currentDate, 1)
|
||||
: addHours(currentDate, 1);
|
||||
}
|
||||
return statistics;
|
||||
};
|
||||
|
@@ -23,7 +23,7 @@ class DemoMoreInfo extends LitElement {
|
||||
<state-card-content
|
||||
.stateObj=${state}
|
||||
.hass=${this.hass}
|
||||
in-dialog
|
||||
inDialog
|
||||
></state-card-content>
|
||||
|
||||
<more-info-content
|
||||
|
@@ -509,7 +509,7 @@ export default {
|
||||
away_mode: "on",
|
||||
aux_heat: "off",
|
||||
unit_of_measurement: "°C",
|
||||
friendly_name: "Hvac",
|
||||
friendly_name: "HVAC",
|
||||
supported_features: 3833,
|
||||
},
|
||||
last_changed: "2018-07-19T10:44:46.200650+00:00",
|
||||
|
@@ -0,0 +1,4 @@
|
||||
---
|
||||
title: Circular Progress
|
||||
subtitle: Can be used to indicate an ongoing task.
|
||||
---
|
64
gallery/src/pages/components/ha-circular-progress.ts
Normal file
64
gallery/src/pages/components/ha-circular-progress.ts
Normal file
@@ -0,0 +1,64 @@
|
||||
import { html, css, LitElement, TemplateResult } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import "../../../../src/components/ha-bar";
|
||||
import "../../../../src/components/ha-card";
|
||||
import "../../../../src/components/ha-circular-progress";
|
||||
import "@material/web/progress/circular-progress";
|
||||
import { HomeAssistant } from "../../../../src/types";
|
||||
|
||||
@customElement("demo-components-ha-circular-progress")
|
||||
export class DemoHaCircularProgress extends LitElement {
|
||||
@property({ attribute: false }) hass!: HomeAssistant;
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`<ha-card header="Basic circular progress">
|
||||
<div class="card-content">
|
||||
<ha-circular-progress indeterminate></ha-circular-progress></div
|
||||
></ha-card>
|
||||
<ha-card header="Different circular progress sizes">
|
||||
<div class="card-content">
|
||||
<ha-circular-progress
|
||||
indeterminate
|
||||
size="tiny"
|
||||
></ha-circular-progress>
|
||||
<ha-circular-progress
|
||||
indeterminate
|
||||
size="small"
|
||||
></ha-circular-progress>
|
||||
<ha-circular-progress
|
||||
indeterminate
|
||||
size="medium"
|
||||
></ha-circular-progress>
|
||||
<ha-circular-progress
|
||||
indeterminate
|
||||
size="large"
|
||||
></ha-circular-progress></div
|
||||
></ha-card>
|
||||
<ha-card header="Circular progress with an aria-label">
|
||||
<div class="card-content">
|
||||
<ha-circular-progress
|
||||
indeterminate
|
||||
aria-label="Doing something..."
|
||||
></ha-circular-progress>
|
||||
<ha-circular-progress
|
||||
indeterminate
|
||||
.ariaLabel=${"Doing something..."}
|
||||
></ha-circular-progress></div
|
||||
></ha-card>`;
|
||||
}
|
||||
|
||||
static get styles() {
|
||||
return css`
|
||||
ha-card {
|
||||
max-width: 600px;
|
||||
margin: 24px auto;
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"demo-components-ha-circular-progress": DemoHaCircularProgress;
|
||||
}
|
||||
}
|
@@ -18,7 +18,7 @@ The Home Assistant interface is based on Material Design. It's a design system c
|
||||
|
||||
We want to make it as easy for designers to contribute as it is for developers. There’s a lot a designer can contribute to:
|
||||
|
||||
- Meet us at <a href="https://discord.gg/BPBc8rZ9" rel="noopener noreferrer" target="_blank">devs_ux Discord</a>. Feel free to share your designs, user test or strategic ideas.
|
||||
- Meet us at <a href="https://www.home-assistant.io/join-chat" rel="noopener noreferrer" target="_blank">devs_ux Discord</a>. Feel free to share your designs, user test or strategic ideas.
|
||||
- Start designing with our <a href="https://www.figma.com/community/file/967153512097289521/Home-Assistant-DesignKit" rel="noopener noreferrer" target="_blank">Figma DesignKit</a>.
|
||||
- Find the latest UX <a href="https://github.com/home-assistant/frontend/discussions?discussions_q=label%3Aux" rel="noopener noreferrer" target="_blank">discussions</a> and <a href="https://github.com/home-assistant/frontend/labels/ux" rel="noopener noreferrer" target="_blank">issues</a> on GitHub. Everyone can start a new issue or discussion!
|
||||
|
||||
|
@@ -35,6 +35,18 @@ const ENTITIES = [
|
||||
friendly_name: "Nest",
|
||||
supported_features: 43,
|
||||
}),
|
||||
getEntity("climate", "sensibo", "fan_only", {
|
||||
current_temperature: null,
|
||||
temperature: null,
|
||||
min_temp: 0,
|
||||
max_temp: 1,
|
||||
target_temp_step: 1,
|
||||
hvac_modes: ["fan_only", "off"],
|
||||
friendly_name: "Sensibo purifier",
|
||||
fan_modes: ["low", "high"],
|
||||
fan_mode: "low",
|
||||
supported_features: 9,
|
||||
}),
|
||||
getEntity("climate", "unavailable", "unavailable", {
|
||||
supported_features: 43,
|
||||
}),
|
||||
@@ -57,6 +69,23 @@ const CONFIGS = [
|
||||
entity: climate.nest
|
||||
`,
|
||||
},
|
||||
{
|
||||
heading: "Fan only example",
|
||||
config: `
|
||||
- type: thermostat
|
||||
entity: climate.sensibo
|
||||
features:
|
||||
- type: climate-hvac-modes
|
||||
hvac_modes:
|
||||
- fan_only
|
||||
- 'off'
|
||||
- type: climate-fan-modes
|
||||
style: icons
|
||||
fan_modes:
|
||||
- low
|
||||
- high
|
||||
`,
|
||||
},
|
||||
{
|
||||
heading: "Unavailable",
|
||||
config: `
|
||||
|
@@ -31,6 +31,21 @@ const ENTITIES = [
|
||||
max_temp: 30,
|
||||
supported_features: ClimateEntityFeature.TARGET_TEMPERATURE,
|
||||
}),
|
||||
getEntity("climate", "fan", "fan_only", {
|
||||
friendly_name: "Basic fan",
|
||||
hvac_modes: ["fan_only", "off"],
|
||||
hvac_mode: "fan_only",
|
||||
fan_modes: ["low", "high"],
|
||||
fan_mode: "low",
|
||||
current_temperature: null,
|
||||
temperature: null,
|
||||
min_temp: 0,
|
||||
max_temp: 1,
|
||||
target_temp_step: 1,
|
||||
supported_features:
|
||||
// eslint-disable-next-line no-bitwise
|
||||
ClimateEntityFeature.TARGET_TEMPERATURE | ClimateEntityFeature.FAN_MODE,
|
||||
}),
|
||||
getEntity("climate", "hvac", "auto", {
|
||||
friendly_name: "Basic hvac",
|
||||
hvac_modes: ["auto", "off"],
|
||||
|
3
gallery/src/pages/more-info/input-text.markdown
Normal file
3
gallery/src/pages/more-info/input-text.markdown
Normal file
@@ -0,0 +1,3 @@
|
||||
---
|
||||
title: Input Text
|
||||
---
|
46
gallery/src/pages/more-info/input-text.ts
Normal file
46
gallery/src/pages/more-info/input-text.ts
Normal file
@@ -0,0 +1,46 @@
|
||||
import { html, LitElement, PropertyValues, TemplateResult } from "lit";
|
||||
import { customElement, property, query } from "lit/decorators";
|
||||
import "../../../../src/components/ha-card";
|
||||
import "../../../../src/dialogs/more-info/more-info-content";
|
||||
import { getEntity } from "../../../../src/fake_data/entity";
|
||||
import {
|
||||
MockHomeAssistant,
|
||||
provideHass,
|
||||
} from "../../../../src/fake_data/provide_hass";
|
||||
import "../../components/demo-more-infos";
|
||||
|
||||
const ENTITIES = [
|
||||
getEntity("input_text", "text", "Inspiration", {
|
||||
friendly_name: "Text",
|
||||
mode: "text",
|
||||
}),
|
||||
];
|
||||
|
||||
@customElement("demo-more-info-input-text")
|
||||
class DemoMoreInfoInputText extends LitElement {
|
||||
@property() public hass!: MockHomeAssistant;
|
||||
|
||||
@query("demo-more-infos") private _demoRoot!: HTMLElement;
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
<demo-more-infos
|
||||
.hass=${this.hass}
|
||||
.entities=${ENTITIES.map((ent) => ent.entityId)}
|
||||
></demo-more-infos>
|
||||
`;
|
||||
}
|
||||
|
||||
protected firstUpdated(changedProperties: PropertyValues) {
|
||||
super.firstUpdated(changedProperties);
|
||||
const hass = provideHass(this._demoRoot);
|
||||
hass.updateTranslations(null, "en");
|
||||
hass.addEntities(ENTITIES);
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"demo-more-info-input-text": DemoMoreInfoInputText;
|
||||
}
|
||||
}
|
3
gallery/src/pages/more-info/lock.markdown
Normal file
3
gallery/src/pages/more-info/lock.markdown
Normal file
@@ -0,0 +1,3 @@
|
||||
---
|
||||
title: Lock
|
||||
---
|
49
gallery/src/pages/more-info/lock.ts
Normal file
49
gallery/src/pages/more-info/lock.ts
Normal file
@@ -0,0 +1,49 @@
|
||||
import { html, LitElement, PropertyValues, TemplateResult } from "lit";
|
||||
import { customElement, property, query } from "lit/decorators";
|
||||
import "../../../../src/components/ha-card";
|
||||
import "../../../../src/dialogs/more-info/more-info-content";
|
||||
import { getEntity } from "../../../../src/fake_data/entity";
|
||||
import {
|
||||
MockHomeAssistant,
|
||||
provideHass,
|
||||
} from "../../../../src/fake_data/provide_hass";
|
||||
import "../../components/demo-more-infos";
|
||||
|
||||
const ENTITIES = [
|
||||
getEntity("lock", "lock", "locked", {
|
||||
friendly_name: "Lock",
|
||||
device_class: "lock",
|
||||
}),
|
||||
getEntity("lock", "unavailable", "unavailable", {
|
||||
friendly_name: "Unavailable lock",
|
||||
}),
|
||||
];
|
||||
|
||||
@customElement("demo-more-info-lock")
|
||||
class DemoMoreInfoLock extends LitElement {
|
||||
@property() public hass!: MockHomeAssistant;
|
||||
|
||||
@query("demo-more-infos") private _demoRoot!: HTMLElement;
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
<demo-more-infos
|
||||
.hass=${this.hass}
|
||||
.entities=${ENTITIES.map((ent) => ent.entityId)}
|
||||
></demo-more-infos>
|
||||
`;
|
||||
}
|
||||
|
||||
protected firstUpdated(changedProperties: PropertyValues) {
|
||||
super.firstUpdated(changedProperties);
|
||||
const hass = provideHass(this._demoRoot);
|
||||
hass.updateTranslations(null, "en");
|
||||
hass.addEntities(ENTITIES);
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"demo-more-info-lock": DemoMoreInfoLock;
|
||||
}
|
||||
}
|
3
gallery/src/pages/more-info/media-player.markdown
Normal file
3
gallery/src/pages/more-info/media-player.markdown
Normal file
@@ -0,0 +1,3 @@
|
||||
---
|
||||
title: Media Player
|
||||
---
|
41
gallery/src/pages/more-info/media-player.ts
Normal file
41
gallery/src/pages/more-info/media-player.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import { html, LitElement, PropertyValues, TemplateResult } from "lit";
|
||||
import { customElement, property, query } from "lit/decorators";
|
||||
import "../../../../src/components/ha-card";
|
||||
import "../../../../src/dialogs/more-info/more-info-content";
|
||||
import {
|
||||
MockHomeAssistant,
|
||||
provideHass,
|
||||
} from "../../../../src/fake_data/provide_hass";
|
||||
import "../../components/demo-more-infos";
|
||||
import { createMediaPlayerEntities } from "../../data/media_players";
|
||||
|
||||
const ENTITIES = createMediaPlayerEntities();
|
||||
|
||||
@customElement("demo-more-info-media-player")
|
||||
class DemoMoreInfoMediaPlayer extends LitElement {
|
||||
@property() public hass!: MockHomeAssistant;
|
||||
|
||||
@query("demo-more-infos") private _demoRoot!: HTMLElement;
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
<demo-more-infos
|
||||
.hass=${this.hass}
|
||||
.entities=${ENTITIES.map((ent) => ent.entityId)}
|
||||
></demo-more-infos>
|
||||
`;
|
||||
}
|
||||
|
||||
protected firstUpdated(changedProperties: PropertyValues) {
|
||||
super.firstUpdated(changedProperties);
|
||||
const hass = provideHass(this._demoRoot);
|
||||
hass.updateTranslations(null, "en");
|
||||
hass.addEntities(ENTITIES);
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"demo-more-info-media-player": DemoMoreInfoMediaPlayer;
|
||||
}
|
||||
}
|
3
gallery/src/pages/more-info/number.markdown
Normal file
3
gallery/src/pages/more-info/number.markdown
Normal file
@@ -0,0 +1,3 @@
|
||||
---
|
||||
title: Number
|
||||
---
|
78
gallery/src/pages/more-info/number.ts
Normal file
78
gallery/src/pages/more-info/number.ts
Normal file
@@ -0,0 +1,78 @@
|
||||
import { html, LitElement, PropertyValues, TemplateResult } from "lit";
|
||||
import { customElement, property, query } from "lit/decorators";
|
||||
import "../../../../src/components/ha-card";
|
||||
import "../../../../src/dialogs/more-info/more-info-content";
|
||||
import { getEntity } from "../../../../src/fake_data/entity";
|
||||
import {
|
||||
MockHomeAssistant,
|
||||
provideHass,
|
||||
} from "../../../../src/fake_data/provide_hass";
|
||||
import "../../components/demo-more-infos";
|
||||
|
||||
const ENTITIES = [
|
||||
getEntity("number", "box1", 0, {
|
||||
friendly_name: "Box1",
|
||||
min: 0,
|
||||
max: 100,
|
||||
step: 1,
|
||||
initial: 0,
|
||||
mode: "box",
|
||||
unit_of_measurement: "items",
|
||||
}),
|
||||
getEntity("number", "slider1", 0, {
|
||||
friendly_name: "Slider1",
|
||||
min: 0,
|
||||
max: 100,
|
||||
step: 1,
|
||||
initial: 0,
|
||||
mode: "slider",
|
||||
unit_of_measurement: "items",
|
||||
}),
|
||||
getEntity("number", "auto1", 0, {
|
||||
friendly_name: "Auto1",
|
||||
min: 0,
|
||||
max: 1000,
|
||||
step: 1,
|
||||
initial: 0,
|
||||
mode: "auto",
|
||||
unit_of_measurement: "items",
|
||||
}),
|
||||
getEntity("number", "auto2", 0, {
|
||||
friendly_name: "Auto2",
|
||||
min: 0,
|
||||
max: 100,
|
||||
step: 1,
|
||||
initial: 0,
|
||||
mode: "auto",
|
||||
unit_of_measurement: "items",
|
||||
}),
|
||||
];
|
||||
|
||||
@customElement("demo-more-info-number")
|
||||
class DemoMoreInfoNumber extends LitElement {
|
||||
@property() public hass!: MockHomeAssistant;
|
||||
|
||||
@query("demo-more-infos") private _demoRoot!: HTMLElement;
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
<demo-more-infos
|
||||
.hass=${this.hass}
|
||||
.entities=${ENTITIES.map((ent) => ent.entityId)}
|
||||
></demo-more-infos>
|
||||
`;
|
||||
}
|
||||
|
||||
protected firstUpdated(changedProperties: PropertyValues) {
|
||||
super.firstUpdated(changedProperties);
|
||||
const hass = provideHass(this._demoRoot);
|
||||
hass.updateTranslations(null, "en");
|
||||
hass.addEntities(ENTITIES);
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"demo-more-info-number": DemoMoreInfoNumber;
|
||||
}
|
||||
}
|
3
gallery/src/pages/more-info/scene.markdown
Normal file
3
gallery/src/pages/more-info/scene.markdown
Normal file
@@ -0,0 +1,3 @@
|
||||
---
|
||||
title: Scene
|
||||
---
|
49
gallery/src/pages/more-info/scene.ts
Normal file
49
gallery/src/pages/more-info/scene.ts
Normal file
@@ -0,0 +1,49 @@
|
||||
import { html, LitElement, PropertyValues, TemplateResult } from "lit";
|
||||
import { customElement, property, query } from "lit/decorators";
|
||||
import "../../../../src/components/ha-card";
|
||||
import "../../../../src/dialogs/more-info/more-info-content";
|
||||
import { getEntity } from "../../../../src/fake_data/entity";
|
||||
import {
|
||||
MockHomeAssistant,
|
||||
provideHass,
|
||||
} from "../../../../src/fake_data/provide_hass";
|
||||
import "../../components/demo-more-infos";
|
||||
|
||||
const ENTITIES = [
|
||||
getEntity("scene", "romantic_lights", "scening", {
|
||||
entity_id: ["light.bed_light", "light.ceiling_lights"],
|
||||
friendly_name: "Romantic Scene",
|
||||
}),
|
||||
getEntity("scene", "unavailable", "unavailable", {
|
||||
friendly_name: "Romantic Scene",
|
||||
}),
|
||||
];
|
||||
|
||||
@customElement("demo-more-info-scene")
|
||||
class DemoMoreInfoScene extends LitElement {
|
||||
@property() public hass!: MockHomeAssistant;
|
||||
|
||||
@query("demo-more-infos") private _demoRoot!: HTMLElement;
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
<demo-more-infos
|
||||
.hass=${this.hass}
|
||||
.entities=${ENTITIES.map((ent) => ent.entityId)}
|
||||
></demo-more-infos>
|
||||
`;
|
||||
}
|
||||
|
||||
protected firstUpdated(changedProperties: PropertyValues) {
|
||||
super.firstUpdated(changedProperties);
|
||||
const hass = provideHass(this._demoRoot);
|
||||
hass.updateTranslations(null, "en");
|
||||
hass.addEntities(ENTITIES);
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"demo-more-info-scene": DemoMoreInfoScene;
|
||||
}
|
||||
}
|
3
gallery/src/pages/more-info/timer.markdown
Normal file
3
gallery/src/pages/more-info/timer.markdown
Normal file
@@ -0,0 +1,3 @@
|
||||
---
|
||||
title: Timer
|
||||
---
|
46
gallery/src/pages/more-info/timer.ts
Normal file
46
gallery/src/pages/more-info/timer.ts
Normal file
@@ -0,0 +1,46 @@
|
||||
import { html, LitElement, PropertyValues, TemplateResult } from "lit";
|
||||
import { customElement, property, query } from "lit/decorators";
|
||||
import "../../../../src/components/ha-card";
|
||||
import "../../../../src/dialogs/more-info/more-info-content";
|
||||
import { getEntity } from "../../../../src/fake_data/entity";
|
||||
import {
|
||||
MockHomeAssistant,
|
||||
provideHass,
|
||||
} from "../../../../src/fake_data/provide_hass";
|
||||
import "../../components/demo-more-infos";
|
||||
|
||||
const ENTITIES = [
|
||||
getEntity("timer", "timer", "idle", {
|
||||
friendly_name: "Timer",
|
||||
duration: "0:05:00",
|
||||
}),
|
||||
];
|
||||
|
||||
@customElement("demo-more-info-timer")
|
||||
class DemoMoreInfoTimer extends LitElement {
|
||||
@property() public hass!: MockHomeAssistant;
|
||||
|
||||
@query("demo-more-infos") private _demoRoot!: HTMLElement;
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
<demo-more-infos
|
||||
.hass=${this.hass}
|
||||
.entities=${ENTITIES.map((ent) => ent.entityId)}
|
||||
></demo-more-infos>
|
||||
`;
|
||||
}
|
||||
|
||||
protected firstUpdated(changedProperties: PropertyValues) {
|
||||
super.firstUpdated(changedProperties);
|
||||
const hass = provideHass(this._demoRoot);
|
||||
hass.updateTranslations(null, "en");
|
||||
hass.addEntities(ENTITIES);
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"demo-more-info-timer": DemoMoreInfoTimer;
|
||||
}
|
||||
}
|
@@ -1,12 +1,6 @@
|
||||
import { html, LitElement, PropertyValues, TemplateResult } from "lit";
|
||||
import { customElement, property, query } from "lit/decorators";
|
||||
import "../../../../src/components/ha-card";
|
||||
import {
|
||||
UPDATE_SUPPORT_BACKUP,
|
||||
UPDATE_SUPPORT_PROGRESS,
|
||||
UPDATE_SUPPORT_INSTALL,
|
||||
UPDATE_SUPPORT_RELEASE_NOTES,
|
||||
} from "../../../../src/data/update";
|
||||
import "../../../../src/dialogs/more-info/more-info-content";
|
||||
import { getEntity } from "../../../../src/fake_data/entity";
|
||||
import {
|
||||
@@ -15,13 +9,14 @@ import {
|
||||
} from "../../../../src/fake_data/provide_hass";
|
||||
import "../../components/demo-more-infos";
|
||||
import { LONG_TEXT } from "../../data/text";
|
||||
import { UpdateEntityFeature } from "../../../../src/data/update";
|
||||
|
||||
const base_attributes = {
|
||||
title: "Awesome",
|
||||
installed_version: "1.2.2",
|
||||
latest_version: "1.2.3",
|
||||
release_url: "https://home-assistant.io",
|
||||
supported_features: UPDATE_SUPPORT_INSTALL,
|
||||
supported_features: UpdateEntityFeature.INSTALL,
|
||||
skipped_version: null,
|
||||
in_progress: false,
|
||||
release_summary:
|
||||
@@ -61,7 +56,7 @@ const ENTITIES = [
|
||||
getEntity("update", "update7", "on", {
|
||||
...base_attributes,
|
||||
supported_features:
|
||||
base_attributes.supported_features + UPDATE_SUPPORT_BACKUP,
|
||||
base_attributes.supported_features + UpdateEntityFeature.BACKUP,
|
||||
friendly_name: "With backup support",
|
||||
}),
|
||||
getEntity("update", "update8", "on", {
|
||||
@@ -73,21 +68,21 @@ const ENTITIES = [
|
||||
...base_attributes,
|
||||
in_progress: 25,
|
||||
supported_features:
|
||||
base_attributes.supported_features + UPDATE_SUPPORT_PROGRESS,
|
||||
base_attributes.supported_features + UpdateEntityFeature.PROGRESS,
|
||||
friendly_name: "With 25 in_progress",
|
||||
}),
|
||||
getEntity("update", "update10", "on", {
|
||||
...base_attributes,
|
||||
in_progress: 50,
|
||||
supported_features:
|
||||
base_attributes.supported_features + UPDATE_SUPPORT_PROGRESS,
|
||||
base_attributes.supported_features + UpdateEntityFeature.PROGRESS,
|
||||
friendly_name: "With 50 in_progress",
|
||||
}),
|
||||
getEntity("update", "update11", "on", {
|
||||
...base_attributes,
|
||||
in_progress: 75,
|
||||
supported_features:
|
||||
base_attributes.supported_features + UPDATE_SUPPORT_PROGRESS,
|
||||
base_attributes.supported_features + UpdateEntityFeature.PROGRESS,
|
||||
friendly_name: "With 75 in_progress",
|
||||
}),
|
||||
getEntity("update", "update12", "unavailable", {
|
||||
@@ -114,19 +109,19 @@ const ENTITIES = [
|
||||
...base_attributes,
|
||||
friendly_name: "Update with release notes",
|
||||
supported_features:
|
||||
base_attributes.supported_features + UPDATE_SUPPORT_RELEASE_NOTES,
|
||||
base_attributes.supported_features + UpdateEntityFeature.RELEASE_NOTES,
|
||||
}),
|
||||
getEntity("update", "update17", "off", {
|
||||
...base_attributes,
|
||||
friendly_name: "Update with release notes error",
|
||||
supported_features:
|
||||
base_attributes.supported_features + UPDATE_SUPPORT_RELEASE_NOTES,
|
||||
base_attributes.supported_features + UpdateEntityFeature.RELEASE_NOTES,
|
||||
}),
|
||||
getEntity("update", "update18", "off", {
|
||||
...base_attributes,
|
||||
friendly_name: "Update with release notes loading",
|
||||
supported_features:
|
||||
base_attributes.supported_features + UPDATE_SUPPORT_RELEASE_NOTES,
|
||||
base_attributes.supported_features + UpdateEntityFeature.RELEASE_NOTES,
|
||||
}),
|
||||
getEntity("update", "update19", "on", {
|
||||
...base_attributes,
|
||||
@@ -142,9 +137,10 @@ const ENTITIES = [
|
||||
getEntity("update", "update21", "on", {
|
||||
...base_attributes,
|
||||
in_progress: true,
|
||||
friendly_name: "Update with in_progress true and UPDATE_SUPPORT_PROGRESS",
|
||||
friendly_name:
|
||||
"Update with in_progress true and UpdateEntityFeature.PROGRESS",
|
||||
supported_features:
|
||||
base_attributes.supported_features + UPDATE_SUPPORT_PROGRESS,
|
||||
base_attributes.supported_features + UpdateEntityFeature.PROGRESS,
|
||||
}),
|
||||
];
|
||||
|
||||
|
3
gallery/src/pages/more-info/vacuum.markdown
Normal file
3
gallery/src/pages/more-info/vacuum.markdown
Normal file
@@ -0,0 +1,3 @@
|
||||
---
|
||||
title: Vacuum
|
||||
---
|
50
gallery/src/pages/more-info/vacuum.ts
Normal file
50
gallery/src/pages/more-info/vacuum.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
import { html, LitElement, PropertyValues, TemplateResult } from "lit";
|
||||
import { customElement, property, query } from "lit/decorators";
|
||||
import "../../../../src/components/ha-card";
|
||||
import "../../../../src/dialogs/more-info/more-info-content";
|
||||
import { getEntity } from "../../../../src/fake_data/entity";
|
||||
import {
|
||||
MockHomeAssistant,
|
||||
provideHass,
|
||||
} from "../../../../src/fake_data/provide_hass";
|
||||
import "../../components/demo-more-infos";
|
||||
import { VacuumEntityFeature } from "../../../../src/data/vacuum";
|
||||
|
||||
const ENTITIES = [
|
||||
getEntity("vacuum", "first_floor_vacuum", "docked", {
|
||||
friendly_name: "First floor vacuum",
|
||||
supported_features:
|
||||
VacuumEntityFeature.START +
|
||||
VacuumEntityFeature.STOP +
|
||||
VacuumEntityFeature.RETURN_HOME,
|
||||
}),
|
||||
];
|
||||
|
||||
@customElement("demo-more-info-vacuum")
|
||||
class DemoMoreInfoVacuum extends LitElement {
|
||||
@property() public hass!: MockHomeAssistant;
|
||||
|
||||
@query("demo-more-infos") private _demoRoot!: HTMLElement;
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
<demo-more-infos
|
||||
.hass=${this.hass}
|
||||
.entities=${ENTITIES.map((ent) => ent.entityId)}
|
||||
></demo-more-infos>
|
||||
`;
|
||||
}
|
||||
|
||||
protected firstUpdated(changedProperties: PropertyValues) {
|
||||
super.firstUpdated(changedProperties);
|
||||
const hass = provideHass(this._demoRoot);
|
||||
hass.updateTranslations(null, "en");
|
||||
hass.addEntities(ENTITIES);
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"demo-more-info-vacuum": DemoMoreInfoVacuum;
|
||||
}
|
||||
}
|
@@ -49,11 +49,9 @@ export class HassioAddonRepositoryEl extends LitElement {
|
||||
return html`
|
||||
<div class="content">
|
||||
<p class="description">
|
||||
${this.supervisor.localize(
|
||||
"store.no_results_found",
|
||||
"repository",
|
||||
repo.name
|
||||
)}
|
||||
${this.supervisor.localize("store.no_results_found", {
|
||||
repository: repo.name,
|
||||
})}
|
||||
</p>
|
||||
</div>
|
||||
`;
|
||||
@@ -86,15 +84,15 @@ export class HassioAddonRepositoryEl extends LitElement {
|
||||
)
|
||||
: this.supervisor.localize("addon.state.installed")
|
||||
: addon.available
|
||||
? this.supervisor.localize("addon.state.not_installed")
|
||||
: this.supervisor.localize("addon.state.not_available")}
|
||||
? this.supervisor.localize("addon.state.not_installed")
|
||||
: this.supervisor.localize("addon.state.not_available")}
|
||||
.iconClass=${addon.installed
|
||||
? addon.update_available
|
||||
? "update"
|
||||
: "installed"
|
||||
: !addon.available
|
||||
? "not_available"
|
||||
: ""}
|
||||
? "not_available"
|
||||
: ""}
|
||||
.iconImage=${atLeastVersion(
|
||||
this.hass.config.version,
|
||||
0,
|
||||
@@ -108,8 +106,8 @@ export class HassioAddonRepositoryEl extends LitElement {
|
||||
? "update"
|
||||
: "installed"
|
||||
: !addon.available
|
||||
? "unavailable"
|
||||
: ""}
|
||||
? "unavailable"
|
||||
: ""}
|
||||
></hassio-card-content>
|
||||
</div>
|
||||
</ha-card>
|
||||
|
@@ -20,7 +20,7 @@ class HassioAddonConfigDashboard extends LitElement {
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (!this.addon) {
|
||||
return html`<ha-circular-progress active></ha-circular-progress>`;
|
||||
return html`<ha-circular-progress indeterminate></ha-circular-progress>`;
|
||||
}
|
||||
const hasConfiguration =
|
||||
(this.addon.options && Object.keys(this.addon.options).length) ||
|
||||
|
@@ -104,50 +104,50 @@ class HassioAddonConfig extends LitElement {
|
||||
selector: { select: { options: entry.options } },
|
||||
}
|
||||
: entry.type === "string"
|
||||
? entry.multiple
|
||||
? {
|
||||
name: entry.name,
|
||||
required: entry.required,
|
||||
selector: {
|
||||
select: { options: [], multiple: true, custom_value: true },
|
||||
},
|
||||
}
|
||||
: {
|
||||
name: entry.name,
|
||||
required: entry.required,
|
||||
selector: {
|
||||
text: {
|
||||
type:
|
||||
entry.format || MASKED_FIELDS.includes(entry.name)
|
||||
? "password"
|
||||
: "text",
|
||||
? entry.multiple
|
||||
? {
|
||||
name: entry.name,
|
||||
required: entry.required,
|
||||
selector: {
|
||||
select: { options: [], multiple: true, custom_value: true },
|
||||
},
|
||||
},
|
||||
}
|
||||
: entry.type === "boolean"
|
||||
? {
|
||||
name: entry.name,
|
||||
required: entry.required,
|
||||
selector: { boolean: {} },
|
||||
}
|
||||
: entry.type === "schema"
|
||||
? {
|
||||
name: entry.name,
|
||||
required: entry.required,
|
||||
selector: { object: {} },
|
||||
}
|
||||
: entry.type === "float" || entry.type === "integer"
|
||||
? {
|
||||
name: entry.name,
|
||||
required: entry.required,
|
||||
selector: {
|
||||
number: {
|
||||
mode: "box",
|
||||
step: entry.type === "float" ? "any" : undefined,
|
||||
},
|
||||
},
|
||||
}
|
||||
: entry
|
||||
}
|
||||
: {
|
||||
name: entry.name,
|
||||
required: entry.required,
|
||||
selector: {
|
||||
text: {
|
||||
type:
|
||||
entry.format || MASKED_FIELDS.includes(entry.name)
|
||||
? "password"
|
||||
: "text",
|
||||
},
|
||||
},
|
||||
}
|
||||
: entry.type === "boolean"
|
||||
? {
|
||||
name: entry.name,
|
||||
required: entry.required,
|
||||
selector: { boolean: {} },
|
||||
}
|
||||
: entry.type === "schema"
|
||||
? {
|
||||
name: entry.name,
|
||||
required: entry.required,
|
||||
selector: { object: {} },
|
||||
}
|
||||
: entry.type === "float" || entry.type === "integer"
|
||||
? {
|
||||
name: entry.name,
|
||||
required: entry.required,
|
||||
selector: {
|
||||
number: {
|
||||
mode: "box",
|
||||
step: entry.type === "float" ? "any" : undefined,
|
||||
},
|
||||
},
|
||||
}
|
||||
: entry
|
||||
)
|
||||
);
|
||||
|
||||
@@ -340,11 +340,9 @@ class HassioAddonConfig extends LitElement {
|
||||
};
|
||||
fireEvent(this, "hass-api-called", eventdata);
|
||||
} catch (err: any) {
|
||||
this._error = this.supervisor.localize(
|
||||
"addon.failed_to_reset",
|
||||
"error",
|
||||
extractApiErrorMessage(err)
|
||||
);
|
||||
this._error = this.supervisor.localize("addon.failed_to_reset", {
|
||||
error: extractApiErrorMessage(err),
|
||||
});
|
||||
}
|
||||
button.progress = false;
|
||||
}
|
||||
@@ -381,11 +379,9 @@ class HassioAddonConfig extends LitElement {
|
||||
await suggestAddonRestart(this, this.hass, this.supervisor, this.addon);
|
||||
}
|
||||
} catch (err: any) {
|
||||
this._error = this.supervisor.localize(
|
||||
"addon.failed_to_save",
|
||||
"error",
|
||||
extractApiErrorMessage(err)
|
||||
);
|
||||
this._error = this.supervisor.localize("addon.failed_to_save", {
|
||||
error: extractApiErrorMessage(err),
|
||||
});
|
||||
eventdata.success = false;
|
||||
}
|
||||
button.progress = false;
|
||||
|
@@ -180,11 +180,9 @@ class HassioAddonNetwork extends LitElement {
|
||||
await suggestAddonRestart(this, this.hass, this.supervisor, this.addon);
|
||||
}
|
||||
} catch (err: any) {
|
||||
this._error = this.supervisor.localize(
|
||||
"addon.failed_to_reset",
|
||||
"error",
|
||||
extractApiErrorMessage(err)
|
||||
);
|
||||
this._error = this.supervisor.localize("addon.failed_to_reset", {
|
||||
error: extractApiErrorMessage(err),
|
||||
});
|
||||
button.actionError();
|
||||
}
|
||||
}
|
||||
@@ -220,11 +218,9 @@ class HassioAddonNetwork extends LitElement {
|
||||
await suggestAddonRestart(this, this.hass, this.supervisor, this.addon);
|
||||
}
|
||||
} catch (err: any) {
|
||||
this._error = this.supervisor.localize(
|
||||
"addon.failed_to_save",
|
||||
"error",
|
||||
extractApiErrorMessage(err)
|
||||
);
|
||||
this._error = this.supervisor.localize("addon.failed_to_save", {
|
||||
error: extractApiErrorMessage(err),
|
||||
});
|
||||
button.actionError();
|
||||
}
|
||||
}
|
||||
|
@@ -34,7 +34,7 @@ class HassioAddonDocumentationDashboard extends LitElement {
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (!this.addon) {
|
||||
return html`<ha-circular-progress active></ha-circular-progress>`;
|
||||
return html`<ha-circular-progress indeterminate></ha-circular-progress>`;
|
||||
}
|
||||
return html`
|
||||
<div class="content">
|
||||
@@ -85,8 +85,7 @@ class HassioAddonDocumentationDashboard extends LitElement {
|
||||
} catch (err: any) {
|
||||
this._error = this.supervisor.localize(
|
||||
"addon.documentation.get_documentation",
|
||||
"error",
|
||||
extractApiErrorMessage(err)
|
||||
{ error: extractApiErrorMessage(err) }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@@ -22,7 +22,7 @@ class HassioAddonInfoDashboard extends LitElement {
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (!this.addon) {
|
||||
return html`<ha-circular-progress active></ha-circular-progress>`;
|
||||
return html`<ha-circular-progress indeterminate></ha-circular-progress>`;
|
||||
}
|
||||
|
||||
return html`
|
||||
|
@@ -451,13 +451,14 @@ class HassioAddonInfo extends LitElement {
|
||||
|
||||
<div class="description light-color">
|
||||
${this.addon.description}.<br />
|
||||
${this.supervisor.localize(
|
||||
"addon.dashboard.visit_addon_page",
|
||||
"name",
|
||||
html`<a href=${this.addon.url!} target="_blank" rel="noreferrer"
|
||||
${this.supervisor.localize("addon.dashboard.visit_addon_page", {
|
||||
name: html`<a
|
||||
href=${this.addon.url!}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>${this.addon.name}</a
|
||||
>`
|
||||
)}
|
||||
>`,
|
||||
})}
|
||||
</div>
|
||||
<div class="addon-container">
|
||||
<div>
|
||||
@@ -624,10 +625,10 @@ class HassioAddonInfo extends LitElement {
|
||||
<ha-alert alert-type="warning">
|
||||
${this.supervisor.localize(
|
||||
"addon.dashboard.not_available_version",
|
||||
"core_version_installed",
|
||||
this.supervisor.core.version,
|
||||
"core_version_needed",
|
||||
addonStoreInfo!.homeassistant
|
||||
{
|
||||
core_version_installed: this.supervisor.core.version,
|
||||
core_version_needed: addonStoreInfo!.homeassistant,
|
||||
}
|
||||
)}
|
||||
</ha-alert>
|
||||
`
|
||||
@@ -800,12 +801,11 @@ class HassioAddonInfo extends LitElement {
|
||||
id === "stage"
|
||||
? this.supervisor.localize(
|
||||
`addon.dashboard.capability.${id}.description`,
|
||||
"icon_stable",
|
||||
`<ha-svg-icon path="${STAGE_ICON.stable}"></ha-svg-icon>`,
|
||||
"icon_experimental",
|
||||
`<ha-svg-icon path="${STAGE_ICON.experimental}"></ha-svg-icon>`,
|
||||
"icon_deprecated",
|
||||
`<ha-svg-icon path="${STAGE_ICON.deprecated}"></ha-svg-icon>`
|
||||
{
|
||||
icon_stable: `<ha-svg-icon path="${STAGE_ICON.stable}"></ha-svg-icon>`,
|
||||
icon_experimental: `<ha-svg-icon path="${STAGE_ICON.experimental}"></ha-svg-icon>`,
|
||||
icon_deprecated: `<ha-svg-icon path="${STAGE_ICON.deprecated}"></ha-svg-icon>`,
|
||||
}
|
||||
)
|
||||
: this.supervisor.localize(
|
||||
`addon.dashboard.capability.${id}.description`
|
||||
@@ -867,11 +867,9 @@ class HassioAddonInfo extends LitElement {
|
||||
};
|
||||
fireEvent(this, "hass-api-called", eventdata);
|
||||
} catch (err: any) {
|
||||
this._error = this.supervisor.localize(
|
||||
"addon.failed_to_save",
|
||||
"error",
|
||||
extractApiErrorMessage(err)
|
||||
);
|
||||
this._error = this.supervisor.localize("addon.failed_to_save", {
|
||||
error: extractApiErrorMessage(err),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -889,11 +887,9 @@ class HassioAddonInfo extends LitElement {
|
||||
};
|
||||
fireEvent(this, "hass-api-called", eventdata);
|
||||
} catch (err: any) {
|
||||
this._error = this.supervisor.localize(
|
||||
"addon.failed_to_save",
|
||||
"error",
|
||||
extractApiErrorMessage(err)
|
||||
);
|
||||
this._error = this.supervisor.localize("addon.failed_to_save", {
|
||||
error: extractApiErrorMessage(err),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -911,11 +907,9 @@ class HassioAddonInfo extends LitElement {
|
||||
};
|
||||
fireEvent(this, "hass-api-called", eventdata);
|
||||
} catch (err: any) {
|
||||
this._error = this.supervisor.localize(
|
||||
"addon.failed_to_save",
|
||||
"error",
|
||||
extractApiErrorMessage(err)
|
||||
);
|
||||
this._error = this.supervisor.localize("addon.failed_to_save", {
|
||||
error: extractApiErrorMessage(err),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -933,11 +927,9 @@ class HassioAddonInfo extends LitElement {
|
||||
};
|
||||
fireEvent(this, "hass-api-called", eventdata);
|
||||
} catch (err: any) {
|
||||
this._error = this.supervisor.localize(
|
||||
"addon.failed_to_save",
|
||||
"error",
|
||||
extractApiErrorMessage(err)
|
||||
);
|
||||
this._error = this.supervisor.localize("addon.failed_to_save", {
|
||||
error: extractApiErrorMessage(err),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -955,11 +947,9 @@ class HassioAddonInfo extends LitElement {
|
||||
};
|
||||
fireEvent(this, "hass-api-called", eventdata);
|
||||
} catch (err: any) {
|
||||
this._error = this.supervisor.localize(
|
||||
"addon.failed_to_save",
|
||||
"error",
|
||||
extractApiErrorMessage(err)
|
||||
);
|
||||
this._error = this.supervisor.localize("addon.failed_to_save", {
|
||||
error: extractApiErrorMessage(err),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -18,7 +18,9 @@ class HassioAddonLogDashboard extends LitElement {
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (!this.addon) {
|
||||
return html` <ha-circular-progress active></ha-circular-progress> `;
|
||||
return html`
|
||||
<ha-circular-progress indeterminate></ha-circular-progress>
|
||||
`;
|
||||
}
|
||||
return html`
|
||||
<div class="content">
|
||||
|
@@ -72,11 +72,9 @@ class HassioAddonLogs extends LitElement {
|
||||
try {
|
||||
this._content = await fetchHassioAddonLogs(this.hass, this.addon.slug);
|
||||
} catch (err: any) {
|
||||
this._error = this.supervisor.localize(
|
||||
"addon.logs.get_logs",
|
||||
"error",
|
||||
extractApiErrorMessage(err)
|
||||
);
|
||||
this._error = this.supervisor.localize("addon.logs.get_logs", {
|
||||
error: extractApiErrorMessage(err),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -6,7 +6,7 @@ export function filterAndSort(addons: StoreAddon[], filter: string) {
|
||||
const options: IFuseOptions<StoreAddon> = {
|
||||
keys: ["name", "description", "slug"],
|
||||
isCaseSensitive: false,
|
||||
minMatchCharLength: 2,
|
||||
minMatchCharLength: Math.min(filter.length, 2),
|
||||
threshold: 0.2,
|
||||
};
|
||||
const fuse = new Fuse(addons, options);
|
||||
|
@@ -17,8 +17,11 @@ class SupervisorFormfieldLabel extends LitElement {
|
||||
${this.imageUrl
|
||||
? html`<img loading="lazy" alt="" src=${this.imageUrl} class="icon" />`
|
||||
: this.iconPath
|
||||
? html`<ha-svg-icon .path=${this.iconPath} class="icon"></ha-svg-icon>`
|
||||
: ""}
|
||||
? html`<ha-svg-icon
|
||||
.path=${this.iconPath}
|
||||
class="icon"
|
||||
></ha-svg-icon>`
|
||||
: ""}
|
||||
<span class="label">${this.label}</span>
|
||||
${this.version
|
||||
? html`<span class="version">(${this.version})</span>`
|
||||
|
@@ -68,17 +68,19 @@ class HassioAddons extends LitElement {
|
||||
.iconTitle=${addon.state !== "started"
|
||||
? this.supervisor.localize("dashboard.addon_stopped")
|
||||
: addon.update_available!
|
||||
? this.supervisor.localize(
|
||||
"dashboard.addon_new_version"
|
||||
)
|
||||
: this.supervisor.localize("dashboard.addon_running")}
|
||||
? this.supervisor.localize(
|
||||
"dashboard.addon_new_version"
|
||||
)
|
||||
: this.supervisor.localize(
|
||||
"dashboard.addon_running"
|
||||
)}
|
||||
.iconClass=${addon.update_available
|
||||
? addon.state === "started"
|
||||
? "update"
|
||||
: "update stopped"
|
||||
: addon.state === "started"
|
||||
? "running"
|
||||
: "stopped"}
|
||||
? "running"
|
||||
: "stopped"}
|
||||
.iconImage=${atLeastVersion(
|
||||
this.hass.config.version,
|
||||
0,
|
||||
|
@@ -46,11 +46,9 @@ export class HassioUpdate extends LitElement {
|
||||
return html`
|
||||
<div class="content">
|
||||
<h1>
|
||||
${this.supervisor.localize(
|
||||
"common.update_available",
|
||||
"count",
|
||||
updatesAvailable
|
||||
)}
|
||||
${this.supervisor.localize("common.update_available", {
|
||||
count: updatesAvailable,
|
||||
})}
|
||||
🎉
|
||||
</h1>
|
||||
<div class="card-group">
|
||||
|
@@ -95,7 +95,7 @@ class HassioBackupDialog
|
||||
</ha-header-bar>
|
||||
</div>
|
||||
${this._restoringBackup
|
||||
? html`<ha-circular-progress active></ha-circular-progress>`
|
||||
? html`<ha-circular-progress indeterminate></ha-circular-progress>`
|
||||
: html`
|
||||
<supervisor-backup-content
|
||||
.hass=${this.hass}
|
||||
|
@@ -57,7 +57,7 @@ class HassioCreateBackupDialog extends LitElement {
|
||||
)}
|
||||
>
|
||||
${this._creatingBackup
|
||||
? html`<ha-circular-progress active></ha-circular-progress>`
|
||||
? html`<ha-circular-progress indeterminate></ha-circular-progress>`
|
||||
: html`<supervisor-backup-content
|
||||
.hass=${this.hass}
|
||||
.supervisor=${this._dialogParams.supervisor}
|
||||
|
@@ -4,7 +4,6 @@ import { customElement, property, state } from "lit/decorators";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { fireEvent } from "../../../../src/common/dom/fire_event";
|
||||
import "../../../../src/components/ha-circular-progress";
|
||||
import "../../../../src/components/ha-markdown";
|
||||
import "../../../../src/components/ha-select";
|
||||
import {
|
||||
extractApiErrorMessage,
|
||||
@@ -71,7 +70,11 @@ class HassioDatadiskDialog extends LitElement {
|
||||
?hideActions=${this.moving}
|
||||
>
|
||||
${this.moving
|
||||
? html` <ha-circular-progress alt="Moving" size="large" active>
|
||||
? html` <ha-circular-progress
|
||||
aria-label="Moving"
|
||||
size="large"
|
||||
indeterminate
|
||||
>
|
||||
</ha-circular-progress>
|
||||
<p class="progress-text">
|
||||
${this.dialogParams.supervisor.localize(
|
||||
@@ -105,12 +108,12 @@ class HassioDatadiskDialog extends LitElement {
|
||||
</ha-select>
|
||||
`
|
||||
: this.devices === undefined
|
||||
? this.dialogParams.supervisor.localize(
|
||||
"dialog.datadisk_move.loading_devices"
|
||||
)
|
||||
: this.dialogParams.supervisor.localize(
|
||||
"dialog.datadisk_move.no_devices"
|
||||
)}
|
||||
? this.dialogParams.supervisor.localize(
|
||||
"dialog.datadisk_move.loading_devices"
|
||||
)
|
||||
: this.dialogParams.supervisor.localize(
|
||||
"dialog.datadisk_move.no_devices"
|
||||
)}
|
||||
|
||||
<mwc-button
|
||||
slot="secondaryAction"
|
||||
|
@@ -145,8 +145,7 @@ export class DialogHassioNetwork
|
||||
? html`<p>
|
||||
${this.supervisor.localize(
|
||||
"dialog.network.connected_to",
|
||||
"ssid",
|
||||
this._interface?.wifi?.ssid
|
||||
{ ssid: this._interface?.wifi?.ssid }
|
||||
)}
|
||||
</p>`
|
||||
: ""}
|
||||
@@ -156,7 +155,11 @@ export class DialogHassioNetwork
|
||||
.disabled=${this._scanning}
|
||||
>
|
||||
${this._scanning
|
||||
? html`<ha-circular-progress active size="small">
|
||||
? html`<ha-circular-progress
|
||||
aria-label="Scanning"
|
||||
indeterminate
|
||||
size="small"
|
||||
>
|
||||
</ha-circular-progress>`
|
||||
: this.supervisor.localize("dialog.network.scan_ap")}
|
||||
</mwc-button>
|
||||
@@ -275,7 +278,7 @@ export class DialogHassioNetwork
|
||||
</mwc-button>
|
||||
<mwc-button @click=${this._updateNetwork} .disabled=${!this._dirty}>
|
||||
${this._processing
|
||||
? html`<ha-circular-progress active size="small">
|
||||
? html`<ha-circular-progress indeterminate size="small">
|
||||
</ha-circular-progress>`
|
||||
: this.supervisor.localize("common.save")}
|
||||
</mwc-button>
|
||||
|
@@ -158,7 +158,7 @@ class HassioRepositoriesDialog extends LitElement {
|
||||
<mwc-button @click=${this._addRepository}>
|
||||
${this._processing
|
||||
? html`<ha-circular-progress
|
||||
active
|
||||
indeterminate
|
||||
size="small"
|
||||
></ha-circular-progress>`
|
||||
: this._dialogParams!.supervisor.localize(
|
||||
|
@@ -76,17 +76,15 @@ class HassioMyRedirect extends LitElement {
|
||||
const redirect = REDIRECTS[path];
|
||||
|
||||
if (!redirect) {
|
||||
this._error = this.supervisor.localize(
|
||||
"my.not_supported",
|
||||
"link",
|
||||
html`<a
|
||||
this._error = this.supervisor.localize("my.not_supported", {
|
||||
link: html`<a
|
||||
target="_blank"
|
||||
rel="noreferrer noopener"
|
||||
href="https://my.home-assistant.io/faq.html#supported-pages"
|
||||
>
|
||||
${this.supervisor.localize("my.faq_link")}
|
||||
</a>`
|
||||
);
|
||||
</a>`,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
|
@@ -67,8 +67,8 @@ class HassioRouter extends HassRouterPage {
|
||||
const route = hassioPanel
|
||||
? this.route
|
||||
: ingressPanel && this.panel.config?.ingress
|
||||
? this._ingressRoute(this.panel.config?.ingress)
|
||||
: this.routeTail;
|
||||
? this._ingressRoute(this.panel.config?.ingress)
|
||||
: this.routeTail;
|
||||
|
||||
el.hass = this.hass;
|
||||
el.narrow = this.narrow;
|
||||
|
@@ -96,13 +96,11 @@ class HassioCoreInfo extends LitElement {
|
||||
slot="primaryAction"
|
||||
class="warning"
|
||||
@click=${this._coreRestart}
|
||||
.title=${this.supervisor.localize(
|
||||
"common.restart_name",
|
||||
"name",
|
||||
"Core"
|
||||
)}
|
||||
.title=${this.supervisor.localize("common.restart_name", {
|
||||
name: "Core",
|
||||
})}
|
||||
>
|
||||
${this.supervisor.localize("common.restart_name", "name", "Core")}
|
||||
${this.supervisor.localize("common.restart_name", { name: "Core" })}
|
||||
</ha-progress-button>
|
||||
</div>
|
||||
</ha-card>
|
||||
@@ -122,16 +120,12 @@ class HassioCoreInfo extends LitElement {
|
||||
button.progress = true;
|
||||
|
||||
const confirmed = await showConfirmationDialog(this, {
|
||||
title: this.supervisor.localize(
|
||||
"confirm.restart.title",
|
||||
"name",
|
||||
"Home Assistant Core"
|
||||
),
|
||||
text: this.supervisor.localize(
|
||||
"confirm.restart.text",
|
||||
"name",
|
||||
"Home Assistant Core"
|
||||
),
|
||||
title: this.supervisor.localize("confirm.restart.title", {
|
||||
name: "Home Assistant Core",
|
||||
}),
|
||||
text: this.supervisor.localize("confirm.restart.text", {
|
||||
name: "Home Assistant Core",
|
||||
}),
|
||||
confirmText: this.supervisor.localize("common.restart"),
|
||||
dismissText: this.supervisor.localize("common.cancel"),
|
||||
});
|
||||
@@ -146,11 +140,9 @@ class HassioCoreInfo extends LitElement {
|
||||
} catch (err: any) {
|
||||
if (this.hass.connection.connected) {
|
||||
showAlertDialog(this, {
|
||||
title: this.supervisor.localize(
|
||||
"common.failed_to_restart_name",
|
||||
"name",
|
||||
"Home AssistantCore"
|
||||
),
|
||||
title: this.supervisor.localize("common.failed_to_restart_name", {
|
||||
name: "Home Assistant Core",
|
||||
}),
|
||||
text: extractApiErrorMessage(err),
|
||||
});
|
||||
}
|
||||
|
@@ -109,19 +109,19 @@ class HassioSupervisorInfo extends LitElement {
|
||||
</ha-progress-button>
|
||||
`
|
||||
: this.supervisor.supervisor.channel === "stable"
|
||||
? html`
|
||||
<ha-progress-button
|
||||
@click=${this._toggleBeta}
|
||||
.title=${this.supervisor.localize(
|
||||
"system.supervisor.join_beta_description"
|
||||
)}
|
||||
>
|
||||
${this.supervisor.localize(
|
||||
"system.supervisor.join_beta_action"
|
||||
)}
|
||||
</ha-progress-button>
|
||||
`
|
||||
: ""}
|
||||
? html`
|
||||
<ha-progress-button
|
||||
@click=${this._toggleBeta}
|
||||
.title=${this.supervisor.localize(
|
||||
"system.supervisor.join_beta_description"
|
||||
)}
|
||||
>
|
||||
${this.supervisor.localize(
|
||||
"system.supervisor.join_beta_action"
|
||||
)}
|
||||
</ha-progress-button>
|
||||
`
|
||||
: ""}
|
||||
</ha-settings-row>
|
||||
|
||||
${this.supervisor.supervisor.supported
|
||||
@@ -200,17 +200,13 @@ class HassioSupervisorInfo extends LitElement {
|
||||
<ha-progress-button
|
||||
class="warning"
|
||||
@click=${this._supervisorRestart}
|
||||
.title=${this.supervisor.localize(
|
||||
"common.restart_name",
|
||||
"name",
|
||||
"Supervisor"
|
||||
)}
|
||||
.title=${this.supervisor.localize("common.restart_name", {
|
||||
name: "Supervisor",
|
||||
})}
|
||||
>
|
||||
${this.supervisor.localize(
|
||||
"common.restart_name",
|
||||
"name",
|
||||
"Supervisor"
|
||||
)}
|
||||
${this.supervisor.localize("common.restart_name", {
|
||||
name: "Supervisor",
|
||||
})}
|
||||
</ha-progress-button>
|
||||
</div>
|
||||
</ha-card>
|
||||
@@ -292,16 +288,12 @@ class HassioSupervisorInfo extends LitElement {
|
||||
button.progress = true;
|
||||
|
||||
const confirmed = await showConfirmationDialog(this, {
|
||||
title: this.supervisor.localize(
|
||||
"confirm.restart.title",
|
||||
"name",
|
||||
"Supervisor"
|
||||
),
|
||||
text: this.supervisor.localize(
|
||||
"confirm.restart.text",
|
||||
"name",
|
||||
"Supervisor"
|
||||
),
|
||||
title: this.supervisor.localize("confirm.restart.title", {
|
||||
name: "Supervisor",
|
||||
}),
|
||||
text: this.supervisor.localize("confirm.restart.text", {
|
||||
name: "Supervisor",
|
||||
}),
|
||||
confirmText: this.supervisor.localize("common.restart"),
|
||||
dismissText: this.supervisor.localize("common.cancel"),
|
||||
});
|
||||
@@ -315,11 +307,9 @@ class HassioSupervisorInfo extends LitElement {
|
||||
await restartSupervisor(this.hass);
|
||||
} catch (err: any) {
|
||||
showAlertDialog(this, {
|
||||
title: this.supervisor.localize(
|
||||
"common.failed_to_restart_name",
|
||||
"name",
|
||||
"Supervisor"
|
||||
),
|
||||
title: this.supervisor.localize("common.failed_to_restart_name", {
|
||||
name: "Supervisor",
|
||||
}),
|
||||
text: extractApiErrorMessage(err),
|
||||
});
|
||||
} finally {
|
||||
@@ -334,8 +324,7 @@ class HassioSupervisorInfo extends LitElement {
|
||||
),
|
||||
text: this.supervisor.localize(
|
||||
"system.supervisor.share_diagonstics_description",
|
||||
"line_break",
|
||||
html`<br /><br />`
|
||||
{ line_break: html`<br /><br />` }
|
||||
),
|
||||
});
|
||||
}
|
||||
|
@@ -124,13 +124,10 @@ class HassioSupervisorLog extends LitElement {
|
||||
this._selectedLogProvider
|
||||
);
|
||||
} catch (err: any) {
|
||||
this._error = this.supervisor.localize(
|
||||
"system.log.get_logs",
|
||||
"provider",
|
||||
this._selectedLogProvider,
|
||||
"error",
|
||||
extractApiErrorMessage(err)
|
||||
);
|
||||
this._error = this.supervisor.localize("system.log.get_logs", {
|
||||
provider: this._selectedLogProvider,
|
||||
error: extractApiErrorMessage(err),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -70,8 +70,8 @@ const changelogUrl = (
|
||||
return version.includes("dev")
|
||||
? "https://github.com/home-assistant/core/commits/dev"
|
||||
: version.includes("b")
|
||||
? "https://next.home-assistant.io/latest-release-notes/"
|
||||
: "https://www.home-assistant.io/latest-release-notes/";
|
||||
? "https://next.home-assistant.io/latest-release-notes/"
|
||||
: "https://www.home-assistant.io/latest-release-notes/";
|
||||
}
|
||||
if (entry === "os") {
|
||||
return version.includes("dev")
|
||||
@@ -141,44 +141,51 @@ class UpdateAvailableCard extends LitElement {
|
||||
})}
|
||||
</p>`
|
||||
: !this._updating
|
||||
? html`
|
||||
${this._changelogContent
|
||||
? html`
|
||||
<ha-faded>
|
||||
<ha-markdown .content=${this._changelogContent}>
|
||||
</ha-markdown>
|
||||
</ha-faded>
|
||||
`
|
||||
: ""}
|
||||
<div class="versions">
|
||||
<p>
|
||||
${this.supervisor.localize("update_available.description", {
|
||||
? html`
|
||||
${this._changelogContent
|
||||
? html`
|
||||
<ha-faded>
|
||||
<ha-markdown .content=${this._changelogContent}>
|
||||
</ha-markdown>
|
||||
</ha-faded>
|
||||
`
|
||||
: ""}
|
||||
<div class="versions">
|
||||
<p>
|
||||
${this.supervisor.localize(
|
||||
"update_available.description",
|
||||
{
|
||||
name: this._name,
|
||||
version: this._version,
|
||||
newest_version: this._version_latest,
|
||||
}
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
${["core", "addon"].includes(this._updateType)
|
||||
? html`
|
||||
<ha-formfield
|
||||
.label=${this.supervisor.localize(
|
||||
"update_available.create_backup"
|
||||
)}
|
||||
>
|
||||
<ha-checkbox checked></ha-checkbox>
|
||||
</ha-formfield>
|
||||
`
|
||||
: ""}
|
||||
`
|
||||
: html`<ha-circular-progress
|
||||
aria-label="Updating"
|
||||
size="large"
|
||||
indeterminate
|
||||
>
|
||||
</ha-circular-progress>
|
||||
<p class="progress-text">
|
||||
${this.supervisor.localize("update_available.updating", {
|
||||
name: this._name,
|
||||
version: this._version,
|
||||
newest_version: this._version_latest,
|
||||
version: this._version_latest,
|
||||
})}
|
||||
</p>
|
||||
</div>
|
||||
${["core", "addon"].includes(this._updateType)
|
||||
? html`
|
||||
<ha-formfield
|
||||
.label=${this.supervisor.localize(
|
||||
"update_available.create_backup"
|
||||
)}
|
||||
>
|
||||
<ha-checkbox checked></ha-checkbox>
|
||||
</ha-formfield>
|
||||
`
|
||||
: ""}
|
||||
`
|
||||
: html`<ha-circular-progress alt="Updating" size="large" active>
|
||||
</ha-circular-progress>
|
||||
<p class="progress-text">
|
||||
${this.supervisor.localize("update_available.updating", {
|
||||
name: this._name,
|
||||
version: this._version_latest,
|
||||
})}
|
||||
</p>`}
|
||||
</p>`}
|
||||
</div>
|
||||
${this._version !== this._version_latest && !this._updating
|
||||
? html`
|
||||
|
145
package.json
145
package.json
@@ -25,42 +25,41 @@
|
||||
"license": "Apache-2.0",
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "7.23.2",
|
||||
"@braintree/sanitize-url": "6.0.4",
|
||||
"@codemirror/autocomplete": "6.11.0",
|
||||
"@codemirror/commands": "6.3.0",
|
||||
"@codemirror/language": "6.9.2",
|
||||
"@babel/runtime": "7.23.6",
|
||||
"@braintree/sanitize-url": "7.0.0",
|
||||
"@codemirror/autocomplete": "6.11.1",
|
||||
"@codemirror/commands": "6.3.2",
|
||||
"@codemirror/language": "6.9.3",
|
||||
"@codemirror/legacy-modes": "6.3.3",
|
||||
"@codemirror/search": "6.5.4",
|
||||
"@codemirror/state": "6.3.1",
|
||||
"@codemirror/view": "6.22.0",
|
||||
"@codemirror/search": "6.5.5",
|
||||
"@codemirror/state": "6.3.3",
|
||||
"@codemirror/view": "6.22.3",
|
||||
"@egjs/hammerjs": "2.0.17",
|
||||
"@formatjs/intl-datetimeformat": "6.11.2",
|
||||
"@formatjs/intl-displaynames": "6.6.2",
|
||||
"@formatjs/intl-datetimeformat": "6.12.0",
|
||||
"@formatjs/intl-displaynames": "6.6.4",
|
||||
"@formatjs/intl-getcanonicallocales": "2.3.0",
|
||||
"@formatjs/intl-listformat": "7.5.1",
|
||||
"@formatjs/intl-locale": "3.4.1",
|
||||
"@formatjs/intl-numberformat": "8.8.1",
|
||||
"@formatjs/intl-pluralrules": "5.2.8",
|
||||
"@formatjs/intl-relativetimeformat": "11.2.8",
|
||||
"@fullcalendar/core": "6.1.9",
|
||||
"@fullcalendar/daygrid": "6.1.9",
|
||||
"@fullcalendar/interaction": "6.1.9",
|
||||
"@fullcalendar/list": "6.1.9",
|
||||
"@fullcalendar/luxon3": "6.1.9",
|
||||
"@fullcalendar/timegrid": "6.1.9",
|
||||
"@lezer/highlight": "1.1.6",
|
||||
"@formatjs/intl-listformat": "7.5.3",
|
||||
"@formatjs/intl-locale": "3.4.3",
|
||||
"@formatjs/intl-numberformat": "8.9.0",
|
||||
"@formatjs/intl-pluralrules": "5.2.10",
|
||||
"@formatjs/intl-relativetimeformat": "11.2.10",
|
||||
"@fullcalendar/core": "6.1.10",
|
||||
"@fullcalendar/daygrid": "6.1.10",
|
||||
"@fullcalendar/interaction": "6.1.10",
|
||||
"@fullcalendar/list": "6.1.10",
|
||||
"@fullcalendar/luxon3": "6.1.10",
|
||||
"@fullcalendar/timegrid": "6.1.10",
|
||||
"@lezer/highlight": "1.2.0",
|
||||
"@lit-labs/context": "0.4.1",
|
||||
"@lit-labs/motion": "1.0.6",
|
||||
"@lit-labs/observers": "2.0.2",
|
||||
"@lit-labs/virtualizer": "2.0.10",
|
||||
"@lit-labs/virtualizer": "2.0.11",
|
||||
"@lrnwebcomponents/simple-tooltip": "7.0.18",
|
||||
"@material/chips": "=14.0.0-canary.53b3cad2f.0",
|
||||
"@material/data-table": "=14.0.0-canary.53b3cad2f.0",
|
||||
"@material/mwc-base": "0.27.0",
|
||||
"@material/mwc-button": "0.27.0",
|
||||
"@material/mwc-checkbox": "0.27.0",
|
||||
"@material/mwc-circular-progress": "0.27.0",
|
||||
"@material/mwc-dialog": "0.27.0",
|
||||
"@material/mwc-drawer": "0.27.0",
|
||||
"@material/mwc-fab": "0.27.0",
|
||||
@@ -81,12 +80,9 @@
|
||||
"@material/mwc-top-app-bar": "0.27.0",
|
||||
"@material/mwc-top-app-bar-fixed": "0.27.0",
|
||||
"@material/top-app-bar": "=14.0.0-canary.53b3cad2f.0",
|
||||
"@material/web": "=1.0.1",
|
||||
"@mdi/js": "7.3.67",
|
||||
"@mdi/svg": "7.3.67",
|
||||
"@polymer/iron-flex-layout": "3.0.1",
|
||||
"@polymer/iron-input": "3.0.1",
|
||||
"@polymer/iron-resizable-behavior": "3.0.1",
|
||||
"@material/web": "=1.1.1",
|
||||
"@mdi/js": "7.4.47",
|
||||
"@mdi/svg": "7.4.47",
|
||||
"@polymer/paper-input": "3.2.1",
|
||||
"@polymer/paper-item": "3.0.1",
|
||||
"@polymer/paper-listbox": "3.0.1",
|
||||
@@ -94,8 +90,8 @@
|
||||
"@polymer/paper-toast": "3.0.1",
|
||||
"@polymer/polymer": "3.5.1",
|
||||
"@thomasloven/round-slider": "0.6.0",
|
||||
"@vaadin/combo-box": "24.2.2",
|
||||
"@vaadin/vaadin-themable-mixin": "24.2.2",
|
||||
"@vaadin/combo-box": "24.3.2",
|
||||
"@vaadin/vaadin-themable-mixin": "24.3.2",
|
||||
"@vibrant/color": "3.2.1-alpha.1",
|
||||
"@vibrant/core": "3.2.1-alpha.1",
|
||||
"@vibrant/quantizer-mmcq": "3.2.1-alpha.1",
|
||||
@@ -103,36 +99,36 @@
|
||||
"@webcomponents/scoped-custom-element-registry": "0.0.9",
|
||||
"@webcomponents/webcomponentsjs": "2.8.0",
|
||||
"app-datepicker": "5.1.1",
|
||||
"chart.js": "4.4.0",
|
||||
"chart.js": "4.4.1",
|
||||
"comlink": "4.4.1",
|
||||
"core-js": "3.33.2",
|
||||
"core-js": "3.34.0",
|
||||
"cropperjs": "1.6.1",
|
||||
"date-fns": "2.30.0",
|
||||
"date-fns-tz": "2.0.0",
|
||||
"deep-clone-simple": "1.1.1",
|
||||
"deep-freeze": "0.0.1",
|
||||
"element-internals-polyfill": "1.3.10",
|
||||
"fuse.js": "7.0.0",
|
||||
"google-timezones-json": "1.2.0",
|
||||
"hls.js": "1.4.12",
|
||||
"hls.js": "1.4.14",
|
||||
"home-assistant-js-websocket": "9.1.0",
|
||||
"idb-keyval": "6.2.1",
|
||||
"intl-messageformat": "10.5.5",
|
||||
"intl-messageformat": "10.5.8",
|
||||
"js-yaml": "4.1.0",
|
||||
"leaflet": "1.9.4",
|
||||
"leaflet-draw": "1.0.4",
|
||||
"lit": "2.8.0",
|
||||
"luxon": "3.4.3",
|
||||
"marked": "9.1.6",
|
||||
"luxon": "3.4.4",
|
||||
"marked": "11.1.0",
|
||||
"memoize-one": "6.0.0",
|
||||
"node-vibrant": "3.2.1-alpha.1",
|
||||
"proxy-polyfill": "0.3.2",
|
||||
"punycode": "2.3.1",
|
||||
"qr-scanner": "1.4.2",
|
||||
"qrcode": "1.5.3",
|
||||
"resize-observer-polyfill": "1.5.1",
|
||||
"roboto-fontface": "0.10.0",
|
||||
"rrule": "2.7.2",
|
||||
"sortablejs": "1.15.0",
|
||||
"rrule": "2.8.1",
|
||||
"sortablejs": "1.15.1",
|
||||
"stacktrace-js": "2.0.2",
|
||||
"superstruct": "1.0.3",
|
||||
"tinykeys": "2.1.0",
|
||||
@@ -140,9 +136,9 @@
|
||||
"tsparticles-preset-links": "2.12.0",
|
||||
"ua-parser-js": "1.0.37",
|
||||
"unfetch": "5.0.0",
|
||||
"vis-data": "7.1.8",
|
||||
"vis-data": "7.1.9",
|
||||
"vis-network": "9.1.9",
|
||||
"vue": "2.7.15",
|
||||
"vue": "2.7.16",
|
||||
"vue2-daterange-picker": "0.6.8",
|
||||
"weekstart": "2.0.0",
|
||||
"workbox-cacheable-response": "7.0.0",
|
||||
@@ -154,21 +150,22 @@
|
||||
"xss": "1.0.14"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "7.23.3",
|
||||
"@babel/plugin-proposal-decorators": "7.23.3",
|
||||
"@babel/plugin-transform-runtime": "7.23.3",
|
||||
"@babel/preset-env": "7.23.3",
|
||||
"@babel/core": "7.23.6",
|
||||
"@babel/helper-define-polyfill-provider": "0.4.4",
|
||||
"@babel/plugin-proposal-decorators": "7.23.6",
|
||||
"@babel/plugin-transform-runtime": "7.23.6",
|
||||
"@babel/preset-env": "7.23.6",
|
||||
"@babel/preset-typescript": "7.23.3",
|
||||
"@bundle-stats/plugin-webpack-filter": "4.8.0",
|
||||
"@koa/cors": "4.0.0",
|
||||
"@lokalise/node-api": "12.0.0",
|
||||
"@bundle-stats/plugin-webpack-filter": "4.8.3",
|
||||
"@koa/cors": "5.0.0",
|
||||
"@lokalise/node-api": "12.1.0",
|
||||
"@octokit/auth-oauth-device": "6.0.1",
|
||||
"@octokit/plugin-retry": "6.0.1",
|
||||
"@octokit/rest": "20.0.2",
|
||||
"@open-wc/dev-server-hmr": "0.1.4",
|
||||
"@rollup/plugin-babel": "6.0.4",
|
||||
"@rollup/plugin-commonjs": "25.0.7",
|
||||
"@rollup/plugin-json": "6.0.1",
|
||||
"@rollup/plugin-json": "6.1.0",
|
||||
"@rollup/plugin-node-resolve": "15.2.3",
|
||||
"@rollup/plugin-replace": "5.0.5",
|
||||
"@types/babel__plugin-transform-runtime": "7.9.5",
|
||||
@@ -178,36 +175,36 @@
|
||||
"@types/html-minifier-terser": "7.0.2",
|
||||
"@types/js-yaml": "4.0.9",
|
||||
"@types/leaflet": "1.9.8",
|
||||
"@types/leaflet-draw": "1.0.10",
|
||||
"@types/luxon": "3.3.4",
|
||||
"@types/mocha": "10.0.4",
|
||||
"@types/leaflet-draw": "1.0.11",
|
||||
"@types/luxon": "3.3.7",
|
||||
"@types/mocha": "10.0.6",
|
||||
"@types/qrcode": "1.5.5",
|
||||
"@types/serve-handler": "6.1.4",
|
||||
"@types/sortablejs": "1.15.5",
|
||||
"@types/tar": "6.1.9",
|
||||
"@types/sortablejs": "1.15.7",
|
||||
"@types/tar": "6.1.10",
|
||||
"@types/ua-parser-js": "0.7.39",
|
||||
"@types/webspeechapi": "0.0.29",
|
||||
"@typescript-eslint/eslint-plugin": "6.10.0",
|
||||
"@typescript-eslint/parser": "6.10.0",
|
||||
"@typescript-eslint/eslint-plugin": "6.16.0",
|
||||
"@typescript-eslint/parser": "6.16.0",
|
||||
"@web/dev-server": "0.1.38",
|
||||
"@web/dev-server-rollup": "0.4.1",
|
||||
"babel-loader": "9.1.3",
|
||||
"babel-plugin-template-html-minifier": "4.1.0",
|
||||
"chai": "4.3.10",
|
||||
"chai": "5.0.0",
|
||||
"del": "7.1.0",
|
||||
"eslint": "8.53.0",
|
||||
"eslint": "8.56.0",
|
||||
"eslint-config-airbnb-base": "15.0.0",
|
||||
"eslint-config-airbnb-typescript": "17.1.0",
|
||||
"eslint-config-prettier": "9.0.0",
|
||||
"eslint-config-prettier": "9.1.0",
|
||||
"eslint-import-resolver-webpack": "0.13.8",
|
||||
"eslint-plugin-disable": "2.0.3",
|
||||
"eslint-plugin-import": "2.29.0",
|
||||
"eslint-plugin-lit": "1.10.1",
|
||||
"eslint-plugin-import": "2.29.1",
|
||||
"eslint-plugin-lit": "1.11.0",
|
||||
"eslint-plugin-lit-a11y": "4.1.1",
|
||||
"eslint-plugin-unused-imports": "3.0.0",
|
||||
"eslint-plugin-wc": "2.0.4",
|
||||
"fancy-log": "2.0.0",
|
||||
"fs-extra": "11.1.1",
|
||||
"fs-extra": "11.2.0",
|
||||
"glob": "10.3.10",
|
||||
"gulp": "4.0.2",
|
||||
"gulp-flatmap": "1.0.2",
|
||||
@@ -219,28 +216,28 @@
|
||||
"husky": "8.0.3",
|
||||
"instant-mocha": "1.5.2",
|
||||
"jszip": "3.10.1",
|
||||
"lint-staged": "15.1.0",
|
||||
"lit-analyzer": "2.0.1",
|
||||
"lint-staged": "15.2.0",
|
||||
"lit-analyzer": "2.0.2",
|
||||
"lodash.template": "4.5.0",
|
||||
"magic-string": "0.30.5",
|
||||
"map-stream": "0.0.7",
|
||||
"mocha": "10.2.0",
|
||||
"object-hash": "3.0.0",
|
||||
"open": "9.1.0",
|
||||
"open": "10.0.2",
|
||||
"pinst": "3.0.0",
|
||||
"prettier": "3.0.3",
|
||||
"prettier": "3.1.1",
|
||||
"rollup": "2.79.1",
|
||||
"rollup-plugin-string": "3.0.0",
|
||||
"rollup-plugin-terser": "7.0.2",
|
||||
"rollup-plugin-visualizer": "5.9.2",
|
||||
"rollup-plugin-visualizer": "5.12.0",
|
||||
"serve-handler": "6.1.5",
|
||||
"sinon": "17.0.1",
|
||||
"source-map-url": "0.4.1",
|
||||
"systemjs": "6.14.2",
|
||||
"tar": "6.2.0",
|
||||
"terser-webpack-plugin": "5.3.9",
|
||||
"terser-webpack-plugin": "5.3.10",
|
||||
"ts-lit-plugin": "2.0.1",
|
||||
"typescript": "5.2.2",
|
||||
"typescript": "5.3.3",
|
||||
"vinyl-buffer": "1.0.1",
|
||||
"vinyl-source-stream": "2.0.0",
|
||||
"webpack": "5.89.0",
|
||||
@@ -248,7 +245,7 @@
|
||||
"webpack-dev-server": "4.15.1",
|
||||
"webpack-manifest-plugin": "5.0.0",
|
||||
"webpack-stats-plugin": "1.1.3",
|
||||
"webpackbar": "5.0.2",
|
||||
"webpackbar": "6.0.0",
|
||||
"workbox-build": "7.0.0"
|
||||
},
|
||||
"_comment": "Polymer 3.2 contained a bug, fixed in https://github.com/Polymer/polymer/pull/5569, add as patch",
|
||||
@@ -256,10 +253,10 @@
|
||||
"@polymer/polymer": "patch:@polymer/polymer@3.5.1#./.yarn/patches/@polymer/polymer/pr-5569.patch",
|
||||
"@material/mwc-button@^0.25.3": "^0.27.0",
|
||||
"lit": "2.8.0",
|
||||
"clean-css": "5.3.2",
|
||||
"clean-css": "5.3.3",
|
||||
"@lit/reactive-element": "1.6.3",
|
||||
"sortablejs@1.15.0": "patch:sortablejs@npm%3A1.15.0#./.yarn/patches/sortablejs-npm-1.15.0-f3a393abcc.patch",
|
||||
"leaflet-draw@1.0.4": "patch:leaflet-draw@npm%3A1.0.4#./.yarn/patches/leaflet-draw-npm-1.0.4-0ca0ebcf65.patch"
|
||||
},
|
||||
"packageManager": "yarn@4.0.1"
|
||||
"packageManager": "yarn@4.0.2"
|
||||
}
|
||||
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "home-assistant-frontend"
|
||||
version = "20231030.0"
|
||||
version = "20240103.0"
|
||||
license = {text = "Apache-2.0"}
|
||||
description = "The Home Assistant frontend"
|
||||
readme = "README.md"
|
||||
|
@@ -40,4 +40,5 @@ docker run \
|
||||
--file /opt/src/${LOCAL_FILE} \
|
||||
--lang-iso ${LANG_ISO} \
|
||||
--convert-placeholders=false \
|
||||
--replace-modified=true
|
||||
--replace-modified=true \
|
||||
# --cleanup-mode=true
|
||||
|
@@ -8,12 +8,20 @@ import "../components/ha-alert";
|
||||
import "../components/ha-checkbox";
|
||||
import { computeInitialHaFormData } from "../components/ha-form/compute-initial-ha-form-data";
|
||||
import "../components/ha-formfield";
|
||||
import { AuthProvider, autocompleteLoginFields } from "../data/auth";
|
||||
import {
|
||||
AuthProvider,
|
||||
autocompleteLoginFields,
|
||||
createLoginFlow,
|
||||
deleteLoginFlow,
|
||||
redirectWithAuthCode,
|
||||
submitLoginFlow,
|
||||
} from "../data/auth";
|
||||
import {
|
||||
DataEntryFlowStep,
|
||||
DataEntryFlowStepForm,
|
||||
} from "../data/data_entry_flow";
|
||||
import "./ha-auth-form";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
|
||||
type State = "loading" | "error" | "step";
|
||||
|
||||
@@ -29,18 +37,18 @@ export class HaAuthFlow extends LitElement {
|
||||
|
||||
@property() public localize!: LocalizeFunc;
|
||||
|
||||
@property({ attribute: false }) public step?: DataEntryFlowStep;
|
||||
|
||||
@property({ type: Boolean }) private storeToken = false;
|
||||
|
||||
@state() private _state: State = "loading";
|
||||
|
||||
@state() private _stepData?: Record<string, any>;
|
||||
|
||||
@state() private _step?: DataEntryFlowStep;
|
||||
|
||||
@state() private _errorMessage?: string;
|
||||
|
||||
@state() private _submitting = false;
|
||||
|
||||
@state() private _storeToken = false;
|
||||
|
||||
createRenderRoot() {
|
||||
return this;
|
||||
}
|
||||
@@ -48,27 +56,29 @@ export class HaAuthFlow extends LitElement {
|
||||
willUpdate(changedProps: PropertyValues) {
|
||||
super.willUpdate(changedProps);
|
||||
|
||||
if (!changedProps.has("_step")) {
|
||||
if (!changedProps.has("step")) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this._step) {
|
||||
if (!this.step) {
|
||||
this._stepData = undefined;
|
||||
return;
|
||||
}
|
||||
|
||||
const oldStep = changedProps.get("_step") as HaAuthFlow["_step"];
|
||||
this._state = "step";
|
||||
|
||||
const oldStep = changedProps.get("step") as HaAuthFlow["step"];
|
||||
|
||||
if (
|
||||
!oldStep ||
|
||||
this._step.flow_id !== oldStep.flow_id ||
|
||||
(this._step.type === "form" &&
|
||||
this.step.flow_id !== oldStep.flow_id ||
|
||||
(this.step.type === "form" &&
|
||||
oldStep.type === "form" &&
|
||||
this._step.step_id !== oldStep.step_id)
|
||||
this.step.step_id !== oldStep.step_id)
|
||||
) {
|
||||
this._stepData =
|
||||
this._step.type === "form"
|
||||
? computeInitialHaFormData(this._step.data_schema)
|
||||
this.step.type === "form"
|
||||
? computeInitialHaFormData(this.step.data_schema)
|
||||
: undefined;
|
||||
}
|
||||
}
|
||||
@@ -76,14 +86,28 @@ export class HaAuthFlow extends LitElement {
|
||||
protected render() {
|
||||
return html`
|
||||
<style>
|
||||
ha-auth-flow .action {
|
||||
margin: 24px 0 8px;
|
||||
text-align: center;
|
||||
}
|
||||
ha-auth-flow .store-token {
|
||||
margin-top: 10px;
|
||||
margin-left: -16px;
|
||||
}
|
||||
a.forgot-password {
|
||||
color: var(--primary-color);
|
||||
text-decoration: none;
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
.space-between {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
form {
|
||||
text-align: center;
|
||||
max-width: 336px;
|
||||
width: 100%;
|
||||
}
|
||||
ha-auth-form {
|
||||
display: block;
|
||||
margin-top: 16px;
|
||||
}
|
||||
</style>
|
||||
<form>${this._renderForm()}</form>
|
||||
`;
|
||||
@@ -117,7 +141,7 @@ export class HaAuthFlow extends LitElement {
|
||||
this._providerChanged(this.authProvider);
|
||||
}
|
||||
|
||||
if (!changedProps.has("_step") || this._step?.type !== "form") {
|
||||
if (!changedProps.has("step") || this.step?.type !== "form") {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -131,20 +155,31 @@ export class HaAuthFlow extends LitElement {
|
||||
}
|
||||
|
||||
private _renderForm() {
|
||||
const showBack =
|
||||
this.step?.type === "form" &&
|
||||
this.authProvider?.users &&
|
||||
!["select_mfa_module", "mfa"].includes(this.step.step_id);
|
||||
|
||||
switch (this._state) {
|
||||
case "step":
|
||||
if (this._step == null) {
|
||||
if (this.step == null) {
|
||||
return nothing;
|
||||
}
|
||||
|
||||
return html`
|
||||
${this._renderStep(this._step)}
|
||||
<div class="action">
|
||||
${this._renderStep(this.step)}
|
||||
<div class="action ${showBack ? "space-between" : ""}">
|
||||
${showBack
|
||||
? html`<mwc-button @click=${this._localFlow}>
|
||||
${this.localize("ui.panel.page-authorize.form.previous")}
|
||||
</mwc-button>`
|
||||
: nothing}
|
||||
<mwc-button
|
||||
raised
|
||||
@click=${this._handleSubmit}
|
||||
.disabled=${this._submitting}
|
||||
>
|
||||
${this._step.type === "form"
|
||||
${this.step.type === "form"
|
||||
? this.localize("ui.panel.page-authorize.form.next")
|
||||
: this.localize("ui.panel.page-authorize.form.start_over")}
|
||||
</mwc-button>
|
||||
@@ -153,11 +188,9 @@ export class HaAuthFlow extends LitElement {
|
||||
case "error":
|
||||
return html`
|
||||
<ha-alert alert-type="error">
|
||||
${this.localize(
|
||||
"ui.panel.page-authorize.form.error",
|
||||
"error",
|
||||
this._errorMessage
|
||||
)}
|
||||
${this.localize("ui.panel.page-authorize.form.error", {
|
||||
error: this._errorMessage,
|
||||
})}
|
||||
</ha-alert>
|
||||
<div class="action">
|
||||
<mwc-button raised @click=${this._startOver}>
|
||||
@@ -187,6 +220,11 @@ export class HaAuthFlow extends LitElement {
|
||||
`;
|
||||
case "form":
|
||||
return html`
|
||||
<h1>
|
||||
${!["select_mfa_module", "mfa"].includes(step.step_id)
|
||||
? this.localize("ui.panel.page-authorize.welcome_home")
|
||||
: this.localize("ui.panel.page-authorize.just_checking")}
|
||||
</h1>
|
||||
${this._computeStepDescription(step)}
|
||||
<ha-auth-form
|
||||
.data=${this._stepData}
|
||||
@@ -200,15 +238,28 @@ export class HaAuthFlow extends LitElement {
|
||||
${this.clientId === genClientId() &&
|
||||
!["select_mfa_module", "mfa"].includes(step.step_id)
|
||||
? html`
|
||||
<ha-formfield
|
||||
class="store-token"
|
||||
.label=${this.localize("ui.panel.page-authorize.store_token")}
|
||||
>
|
||||
<ha-checkbox
|
||||
.checked=${this._storeToken}
|
||||
@change=${this._storeTokenChanged}
|
||||
></ha-checkbox>
|
||||
</ha-formfield>
|
||||
<div class="space-between">
|
||||
<ha-formfield
|
||||
class="store-token"
|
||||
.label=${this.localize(
|
||||
"ui.panel.page-authorize.store_token"
|
||||
)}
|
||||
>
|
||||
<ha-checkbox
|
||||
.checked=${this.storeToken}
|
||||
@change=${this._storeTokenChanged}
|
||||
></ha-checkbox>
|
||||
</ha-formfield>
|
||||
<a
|
||||
class="forgot-password"
|
||||
href="https://www.home-assistant.io/docs/locked_out/#forgot-password"
|
||||
target="_blank"
|
||||
rel="noreferrer noopener"
|
||||
>${this.localize(
|
||||
"ui.panel.page-authorize.forgot_password"
|
||||
)}</a
|
||||
>
|
||||
</div>
|
||||
`
|
||||
: ""}
|
||||
`;
|
||||
@@ -218,15 +269,12 @@ export class HaAuthFlow extends LitElement {
|
||||
}
|
||||
|
||||
private _storeTokenChanged(e: CustomEvent<HTMLInputElement>) {
|
||||
this._storeToken = (e.currentTarget as HTMLInputElement).checked;
|
||||
this.storeToken = (e.currentTarget as HTMLInputElement).checked;
|
||||
}
|
||||
|
||||
private async _providerChanged(newProvider?: AuthProvider) {
|
||||
if (this._step && this._step.type === "form") {
|
||||
fetch(`/auth/login_flow/${this._step.flow_id}`, {
|
||||
method: "DELETE",
|
||||
credentials: "same-origin",
|
||||
}).catch((err) => {
|
||||
if (this.step && this.step.type === "form") {
|
||||
deleteLoginFlow(this.step.flow_id).catch((err) => {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error("Error delete obsoleted auth flow", err);
|
||||
});
|
||||
@@ -241,26 +289,26 @@ export class HaAuthFlow extends LitElement {
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch("/auth/login_flow", {
|
||||
method: "POST",
|
||||
credentials: "same-origin",
|
||||
body: JSON.stringify({
|
||||
client_id: this.clientId,
|
||||
handler: [newProvider.type, newProvider.id],
|
||||
redirect_uri: this.redirectUri,
|
||||
}),
|
||||
});
|
||||
const response = await createLoginFlow(this.clientId, this.redirectUri, [
|
||||
newProvider.type,
|
||||
newProvider.id,
|
||||
]);
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (response.ok) {
|
||||
// allow auth provider bypass the login form
|
||||
if (data.type === "create_entry") {
|
||||
this._redirect(data.result);
|
||||
redirectWithAuthCode(
|
||||
this.redirectUri!,
|
||||
data.result,
|
||||
this.oauth2State,
|
||||
this.storeToken
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
this._step = data;
|
||||
this.step = data;
|
||||
this._state = "step";
|
||||
} else {
|
||||
this._state = "error";
|
||||
@@ -274,27 +322,6 @@ export class HaAuthFlow extends LitElement {
|
||||
}
|
||||
}
|
||||
|
||||
private _redirect(authCode: string) {
|
||||
// OAuth 2: 3.1.2 we need to retain query component of a redirect URI
|
||||
let url = this.redirectUri!;
|
||||
if (!url.includes("?")) {
|
||||
url += "?";
|
||||
} else if (!url.endsWith("&")) {
|
||||
url += "&";
|
||||
}
|
||||
|
||||
url += `code=${encodeURIComponent(authCode)}`;
|
||||
|
||||
if (this.oauth2State) {
|
||||
url += `&state=${encodeURIComponent(this.oauth2State)}`;
|
||||
}
|
||||
if (this._storeToken) {
|
||||
url += `&storeToken=true`;
|
||||
}
|
||||
|
||||
document.location.assign(url);
|
||||
}
|
||||
|
||||
private _stepDataChanged(ev: CustomEvent) {
|
||||
this._stepData = ev.detail.value;
|
||||
}
|
||||
@@ -331,10 +358,10 @@ export class HaAuthFlow extends LitElement {
|
||||
|
||||
private async _handleSubmit(ev: Event) {
|
||||
ev.preventDefault();
|
||||
if (this._step == null) {
|
||||
if (this.step == null) {
|
||||
return;
|
||||
}
|
||||
if (this._step.type !== "form") {
|
||||
if (this.step.type !== "form") {
|
||||
this._providerChanged(this.authProvider);
|
||||
return;
|
||||
}
|
||||
@@ -343,11 +370,7 @@ export class HaAuthFlow extends LitElement {
|
||||
const postData = { ...this._stepData, client_id: this.clientId };
|
||||
|
||||
try {
|
||||
const response = await fetch(`/auth/login_flow/${this._step.flow_id}`, {
|
||||
method: "POST",
|
||||
credentials: "same-origin",
|
||||
body: JSON.stringify(postData),
|
||||
});
|
||||
const response = await submitLoginFlow(this.step.flow_id, postData);
|
||||
|
||||
const newStep = await response.json();
|
||||
|
||||
@@ -358,10 +381,15 @@ export class HaAuthFlow extends LitElement {
|
||||
}
|
||||
|
||||
if (newStep.type === "create_entry") {
|
||||
this._redirect(newStep.result);
|
||||
redirectWithAuthCode(
|
||||
this.redirectUri!,
|
||||
newStep.result,
|
||||
this.oauth2State,
|
||||
this.storeToken
|
||||
);
|
||||
return;
|
||||
}
|
||||
this._step = newStep;
|
||||
this.step = newStep;
|
||||
this._state = "step";
|
||||
} catch (err: any) {
|
||||
// eslint-disable-next-line no-console
|
||||
@@ -372,6 +400,10 @@ export class HaAuthFlow extends LitElement {
|
||||
this._submitting = false;
|
||||
}
|
||||
}
|
||||
|
||||
private _localFlow() {
|
||||
fireEvent(this, "default-login-flow", { value: false });
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
|
@@ -41,8 +41,8 @@ export class HaAuthFormString extends HaFormString {
|
||||
!this.isPassword
|
||||
? this.stringType
|
||||
: this.unmaskedPassword
|
||||
? "text"
|
||||
: "password"
|
||||
? "text"
|
||||
: "password"
|
||||
}
|
||||
.label=${this.label}
|
||||
.value=${this.data || ""}
|
||||
|
@@ -13,6 +13,7 @@ import {
|
||||
import { litLocalizeLiteMixin } from "../mixins/lit-localize-lite-mixin";
|
||||
import { registerServiceWorker } from "../util/register-service-worker";
|
||||
import "./ha-auth-flow";
|
||||
import "./ha-local-auth-flow";
|
||||
|
||||
import("./ha-pick-auth-provider");
|
||||
|
||||
@@ -39,6 +40,8 @@ export class HaAuthorize extends litLocalizeLiteMixin(LitElement) {
|
||||
|
||||
@state() private _error?: string;
|
||||
|
||||
@state() private _forceDefaultLogin = false;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
const query = extractSearchParamsObject() as AuthUrlSearchParams;
|
||||
@@ -60,6 +63,7 @@ export class HaAuthorize extends litLocalizeLiteMixin(LitElement) {
|
||||
ha-authorize ha-alert {
|
||||
display: block;
|
||||
margin: 16px 0;
|
||||
background-color: var(--primary-background-color, #fafafa);
|
||||
}
|
||||
</style>
|
||||
<ha-alert alert-type="error"
|
||||
@@ -68,19 +72,7 @@ export class HaAuthorize extends litLocalizeLiteMixin(LitElement) {
|
||||
`;
|
||||
}
|
||||
|
||||
if (!this._authProviders) {
|
||||
return html`
|
||||
<style>
|
||||
ha-authorize p {
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
}
|
||||
</style>
|
||||
<p>${this.localize("ui.panel.page-authorize.initializing")}</p>
|
||||
`;
|
||||
}
|
||||
|
||||
const inactiveProviders = this._authProviders.filter(
|
||||
const inactiveProviders = this._authProviders?.filter(
|
||||
(prv) => prv !== this._authProvider
|
||||
);
|
||||
|
||||
@@ -89,21 +81,83 @@ export class HaAuthorize extends litLocalizeLiteMixin(LitElement) {
|
||||
return html`
|
||||
<style>
|
||||
ha-pick-auth-provider {
|
||||
display: block;
|
||||
margin-top: 48px;
|
||||
}
|
||||
ha-auth-flow {
|
||||
display: block;
|
||||
margin-top: 24px;
|
||||
}
|
||||
ha-auth-flow,
|
||||
ha-local-auth-flow {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
ha-alert {
|
||||
display: block;
|
||||
margin: 16px 0;
|
||||
background-color: var(--primary-background-color, #fafafa);
|
||||
}
|
||||
p {
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
}
|
||||
.card-content {
|
||||
background: var(
|
||||
--ha-card-background,
|
||||
var(--card-background-color, white)
|
||||
);
|
||||
box-shadow: var(--ha-card-box-shadow, none);
|
||||
box-sizing: border-box;
|
||||
border-radius: var(--ha-card-border-radius, 12px);
|
||||
border-width: var(--ha-card-border-width, 1px);
|
||||
border-style: solid;
|
||||
border-color: var(
|
||||
--ha-card-border-color,
|
||||
var(--divider-color, #e0e0e0)
|
||||
);
|
||||
color: var(--primary-text-color);
|
||||
position: relative;
|
||||
padding: 16px;
|
||||
}
|
||||
.action {
|
||||
margin: 16px 0 8px;
|
||||
display: flex;
|
||||
width: 100%;
|
||||
max-width: 336px;
|
||||
justify-content: center;
|
||||
}
|
||||
.space-between {
|
||||
justify-content: space-between;
|
||||
}
|
||||
.footer {
|
||||
padding-top: 8px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
ha-language-picker {
|
||||
width: 200px;
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
--ha-select-height: 40px;
|
||||
--mdc-select-fill-color: none;
|
||||
--mdc-select-label-ink-color: var(--primary-text-color, #212121);
|
||||
--mdc-select-ink-color: var(--primary-text-color, #212121);
|
||||
--mdc-select-idle-line-color: transparent;
|
||||
--mdc-select-hover-line-color: transparent;
|
||||
--mdc-select-dropdown-icon-color: var(--primary-text-color, #212121);
|
||||
--mdc-shape-small: 0;
|
||||
}
|
||||
.footer a {
|
||||
text-decoration: none;
|
||||
color: var(--primary-text-color);
|
||||
margin-right: 16px;
|
||||
}
|
||||
h1 {
|
||||
font-size: 28px;
|
||||
font-weight: 400;
|
||||
margin-top: 16px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
</style>
|
||||
|
||||
${!this._ownInstance
|
||||
@@ -120,33 +174,61 @@ export class HaAuthorize extends litLocalizeLiteMixin(LitElement) {
|
||||
>`,
|
||||
})}
|
||||
</ha-alert>`
|
||||
: html`<p>${this.localize("ui.panel.page-authorize.authorizing")}</p>`}
|
||||
${inactiveProviders.length > 0
|
||||
? html`<p>
|
||||
${this.localize("ui.panel.page-authorize.logging_in_with", {
|
||||
authProviderName: html`<b>${this._authProvider!.name}</b>`,
|
||||
})}
|
||||
</p>`
|
||||
: nothing}
|
||||
|
||||
<ha-auth-flow
|
||||
.clientId=${this.clientId}
|
||||
.redirectUri=${this.redirectUri}
|
||||
.oauth2State=${this.oauth2State}
|
||||
.authProvider=${this._authProvider}
|
||||
.localize=${this.localize}
|
||||
></ha-auth-flow>
|
||||
|
||||
${inactiveProviders.length > 0
|
||||
? html`
|
||||
<ha-pick-auth-provider
|
||||
.localize=${this.localize}
|
||||
.clientId=${this.clientId}
|
||||
.authProviders=${inactiveProviders}
|
||||
@pick-auth-provider=${this._handleAuthProviderPick}
|
||||
></ha-pick-auth-provider>
|
||||
`
|
||||
: ""}
|
||||
<div
|
||||
class="card-content"
|
||||
@default-login-flow=${this._handleDefaultLoginFlow}
|
||||
>
|
||||
${!this._authProvider
|
||||
? html`<p>
|
||||
${this.localize("ui.panel.page-authorize.initializing")}
|
||||
</p> `
|
||||
: !this._forceDefaultLogin &&
|
||||
this._authProvider!.users &&
|
||||
this.clientId != null &&
|
||||
this.redirectUri != null
|
||||
? html`<ha-local-auth-flow
|
||||
.clientId=${this.clientId}
|
||||
.redirectUri=${this.redirectUri}
|
||||
.oauth2State=${this.oauth2State}
|
||||
.authProvider=${this._authProvider}
|
||||
.authProviders=${this._authProviders}
|
||||
.localize=${this.localize}
|
||||
.ownInstance=${this._ownInstance}
|
||||
></ha-local-auth-flow>`
|
||||
: html`<ha-auth-flow
|
||||
.clientId=${this.clientId}
|
||||
.redirectUri=${this.redirectUri}
|
||||
.oauth2State=${this.oauth2State}
|
||||
.authProvider=${this._authProvider}
|
||||
.localize=${this.localize}
|
||||
></ha-auth-flow>
|
||||
${inactiveProviders!.length > 0
|
||||
? html`
|
||||
<ha-pick-auth-provider
|
||||
.localize=${this.localize}
|
||||
.clientId=${this.clientId}
|
||||
.authProviders=${inactiveProviders}
|
||||
@pick-auth-provider=${this._handleAuthProviderPick}
|
||||
></ha-pick-auth-provider>
|
||||
`
|
||||
: ""}`}
|
||||
</div>
|
||||
<div class="footer">
|
||||
<ha-language-picker
|
||||
.value=${this.language}
|
||||
.label=${""}
|
||||
nativeName
|
||||
@value-changed=${this._languageChanged}
|
||||
></ha-language-picker>
|
||||
<a
|
||||
href="https://www.home-assistant.io/docs/authentication/"
|
||||
target="_blank"
|
||||
rel="noreferrer noopener"
|
||||
>${this.localize("ui.panel.page-authorize.help")}</a
|
||||
>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -199,12 +281,18 @@ export class HaAuthorize extends litLocalizeLiteMixin(LitElement) {
|
||||
);
|
||||
}
|
||||
|
||||
if (window.innerWidth > 450) {
|
||||
import("../resources/particles");
|
||||
}
|
||||
|
||||
// If we are logging into the instance that is hosting this auth form
|
||||
// we will register the service worker to start preloading.
|
||||
if (url.host === location.host) {
|
||||
this._ownInstance = true;
|
||||
registerServiceWorker(this, false);
|
||||
}
|
||||
|
||||
import("../components/ha-language-picker");
|
||||
}
|
||||
|
||||
protected updated(changedProps: PropertyValues) {
|
||||
@@ -245,7 +333,22 @@ export class HaAuthorize extends litLocalizeLiteMixin(LitElement) {
|
||||
}
|
||||
}
|
||||
|
||||
private _handleDefaultLoginFlow(ev) {
|
||||
this._forceDefaultLogin = ev.detail.value;
|
||||
}
|
||||
|
||||
private async _handleAuthProviderPick(ev) {
|
||||
this._authProvider = ev.detail;
|
||||
}
|
||||
|
||||
private _languageChanged(ev: CustomEvent) {
|
||||
const language = ev.detail.value;
|
||||
this.language = language;
|
||||
|
||||
try {
|
||||
localStorage.setItem("selectedLanguage", JSON.stringify(language));
|
||||
} catch (err: any) {
|
||||
// Ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
|
485
src/auth/ha-local-auth-flow.ts
Normal file
485
src/auth/ha-local-auth-flow.ts
Normal file
@@ -0,0 +1,485 @@
|
||||
/* eslint-disable lit/prefer-static-styles */
|
||||
import "@material/mwc-button";
|
||||
import { mdiEye, mdiEyeOff } from "@mdi/js";
|
||||
import { html, LitElement, nothing, PropertyValues } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
import { LocalizeFunc } from "../common/translations/localize";
|
||||
import "../components/ha-alert";
|
||||
import "../components/ha-button";
|
||||
import "../components/ha-icon-button";
|
||||
import "../components/user/ha-person-badge";
|
||||
import {
|
||||
AuthProvider,
|
||||
createLoginFlow,
|
||||
deleteLoginFlow,
|
||||
redirectWithAuthCode,
|
||||
submitLoginFlow,
|
||||
} from "../data/auth";
|
||||
import { DataEntryFlowStep } from "../data/data_entry_flow";
|
||||
import { BasePerson, listUserPersons } from "../data/person";
|
||||
import "./ha-auth-textfield";
|
||||
import type { HaAuthTextField } from "./ha-auth-textfield";
|
||||
|
||||
@customElement("ha-local-auth-flow")
|
||||
export class HaLocalAuthFlow extends LitElement {
|
||||
@property({ attribute: false }) public authProvider?: AuthProvider;
|
||||
|
||||
@property({ attribute: false }) public authProviders?: AuthProvider[];
|
||||
|
||||
@property() public clientId?: string;
|
||||
|
||||
@property() public redirectUri?: string;
|
||||
|
||||
@property() public oauth2State?: string;
|
||||
|
||||
@property({ type: Boolean }) public ownInstance = false;
|
||||
|
||||
@property() public localize!: LocalizeFunc;
|
||||
|
||||
@state() private _error?: string;
|
||||
|
||||
@state() private _step?: DataEntryFlowStep;
|
||||
|
||||
@state() private _submitting = false;
|
||||
|
||||
@state() private _persons?: Record<string, BasePerson>;
|
||||
|
||||
@state() private _selectedUser?: string;
|
||||
|
||||
@state() private _unmaskedPassword = false;
|
||||
|
||||
createRenderRoot() {
|
||||
return this;
|
||||
}
|
||||
|
||||
willUpdate(changedProps: PropertyValues) {
|
||||
super.willUpdate(changedProps);
|
||||
|
||||
if (!this.hasUpdated) {
|
||||
this._load();
|
||||
}
|
||||
}
|
||||
|
||||
protected render() {
|
||||
if (!this.authProvider?.users || !this._persons) {
|
||||
return nothing;
|
||||
}
|
||||
const userIds = Object.keys(this.authProvider.users).filter(
|
||||
(userId) => userId in this._persons!
|
||||
);
|
||||
return html`
|
||||
<style>
|
||||
.content {
|
||||
max-width: 560px;
|
||||
}
|
||||
.persons {
|
||||
margin-top: 24px;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 16px;
|
||||
justify-content: center;
|
||||
}
|
||||
.persons.force-small {
|
||||
max-width: 350px;
|
||||
}
|
||||
.person {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
flex-shrink: 0;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
width: 80px;
|
||||
}
|
||||
.person[role="button"] {
|
||||
outline: none;
|
||||
padding: 8px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.person[role="button"]:focus-visible {
|
||||
background: rgba(var(--rgb-primary-color), 0.1);
|
||||
}
|
||||
.person p {
|
||||
margin-bottom: 0;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
}
|
||||
ha-person-badge {
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
--person-badge-font-size: 2em;
|
||||
}
|
||||
form {
|
||||
width: 100%;
|
||||
}
|
||||
ha-auth-textfield {
|
||||
display: block !important;
|
||||
position: relative;
|
||||
}
|
||||
ha-auth-textfield ha-icon-button {
|
||||
position: absolute;
|
||||
top: 4px;
|
||||
right: 4px;
|
||||
z-index: 9;
|
||||
}
|
||||
.login-form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
max-width: 336px;
|
||||
margin-top: 24px;
|
||||
}
|
||||
.login-form .person {
|
||||
cursor: default;
|
||||
width: auto;
|
||||
}
|
||||
.login-form .person p {
|
||||
font-size: 28px;
|
||||
margin-top: 24px;
|
||||
margin-bottom: 32px;
|
||||
line-height: normal;
|
||||
}
|
||||
.login-form ha-person-badge {
|
||||
width: 120px;
|
||||
height: 120px;
|
||||
--person-badge-font-size: 3em;
|
||||
}
|
||||
ha-list-item {
|
||||
margin-top: 16px;
|
||||
}
|
||||
ha-button {
|
||||
--mdc-typography-button-text-transform: none;
|
||||
}
|
||||
.forgot-password-container {
|
||||
text-align: right;
|
||||
padding: 8px 0 16px 0;
|
||||
}
|
||||
a.forgot-password {
|
||||
color: var(--primary-color);
|
||||
text-decoration: none;
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
button {
|
||||
color: var(--primary-color);
|
||||
background: none;
|
||||
border: none;
|
||||
padding: 8px;
|
||||
font: inherit;
|
||||
font-size: 0.875rem;
|
||||
text-align: left;
|
||||
cursor: pointer;
|
||||
outline: none;
|
||||
border-radius: 4px;
|
||||
}
|
||||
button:focus-visible {
|
||||
background: rgba(var(--rgb-primary-color), 0.1);
|
||||
}
|
||||
</style>
|
||||
${this._error
|
||||
? html`<ha-alert alert-type="error">${this._error}</ha-alert>`
|
||||
: nothing}
|
||||
${this._step
|
||||
? html`<ha-auth-flow
|
||||
.clientId=${this.clientId}
|
||||
.redirectUri=${this.redirectUri}
|
||||
.oauth2State=${this.oauth2State}
|
||||
.step=${this._step}
|
||||
storeToken
|
||||
.localize=${this.localize}
|
||||
></ha-auth-flow>`
|
||||
: this._selectedUser
|
||||
? html`<div class="login-form">
|
||||
<div class="person">
|
||||
<ha-person-badge
|
||||
.person=${this._persons[this._selectedUser]}
|
||||
></ha-person-badge>
|
||||
<p>${this._persons[this._selectedUser].name}</p>
|
||||
</div>
|
||||
<form>
|
||||
<input
|
||||
type="hidden"
|
||||
name="username"
|
||||
autocomplete="username"
|
||||
readonly
|
||||
.value=${this.authProvider.users[this._selectedUser]}
|
||||
/>
|
||||
<ha-auth-textfield
|
||||
.type=${this._unmaskedPassword ? "text" : "password"}
|
||||
autocomplete="current-password"
|
||||
id="password"
|
||||
name="password"
|
||||
.label=${this.localize(
|
||||
"ui.panel.page-authorize.form.providers.homeassistant.step.init.data.password"
|
||||
)}
|
||||
required
|
||||
autoValidate
|
||||
iconTrailing
|
||||
validationMessage="Required"
|
||||
>
|
||||
<ha-icon-button
|
||||
toggles
|
||||
.label=${this.localize(
|
||||
this._unmaskedPassword
|
||||
? "ui.panel.page-authorize.form.hide_password"
|
||||
: "ui.panel.page-authorize.form.show_password"
|
||||
) ||
|
||||
(this._unmaskedPassword
|
||||
? "Hide password"
|
||||
: "Show password")}
|
||||
@click=${this._toggleUnmaskedPassword}
|
||||
.path=${this._unmaskedPassword ? mdiEyeOff : mdiEye}
|
||||
></ha-icon-button>
|
||||
</ha-auth-textfield>
|
||||
<div class="forgot-password-container">
|
||||
<a
|
||||
class="forgot-password"
|
||||
href="https://www.home-assistant.io/docs/locked_out/#forgot-password"
|
||||
target="_blank"
|
||||
rel="noreferrer noopener"
|
||||
>${this.localize(
|
||||
"ui.panel.page-authorize.forgot_password"
|
||||
)}</a
|
||||
>
|
||||
</div>
|
||||
<div class="action space-between">
|
||||
<mwc-button
|
||||
@click=${this._restart}
|
||||
.disabled=${this._submitting}
|
||||
>
|
||||
${this.localize("ui.panel.page-authorize.form.previous")}
|
||||
</mwc-button>
|
||||
<mwc-button
|
||||
raised
|
||||
@click=${this._handleSubmit}
|
||||
.disabled=${this._submitting}
|
||||
>
|
||||
${this.localize("ui.panel.page-authorize.form.next")}
|
||||
</mwc-button>
|
||||
</div>
|
||||
</form>
|
||||
</div>`
|
||||
: html`<h1>
|
||||
${this.localize("ui.panel.page-authorize.welcome_home")}
|
||||
</h1>
|
||||
${this.localize("ui.panel.page-authorize.who_is_logging_in")}
|
||||
<div
|
||||
class="persons ${userIds.length < 10 && userIds.length % 4 === 1
|
||||
? "force-small"
|
||||
: ""}"
|
||||
>
|
||||
${userIds.map((userId) => {
|
||||
const person = this._persons![userId];
|
||||
|
||||
return html`<div
|
||||
class="person"
|
||||
.userId=${userId}
|
||||
@click=${this._personSelected}
|
||||
@keyup=${this._handleKeyUp}
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
<ha-person-badge .person=${person}></ha-person-badge>
|
||||
<p>${person.name}</p>
|
||||
</div>`;
|
||||
})}
|
||||
</div>
|
||||
<div class="action">
|
||||
<button @click=${this._otherLogin} tabindex="0">
|
||||
${this.localize("ui.panel.page-authorize.other_options")}
|
||||
</button>
|
||||
</div>`}
|
||||
`;
|
||||
}
|
||||
|
||||
protected firstUpdated(changedProps: PropertyValues) {
|
||||
super.firstUpdated(changedProps);
|
||||
|
||||
this.addEventListener("keypress", (ev) => {
|
||||
if (ev.key === "Enter") {
|
||||
this._handleSubmit(ev);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected updated(changedProps: PropertyValues) {
|
||||
if (changedProps.has("_selectedUser") && this._selectedUser) {
|
||||
const passwordElement = this.renderRoot.querySelector(
|
||||
"#password"
|
||||
) as HaAuthTextField;
|
||||
passwordElement.updateComplete.then(() => {
|
||||
passwordElement.focus();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private async _load() {
|
||||
try {
|
||||
this._persons = await listUserPersons();
|
||||
} catch {
|
||||
this._persons = {};
|
||||
this._error = "Failed to fetch persons";
|
||||
}
|
||||
}
|
||||
|
||||
private _restart() {
|
||||
this._selectedUser = undefined;
|
||||
this._error = undefined;
|
||||
}
|
||||
|
||||
private _toggleUnmaskedPassword() {
|
||||
this._unmaskedPassword = !this._unmaskedPassword;
|
||||
}
|
||||
|
||||
private _handleKeyUp(ev: KeyboardEvent) {
|
||||
if (ev.key === "Enter" || ev.key === " ") {
|
||||
this._personSelected(ev);
|
||||
}
|
||||
}
|
||||
|
||||
private async _personSelected(ev) {
|
||||
const userId = ev.currentTarget.userId;
|
||||
if (
|
||||
this.ownInstance &&
|
||||
this.authProviders?.find((prv) => prv.type === "trusted_networks")
|
||||
) {
|
||||
try {
|
||||
const flowResponse = await createLoginFlow(
|
||||
this.clientId,
|
||||
this.redirectUri,
|
||||
["trusted_networks", null]
|
||||
);
|
||||
|
||||
const data = await flowResponse.json();
|
||||
|
||||
if (data.type === "create_entry") {
|
||||
redirectWithAuthCode(
|
||||
this.redirectUri!,
|
||||
data.result,
|
||||
this.oauth2State,
|
||||
true
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
if (!data.data_schema[0].options.find((opt) => opt[0] === userId)) {
|
||||
throw new Error("User not available");
|
||||
}
|
||||
|
||||
const postData = { user: userId, client_id: this.clientId };
|
||||
|
||||
const response = await submitLoginFlow(data.flow_id, postData);
|
||||
|
||||
if (response.ok) {
|
||||
const result = await response.json();
|
||||
|
||||
if (result.type === "create_entry") {
|
||||
redirectWithAuthCode(
|
||||
this.redirectUri!,
|
||||
result.result,
|
||||
this.oauth2State,
|
||||
true
|
||||
);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
throw new Error("Invalid response");
|
||||
}
|
||||
} catch {
|
||||
deleteLoginFlow(data.flow_id).catch((err) => {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error("Error delete obsoleted auth flow", err);
|
||||
});
|
||||
}
|
||||
} catch {
|
||||
// Ignore
|
||||
}
|
||||
}
|
||||
this._selectedUser = userId;
|
||||
}
|
||||
|
||||
private async _handleSubmit(ev: Event) {
|
||||
ev.preventDefault();
|
||||
|
||||
if (!this.authProvider?.users || !this._selectedUser) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._error = undefined;
|
||||
this._submitting = true;
|
||||
|
||||
const flowResponse = await createLoginFlow(
|
||||
this.clientId,
|
||||
this.redirectUri,
|
||||
["homeassistant", null]
|
||||
);
|
||||
|
||||
const data = await flowResponse.json();
|
||||
|
||||
const postData = {
|
||||
username: this.authProvider.users[this._selectedUser],
|
||||
password: (this.renderRoot.querySelector("#password") as HaAuthTextField)
|
||||
.value,
|
||||
client_id: this.clientId,
|
||||
};
|
||||
|
||||
try {
|
||||
const response = await submitLoginFlow(data.flow_id, postData);
|
||||
|
||||
const newStep = await response.json();
|
||||
|
||||
if (response.status === 403) {
|
||||
this._error = newStep.message;
|
||||
return;
|
||||
}
|
||||
|
||||
if (newStep.type === "create_entry") {
|
||||
redirectWithAuthCode(
|
||||
this.redirectUri!,
|
||||
newStep.result,
|
||||
this.oauth2State,
|
||||
true
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (newStep.errors.base) {
|
||||
this._error = this.localize(
|
||||
`ui.panel.page-authorize.form.providers.homeassistant.error.${newStep.errors.base}`
|
||||
);
|
||||
throw new Error(this._error);
|
||||
}
|
||||
|
||||
this._step = newStep;
|
||||
} catch {
|
||||
deleteLoginFlow(data.flow_id).catch((err) => {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error("Error delete obsoleted auth flow", err);
|
||||
});
|
||||
if (!this._error) {
|
||||
this._error = this.localize(
|
||||
"ui.panel.page-authorize.form.unknown_error"
|
||||
);
|
||||
}
|
||||
} finally {
|
||||
this._submitting = false;
|
||||
}
|
||||
}
|
||||
|
||||
private _otherLogin() {
|
||||
fireEvent(this, "default-login-flow", { value: true });
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-local-auth-flow": HaLocalAuthFlow;
|
||||
}
|
||||
interface HASSDomEvents {
|
||||
"default-login-flow": { value: boolean };
|
||||
}
|
||||
}
|
@@ -21,7 +21,11 @@ export class HaPickAuthProvider extends LitElement {
|
||||
|
||||
protected render() {
|
||||
return html`
|
||||
<p>${this.localize("ui.panel.page-authorize.pick_auth_provider")}:</p>
|
||||
<h3>
|
||||
<span
|
||||
>${this.localize("ui.panel.page-authorize.pick_auth_provider")}</span
|
||||
>
|
||||
</h3>
|
||||
<mwc-list>
|
||||
${this.authProviders.map(
|
||||
(provider) => html`
|
||||
@@ -35,8 +39,8 @@ export class HaPickAuthProvider extends LitElement {
|
||||
<ha-icon-next slot="meta"></ha-icon-next>
|
||||
</ha-list-item>
|
||||
`
|
||||
)}</mwc-list
|
||||
>
|
||||
)}
|
||||
</mwc-list>
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -45,12 +49,34 @@ export class HaPickAuthProvider extends LitElement {
|
||||
}
|
||||
|
||||
static styles = css`
|
||||
p {
|
||||
margin-top: 0;
|
||||
h3 {
|
||||
margin: 0 -16px;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
text-align: center;
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
line-height: 20px;
|
||||
}
|
||||
h3:before {
|
||||
border-top: 1px solid var(--divider-color);
|
||||
content: "";
|
||||
margin: 0 auto;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
z-index: -1;
|
||||
}
|
||||
h3 span {
|
||||
background: var(--card-background-color);
|
||||
padding: 0 15px;
|
||||
}
|
||||
mwc-list {
|
||||
margin: 0 -16px;
|
||||
--mdc-list-side-padding: 16px;
|
||||
margin: 16px -16px 0;
|
||||
--mdc-list-side-padding: 24px;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
@@ -10,10 +10,10 @@ const isLoadedIntegration = (hass: HomeAssistant, page: PageNavigation) =>
|
||||
page.component
|
||||
? isComponentLoaded(hass, page.component)
|
||||
: page.components
|
||||
? page.components.some((integration) =>
|
||||
isComponentLoaded(hass, integration)
|
||||
)
|
||||
: true;
|
||||
? page.components.some((integration) =>
|
||||
isComponentLoaded(hass, integration)
|
||||
)
|
||||
: true;
|
||||
const isCore = (page: PageNavigation) => page.core;
|
||||
const isAdvancedPage = (page: PageNavigation) => page.advancedOnly;
|
||||
const userWantsAdvanced = (hass: HomeAssistant) => hass.userData?.showAdvanced;
|
||||
|
@@ -29,6 +29,7 @@ import {
|
||||
mdiFlash,
|
||||
mdiFlower,
|
||||
mdiFormatListBulleted,
|
||||
mdiFormatListCheckbox,
|
||||
mdiFormTextbox,
|
||||
mdiGauge,
|
||||
mdiGoogleAssistant,
|
||||
@@ -64,6 +65,7 @@ import {
|
||||
mdiTransmissionTower,
|
||||
mdiWater,
|
||||
mdiWaterPercent,
|
||||
mdiWeatherPartlyCloudy,
|
||||
mdiWeatherPouring,
|
||||
mdiWeatherRainy,
|
||||
mdiWeatherWindy,
|
||||
@@ -128,6 +130,7 @@ export const FIXED_DOMAIN_ICONS = {
|
||||
updater: mdiCloudUpload,
|
||||
vacuum: mdiRobotVacuum,
|
||||
wake_word: mdiChatSleep,
|
||||
weather: mdiWeatherPartlyCloudy,
|
||||
zone: mdiMapMarkerRadius,
|
||||
};
|
||||
|
||||
@@ -166,6 +169,7 @@ export const FIXED_DEVICE_CLASS_ICONS = {
|
||||
precipitation_intensity: mdiWeatherPouring,
|
||||
pressure: mdiGauge,
|
||||
reactive_power: mdiFlash,
|
||||
shopping_List: mdiFormatListCheckbox,
|
||||
signal_strength: mdiWifi,
|
||||
sound_pressure: mdiEarHearing,
|
||||
speed: mdiSpeedometer,
|
||||
@@ -203,6 +207,7 @@ export const DOMAINS_WITH_CARD = [
|
||||
"select",
|
||||
"timer",
|
||||
"text",
|
||||
"update",
|
||||
"vacuum",
|
||||
"water_heater",
|
||||
];
|
||||
|
31
src/common/datetime/localize_date.ts
Normal file
31
src/common/datetime/localize_date.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import memoizeOne from "memoize-one";
|
||||
|
||||
export const localizeWeekdays = memoizeOne(
|
||||
(language: string, short: boolean): string[] => {
|
||||
const days: string[] = [];
|
||||
const format = new Intl.DateTimeFormat(language, {
|
||||
weekday: short ? "short" : "long",
|
||||
timeZone: "UTC",
|
||||
});
|
||||
for (let i = 0; i < 7; i++) {
|
||||
const date = new Date(Date.UTC(1970, 0, 1 + 3 + i));
|
||||
days.push(format.format(date));
|
||||
}
|
||||
return days;
|
||||
}
|
||||
);
|
||||
|
||||
export const localizeMonths = memoizeOne(
|
||||
(language: string, short: boolean): string[] => {
|
||||
const months: string[] = [];
|
||||
const format = new Intl.DateTimeFormat(language, {
|
||||
month: short ? "short" : "long",
|
||||
timeZone: "UTC",
|
||||
});
|
||||
for (let i = 0; i < 12; i++) {
|
||||
const date = new Date(Date.UTC(1970, 0 + i, 1));
|
||||
months.push(format.format(date));
|
||||
}
|
||||
return months;
|
||||
}
|
||||
);
|
@@ -1,33 +0,0 @@
|
||||
/**
|
||||
* Update root's child element to be newElementTag replacing another existing child if any.
|
||||
* Copy attributes into the child element.
|
||||
*/
|
||||
export default function dynamicContentUpdater(root, newElementTag, attributes) {
|
||||
const rootEl = root;
|
||||
let customEl;
|
||||
|
||||
if (rootEl.lastChild && rootEl.lastChild.tagName === newElementTag) {
|
||||
customEl = rootEl.lastChild;
|
||||
} else {
|
||||
if (rootEl.lastChild) {
|
||||
rootEl.removeChild(rootEl.lastChild);
|
||||
}
|
||||
// Creating an element with upper case works fine in Chrome, but in FF it doesn't immediately
|
||||
// become a defined Custom Element. Polymer does that in some later pass.
|
||||
customEl = document.createElement(newElementTag.toLowerCase());
|
||||
}
|
||||
|
||||
if (customEl.setProperties) {
|
||||
customEl.setProperties(attributes);
|
||||
} else {
|
||||
// If custom element definition wasn't loaded yet - setProperties would be
|
||||
// missing, but no harm in setting attributes one-by-one then.
|
||||
Object.keys(attributes).forEach((key) => {
|
||||
customEl[key] = attributes[key];
|
||||
});
|
||||
}
|
||||
|
||||
if (customEl.parentNode === null) {
|
||||
rootEl.appendChild(customEl);
|
||||
}
|
||||
}
|
@@ -4,5 +4,5 @@ export const mainWindow =
|
||||
window.name === MAIN_WINDOW_NAME
|
||||
? window
|
||||
: parent.name === MAIN_WINDOW_NAME
|
||||
? parent
|
||||
: top!;
|
||||
? parent
|
||||
: top!;
|
||||
|
@@ -1,5 +1,6 @@
|
||||
import { HassConfig, HassEntity } from "home-assistant-js-websocket";
|
||||
import {
|
||||
DOMAIN_ATTRIBUTES_FORMATERS,
|
||||
DOMAIN_ATTRIBUTES_UNITS,
|
||||
TEMPERATURE_ATTRIBUTES,
|
||||
} from "../../data/entity_attributes";
|
||||
@@ -14,11 +15,10 @@ import { formatNumber } from "../number/format_number";
|
||||
import { capitalizeFirstLetter } from "../string/capitalize-first-letter";
|
||||
import { isDate } from "../string/is_date";
|
||||
import { isTimestamp } from "../string/is_timestamp";
|
||||
import { blankBeforePercent } from "../translations/blank_before_percent";
|
||||
import { blankBeforeUnit } from "../translations/blank_before_unit";
|
||||
import { LocalizeFunc } from "../translations/localize";
|
||||
import { computeDomain } from "./compute_domain";
|
||||
import { computeStateDomain } from "./compute_state_domain";
|
||||
import { blankBeforeUnit } from "../translations/blank_before_unit";
|
||||
|
||||
export const computeAttributeValueDisplay = (
|
||||
localize: LocalizeFunc,
|
||||
@@ -39,19 +39,18 @@ export const computeAttributeValueDisplay = (
|
||||
|
||||
// Number value, return formatted number
|
||||
if (typeof attributeValue === "number") {
|
||||
const formattedValue = formatNumber(attributeValue, locale);
|
||||
|
||||
const domain = computeStateDomain(stateObj);
|
||||
|
||||
const formatter = DOMAIN_ATTRIBUTES_FORMATERS[domain]?.[attribute];
|
||||
|
||||
const formattedValue = formatter
|
||||
? formatter(attributeValue, locale)
|
||||
: formatNumber(attributeValue, locale);
|
||||
|
||||
let unit = DOMAIN_ATTRIBUTES_UNITS[domain]?.[attribute] as
|
||||
| string
|
||||
| undefined;
|
||||
|
||||
if (domain === "light" && attribute === "brightness") {
|
||||
const percentage = Math.round((attributeValue / 255) * 100);
|
||||
return `${percentage}${blankBeforePercent(locale)}%`;
|
||||
}
|
||||
|
||||
if (domain === "weather") {
|
||||
unit = getWeatherUnit(config, stateObj as WeatherEntity, attribute);
|
||||
}
|
||||
|
@@ -2,10 +2,6 @@ import { HassConfig, HassEntity } from "home-assistant-js-websocket";
|
||||
import { UNAVAILABLE, UNKNOWN } from "../../data/entity";
|
||||
import { EntityRegistryDisplayEntry } from "../../data/entity_registry";
|
||||
import { FrontendLocaleData, TimeZone } from "../../data/translation";
|
||||
import {
|
||||
UPDATE_SUPPORT_PROGRESS,
|
||||
updateIsInstallingFromAttributes,
|
||||
} from "../../data/update";
|
||||
import { HomeAssistant } from "../../types";
|
||||
import {
|
||||
UNIT_TO_MILLISECOND_CONVERT,
|
||||
@@ -19,10 +15,9 @@ import {
|
||||
getNumberFormatOptions,
|
||||
isNumericFromAttributes,
|
||||
} from "../number/format_number";
|
||||
import { blankBeforeUnit } from "../translations/blank_before_unit";
|
||||
import { LocalizeFunc } from "../translations/localize";
|
||||
import { computeDomain } from "./compute_domain";
|
||||
import { supportsFeatureFromAttributes } from "./supports-feature";
|
||||
import { blankBeforeUnit } from "../translations/blank_before_unit";
|
||||
|
||||
export const computeStateDisplaySingleEntity = (
|
||||
localize: LocalizeFunc,
|
||||
@@ -208,27 +203,6 @@ export const computeStateDisplayFromEntityAttributes = (
|
||||
}
|
||||
}
|
||||
|
||||
if (domain === "update") {
|
||||
// When updating, and entity does not support % show "Installing"
|
||||
// When updating, and entity does support % show "Installing (xx%)"
|
||||
// When update available, show the version
|
||||
// When the latest version is skipped, show the latest version
|
||||
// When update is not available, show "Up-to-date"
|
||||
// When update is not available and there is no latest_version show "Unavailable"
|
||||
return state === "on"
|
||||
? updateIsInstallingFromAttributes(attributes)
|
||||
? supportsFeatureFromAttributes(attributes, UPDATE_SUPPORT_PROGRESS) &&
|
||||
typeof attributes.in_progress === "number"
|
||||
? localize("ui.card.update.installing_with_progress", {
|
||||
progress: attributes.in_progress,
|
||||
})
|
||||
: localize("ui.card.update.installing")
|
||||
: attributes.latest_version
|
||||
: attributes.skipped_version === attributes.latest_version
|
||||
? attributes.latest_version ?? localize("state.default.unavailable")
|
||||
: localize("ui.card.update.up_to_date");
|
||||
}
|
||||
|
||||
return (
|
||||
(entity?.translation_key &&
|
||||
localize(
|
||||
|
@@ -28,10 +28,12 @@ import {
|
||||
mdiLockAlert,
|
||||
mdiLockClock,
|
||||
mdiLockOpen,
|
||||
mdiMeterGas,
|
||||
mdiMotionSensor,
|
||||
mdiPackage,
|
||||
mdiPackageDown,
|
||||
mdiPackageUp,
|
||||
mdiPipeValve,
|
||||
mdiPowerPlug,
|
||||
mdiPowerPlugOff,
|
||||
mdiRestart,
|
||||
@@ -100,8 +102,8 @@ export const domainIconWithoutDefault = (
|
||||
return compareState === "unavailable"
|
||||
? mdiRobotConfused
|
||||
: compareState === "off"
|
||||
? mdiRobotOff
|
||||
: mdiRobot;
|
||||
? mdiRobotOff
|
||||
: mdiRobot;
|
||||
|
||||
case "binary_sensor":
|
||||
return binarySensorIcon(compareState, stateObj);
|
||||
@@ -274,6 +276,16 @@ export const domainIconWithoutDefault = (
|
||||
: mdiPackageUp
|
||||
: mdiPackage;
|
||||
|
||||
case "valve":
|
||||
switch (stateObj?.attributes.device_class) {
|
||||
case "water":
|
||||
return mdiPipeValve;
|
||||
case "gas":
|
||||
return mdiMeterGas;
|
||||
default:
|
||||
return mdiPipeValve;
|
||||
}
|
||||
|
||||
case "water_heater":
|
||||
return compareState === "off" ? mdiWaterBoilerOff : mdiWaterBoiler;
|
||||
|
||||
|
@@ -42,6 +42,8 @@ export function stateActive(stateObj: HassEntity, state?: string): boolean {
|
||||
return compareState !== "standby";
|
||||
case "vacuum":
|
||||
return !["idle", "docked", "paused"].includes(compareState);
|
||||
case "valve":
|
||||
return compareState !== "closed";
|
||||
case "plant":
|
||||
return compareState === "problem";
|
||||
case "group":
|
||||
|
@@ -37,6 +37,7 @@ const STATE_COLORED_DOMAIN = new Set([
|
||||
"timer",
|
||||
"update",
|
||||
"vacuum",
|
||||
"valve",
|
||||
"water_heater",
|
||||
]);
|
||||
|
||||
|
@@ -1,4 +1,5 @@
|
||||
import IntlMessageFormat from "intl-messageformat";
|
||||
import type { HTMLTemplateResult } from "lit";
|
||||
import { polyfillLocaleData } from "../../resources/locale-data-polyfill";
|
||||
import { Resources, TranslationDict } from "../../types";
|
||||
|
||||
@@ -40,9 +41,13 @@ export type FlattenObjectKeys<
|
||||
: `${Key}`
|
||||
: never;
|
||||
|
||||
// Later, don't return string when HTML is passed, and don't allow undefined
|
||||
export type LocalizeFunc<Keys extends string = LocalizeKeys> = (
|
||||
key: Keys,
|
||||
...args: any[]
|
||||
values?: Record<
|
||||
string,
|
||||
string | number | HTMLTemplateResult | null | undefined
|
||||
>
|
||||
) => string;
|
||||
|
||||
interface FormatType {
|
||||
@@ -124,6 +129,7 @@ export const computeLocalize = async <Keys extends string = LocalizeKeys>(
|
||||
argObject = args[0];
|
||||
} else {
|
||||
for (let i = 0; i < args.length; i += 2) {
|
||||
// @ts-expect-error in some places the old format (key, value, key, value) is used
|
||||
argObject[args[i]] = args[i + 1];
|
||||
}
|
||||
}
|
||||
|
@@ -40,15 +40,15 @@ export class HaProgressButton extends LitElement {
|
||||
${this._result === "success"
|
||||
? html`<ha-svg-icon .path=${mdiCheckBold}></ha-svg-icon>`
|
||||
: this._result === "error"
|
||||
? html`<ha-svg-icon .path=${mdiAlertOctagram}></ha-svg-icon>`
|
||||
: this.progress
|
||||
? html`
|
||||
<ha-circular-progress
|
||||
size="small"
|
||||
active
|
||||
></ha-circular-progress>
|
||||
`
|
||||
: ""}
|
||||
? html`<ha-svg-icon .path=${mdiAlertOctagram}></ha-svg-icon>`
|
||||
: this.progress
|
||||
? html`
|
||||
<ha-circular-progress
|
||||
size="small"
|
||||
indeterminate
|
||||
></ha-circular-progress>
|
||||
`
|
||||
: ""}
|
||||
</div>
|
||||
`}
|
||||
`;
|
||||
|
@@ -469,6 +469,7 @@ export class HaChartBase extends LitElement {
|
||||
.chartTooltip li {
|
||||
display: flex;
|
||||
white-space: pre-line;
|
||||
word-break: break-word;
|
||||
align-items: center;
|
||||
line-height: 16px;
|
||||
padding: 4px 0;
|
||||
@@ -476,6 +477,7 @@ export class HaChartBase extends LitElement {
|
||||
.chartTooltip .title {
|
||||
text-align: center;
|
||||
font-weight: 500;
|
||||
word-break: break-word;
|
||||
direction: ltr;
|
||||
}
|
||||
.chartTooltip .footer {
|
||||
|
@@ -45,10 +45,14 @@ export class StateHistoryChartLine extends LitElement {
|
||||
|
||||
@property({ type: Number }) public chartIndex?;
|
||||
|
||||
@property({ type: Boolean }) public logarithmicScale = false;
|
||||
|
||||
@state() private _chartData?: ChartData<"line">;
|
||||
|
||||
@state() private _entityIds: string[] = [];
|
||||
|
||||
private _datasetToDataIndex: number[] = [];
|
||||
|
||||
@state() private _chartOptions?: ChartOptions;
|
||||
|
||||
@state() private _yWidth = 0;
|
||||
@@ -78,7 +82,9 @@ export class StateHistoryChartLine extends LitElement {
|
||||
!this.hasUpdated ||
|
||||
changedProps.has("showNames") ||
|
||||
changedProps.has("startTime") ||
|
||||
changedProps.has("endTime")
|
||||
changedProps.has("endTime") ||
|
||||
changedProps.has("unit") ||
|
||||
changedProps.has("logarithmicScale")
|
||||
) {
|
||||
this._chartOptions = {
|
||||
parsing: false,
|
||||
@@ -132,20 +138,38 @@ export class StateHistoryChartLine extends LitElement {
|
||||
}
|
||||
},
|
||||
position: computeRTL(this.hass) ? "right" : "left",
|
||||
type: this.logarithmicScale ? "logarithmic" : "linear",
|
||||
},
|
||||
},
|
||||
plugins: {
|
||||
tooltip: {
|
||||
callbacks: {
|
||||
label: (context) =>
|
||||
`${context.dataset.label}: ${formatNumber(
|
||||
label: (context) => {
|
||||
let label = `${context.dataset.label}: ${formatNumber(
|
||||
context.parsed.y,
|
||||
this.hass.locale,
|
||||
getNumberFormatOptions(
|
||||
undefined,
|
||||
this.hass.entities[this._entityIds[context.datasetIndex]]
|
||||
)
|
||||
)} ${this.unit}`,
|
||||
)} ${this.unit}`;
|
||||
const dataIndex =
|
||||
this._datasetToDataIndex[context.datasetIndex];
|
||||
const data = this.data[dataIndex];
|
||||
if (data.statistics && data.statistics.length > 0) {
|
||||
const source =
|
||||
data.states.length === 0 ||
|
||||
context.parsed.x < data.states[0].last_changed
|
||||
? `\n${this.hass.localize(
|
||||
"ui.components.history_charts.source_stats"
|
||||
)}`
|
||||
: `\n${this.hass.localize(
|
||||
"ui.components.history_charts.source_history"
|
||||
)}`;
|
||||
label += source;
|
||||
}
|
||||
return label;
|
||||
},
|
||||
},
|
||||
},
|
||||
filler: {
|
||||
@@ -167,6 +191,19 @@ export class StateHistoryChartLine extends LitElement {
|
||||
hitRadius: 50,
|
||||
},
|
||||
},
|
||||
segment: {
|
||||
borderColor: (context) => {
|
||||
// render stat data with a slightly transparent line
|
||||
const dataIndex = this._datasetToDataIndex[context.datasetIndex];
|
||||
const data = this.data[dataIndex];
|
||||
return data.statistics &&
|
||||
data.statistics.length > 0 &&
|
||||
(data.states.length === 0 ||
|
||||
context.p0.parsed.x < data.states[0].last_changed)
|
||||
? this._chartData!.datasets[dataIndex].borderColor + "7F"
|
||||
: undefined;
|
||||
},
|
||||
},
|
||||
// @ts-expect-error
|
||||
locale: numberFormatToLocale(this.hass.locale),
|
||||
onClick: (e: any) => {
|
||||
@@ -212,6 +249,7 @@ export class StateHistoryChartLine extends LitElement {
|
||||
const entityStates = this.data;
|
||||
const datasets: ChartDataset<"line">[] = [];
|
||||
const entityIds: string[] = [];
|
||||
const datasetToDataIndex: number[] = [];
|
||||
if (entityStates.length === 0) {
|
||||
return;
|
||||
}
|
||||
@@ -219,7 +257,7 @@ export class StateHistoryChartLine extends LitElement {
|
||||
this._chartTime = new Date();
|
||||
const endTime = this.endTime;
|
||||
const names = this.names || {};
|
||||
entityStates.forEach((states) => {
|
||||
entityStates.forEach((states, dataIdx) => {
|
||||
const domain = states.domain;
|
||||
const name = names[states.entity_id] || states.name;
|
||||
// array containing [value1, value2, etc]
|
||||
@@ -264,6 +302,7 @@ export class StateHistoryChartLine extends LitElement {
|
||||
data: [],
|
||||
});
|
||||
entityIds.push(states.entity_id);
|
||||
datasetToDataIndex.push(dataIdx);
|
||||
};
|
||||
|
||||
if (
|
||||
@@ -470,7 +509,7 @@ export class StateHistoryChartLine extends LitElement {
|
||||
|
||||
// Process chart data.
|
||||
// When state is `unknown`, calculate the value and break the line.
|
||||
states.states.forEach((entityState) => {
|
||||
const processData = (entityState: LineChartState) => {
|
||||
const value = safeParseFloat(entityState.state);
|
||||
const date = new Date(entityState.last_changed);
|
||||
if (value !== null && lastNullDate) {
|
||||
@@ -499,6 +538,22 @@ export class StateHistoryChartLine extends LitElement {
|
||||
) {
|
||||
lastNullDate = date;
|
||||
}
|
||||
};
|
||||
|
||||
if (states.statistics) {
|
||||
const stopTime =
|
||||
!states.states || states.states.length === 0
|
||||
? 0
|
||||
: states.states[0].last_changed;
|
||||
for (let i = 0; i < states.statistics.length; i++) {
|
||||
if (stopTime && states.statistics[i].last_changed >= stopTime) {
|
||||
break;
|
||||
}
|
||||
processData(states.statistics[i]);
|
||||
}
|
||||
}
|
||||
states.states.forEach((entityState) => {
|
||||
processData(entityState);
|
||||
});
|
||||
if (lastNullDate !== null) {
|
||||
pushData(lastNullDate, [null]);
|
||||
@@ -516,6 +571,7 @@ export class StateHistoryChartLine extends LitElement {
|
||||
datasets,
|
||||
};
|
||||
this._entityIds = entityIds;
|
||||
this._datasetToDataIndex = datasetToDataIndex;
|
||||
}
|
||||
}
|
||||
customElements.define("state-history-chart-line", StateHistoryChartLine);
|
||||
|
@@ -161,8 +161,8 @@ export class StateHistoryChartTimeline extends LitElement {
|
||||
const yWidth = this.showNames
|
||||
? y.width ?? 0
|
||||
: computeRTL(this.hass)
|
||||
? 0
|
||||
: y.left ?? 0;
|
||||
? 0
|
||||
: y.left ?? 0;
|
||||
if (
|
||||
this._yWidth !== Math.floor(yWidth) &&
|
||||
y.ticks.length === this.data.length
|
||||
|
@@ -73,6 +73,8 @@ export class StateHistoryCharts extends LitElement {
|
||||
|
||||
@property({ type: Boolean }) public isLoadingData = false;
|
||||
|
||||
@property({ type: Boolean }) public logarithmicScale = false;
|
||||
|
||||
private _computedStartTime!: Date;
|
||||
|
||||
private _computedEndTime!: Date;
|
||||
@@ -159,6 +161,7 @@ export class StateHistoryCharts extends LitElement {
|
||||
.names=${this.names}
|
||||
.chartIndex=${index}
|
||||
.clickForMoreInfo=${this.clickForMoreInfo}
|
||||
.logarithmicScale=${this.logarithmicScale}
|
||||
@y-width-changed=${this._yWidthChanged}
|
||||
></state-history-chart-line>
|
||||
</div> `;
|
||||
@@ -300,6 +303,11 @@ export class StateHistoryCharts extends LitElement {
|
||||
padding-right: 1px;
|
||||
}
|
||||
|
||||
.entry-container:not(:first-child) {
|
||||
border-top: 2px solid var(--divider-color);
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.container,
|
||||
lit-virtualizer {
|
||||
height: 100%;
|
||||
|
@@ -71,6 +71,8 @@ export class StatisticsChart extends LitElement {
|
||||
|
||||
@property({ type: Boolean }) public hideLegend = false;
|
||||
|
||||
@property({ type: Boolean }) public logarithmicScale = false;
|
||||
|
||||
@property({ type: Boolean }) public isLoadingData = false;
|
||||
|
||||
@property() public period?: string;
|
||||
@@ -98,7 +100,9 @@ export class StatisticsChart extends LitElement {
|
||||
!this.hasUpdated ||
|
||||
changedProps.has("unit") ||
|
||||
changedProps.has("period") ||
|
||||
changedProps.has("chartType")
|
||||
changedProps.has("chartType") ||
|
||||
changedProps.has("logarithmicScale") ||
|
||||
changedProps.has("hideLegend")
|
||||
) {
|
||||
this._createOptions();
|
||||
}
|
||||
@@ -198,6 +202,7 @@ export class StatisticsChart extends LitElement {
|
||||
display: unit || this.unit,
|
||||
text: unit || this.unit,
|
||||
},
|
||||
type: this.logarithmicScale ? "logarithmic" : "linear",
|
||||
},
|
||||
},
|
||||
plugins: {
|
||||
@@ -396,8 +401,8 @@ export class StatisticsChart extends LitElement {
|
||||
? type === "min" && hasMean
|
||||
? "+1"
|
||||
: type === "max"
|
||||
? "-1"
|
||||
: false
|
||||
? "-1"
|
||||
: false
|
||||
: false,
|
||||
borderColor:
|
||||
band && hasMean ? color + (this.hideLegend ? "00" : "7F") : color,
|
||||
|
@@ -40,8 +40,8 @@ export class TextBarElement extends BarElement {
|
||||
(options?.backgroundColor === "transparent"
|
||||
? "transparent"
|
||||
: luminosity(hex2rgb(options.backgroundColor)) > 0.5
|
||||
? "#000"
|
||||
: "#fff");
|
||||
? "#000"
|
||||
: "#fff");
|
||||
|
||||
// ctx.font = "12px arial";
|
||||
ctx.fillStyle = textColor;
|
||||
|
@@ -1,3 +1,4 @@
|
||||
import "element-internals-polyfill";
|
||||
import { MdAssistChip } from "@material/web/chips/assist-chip";
|
||||
import { css, html } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
|
@@ -1,3 +1,4 @@
|
||||
import "element-internals-polyfill";
|
||||
import { MdChipSet } from "@material/web/chips/chip-set";
|
||||
import { customElement } from "lit/decorators";
|
||||
|
||||
|
@@ -1,3 +1,4 @@
|
||||
import "element-internals-polyfill";
|
||||
import { MdFilterChip } from "@material/web/chips/filter-chip";
|
||||
import { css, html } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
|
@@ -1,3 +1,4 @@
|
||||
import "element-internals-polyfill";
|
||||
import { MdInputChip } from "@material/web/chips/input-chip";
|
||||
import { css } from "lit";
|
||||
import { customElement } from "lit/decorators";
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user