mirror of
https://github.com/home-assistant/frontend.git
synced 2025-08-20 14:49:27 +00:00
Compare commits
687 Commits
restore-co
...
20200917.0
Author | SHA1 | Date | |
---|---|---|---|
![]() |
00dbba04a2 | ||
![]() |
3efd1a0451 | ||
![]() |
b7d7ca4014 | ||
![]() |
854a54e9c6 | ||
![]() |
4f4edb109f | ||
![]() |
265bfeb889 | ||
![]() |
96110637d9 | ||
![]() |
ad34f98e6d | ||
![]() |
a8a1563586 | ||
![]() |
9b25a54a47 | ||
![]() |
4b8c96c769 | ||
![]() |
c62ff85e73 | ||
![]() |
7d5a27ec0f | ||
![]() |
d6aba040dd | ||
![]() |
ca4757db5b | ||
![]() |
c917b67cbd | ||
![]() |
9659c97978 | ||
![]() |
7d862d6f2a | ||
![]() |
9c80776d8c | ||
![]() |
d5cd288fe8 | ||
![]() |
239e817779 | ||
![]() |
1986215919 | ||
![]() |
239f5f1a2f | ||
![]() |
3bca32c6d5 | ||
![]() |
183eff745d | ||
![]() |
4392d78ff6 | ||
![]() |
858196ab53 | ||
![]() |
fb75d8c1f2 | ||
![]() |
7628569579 | ||
![]() |
8a9d5f7753 | ||
![]() |
cdcccf5089 | ||
![]() |
de95c92e2d | ||
![]() |
3030b8d476 | ||
![]() |
92ed14c0e4 | ||
![]() |
5b94a4de9a | ||
![]() |
709112c498 | ||
![]() |
e465ec8835 | ||
![]() |
f6eb31bf9d | ||
![]() |
426f939982 | ||
![]() |
fab6cebf0d | ||
![]() |
ff081dd0f1 | ||
![]() |
868399ed6f | ||
![]() |
1bc9b95289 | ||
![]() |
9af805ab5e | ||
![]() |
6b88081360 | ||
![]() |
667c828359 | ||
![]() |
50d37ce4f6 | ||
![]() |
af0246cd27 | ||
![]() |
857e4e49d8 | ||
![]() |
c1afed7f98 | ||
![]() |
5480e54185 | ||
![]() |
99d0a0a6fd | ||
![]() |
8a998369d6 | ||
![]() |
8b490c5047 | ||
![]() |
7e70ba6ab2 | ||
![]() |
90e09fc384 | ||
![]() |
266f2e763d | ||
![]() |
c979cfb912 | ||
![]() |
8ee29b1e43 | ||
![]() |
26fbc07cac | ||
![]() |
f01fe65be4 | ||
![]() |
3fdd6a80f9 | ||
![]() |
da1de8db1d | ||
![]() |
dd1bf7b49d | ||
![]() |
f18913b5a0 | ||
![]() |
a2cd227f1a | ||
![]() |
78e64e1f60 | ||
![]() |
23a9b79320 | ||
![]() |
76394ce341 | ||
![]() |
1935df1faa | ||
![]() |
5af4ce28ab | ||
![]() |
ce8ee569c4 | ||
![]() |
b0508f430e | ||
![]() |
2139a80a7a | ||
![]() |
aa4bc2ce03 | ||
![]() |
fa65f84e09 | ||
![]() |
c06357a351 | ||
![]() |
092a02a624 | ||
![]() |
b9699f745f | ||
![]() |
7fa9f10c30 | ||
![]() |
7bf0655dae | ||
![]() |
96c5fdcbeb | ||
![]() |
c2e6d40382 | ||
![]() |
810d2a1ceb | ||
![]() |
af74f21af9 | ||
![]() |
cdf7558a8e | ||
![]() |
41b86e6c10 | ||
![]() |
085c6f8bdd | ||
![]() |
3039c678a5 | ||
![]() |
498882d014 | ||
![]() |
6c2b8c2abb | ||
![]() |
e955cc4378 | ||
![]() |
eb96dd4803 | ||
![]() |
e0bdef98a6 | ||
![]() |
1130007d14 | ||
![]() |
d99d092784 | ||
![]() |
e3b18a33ca | ||
![]() |
1890aab1e6 | ||
![]() |
42bf350034 | ||
![]() |
5ff52ea113 | ||
![]() |
432e3ba636 | ||
![]() |
f7ab52fe9a | ||
![]() |
ad8430049d | ||
![]() |
2dffe7ba9e | ||
![]() |
5b8f97e0f6 | ||
![]() |
b3a763a48d | ||
![]() |
07569f10b5 | ||
![]() |
7c5a78a1cf | ||
![]() |
5cca5bfe86 | ||
![]() |
0e021e7d7d | ||
![]() |
b30ee884a7 | ||
![]() |
869b7c85ca | ||
![]() |
4d0d1ed2a1 | ||
![]() |
291983e4c3 | ||
![]() |
909cff2158 | ||
![]() |
4e676b1dba | ||
![]() |
9149bb9333 | ||
![]() |
4631994f20 | ||
![]() |
82e9178320 | ||
![]() |
67b4688168 | ||
![]() |
6e0e169b6e | ||
![]() |
100ba8edfa | ||
![]() |
d7448ecb95 | ||
![]() |
8b1801f378 | ||
![]() |
01a4d57566 | ||
![]() |
7edc9064d9 | ||
![]() |
30c47a65f4 | ||
![]() |
0889f42a00 | ||
![]() |
4999f1ad51 | ||
![]() |
f15fbe53cf | ||
![]() |
046f7b5153 | ||
![]() |
5339fe6e06 | ||
![]() |
de7ffb10cb | ||
![]() |
80224e6974 | ||
![]() |
0c7c536f73 | ||
![]() |
e5c386c39a | ||
![]() |
bb2462483e | ||
![]() |
d5bc498373 | ||
![]() |
979b7ae651 | ||
![]() |
c73330a466 | ||
![]() |
efe8eca4e3 | ||
![]() |
a37aad18b7 | ||
![]() |
cfa0c45213 | ||
![]() |
509481ef06 | ||
![]() |
9aa8175e23 | ||
![]() |
76f59d99a2 | ||
![]() |
bd66bd6cf0 | ||
![]() |
d69333dea4 | ||
![]() |
3fd7899b93 | ||
![]() |
8f8a2cea56 | ||
![]() |
879011c8e9 | ||
![]() |
d5794c3e2e | ||
![]() |
61dbae8b8b | ||
![]() |
fcc22ba560 | ||
![]() |
2adeb88fe6 | ||
![]() |
e63a78bcdb | ||
![]() |
b065f002a4 | ||
![]() |
349a5f52b1 | ||
![]() |
aa5e20df05 | ||
![]() |
793b9f238c | ||
![]() |
9c4fdaa4f3 | ||
![]() |
d1a9cb488a | ||
![]() |
faee2c3e1b | ||
![]() |
b7845c318e | ||
![]() |
426a0727c3 | ||
![]() |
584e509a9c | ||
![]() |
f3639c2663 | ||
![]() |
1431e75f8b | ||
![]() |
be8812e0af | ||
![]() |
fd6436d490 | ||
![]() |
fd1342f9d1 | ||
![]() |
5fa0012195 | ||
![]() |
9dbb67ef01 | ||
![]() |
d16e2f37d4 | ||
![]() |
d9e8b53ffe | ||
![]() |
1997e63b7c | ||
![]() |
6f673359ff | ||
![]() |
45dfbff10a | ||
![]() |
348ee96274 | ||
![]() |
8edee32e77 | ||
![]() |
6d8d263ca6 | ||
![]() |
35923709e2 | ||
![]() |
fdd4d53448 | ||
![]() |
06419f662e | ||
![]() |
57763ef032 | ||
![]() |
8e506f7749 | ||
![]() |
c7f8fe1468 | ||
![]() |
4156a4e36d | ||
![]() |
ba3cc7df0f | ||
![]() |
0c212d39eb | ||
![]() |
3bd2e8dbf5 | ||
![]() |
5292119e6e | ||
![]() |
994a397231 | ||
![]() |
353b71f803 | ||
![]() |
eb12afe8cc | ||
![]() |
4a176f1b43 | ||
![]() |
8e228baa82 | ||
![]() |
154b53b0d8 | ||
![]() |
a3f680d80c | ||
![]() |
0d75fe6b81 | ||
![]() |
4070380ded | ||
![]() |
41195dcef0 | ||
![]() |
78a1e45be2 | ||
![]() |
d8e88bc58d | ||
![]() |
448e9b71b8 | ||
![]() |
2e178164cc | ||
![]() |
9f2e3f05a1 | ||
![]() |
405bd29ebd | ||
![]() |
b39b54e0ac | ||
![]() |
119c5c9071 | ||
![]() |
7a4c9b128c | ||
![]() |
dc5b92030f | ||
![]() |
db0a010d7c | ||
![]() |
a117a19bdf | ||
![]() |
5f46fdb406 | ||
![]() |
f0201de4cc | ||
![]() |
6cd51a318b | ||
![]() |
c1a4b27bc7 | ||
![]() |
5113222050 | ||
![]() |
90f12eea5e | ||
![]() |
2403743701 | ||
![]() |
3e6a759309 | ||
![]() |
35a430e9f4 | ||
![]() |
b644f7d23d | ||
![]() |
7702a05464 | ||
![]() |
493af5fe82 | ||
![]() |
ac66a59cec | ||
![]() |
e10c8faa47 | ||
![]() |
9b7d17433c | ||
![]() |
a40eb1ff43 | ||
![]() |
04df6c3e9e | ||
![]() |
1b970e5a66 | ||
![]() |
75406c2d01 | ||
![]() |
64d3511fbc | ||
![]() |
c610f54977 | ||
![]() |
090ad34f78 | ||
![]() |
358c5205d2 | ||
![]() |
5503cd0589 | ||
![]() |
dae42b1bd9 | ||
![]() |
06a25284e8 | ||
![]() |
5989560f15 | ||
![]() |
63c995e5da | ||
![]() |
dc5607f554 | ||
![]() |
d49302c032 | ||
![]() |
63fef9bd4b | ||
![]() |
6599351d45 | ||
![]() |
47e9531972 | ||
![]() |
3ba31483f4 | ||
![]() |
f4ca94f2e1 | ||
![]() |
67f9be2b77 | ||
![]() |
e2fd155e1b | ||
![]() |
931068dede | ||
![]() |
bc4c9cc40d | ||
![]() |
294665fbe8 | ||
![]() |
e8f6a79c8f | ||
![]() |
5fd8b5c5b9 | ||
![]() |
226b2a73af | ||
![]() |
42d421a6fc | ||
![]() |
a90203f256 | ||
![]() |
c3ef79caa9 | ||
![]() |
1439afcd9c | ||
![]() |
d263b19910 | ||
![]() |
1e477226ea | ||
![]() |
026fc1d2e3 | ||
![]() |
2d4bd9857a | ||
![]() |
8f48f5b45c | ||
![]() |
22210b7400 | ||
![]() |
7d05855ee0 | ||
![]() |
b2460cbc3d | ||
![]() |
4561957e56 | ||
![]() |
3367fadc3a | ||
![]() |
d7e409b042 | ||
![]() |
a0b28e8ad1 | ||
![]() |
f928a8e58e | ||
![]() |
0bc4b3d0fa | ||
![]() |
e352768388 | ||
![]() |
6835b73e49 | ||
![]() |
f1503f871b | ||
![]() |
c4d8aba5c8 | ||
![]() |
39f24c41ad | ||
![]() |
21644ec889 | ||
![]() |
613470b44d | ||
![]() |
6c918e346b | ||
![]() |
bce8539572 | ||
![]() |
aab86e00ec | ||
![]() |
2a58726caf | ||
![]() |
4163b35b32 | ||
![]() |
9c6dac8180 | ||
![]() |
80fc37724b | ||
![]() |
77b25f5132 | ||
![]() |
684f098450 | ||
![]() |
d09f74d30f | ||
![]() |
3d973b112e | ||
![]() |
96986164a4 | ||
![]() |
78152c20a9 | ||
![]() |
2bb64e9e2f | ||
![]() |
746844dfc8 | ||
![]() |
41b613a2d7 | ||
![]() |
3b3aeea224 | ||
![]() |
71c592a0ce | ||
![]() |
15193fcf5f | ||
![]() |
a31f53395f | ||
![]() |
283b134d84 | ||
![]() |
271eb614cd | ||
![]() |
16167bef07 | ||
![]() |
1eac9fa1cd | ||
![]() |
7f819f0020 | ||
![]() |
dec1f99a5f | ||
![]() |
c705e74fc8 | ||
![]() |
01df10f93e | ||
![]() |
9877f08cf4 | ||
![]() |
3dc4b1d775 | ||
![]() |
02791c51ae | ||
![]() |
49683326e6 | ||
![]() |
947773a82e | ||
![]() |
2a229df624 | ||
![]() |
e605ad5e46 | ||
![]() |
0d4f43472b | ||
![]() |
b30e467685 | ||
![]() |
a56c0b52d5 | ||
![]() |
c17ebfd279 | ||
![]() |
5400b1da96 | ||
![]() |
69f4a618b2 | ||
![]() |
16b8b6698c | ||
![]() |
b29a700d40 | ||
![]() |
bbb1468439 | ||
![]() |
72f9d6a8d3 | ||
![]() |
3ec8da1f17 | ||
![]() |
dbea3848df | ||
![]() |
33871435e1 | ||
![]() |
f1f22b43dc | ||
![]() |
2fb9a56e0b | ||
![]() |
14e8f66ed7 | ||
![]() |
e6f5072462 | ||
![]() |
a64f50fa72 | ||
![]() |
bb5f6e88d0 | ||
![]() |
6991403203 | ||
![]() |
410bd22f8a | ||
![]() |
b81d823602 | ||
![]() |
bd5115f9aa | ||
![]() |
7bcbed80d7 | ||
![]() |
8fb62ebf5f | ||
![]() |
209dd9923f | ||
![]() |
c75207e391 | ||
![]() |
d957f36927 | ||
![]() |
9ac459b6d9 | ||
![]() |
e08b2817ba | ||
![]() |
4ca13c409b | ||
![]() |
0d515e2303 | ||
![]() |
a2153bc6aa | ||
![]() |
ca171afe6f | ||
![]() |
bf4e97bd48 | ||
![]() |
8c59a12a03 | ||
![]() |
89569355be | ||
![]() |
3a41b3bdcf | ||
![]() |
12bd7037b3 | ||
![]() |
ca4f573be0 | ||
![]() |
07fceeab5a | ||
![]() |
3aa376e912 | ||
![]() |
92d30a8896 | ||
![]() |
83876fb9da | ||
![]() |
29bdf7877c | ||
![]() |
29199e2782 | ||
![]() |
68e1378615 | ||
![]() |
cf7efb5bfc | ||
![]() |
8634ee536d | ||
![]() |
632d3cda24 | ||
![]() |
29b6a907d4 | ||
![]() |
7474d09e5d | ||
![]() |
fc7bcd7e00 | ||
![]() |
f6fb2e4b1d | ||
![]() |
8c8673a272 | ||
![]() |
4404a1173b | ||
![]() |
e08c10315e | ||
![]() |
16473c9177 | ||
![]() |
235fd5603f | ||
![]() |
d07d5832f5 | ||
![]() |
ef8be5d559 | ||
![]() |
ccafdc6e1f | ||
![]() |
11827aa4c0 | ||
![]() |
6b0589d343 | ||
![]() |
cec1eed99e | ||
![]() |
d7e1e9e284 | ||
![]() |
cae46453a7 | ||
![]() |
a6e948c808 | ||
![]() |
7638020bfc | ||
![]() |
10a62ca17c | ||
![]() |
0afc7c184f | ||
![]() |
168e26aeb4 | ||
![]() |
e6b9389b33 | ||
![]() |
377c37425e | ||
![]() |
4af26602bb | ||
![]() |
c6624e5cb6 | ||
![]() |
f7ae5b91bf | ||
![]() |
07e68496c0 | ||
![]() |
d5a947e2cc | ||
![]() |
3f920767f1 | ||
![]() |
3e14d27a1e | ||
![]() |
cfa4c14108 | ||
![]() |
209056dbe1 | ||
![]() |
10356a7496 | ||
![]() |
d4ae74de44 | ||
![]() |
88d5e7dd5e | ||
![]() |
06c7b0b82e | ||
![]() |
689febda60 | ||
![]() |
80bc6fda8b | ||
![]() |
346eb78c4e | ||
![]() |
2df02f1b09 | ||
![]() |
92915eddc2 | ||
![]() |
cddbf460f8 | ||
![]() |
3c63c23e5a | ||
![]() |
ba67b1291f | ||
![]() |
7bced28327 | ||
![]() |
db2b60700c | ||
![]() |
9034822c44 | ||
![]() |
e8254f9aae | ||
![]() |
a14179b81a | ||
![]() |
427c5db7f4 | ||
![]() |
fcb5865468 | ||
![]() |
41370be2b8 | ||
![]() |
d7d8dd8986 | ||
![]() |
a0f596e419 | ||
![]() |
0a8894feb7 | ||
![]() |
1db9eea0f8 | ||
![]() |
489783c398 | ||
![]() |
be62f327ee | ||
![]() |
32359adb6d | ||
![]() |
2e198af8c3 | ||
![]() |
d154fcbd71 | ||
![]() |
21e277b8a2 | ||
![]() |
f98cdd0749 | ||
![]() |
e60e306426 | ||
![]() |
ec36d396d9 | ||
![]() |
135232d880 | ||
![]() |
9c42ca0315 | ||
![]() |
9ad9c569a6 | ||
![]() |
a9071d7920 | ||
![]() |
1b4a10fac1 | ||
![]() |
d340f3b383 | ||
![]() |
f8c5eeab5d | ||
![]() |
9cd2d0df93 | ||
![]() |
78914091b1 | ||
![]() |
e12c324613 | ||
![]() |
7cf396b518 | ||
![]() |
8b3b40e627 | ||
![]() |
90e14762e3 | ||
![]() |
d1dd8231cd | ||
![]() |
e70a3e09bf | ||
![]() |
98656b0044 | ||
![]() |
a48aa3c778 | ||
![]() |
05d7b98ba0 | ||
![]() |
f291ea6647 | ||
![]() |
5d6e332044 | ||
![]() |
acb471fbe5 | ||
![]() |
894f4379e6 | ||
![]() |
1c73007ae6 | ||
![]() |
2f7d744228 | ||
![]() |
e2cba90f8d | ||
![]() |
352214ba0a | ||
![]() |
bd9b72fb22 | ||
![]() |
50c9a667b3 | ||
![]() |
3d32e6310d | ||
![]() |
3bc54aa9e0 | ||
![]() |
def1ec3518 | ||
![]() |
077802f972 | ||
![]() |
914b47f340 | ||
![]() |
b2a78fd063 | ||
![]() |
7d1835e59c | ||
![]() |
833ccf3637 | ||
![]() |
51be916f39 | ||
![]() |
e375408777 | ||
![]() |
5078dc1cbf | ||
![]() |
875148366e | ||
![]() |
c9ec4b4e24 | ||
![]() |
efa2b2db27 | ||
![]() |
8ce120b74d | ||
![]() |
26e678a97d | ||
![]() |
e71dd7409e | ||
![]() |
58ffc2c6ca | ||
![]() |
d3f29362b9 | ||
![]() |
b429fe8254 | ||
![]() |
e1cb549b28 | ||
![]() |
65a22257cc | ||
![]() |
e2f753eaa7 | ||
![]() |
c7127b65bf | ||
![]() |
0c58c3572a | ||
![]() |
26ae5fd728 | ||
![]() |
370d92213b | ||
![]() |
6e8321a22a | ||
![]() |
a8e8c1ce5d | ||
![]() |
a8a8cafd2b | ||
![]() |
b609890f28 | ||
![]() |
aac09ae092 | ||
![]() |
f1ff872944 | ||
![]() |
b195d2980a | ||
![]() |
d11736181f | ||
![]() |
3e84486dd5 | ||
![]() |
a674ce36e4 | ||
![]() |
f6569a2625 | ||
![]() |
da10da79b3 | ||
![]() |
f236b76d5c | ||
![]() |
a71c22bedd | ||
![]() |
cc528e41cf | ||
![]() |
351962475f | ||
![]() |
6c73392a57 | ||
![]() |
072ad87831 | ||
![]() |
370a1f0574 | ||
![]() |
9ca7aca4b7 | ||
![]() |
0f2e9f66b1 | ||
![]() |
d03c3ab713 | ||
![]() |
57c0b34ae9 | ||
![]() |
06a94f0f28 | ||
![]() |
750e7b1262 | ||
![]() |
19e32752bb | ||
![]() |
89c0729964 | ||
![]() |
fd07152aea | ||
![]() |
b656f189b6 | ||
![]() |
9ac8d70152 | ||
![]() |
8cc0b46335 | ||
![]() |
1f15094da7 | ||
![]() |
b881adb853 | ||
![]() |
4bfc3a5629 | ||
![]() |
ae6c0bfe40 | ||
![]() |
4ce9c71521 | ||
![]() |
ec48323a7d | ||
![]() |
7d9bae16cd | ||
![]() |
163ff3d4e4 | ||
![]() |
43fbf97e10 | ||
![]() |
71faaf2ab1 | ||
![]() |
7b0e743eca | ||
![]() |
31a0c53855 | ||
![]() |
e8996063dd | ||
![]() |
00842a3354 | ||
![]() |
d33f18ecb7 | ||
![]() |
fb7f620316 | ||
![]() |
712e0d3e3b | ||
![]() |
1870dc29c0 | ||
![]() |
da6fdc74d8 | ||
![]() |
515e39154a | ||
![]() |
ff7731d063 | ||
![]() |
e9a3666dd5 | ||
![]() |
55c56d53f4 | ||
![]() |
e4d55e6842 | ||
![]() |
d8661cf2db | ||
![]() |
8815b126b5 | ||
![]() |
2cd367f29f | ||
![]() |
7395d19489 | ||
![]() |
d55cb95479 | ||
![]() |
68ece7d363 | ||
![]() |
6e4a8ac6df | ||
![]() |
790629849f | ||
![]() |
f0443a43b2 | ||
![]() |
3041eb5ce0 | ||
![]() |
c69247f190 | ||
![]() |
27d6a62e67 | ||
![]() |
6e7fc914aa | ||
![]() |
65d587843b | ||
![]() |
c54792af22 | ||
![]() |
7b7e023103 | ||
![]() |
7637d36146 | ||
![]() |
6c62afb123 | ||
![]() |
44210ce6f2 | ||
![]() |
e21efc0a5c | ||
![]() |
09a965022f | ||
![]() |
7534ecd2f2 | ||
![]() |
46bf5cf830 | ||
![]() |
7ba7761a57 | ||
![]() |
5268afabdb | ||
![]() |
3ea7506003 | ||
![]() |
ee14d206c8 | ||
![]() |
b65f4b9af6 | ||
![]() |
6d000a3f9a | ||
![]() |
9ba0de67f5 | ||
![]() |
d22eaa1318 | ||
![]() |
3f4bfab7fe | ||
![]() |
9292f217c5 | ||
![]() |
3b779bf423 | ||
![]() |
ea410d3af1 | ||
![]() |
4e71c2c500 | ||
![]() |
454ddf366a | ||
![]() |
d0ba5696d1 | ||
![]() |
c53fd0d1e1 | ||
![]() |
7bbecfde2b | ||
![]() |
a06f378582 | ||
![]() |
b3b42b741d | ||
![]() |
020f115d7c | ||
![]() |
2cc9d70915 | ||
![]() |
b242c6651a | ||
![]() |
14a51799a6 | ||
![]() |
79a6dacd2f | ||
![]() |
6891f1df1c | ||
![]() |
497494620d | ||
![]() |
7a13242077 | ||
![]() |
b9d6973a79 | ||
![]() |
ed0e8c5e8d | ||
![]() |
d8f530f8ac | ||
![]() |
a46874b7ff | ||
![]() |
cf68f25a03 | ||
![]() |
a496563b5c | ||
![]() |
a763ad5bf1 | ||
![]() |
f0b0200932 | ||
![]() |
342f22e6a1 | ||
![]() |
372ecc6557 | ||
![]() |
16c604937e | ||
![]() |
5cbffb23fd | ||
![]() |
6de38d3b85 | ||
![]() |
b34ce577d9 | ||
![]() |
4b9fcd7de7 | ||
![]() |
cc71ccaafa | ||
![]() |
9ff2eece3a | ||
![]() |
2bc97cc9c8 | ||
![]() |
a87570cf5a | ||
![]() |
9ffd25e3a0 | ||
![]() |
61fdab294a | ||
![]() |
d4e137bb58 | ||
![]() |
c251e4f241 | ||
![]() |
a55d0f347b | ||
![]() |
f15cc0b424 | ||
![]() |
4e17875011 | ||
![]() |
256b64b6b3 | ||
![]() |
8c0c0592e2 | ||
![]() |
f53f81dbc4 | ||
![]() |
d6c85719c9 | ||
![]() |
c51c80bf47 | ||
![]() |
544832756d | ||
![]() |
ca8586789a | ||
![]() |
1afc2b3518 | ||
![]() |
17352ea5bd | ||
![]() |
6f5e3c2711 | ||
![]() |
bee21cd3fe | ||
![]() |
c4340e05d2 | ||
![]() |
6d6eef4e97 | ||
![]() |
71137032df | ||
![]() |
ffff4dc03d | ||
![]() |
4033131f2e | ||
![]() |
65b16c763e | ||
![]() |
33af3de4a3 | ||
![]() |
a822c1eb2f | ||
![]() |
4eb46bc275 | ||
![]() |
ccc9b73f9b | ||
![]() |
e9ffdeff19 | ||
![]() |
a0ab4dffc9 | ||
![]() |
f5f8ad0e02 | ||
![]() |
82957ff6ef | ||
![]() |
0864aeb9c6 | ||
![]() |
cda6310373 | ||
![]() |
26a87e9280 | ||
![]() |
0b16a4880a | ||
![]() |
db68c5852c | ||
![]() |
256aec5308 | ||
![]() |
20dd3ca21c | ||
![]() |
25cc76e022 | ||
![]() |
168cc607aa | ||
![]() |
edc4601f8e | ||
![]() |
8f86a7ad8c | ||
![]() |
4d7ad0dc51 | ||
![]() |
34ac80567e | ||
![]() |
23bdc03f29 | ||
![]() |
3099748a6d | ||
![]() |
e384f76ac1 | ||
![]() |
7050d19be7 | ||
![]() |
ca678330d3 | ||
![]() |
10a5b3f9c3 | ||
![]() |
0fcedc5046 | ||
![]() |
f558f7fb8c | ||
![]() |
06207defe7 | ||
![]() |
986f9d7633 | ||
![]() |
81277fd12e | ||
![]() |
30442b25c0 | ||
![]() |
67ac3b4ba3 | ||
![]() |
f819e2cf8d | ||
![]() |
a376f4525b | ||
![]() |
5c1553286a | ||
![]() |
2c5d3f7492 | ||
![]() |
58f01ba11a | ||
![]() |
3fdf9a2e28 | ||
![]() |
4cbd8e7673 | ||
![]() |
20ca642e51 | ||
![]() |
05ad7ea011 | ||
![]() |
faea8c9f4c | ||
![]() |
0d4c51f26e | ||
![]() |
5d5d6b247f | ||
![]() |
404d0b8d05 | ||
![]() |
fe63c12cd9 | ||
![]() |
44023c3db7 | ||
![]() |
6242997849 | ||
![]() |
ec5d7508c9 |
@@ -1,4 +0,0 @@
|
||||
node_modules
|
||||
hass_frontend
|
||||
hass_frontend_es5
|
||||
.git
|
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"extends": [
|
||||
"plugin:@typescript-eslint/recommended",
|
||||
"airbnb-typescript/base",
|
||||
"plugin:@typescript-eslint/recommended",
|
||||
"plugin:wc/recommended",
|
||||
"plugin:lit/recommended",
|
||||
"prettier",
|
||||
@@ -45,16 +45,16 @@
|
||||
"func-names": 0,
|
||||
"prefer-arrow-callback": 0,
|
||||
"no-underscore-dangle": 0,
|
||||
"no-var": 0,
|
||||
"strict": 0,
|
||||
"prefer-spread": 0,
|
||||
"no-plusplus": 0,
|
||||
"no-bitwise": 0,
|
||||
"no-bitwise": 2,
|
||||
"comma-dangle": 0,
|
||||
"vars-on-top": 0,
|
||||
"no-continue": 0,
|
||||
"no-param-reassign": 0,
|
||||
"no-multi-assign": 0,
|
||||
"no-console": 2,
|
||||
"radix": 0,
|
||||
"no-alert": 0,
|
||||
"no-return-await": 0,
|
||||
|
2
.github/ISSUE_TEMPLATE/BUG_REPORT.md
vendored
2
.github/ISSUE_TEMPLATE/BUG_REPORT.md
vendored
@@ -51,7 +51,7 @@ DO NOT DELETE ANY TEXT from this template! Otherwise, your issue may be closed w
|
||||
<!--
|
||||
Provide details about the versions you are using, which helps us reproducing
|
||||
and finding the issue quicker. Version information is found in the
|
||||
Home Assistant frontend: Developer tools -> Info.
|
||||
Home Assistant frontend: Configuration -> Info.
|
||||
|
||||
Browser version and operating system is important! Please try to replicate
|
||||
your issue in a different browser and be sure to include your findings.
|
||||
|
6
.github/workflows/ci.yaml
vendored
6
.github/workflows/ci.yaml
vendored
@@ -34,10 +34,8 @@ jobs:
|
||||
run: yarn install
|
||||
env:
|
||||
CI: true
|
||||
- name: Build icons
|
||||
run: ./node_modules/.bin/gulp gen-icons-json
|
||||
- name: Build translations
|
||||
run: ./node_modules/.bin/gulp build-translations
|
||||
- name: Build resources
|
||||
run: ./node_modules/.bin/gulp gen-icons-json build-translations gather-gallery-demos
|
||||
- name: Run eslint
|
||||
run: ./node_modules/.bin/eslint '{**/src,src}/**/*.{js,ts,html}' --ignore-path .gitignore
|
||||
- name: Run tsc
|
||||
|
60
.github/workflows/codeql-analysis.yml
vendored
Normal file
60
.github/workflows/codeql-analysis.yml
vendored
Normal file
@@ -0,0 +1,60 @@
|
||||
name: "CodeQL"
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [dev, master]
|
||||
pull_request:
|
||||
# The branches below must be a subset of the branches above
|
||||
branches: [dev]
|
||||
|
||||
jobs:
|
||||
analyze:
|
||||
name: Analyze
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
# Override automatic language detection by changing the below list
|
||||
# Supported options are ['csharp', 'cpp', 'go', 'java', 'javascript', 'python']
|
||||
language: ['javascript']
|
||||
# Learn more...
|
||||
# https://docs.github.com/en/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#overriding-automatic-language-detection
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
# We must fetch at least the immediate parents so that if this is
|
||||
# a pull request then we can checkout the head.
|
||||
fetch-depth: 2
|
||||
|
||||
# If this run was triggered by a pull request event, then checkout
|
||||
# the head of the pull request instead of the merge commit.
|
||||
- run: git checkout HEAD^2
|
||||
if: ${{ github.event_name == 'pull_request' }}
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v1
|
||||
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@v1
|
||||
|
||||
# ℹ️ Command-line programs to run using the OS shell.
|
||||
# 📚 https://git.io/JvXDl
|
||||
|
||||
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
|
||||
# and modify them (or add more) to build your code if your project
|
||||
# uses a compiled language
|
||||
|
||||
#- run: |
|
||||
# make bootstrap
|
||||
# make release
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v1
|
2
.gitignore
vendored
2
.gitignore
vendored
@@ -24,7 +24,7 @@ dist
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
|
||||
# Cast dev settings
|
||||
# Cast dev settings
|
||||
src/cast/dev_const.ts
|
||||
|
||||
# Secrets
|
||||
|
31
Dockerfile
31
Dockerfile
@@ -1,31 +0,0 @@
|
||||
FROM node:8.11.1-alpine
|
||||
|
||||
# install yarn
|
||||
ENV PATH /root/.yarn/bin:$PATH
|
||||
|
||||
## Install/force base tools
|
||||
RUN apk update \
|
||||
&& apk add make g++ curl bash binutils tar git python2 python3 \
|
||||
&& rm -rf /var/cache/apk/* \
|
||||
&& /bin/bash \
|
||||
&& touch ~/.bashrc
|
||||
|
||||
## Install yarn
|
||||
RUN curl -o- -L https://yarnpkg.com/install.sh | bash
|
||||
|
||||
## Setup the project
|
||||
RUN mkdir -p /frontend
|
||||
|
||||
WORKDIR /frontend
|
||||
|
||||
COPY package.json yarn.lock ./
|
||||
|
||||
RUN yarn install --frozen-lockfile
|
||||
|
||||
COPY . .
|
||||
|
||||
COPY script/docker_entrypoint.sh /usr/bin/docker_entrypoint.sh
|
||||
|
||||
RUN chmod +x /usr/bin/docker_entrypoint.sh
|
||||
|
||||
CMD [ "docker_entrypoint.sh" ]
|
@@ -22,15 +22,6 @@ This is the repository for the official [Home Assistant](https://home-assistant.
|
||||
|
||||
A complete guide can be found at the following [link](https://www.home-assistant.io/developers/frontend/). It describes a short guide for the build of project.
|
||||
|
||||
### Docker environment
|
||||
|
||||
It is possible to compile the project and/or run commands in the development environment having only the [Docker](https://www.docker.com) pre-installed in the system. On the root of project you can do:
|
||||
|
||||
- `sh ./script/docker_run.sh build` Build all the project with one command
|
||||
- `sh ./script/docker_run.sh bash` Open an interactive shell (the same environment generated by the _classic environment_) where you can run commands. This bash work on your project directory and any change on your file is automatically present within your build bash.
|
||||
|
||||
**Note**: if you have installed `npm` in addition to the `docker`, you can use the commands `npm run docker_build` and `npm run bash` to get a full build or bash as explained above
|
||||
|
||||
## License
|
||||
|
||||
Home Assistant is open-source and Apache 2 licensed. Feel free to browse the repository, learn and reuse parts in your own projects.
|
||||
|
@@ -57,7 +57,7 @@ module.exports.babelOptions = ({ latestBuild }) => ({
|
||||
].filter(Boolean),
|
||||
plugins: [
|
||||
// Part of ES2018. Converts {...a, b: 2} to Object.assign({}, a, {b: 2})
|
||||
[
|
||||
!latestBuild && [
|
||||
"@babel/plugin-proposal-object-rest-spread",
|
||||
{ loose: true, useBuiltIns: true },
|
||||
],
|
||||
@@ -73,7 +73,7 @@ module.exports.babelOptions = ({ latestBuild }) => ({
|
||||
require("@babel/plugin-proposal-class-properties").default,
|
||||
{ loose: true },
|
||||
],
|
||||
],
|
||||
].filter(Boolean),
|
||||
});
|
||||
|
||||
// Are already ES5, cause warnings when babelified.
|
||||
@@ -85,8 +85,8 @@ module.exports.babelExclude = () => [
|
||||
const outputPath = (outputRoot, latestBuild) =>
|
||||
path.resolve(outputRoot, latestBuild ? "frontend_latest" : "frontend_es5");
|
||||
|
||||
const publicPath = (latestBuild) =>
|
||||
latestBuild ? "/frontend_latest/" : "/frontend_es5/";
|
||||
const publicPath = (latestBuild, root = "") =>
|
||||
latestBuild ? `${root}/frontend_latest/` : `${root}/frontend_es5/`;
|
||||
|
||||
/*
|
||||
BundleConfig {
|
||||
@@ -170,18 +170,14 @@ module.exports.config = {
|
||||
},
|
||||
|
||||
hassio({ isProdBuild, latestBuild }) {
|
||||
if (latestBuild) {
|
||||
throw new Error("Hass.io does not support latest build!");
|
||||
}
|
||||
return {
|
||||
entry: {
|
||||
entrypoint: path.resolve(paths.hassio_dir, "src/entrypoint.ts"),
|
||||
},
|
||||
outputPath: paths.hassio_output_root,
|
||||
publicPath: paths.hassio_publicPath,
|
||||
outputPath: outputPath(paths.hassio_output_root, latestBuild),
|
||||
publicPath: publicPath(latestBuild, paths.hassio_publicPath),
|
||||
isProdBuild,
|
||||
latestBuild,
|
||||
dontHash: new Set(["entrypoint"]),
|
||||
};
|
||||
},
|
||||
|
||||
|
@@ -20,6 +20,7 @@ gulp.task(
|
||||
"translations-enable-merge-backend",
|
||||
gulp.parallel("gen-icons-json", "build-translations"),
|
||||
"copy-static-cast",
|
||||
"gen-index-cast-dev",
|
||||
env.useRollup() ? "rollup-dev-server-cast" : "webpack-dev-server-cast"
|
||||
)
|
||||
);
|
||||
|
@@ -6,30 +6,32 @@ const merge = require("merge-stream");
|
||||
const path = require("path");
|
||||
const paths = require("../paths");
|
||||
|
||||
const zopfliOptions = { threshold: 150 };
|
||||
|
||||
gulp.task("compress-app", function compressApp() {
|
||||
const jsLatest = gulp
|
||||
.src(path.resolve(paths.app_output_latest, "**/*.js"))
|
||||
.pipe(zopfli({ threshold: 150 }))
|
||||
.pipe(zopfli(zopfliOptions))
|
||||
.pipe(gulp.dest(paths.app_output_latest));
|
||||
|
||||
const jsEs5 = gulp
|
||||
.src(path.resolve(paths.app_output_es5, "**/*.js"))
|
||||
.pipe(zopfli({ threshold: 150 }))
|
||||
.pipe(zopfli(zopfliOptions))
|
||||
.pipe(gulp.dest(paths.app_output_es5));
|
||||
|
||||
const polyfills = gulp
|
||||
.src(path.resolve(paths.app_output_static, "polyfills/*.js"))
|
||||
.pipe(zopfli({ threshold: 150 }))
|
||||
.pipe(zopfli(zopfliOptions))
|
||||
.pipe(gulp.dest(path.resolve(paths.app_output_static, "polyfills")));
|
||||
|
||||
const translations = gulp
|
||||
.src(path.resolve(paths.app_output_static, "translations/**/*.json"))
|
||||
.pipe(zopfli({ threshold: 150 }))
|
||||
.pipe(zopfli(zopfliOptions))
|
||||
.pipe(gulp.dest(path.resolve(paths.app_output_static, "translations")));
|
||||
|
||||
const icons = gulp
|
||||
.src(path.resolve(paths.app_output_static, "mdi/*.json"))
|
||||
.pipe(zopfli({ threshold: 150 }))
|
||||
.pipe(zopfli(zopfliOptions))
|
||||
.pipe(gulp.dest(path.resolve(paths.app_output_static, "mdi")));
|
||||
|
||||
return merge(jsLatest, jsEs5, polyfills, translations, icons);
|
||||
@@ -38,6 +40,6 @@ gulp.task("compress-app", function compressApp() {
|
||||
gulp.task("compress-hassio", function compressApp() {
|
||||
return gulp
|
||||
.src(path.resolve(paths.hassio_output_root, "**/*.js"))
|
||||
.pipe(zopfli())
|
||||
.pipe(zopfli(zopfliOptions))
|
||||
.pipe(gulp.dest(paths.hassio_output_root));
|
||||
});
|
||||
|
@@ -90,8 +90,6 @@ gulp.task("gen-pages-prod", (done) => {
|
||||
});
|
||||
|
||||
gulp.task("gen-index-app-dev", (done) => {
|
||||
// In dev mode we don't mangle names, so we hardcode urls. That way we can
|
||||
// run webpack as last in watch mode, which blocks output.
|
||||
const content = renderTemplate("index", {
|
||||
latestAppJS: "/frontend_latest/app.js",
|
||||
latestCoreJS: "/frontend_latest/core.js",
|
||||
@@ -201,8 +199,6 @@ gulp.task("gen-index-cast-prod", (done) => {
|
||||
});
|
||||
|
||||
gulp.task("gen-index-demo-dev", (done) => {
|
||||
// In dev mode we don't mangle names, so we hardcode urls. That way we can
|
||||
// run webpack as last in watch mode, which blocks output.
|
||||
const content = renderDemoTemplate("index", {
|
||||
latestDemoJS: "/frontend_latest/main.js",
|
||||
|
||||
@@ -240,8 +236,6 @@ gulp.task("gen-index-demo-prod", (done) => {
|
||||
});
|
||||
|
||||
gulp.task("gen-index-gallery-dev", (done) => {
|
||||
// In dev mode we don't mangle names, so we hardcode urls. That way we can
|
||||
// run webpack as last in watch mode, which blocks output.
|
||||
const content = renderGalleryTemplate("index", {
|
||||
latestGalleryJS: "./frontend_latest/entrypoint.js",
|
||||
});
|
||||
@@ -269,3 +263,42 @@ gulp.task("gen-index-gallery-prod", (done) => {
|
||||
);
|
||||
done();
|
||||
});
|
||||
|
||||
gulp.task("gen-index-hassio-dev", async () => {
|
||||
writeHassioEntrypoint(
|
||||
`${paths.hassio_publicPath}/frontend_latest/entrypoint.js`,
|
||||
`${paths.hassio_publicPath}/frontend_es5/entrypoint.js`
|
||||
);
|
||||
});
|
||||
|
||||
gulp.task("gen-index-hassio-prod", async () => {
|
||||
const latestManifest = require(path.resolve(
|
||||
paths.hassio_output_latest,
|
||||
"manifest.json"
|
||||
));
|
||||
const es5Manifest = require(path.resolve(
|
||||
paths.hassio_output_es5,
|
||||
"manifest.json"
|
||||
));
|
||||
writeHassioEntrypoint(
|
||||
latestManifest["entrypoint.js"],
|
||||
es5Manifest["entrypoint.js"]
|
||||
);
|
||||
});
|
||||
|
||||
function writeHassioEntrypoint(latestEntrypoint, es5Entrypoint) {
|
||||
fs.mkdirSync(paths.hassio_output_root, { recursive: true });
|
||||
fs.writeFileSync(
|
||||
path.resolve(paths.hassio_output_root, "entrypoint.js"),
|
||||
`
|
||||
try {
|
||||
new Function("import('${latestEntrypoint}')")();
|
||||
} catch (err) {
|
||||
var el = document.createElement('script');
|
||||
el.src = '${es5Entrypoint}';
|
||||
document.body.appendChild(el);
|
||||
}
|
||||
`,
|
||||
{ encoding: "utf-8" }
|
||||
);
|
||||
}
|
||||
|
@@ -1,7 +1,10 @@
|
||||
// Run demo develop mode
|
||||
const gulp = require("gulp");
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
|
||||
const env = require("../env");
|
||||
const paths = require("../paths");
|
||||
|
||||
require("./clean.js");
|
||||
require("./translations.js");
|
||||
@@ -12,6 +15,31 @@ require("./service-worker.js");
|
||||
require("./entry-html.js");
|
||||
require("./rollup.js");
|
||||
|
||||
gulp.task("gather-gallery-demos", async function gatherDemos() {
|
||||
const files = await fs.promises.readdir(
|
||||
path.resolve(paths.gallery_dir, "src/demos")
|
||||
);
|
||||
|
||||
let content = "export const DEMOS = {\n";
|
||||
|
||||
for (const file of files) {
|
||||
const demoId = path.basename(file, ".ts");
|
||||
const demoPath = "../src/demos/" + demoId;
|
||||
content += ` "${demoId}": () => import("${demoPath}"),\n`;
|
||||
}
|
||||
|
||||
content += "};";
|
||||
|
||||
const galleryBuild = path.resolve(paths.gallery_dir, "build");
|
||||
|
||||
fs.mkdirSync(galleryBuild, { recursive: true });
|
||||
fs.writeFileSync(
|
||||
path.resolve(galleryBuild, "import-demos.ts"),
|
||||
content,
|
||||
"utf-8"
|
||||
);
|
||||
});
|
||||
|
||||
gulp.task(
|
||||
"develop-gallery",
|
||||
gulp.series(
|
||||
@@ -20,7 +48,11 @@ gulp.task(
|
||||
},
|
||||
"clean-gallery",
|
||||
"translations-enable-merge-backend",
|
||||
gulp.parallel("gen-icons-json", "build-translations"),
|
||||
gulp.parallel(
|
||||
"gen-icons-json",
|
||||
"build-translations",
|
||||
"gather-gallery-demos"
|
||||
),
|
||||
"copy-static-gallery",
|
||||
"gen-index-gallery-dev",
|
||||
env.useRollup() ? "rollup-dev-server-gallery" : "webpack-dev-server-gallery"
|
||||
@@ -35,7 +67,11 @@ gulp.task(
|
||||
},
|
||||
"clean-gallery",
|
||||
"translations-enable-merge-backend",
|
||||
gulp.parallel("gen-icons-json", "build-translations"),
|
||||
gulp.parallel(
|
||||
"gen-icons-json",
|
||||
"build-translations",
|
||||
"gather-gallery-demos"
|
||||
),
|
||||
"copy-static-gallery",
|
||||
env.useRollup() ? "rollup-prod-gallery" : "webpack-prod-gallery",
|
||||
"gen-index-gallery-prod"
|
||||
|
@@ -36,11 +36,13 @@ function copyMdiIcons(staticDir) {
|
||||
function copyPolyfills(staticDir) {
|
||||
const staticPath = genStaticPath(staticDir);
|
||||
|
||||
// Web Component polyfills and adapters
|
||||
// For custom panels using ES5 builds that don't use Babel 7+
|
||||
copyFileDir(
|
||||
npmPath("@webcomponents/webcomponentsjs/custom-elements-es5-adapter.js"),
|
||||
staticPath("polyfills/")
|
||||
);
|
||||
|
||||
// Web Component polyfills and adapters
|
||||
copyFileDir(
|
||||
npmPath("@webcomponents/webcomponentsjs/webcomponents-bundle.js"),
|
||||
staticPath("polyfills/")
|
||||
|
@@ -11,6 +11,7 @@ const META_PATH = path.resolve(ICON_PACKAGE_PATH, "meta.json");
|
||||
const PACKAGE_PATH = path.resolve(ICON_PACKAGE_PATH, "package.json");
|
||||
const ICON_PATH = path.resolve(ICON_PACKAGE_PATH, "svg");
|
||||
const OUTPUT_DIR = path.resolve(__dirname, "../../build/mdi");
|
||||
const REMOVED_ICONS_PATH = path.resolve(__dirname, "../removedIcons.json");
|
||||
|
||||
const encoding = "utf8";
|
||||
|
||||
@@ -25,6 +26,13 @@ const getMeta = () => {
|
||||
});
|
||||
};
|
||||
|
||||
const addRemovedMeta = (meta) => {
|
||||
const file = fs.readFileSync(REMOVED_ICONS_PATH, { encoding });
|
||||
const removed = JSON.parse(file);
|
||||
const combinedMeta = [...meta, ...removed];
|
||||
return combinedMeta.sort((a, b) => a.name.localeCompare(b.name));
|
||||
};
|
||||
|
||||
const splitBySize = (meta) => {
|
||||
const chunks = [];
|
||||
const CHUNK_SIZE = 50000;
|
||||
@@ -69,7 +77,7 @@ const findDifferentiator = (curString, prevString) => {
|
||||
};
|
||||
|
||||
gulp.task("gen-icons-json", (done) => {
|
||||
const meta = getMeta();
|
||||
const meta = addRemovedMeta(getMeta());
|
||||
const split = splitBySize(meta);
|
||||
|
||||
if (!fs.existsSync(OUTPUT_DIR)) {
|
||||
|
@@ -1,6 +1,9 @@
|
||||
const gulp = require("gulp");
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
|
||||
const env = require("../env");
|
||||
const paths = require("../paths");
|
||||
|
||||
require("./clean.js");
|
||||
require("./gen-icons-json.js");
|
||||
@@ -16,6 +19,7 @@ gulp.task(
|
||||
},
|
||||
"clean-hassio",
|
||||
"gen-icons-json",
|
||||
"gen-index-hassio-dev",
|
||||
env.useRollup() ? "rollup-watch-hassio" : "webpack-watch-hassio"
|
||||
)
|
||||
);
|
||||
@@ -29,6 +33,7 @@ gulp.task(
|
||||
"clean-hassio",
|
||||
"gen-icons-json",
|
||||
env.useRollup() ? "rollup-prod-hassio" : "webpack-prod-hassio",
|
||||
"gen-index-hassio-prod",
|
||||
...// Don't compress running tests
|
||||
(env.isTest() ? [] : ["compress-hassio"])
|
||||
)
|
||||
|
@@ -92,11 +92,7 @@ gulp.task("rollup-watch-app", () => {
|
||||
});
|
||||
|
||||
gulp.task("rollup-watch-hassio", () => {
|
||||
watchRollup(
|
||||
// Force latestBuild = false for hassio config.
|
||||
(conf) => rollupConfig.createHassioConfig({ ...conf, latestBuild: false }),
|
||||
["hassio/src/**"]
|
||||
);
|
||||
watchRollup(rollupConfig.createHassioConfig, ["hassio/src/**"]);
|
||||
});
|
||||
|
||||
gulp.task("rollup-dev-server-demo", () => {
|
||||
@@ -137,12 +133,7 @@ gulp.task(
|
||||
);
|
||||
|
||||
gulp.task("rollup-prod-hassio", () =>
|
||||
buildRollup(
|
||||
rollupConfig.createHassioConfig({
|
||||
isProdBuild: true,
|
||||
latestBuild: false,
|
||||
})
|
||||
)
|
||||
bothBuilds(rollupConfig.createHassioConfig, { isProdBuild: true })
|
||||
);
|
||||
|
||||
gulp.task("rollup-prod-gallery", () =>
|
||||
|
@@ -129,7 +129,7 @@ gulp.task("webpack-watch-hassio", () => {
|
||||
webpack(
|
||||
createHassioConfig({
|
||||
isProdBuild: false,
|
||||
latestBuild: false,
|
||||
latestBuild: true,
|
||||
})
|
||||
).watch({}, handler());
|
||||
});
|
||||
@@ -139,9 +139,8 @@ gulp.task(
|
||||
() =>
|
||||
new Promise((resolve) =>
|
||||
webpack(
|
||||
createHassioConfig({
|
||||
bothBuilds(createHassioConfig, {
|
||||
isProdBuild: true,
|
||||
latestBuild: false,
|
||||
}),
|
||||
handler(resolve)
|
||||
)
|
||||
|
@@ -34,7 +34,12 @@ module.exports = {
|
||||
|
||||
hassio_dir: path.resolve(__dirname, "../hassio"),
|
||||
hassio_output_root: path.resolve(__dirname, "../hassio/build"),
|
||||
hassio_publicPath: "/api/hassio/app/",
|
||||
hassio_output_latest: path.resolve(
|
||||
__dirname,
|
||||
"../hassio/build/frontend_latest"
|
||||
),
|
||||
hassio_output_es5: path.resolve(__dirname, "../hassio/build/frontend_es5"),
|
||||
hassio_publicPath: "/api/hassio/app",
|
||||
|
||||
translations_src: path.resolve(__dirname, "../src/translations"),
|
||||
};
|
||||
|
1
build-scripts/removedIcons.json
Normal file
1
build-scripts/removedIcons.json
Normal file
@@ -0,0 +1 @@
|
||||
[]
|
@@ -70,7 +70,9 @@ const createWebpackConfig = ({
|
||||
if (
|
||||
!context.includes("/node_modules/") ||
|
||||
// calling define.amd will call require("!!webpack amd options")
|
||||
resource.startsWith("!!webpack")
|
||||
resource.startsWith("!!webpack") ||
|
||||
// loaded by webpack dev server but doesn't exist.
|
||||
resource === "webpack/hot"
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
@@ -80,7 +82,11 @@ const createWebpackConfig = ({
|
||||
? path.resolve(context, resource)
|
||||
: require.resolve(resource);
|
||||
} catch (err) {
|
||||
console.error("Error in ignore plugin", resource, context);
|
||||
console.error(
|
||||
"Error in Home Assistant ignore plugin",
|
||||
resource,
|
||||
context
|
||||
);
|
||||
throw err;
|
||||
}
|
||||
|
||||
|
@@ -37,24 +37,21 @@
|
||||
<body>
|
||||
<%= renderTemplate('_js_base') %>
|
||||
|
||||
<script type="module" crossorigin="use-credentials">
|
||||
import "<%= latestLauncherJS %>";
|
||||
<script>
|
||||
import("<%= latestLauncherJS %>");
|
||||
window.latestJS = true;
|
||||
</script>
|
||||
|
||||
<script nomodule>
|
||||
(function() {
|
||||
// // Safari 10.1 supports type=module but ignores nomodule, so we add this check.
|
||||
if (!isS101) {
|
||||
_ls("/static/polyfills/custom-elements-es5-adapter.js");
|
||||
<% if (useRollup) { %>
|
||||
_ls("/static/js/s.min.js").onload = function() {
|
||||
System.import("<%= es5LauncherJS %>");
|
||||
};
|
||||
<% } else { %>
|
||||
_ls("<%= es5LauncherJS %>");
|
||||
<% } %>
|
||||
}
|
||||
})();
|
||||
<script>
|
||||
if (!window.latestJS) {
|
||||
<% if (useRollup) { %>
|
||||
_ls("/static/js/s.min.js").onload = function() {
|
||||
System.import("<%= es5LauncherJS %>");
|
||||
};
|
||||
<% } else { %>
|
||||
_ls("<%= es5LauncherJS %>");
|
||||
<% } %>
|
||||
}
|
||||
</script>
|
||||
|
||||
<hc-layout subtitle="FAQ">
|
||||
@@ -254,7 +251,7 @@ http:
|
||||
|
||||
<script>
|
||||
var _gaq = [["_setAccount", "UA-57927901-9"], ["_trackPageview"]];
|
||||
(function(d, t) {
|
||||
(function (d, t) {
|
||||
var g = d.createElement(t),
|
||||
s = d.getElementsByTagName(t)[0];
|
||||
g.src =
|
||||
|
@@ -28,24 +28,21 @@
|
||||
|
||||
<hc-connect></hc-connect>
|
||||
|
||||
<script type="module" crossorigin="use-credentials">
|
||||
import "<%= latestLauncherJS %>";
|
||||
<script>
|
||||
import("<%= latestLauncherJS %>");
|
||||
window.latestJS = true;
|
||||
</script>
|
||||
|
||||
<script nomodule>
|
||||
(function() {
|
||||
// // Safari 10.1 supports type=module but ignores nomodule, so we add this check.
|
||||
if (!isS101) {
|
||||
_ls("/static/polyfills/custom-elements-es5-adapter.js");
|
||||
<% if (useRollup) { %>
|
||||
_ls("/static/js/s.min.js").onload = function() {
|
||||
System.import("<%= es5LauncherJS %>");
|
||||
};
|
||||
<% } else { %>
|
||||
_ls("<%= es5LauncherJS %>");
|
||||
<% } %>
|
||||
}
|
||||
})();
|
||||
<script>
|
||||
if (!window.latestJS) {
|
||||
<% if (useRollup) { %>
|
||||
_ls("/static/js/s.min.js").onload = function() {
|
||||
System.import("<%= es5LauncherJS %>");
|
||||
};
|
||||
<% } else { %>
|
||||
_ls("<%= es5LauncherJS %>");
|
||||
<% } %>
|
||||
}
|
||||
</script>
|
||||
<script>
|
||||
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
|
||||
|
@@ -1,3 +1,4 @@
|
||||
import "../../../src/resources/safari-14-attachshadow-patch";
|
||||
import "../../../src/resources/ha-style";
|
||||
import "../../../src/resources/roboto";
|
||||
import "./layout/hc-connect";
|
||||
|
@@ -8,6 +8,7 @@ import {
|
||||
html,
|
||||
LitElement,
|
||||
property,
|
||||
internalProperty,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import { CastManager } from "../../../../src/cast/cast_manager";
|
||||
@@ -28,7 +29,7 @@ import {
|
||||
getLovelaceCollection,
|
||||
LovelaceConfig,
|
||||
} from "../../../../src/data/lovelace";
|
||||
import "../../../../src/layouts/loading-screen";
|
||||
import "../../../../src/layouts/hass-loading-screen";
|
||||
import { generateDefaultViewConfig } from "../../../../src/panels/lovelace/common/generate-lovelace-config";
|
||||
import "./hc-layout";
|
||||
import "@material/mwc-button/mwc-button";
|
||||
@@ -41,13 +42,13 @@ class HcCast extends LitElement {
|
||||
|
||||
@property() public castManager!: CastManager;
|
||||
|
||||
@property() private askWrite = false;
|
||||
@internalProperty() private askWrite = false;
|
||||
|
||||
@property() private lovelaceConfig?: LovelaceConfig | null;
|
||||
@internalProperty() private lovelaceConfig?: LovelaceConfig | null;
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (this.lovelaceConfig === undefined) {
|
||||
return html` <loading-screen></loading-screen>> `;
|
||||
return html` <hass-loading-screen no-toolbar></hass-loading-screen>> `;
|
||||
}
|
||||
|
||||
const error =
|
||||
|
@@ -17,8 +17,8 @@ import {
|
||||
customElement,
|
||||
html,
|
||||
LitElement,
|
||||
property,
|
||||
TemplateResult,
|
||||
internalProperty,
|
||||
} from "lit-element";
|
||||
import { CastManager, getCastManager } from "../../../../src/cast/cast_manager";
|
||||
import { castSendShowDemo } from "../../../../src/cast/receiver_messages";
|
||||
@@ -27,7 +27,7 @@ import {
|
||||
saveTokens,
|
||||
} from "../../../../src/common/auth/token_storage";
|
||||
import "../../../../src/components/ha-icon";
|
||||
import "../../../../src/layouts/loading-screen";
|
||||
import "../../../../src/layouts/hass-loading-screen";
|
||||
import { registerServiceWorker } from "../../../../src/util/register-service-worker";
|
||||
import "./hc-layout";
|
||||
|
||||
@@ -60,19 +60,19 @@ const INTRO = html`
|
||||
|
||||
@customElement("hc-connect")
|
||||
export class HcConnect extends LitElement {
|
||||
@property() private loading = false;
|
||||
@internalProperty() private loading = false;
|
||||
|
||||
// If we had stored credentials but we cannot connect,
|
||||
// show a screen asking retry or logout.
|
||||
@property() private cannotConnect = false;
|
||||
@internalProperty() private cannotConnect = false;
|
||||
|
||||
@property() private error?: string | TemplateResult;
|
||||
@internalProperty() private error?: string | TemplateResult;
|
||||
|
||||
@property() private auth?: Auth;
|
||||
@internalProperty() private auth?: Auth;
|
||||
|
||||
@property() private connection?: Connection;
|
||||
@internalProperty() private connection?: Connection;
|
||||
|
||||
@property() private castManager?: CastManager | null;
|
||||
@internalProperty() private castManager?: CastManager | null;
|
||||
|
||||
private openDemo = false;
|
||||
|
||||
@@ -98,7 +98,7 @@ export class HcConnect extends LitElement {
|
||||
}
|
||||
|
||||
if (this.castManager === undefined || this.loading) {
|
||||
return html` <loading-screen></loading-screen> `;
|
||||
return html` <hass-loading-screen no-toolbar></hass-loading-screen> `;
|
||||
}
|
||||
|
||||
if (this.castManager === null) {
|
||||
|
@@ -1,4 +1,10 @@
|
||||
import { customElement, html, property, TemplateResult } from "lit-element";
|
||||
import {
|
||||
customElement,
|
||||
html,
|
||||
property,
|
||||
internalProperty,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import { mockHistory } from "../../../../demo/src/stubs/history";
|
||||
import { LovelaceConfig } from "../../../../src/data/lovelace";
|
||||
import {
|
||||
@@ -13,9 +19,9 @@ import "./hc-lovelace";
|
||||
|
||||
@customElement("hc-demo")
|
||||
class HcDemo extends HassElement {
|
||||
@property() public lovelacePath!: string;
|
||||
@property({ attribute: false }) public lovelacePath!: string;
|
||||
|
||||
@property() private _lovelaceConfig?: LovelaceConfig;
|
||||
@internalProperty() private _lovelaceConfig?: LovelaceConfig;
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (!this._lovelaceConfig) {
|
||||
|
@@ -11,7 +11,7 @@ import { HomeAssistant } from "../../../../src/types";
|
||||
|
||||
@customElement("hc-launch-screen")
|
||||
class HcLaunchScreen extends LitElement {
|
||||
@property() public hass?: HomeAssistant;
|
||||
@property({ attribute: false }) public hass?: HomeAssistant;
|
||||
|
||||
@property() public error?: string;
|
||||
|
||||
|
@@ -16,12 +16,14 @@ import "./hc-launch-screen";
|
||||
|
||||
@customElement("hc-lovelace")
|
||||
class HcLovelace extends LitElement {
|
||||
@property() public hass!: HomeAssistant;
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property() public lovelaceConfig!: LovelaceConfig;
|
||||
@property({ attribute: false }) public lovelaceConfig!: LovelaceConfig;
|
||||
|
||||
@property() public viewPath?: string | number;
|
||||
|
||||
public urlPath?: string | null;
|
||||
|
||||
protected render(): TemplateResult {
|
||||
const index = this._viewIndex;
|
||||
if (index === undefined) {
|
||||
@@ -35,6 +37,7 @@ class HcLovelace extends LitElement {
|
||||
const lovelace: Lovelace = {
|
||||
config: this.lovelaceConfig,
|
||||
editMode: false,
|
||||
urlPath: this.urlPath!,
|
||||
enableFullEditMode: () => undefined,
|
||||
mode: "storage",
|
||||
language: "en",
|
||||
|
@@ -3,7 +3,12 @@ import {
|
||||
getAuth,
|
||||
UnsubscribeFunc,
|
||||
} from "home-assistant-js-websocket";
|
||||
import { customElement, html, property, TemplateResult } from "lit-element";
|
||||
import {
|
||||
customElement,
|
||||
html,
|
||||
internalProperty,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import { CAST_NS } from "../../../../src/cast/const";
|
||||
import {
|
||||
ConnectMessage,
|
||||
@@ -31,13 +36,13 @@ let resourcesLoaded = false;
|
||||
|
||||
@customElement("hc-main")
|
||||
export class HcMain extends HassElement {
|
||||
@property() private _showDemo = false;
|
||||
@internalProperty() private _showDemo = false;
|
||||
|
||||
@property() private _lovelaceConfig?: LovelaceConfig;
|
||||
@internalProperty() private _lovelaceConfig?: LovelaceConfig;
|
||||
|
||||
@property() private _lovelacePath: string | number | null = null;
|
||||
@internalProperty() private _lovelacePath: string | number | null = null;
|
||||
|
||||
@property() private _error?: string;
|
||||
@internalProperty() private _error?: string;
|
||||
|
||||
private _unsubLovelace?: UnsubscribeFunc;
|
||||
|
||||
@@ -82,6 +87,7 @@ export class HcMain extends HassElement {
|
||||
.hass=${this.hass}
|
||||
.lovelaceConfig=${this._lovelaceConfig}
|
||||
.viewPath=${this._lovelacePath}
|
||||
.urlPath=${this._urlPath}
|
||||
@config-refresh=${this._generateLovelaceConfig}
|
||||
></hc-lovelace>
|
||||
`;
|
||||
@@ -192,6 +198,8 @@ export class HcMain extends HassElement {
|
||||
this._handleNewLovelaceConfig(lovelaceConfig)
|
||||
);
|
||||
} catch (err) {
|
||||
// eslint-disable-next-line
|
||||
console.log("Error fetching Lovelace configuration", err, msg);
|
||||
// Generate a Lovelace config.
|
||||
this._unsubLovelace = () => undefined;
|
||||
await this._generateLovelaceConfig();
|
||||
|
@@ -1,11 +1,8 @@
|
||||
const { createCastConfig } = require("../build-scripts/webpack.js");
|
||||
const { isProdBuild } = require("../build-scripts/env.js");
|
||||
|
||||
// File just used for stats builds
|
||||
|
||||
const latestBuild = true;
|
||||
const { isProdBuild, isStatsBuild } = require("../build-scripts/env.js");
|
||||
|
||||
module.exports = createCastConfig({
|
||||
isProdBuild: isProdBuild(),
|
||||
latestBuild,
|
||||
isStatsBuild: isStatsBuild(),
|
||||
latestBuild: true,
|
||||
});
|
||||
|
@@ -26,9 +26,9 @@ export const demoThemeJimpower = () => ({
|
||||
"switch-checked-color": "var(--accent-color)",
|
||||
"paper-dialog-background-color": "#434954",
|
||||
"secondary-text-color": "#5294E2",
|
||||
"google-red-500": "#E45E65",
|
||||
"error-color": "#E45E65",
|
||||
"divider-color": "rgba(0, 0, 0, .12)",
|
||||
"google-green-500": "#39E949",
|
||||
"success-color": "#39E949",
|
||||
"switch-unchecked-button-color": "var(--disabled-text-color)",
|
||||
"label-badge-border-color": "green",
|
||||
"paper-listbox-color": "var(--primary-color)",
|
||||
|
@@ -27,9 +27,9 @@ export const demoThemeKernehed = () => ({
|
||||
"switch-checked-color": "var(--accent-color)",
|
||||
"paper-dialog-background-color": "#292929",
|
||||
"secondary-text-color": "#b58e31",
|
||||
"google-red-500": "#b58e31",
|
||||
"error-color": "#b58e31",
|
||||
"divider-color": "rgba(0, 0, 0, .12)",
|
||||
"google-green-500": "#2980b9",
|
||||
"success-color": "#2980b9",
|
||||
"switch-unchecked-button-color": "var(--disabled-text-color)",
|
||||
"label-badge-border-color": "green",
|
||||
"paper-listbox-color": "#777777",
|
||||
|
@@ -4,7 +4,7 @@ import {
|
||||
customElement,
|
||||
html,
|
||||
LitElement,
|
||||
property,
|
||||
internalProperty,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import { CastManager } from "../../../src/cast/cast_manager";
|
||||
@@ -20,7 +20,7 @@ import { HomeAssistant } from "../../../src/types";
|
||||
class CastDemoRow extends LitElement implements LovelaceRow {
|
||||
public hass!: HomeAssistant;
|
||||
|
||||
@property() private _castManager?: CastManager | null;
|
||||
@internalProperty() private _castManager?: CastManager | null;
|
||||
|
||||
public setConfig(_config: CastConfig): void {
|
||||
// No config possible.
|
||||
@@ -52,7 +52,6 @@ class CastDemoRow extends LitElement implements LovelaceRow {
|
||||
});
|
||||
mgr.castContext.addEventListener(
|
||||
// eslint-disable-next-line no-undef
|
||||
// @ts-ignore
|
||||
cast.framework.CastContextEventType.SESSION_STATE_CHANGED,
|
||||
(ev) => {
|
||||
// On Android, opening a new session always results in SESSION_RESUMED.
|
||||
|
@@ -1,15 +1,16 @@
|
||||
import "@material/mwc-button";
|
||||
import "@polymer/paper-spinner/paper-spinner-lite";
|
||||
import {
|
||||
css,
|
||||
CSSResult,
|
||||
html,
|
||||
LitElement,
|
||||
property,
|
||||
internalProperty,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import { until } from "lit-html/directives/until";
|
||||
import "../../../src/components/ha-card";
|
||||
import "../../../src/components/ha-circular-progress";
|
||||
import { LovelaceCardConfig } from "../../../src/data/lovelace";
|
||||
import { MockHomeAssistant } from "../../../src/fake_data/provide_hass";
|
||||
import { Lovelace, LovelaceCard } from "../../../src/panels/lovelace/types";
|
||||
@@ -21,11 +22,11 @@ import {
|
||||
} from "../configs/demo-configs";
|
||||
|
||||
export class HADemoCard extends LitElement implements LovelaceCard {
|
||||
@property() public lovelace?: Lovelace;
|
||||
@property({ attribute: false }) public lovelace?: Lovelace;
|
||||
|
||||
@property() public hass!: MockHomeAssistant;
|
||||
@property({ attribute: false }) public hass!: MockHomeAssistant;
|
||||
|
||||
@property() private _switching?: boolean;
|
||||
@internalProperty() private _switching?: boolean;
|
||||
|
||||
private _hidden = localStorage.hide_demo_card;
|
||||
|
||||
@@ -49,7 +50,7 @@ export class HADemoCard extends LitElement implements LovelaceCard {
|
||||
<div class="picker">
|
||||
<div class="label">
|
||||
${this._switching
|
||||
? html` <paper-spinner-lite active></paper-spinner-lite> `
|
||||
? html`<ha-circular-progress active></ha-circular-progress>`
|
||||
: until(
|
||||
selectedDemoConfig.then(
|
||||
(conf) => html`
|
||||
|
@@ -1,3 +1,4 @@
|
||||
import "../../src/resources/safari-14-attachshadow-patch";
|
||||
import "@polymer/polymer/lib/elements/dom-if";
|
||||
import "@polymer/polymer/lib/elements/dom-repeat";
|
||||
import "../../src/resources/ha-style";
|
||||
|
@@ -86,27 +86,26 @@
|
||||
<%= renderTemplate('_js_base') %>
|
||||
<%= renderTemplate('_preload_roboto') %>
|
||||
|
||||
<script type="module" src="<%= latestDemoJS %>"></script>
|
||||
<script>
|
||||
import("<%= latestDemoJS %>");
|
||||
window.latestJS = true;
|
||||
</script>
|
||||
|
||||
<script nomodule>
|
||||
(function() {
|
||||
// // Safari 10.1 supports type=module but ignores nomodule, so we add this check.
|
||||
if (!isS101) {
|
||||
_ls("/static/polyfills/custom-elements-es5-adapter.js");
|
||||
<% if (useRollup) { %>
|
||||
_ls("/static/js/s.min.js").onload = function() {
|
||||
System.import("<%= es5DemoJS %>");
|
||||
};
|
||||
<% } else { %>
|
||||
_ls("<%= es5DemoJS %>");
|
||||
<% } %>
|
||||
}
|
||||
})();
|
||||
<script>
|
||||
if (!window.latestJS) {
|
||||
<% if (useRollup) { %>
|
||||
_ls("/static/js/s.min.js").onload = function() {
|
||||
System.import("<%= es5DemoJS %>");
|
||||
};
|
||||
<% } else { %>
|
||||
_ls("<%= es5DemoJS %>");
|
||||
<% } %>
|
||||
}
|
||||
</script>
|
||||
|
||||
<script>
|
||||
var _gaq = [["_setAccount", "UA-57927901-5"], ["_trackPageview"]];
|
||||
(function(d, t) {
|
||||
(function (d, t) {
|
||||
var g = d.createElement(t),
|
||||
s = d.getElementsByTagName(t)[0];
|
||||
g.src =
|
||||
|
@@ -3,6 +3,7 @@ import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||
/* eslint-plugin-disable lit */
|
||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||
import "../../../src/components/ha-switch";
|
||||
import "../../../src/components/ha-formfield";
|
||||
import "./demo-card";
|
||||
|
||||
class DemoCards extends PolymerElement {
|
||||
@@ -26,9 +27,10 @@ class DemoCards extends PolymerElement {
|
||||
</style>
|
||||
<app-toolbar>
|
||||
<div class="filters">
|
||||
<ha-switch checked="[[_showConfig]]" on-change="_showConfigToggled">
|
||||
Show config
|
||||
</ha-switch>
|
||||
<ha-formfield label="Show config">
|
||||
<ha-switch checked="[[_showConfig]]" on-change="_showConfigToggled">
|
||||
</ha-switch>
|
||||
</ha-formfield>
|
||||
</div>
|
||||
</app-toolbar>
|
||||
<div class="cards">
|
||||
|
@@ -2,8 +2,8 @@ import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||
/* eslint-plugin-disable lit */
|
||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||
import "../../../src/components/ha-card";
|
||||
import "../../../src/dialogs/more-info/controls/more-info-content";
|
||||
import "../../../src/state-summary/state-card-content";
|
||||
import "./more-info-content";
|
||||
|
||||
class DemoMoreInfo extends PolymerElement {
|
||||
static get template() {
|
||||
|
73
gallery/src/components/more-info-content.ts
Normal file
73
gallery/src/components/more-info-content.ts
Normal file
@@ -0,0 +1,73 @@
|
||||
import { HassEntity } from "home-assistant-js-websocket";
|
||||
import { property, PropertyValues, UpdatingElement } from "lit-element";
|
||||
import dynamicContentUpdater from "../../../src/common/dom/dynamic_content_updater";
|
||||
import { stateMoreInfoType } from "../../../src/common/entity/state_more_info_type";
|
||||
import "../../../src/dialogs/more-info/controls/more-info-alarm_control_panel";
|
||||
import "../../../src/dialogs/more-info/controls/more-info-automation";
|
||||
import "../../../src/dialogs/more-info/controls/more-info-camera";
|
||||
import "../../../src/dialogs/more-info/controls/more-info-climate";
|
||||
import "../../../src/dialogs/more-info/controls/more-info-configurator";
|
||||
import "../../../src/dialogs/more-info/controls/more-info-counter";
|
||||
import "../../../src/dialogs/more-info/controls/more-info-cover";
|
||||
import "../../../src/dialogs/more-info/controls/more-info-default";
|
||||
import "../../../src/dialogs/more-info/controls/more-info-fan";
|
||||
import "../../../src/dialogs/more-info/controls/more-info-group";
|
||||
import "../../../src/dialogs/more-info/controls/more-info-humidifier";
|
||||
import "../../../src/dialogs/more-info/controls/more-info-input_datetime";
|
||||
import "../../../src/dialogs/more-info/controls/more-info-light";
|
||||
import "../../../src/dialogs/more-info/controls/more-info-lock";
|
||||
import "../../../src/dialogs/more-info/controls/more-info-media_player";
|
||||
import "../../../src/dialogs/more-info/controls/more-info-person";
|
||||
import "../../../src/dialogs/more-info/controls/more-info-script";
|
||||
import "../../../src/dialogs/more-info/controls/more-info-sun";
|
||||
import "../../../src/dialogs/more-info/controls/more-info-timer";
|
||||
import "../../../src/dialogs/more-info/controls/more-info-vacuum";
|
||||
import "../../../src/dialogs/more-info/controls/more-info-water_heater";
|
||||
import "../../../src/dialogs/more-info/controls/more-info-weather";
|
||||
import { HomeAssistant } from "../../../src/types";
|
||||
|
||||
class MoreInfoContent extends UpdatingElement {
|
||||
@property({ attribute: false }) public hass?: HomeAssistant;
|
||||
|
||||
@property() public stateObj?: HassEntity;
|
||||
|
||||
private _detachedChild?: ChildNode;
|
||||
|
||||
protected firstUpdated(): void {
|
||||
this.style.position = "relative";
|
||||
this.style.display = "block";
|
||||
}
|
||||
|
||||
// This is not a lit element, but an updating element, so we implement update
|
||||
protected update(changedProps: PropertyValues): void {
|
||||
super.update(changedProps);
|
||||
const stateObj = this.stateObj;
|
||||
const hass = this.hass;
|
||||
|
||||
if (!stateObj || !hass) {
|
||||
if (this.lastChild) {
|
||||
this._detachedChild = this.lastChild;
|
||||
// Detach child to prevent it from doing work.
|
||||
this.removeChild(this.lastChild);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._detachedChild) {
|
||||
this.appendChild(this._detachedChild);
|
||||
this._detachedChild = undefined;
|
||||
}
|
||||
|
||||
const moreInfoType =
|
||||
stateObj.attributes && "custom_ui_more_info" in stateObj.attributes
|
||||
? stateObj.attributes.custom_ui_more_info
|
||||
: "more-info-" + stateMoreInfoType(stateObj);
|
||||
|
||||
dynamicContentUpdater(this, moreInfoType.toUpperCase(), {
|
||||
hass,
|
||||
stateObj,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("more-info-content", MoreInfoContent);
|
@@ -420,15 +420,6 @@ export default {
|
||||
last_changed: "2018-07-19T10:44:46.105940+00:00",
|
||||
last_updated: "2018-07-19T10:44:46.105940+00:00",
|
||||
},
|
||||
"weblink.router": {
|
||||
entity_id: "weblink.router",
|
||||
state: "http://192.168.1.1",
|
||||
attributes: {
|
||||
friendly_name: "Router",
|
||||
},
|
||||
last_changed: "2018-07-19T10:44:46.107286+00:00",
|
||||
last_updated: "2018-07-19T10:44:46.107286+00:00",
|
||||
},
|
||||
"group.all_plants": {
|
||||
entity_id: "group.all_plants",
|
||||
state: "ok",
|
||||
@@ -1090,18 +1081,6 @@ export default {
|
||||
last_changed: "2018-07-19T10:44:46.510448+00:00",
|
||||
last_updated: "2018-07-19T10:44:46.510448+00:00",
|
||||
},
|
||||
"history_graph.recent_switches": {
|
||||
entity_id: "history_graph.recent_switches",
|
||||
state: "unknown",
|
||||
attributes: {
|
||||
hours_to_show: 1,
|
||||
refresh: 60,
|
||||
entity_id: ["switch.ac", "switch.decorative_lights"],
|
||||
friendly_name: "Recent Switches",
|
||||
},
|
||||
last_changed: "2018-07-19T10:44:46.512351+00:00",
|
||||
last_updated: "2018-07-19T10:44:46.512351+00:00",
|
||||
},
|
||||
"scene.switch_on_and_off": {
|
||||
entity_id: "scene.switch_on_and_off",
|
||||
state: "scening",
|
||||
|
@@ -3,10 +3,10 @@ import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||
import "../../../src/components/ha-card";
|
||||
import { SUPPORT_BRIGHTNESS } from "../../../src/data/light";
|
||||
import "../../../src/dialogs/more-info/controls/more-info-content";
|
||||
import { getEntity } from "../../../src/fake_data/entity";
|
||||
import { provideHass } from "../../../src/fake_data/provide_hass";
|
||||
import "../components/demo-more-infos";
|
||||
import "../components/more-info-content";
|
||||
|
||||
const ENTITIES = [
|
||||
getEntity("light", "bed_light", "on", {
|
||||
|
@@ -1,19 +1,18 @@
|
||||
import "@polymer/app-layout/app-header-layout/app-header-layout";
|
||||
import "@polymer/app-layout/app-header/app-header";
|
||||
import "@polymer/app-layout/app-toolbar/app-toolbar";
|
||||
import "../../src/components/ha-icon-button";
|
||||
import "../../src/components/ha-icon";
|
||||
import "@polymer/paper-item/paper-item";
|
||||
import "@polymer/paper-item/paper-item-body";
|
||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||
/* eslint-plugin-disable lit */
|
||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||
import "../../src/components/ha-card";
|
||||
import "../../src/components/ha-icon";
|
||||
import "../../src/components/ha-icon-button";
|
||||
import "../../src/managers/notification-manager";
|
||||
import "../../src/styles/polymer-ha-style";
|
||||
|
||||
// eslint-disable-next-line no-undef
|
||||
const DEMOS = require.context("./demos", true, /^(.*\.(ts$))[^.]*$/im);
|
||||
// eslint-disable-next-line import/extensions
|
||||
import { DEMOS } from "../build/import-demos";
|
||||
|
||||
const fixPath = (path) => path.substr(2, path.length - 5);
|
||||
|
||||
@@ -163,7 +162,7 @@ class HaGallery extends PolymerElement {
|
||||
},
|
||||
_demos: {
|
||||
type: Array,
|
||||
value: DEMOS.keys().map(fixPath),
|
||||
value: Object.keys(DEMOS),
|
||||
},
|
||||
_lovelaceDemos: {
|
||||
type: Array,
|
||||
@@ -210,7 +209,7 @@ class HaGallery extends PolymerElement {
|
||||
while (root.lastChild) root.removeChild(root.lastChild);
|
||||
|
||||
if (demo) {
|
||||
DEMOS(`./${demo}.ts`);
|
||||
DEMOS[demo]();
|
||||
const el = document.createElement(demo);
|
||||
root.appendChild(el);
|
||||
}
|
||||
|
@@ -1,5 +1,8 @@
|
||||
const { createGalleryConfig } = require("../build-scripts/webpack.js");
|
||||
const { isProdBuild, isStatsBuild } = require("../build-scripts/env.js");
|
||||
|
||||
module.exports = createGalleryConfig({
|
||||
isProdBuild: isProdBuild(),
|
||||
isStatsBuild: isStatsBuild(),
|
||||
latestBuild: true,
|
||||
});
|
||||
|
@@ -21,7 +21,7 @@ import { filterAndSort } from "../components/hassio-filter-addons";
|
||||
import { hassioStyle } from "../resources/hassio-style";
|
||||
|
||||
class HassioAddonRepositoryEl extends LitElement {
|
||||
@property() public hass!: HomeAssistant;
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property() public repo!: HassioAddonRepository;
|
||||
|
||||
|
@@ -1,9 +1,11 @@
|
||||
import "@material/mwc-icon-button/mwc-icon-button";
|
||||
import { ActionDetail } from "@material/mwc-list/mwc-list-foundation";
|
||||
import "@material/mwc-list/mwc-list-item";
|
||||
import { mdiDotsVertical } from "@mdi/js";
|
||||
import {
|
||||
css,
|
||||
CSSResult,
|
||||
internalProperty,
|
||||
LitElement,
|
||||
property,
|
||||
PropertyValues,
|
||||
@@ -18,11 +20,12 @@ import {
|
||||
HassioAddonRepository,
|
||||
reloadHassioAddons,
|
||||
} from "../../../src/data/hassio/addon";
|
||||
import { extractApiErrorMessage } from "../../../src/data/hassio/common";
|
||||
import "../../../src/layouts/hass-loading-screen";
|
||||
import "../../../src/layouts/hass-tabs-subpage";
|
||||
import "../../../src/layouts/loading-screen";
|
||||
import { HomeAssistant, Route } from "../../../src/types";
|
||||
import { showRepositoriesDialog } from "../dialogs/repositories/show-dialog-repositories";
|
||||
import { supervisorTabs } from "../hassio-panel";
|
||||
import { supervisorTabs } from "../hassio-tabs";
|
||||
import "./hassio-addon-repository";
|
||||
|
||||
const sortRepos = (a: HassioAddonRepository, b: HassioAddonRepository) => {
|
||||
@@ -52,7 +55,7 @@ class HassioAddonStore extends LitElement {
|
||||
|
||||
@property({ attribute: false }) private _repos?: HassioAddonRepository[];
|
||||
|
||||
@property() private _filter?: string;
|
||||
@internalProperty() private _filter?: string;
|
||||
|
||||
public async refreshData() {
|
||||
this._repos = undefined;
|
||||
@@ -96,19 +99,23 @@ class HassioAddonStore extends LitElement {
|
||||
.tabs=${supervisorTabs}
|
||||
>
|
||||
<span slot="header">Add-on store</span>
|
||||
<ha-button-menu corner="BOTTOM_START" slot="toolbar-icon">
|
||||
<ha-button-menu
|
||||
corner="BOTTOM_START"
|
||||
slot="toolbar-icon"
|
||||
@action=${this._handleAction}
|
||||
>
|
||||
<mwc-icon-button slot="trigger" alt="menu">
|
||||
<ha-svg-icon path=${mdiDotsVertical}></ha-svg-icon>
|
||||
</mwc-icon-button>
|
||||
<mwc-list-item @tap=${this._manageRepositories}>
|
||||
<mwc-list-item>
|
||||
Repositories
|
||||
</mwc-list-item>
|
||||
<mwc-list-item @tap=${this.refreshData}>
|
||||
<mwc-list-item>
|
||||
Reload
|
||||
</mwc-list-item>
|
||||
</ha-button-menu>
|
||||
${repos.length === 0
|
||||
? html`<loading-screen></loading-screen>`
|
||||
? html`<hass-loading-screen no-toolbar></hass-loading-screen>`
|
||||
: html`
|
||||
<div class="search">
|
||||
<search-input
|
||||
@@ -142,6 +149,17 @@ class HassioAddonStore extends LitElement {
|
||||
this._loadData();
|
||||
}
|
||||
|
||||
private _handleAction(ev: CustomEvent<ActionDetail>) {
|
||||
switch (ev.detail.index) {
|
||||
case 0:
|
||||
this._manageRepositories();
|
||||
break;
|
||||
case 1:
|
||||
this.refreshData();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private apiCalled(ev) {
|
||||
if (ev.detail.success) {
|
||||
this._loadData();
|
||||
@@ -162,7 +180,7 @@ class HassioAddonStore extends LitElement {
|
||||
this._repos.sort(sortRepos);
|
||||
this._addons = addonsInfo.addons;
|
||||
} catch (err) {
|
||||
alert("Failed to fetch add-on info");
|
||||
alert(extractApiErrorMessage(err));
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -9,6 +9,7 @@ import {
|
||||
html,
|
||||
LitElement,
|
||||
property,
|
||||
internalProperty,
|
||||
PropertyValues,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
@@ -27,6 +28,7 @@ import { haStyle } from "../../../../src/resources/styles";
|
||||
import { HomeAssistant } from "../../../../src/types";
|
||||
import { suggestAddonRestart } from "../../dialogs/suggestAddonRestart";
|
||||
import { hassioStyle } from "../../resources/hassio-style";
|
||||
import "../../../../src/components/buttons/ha-progress-button";
|
||||
|
||||
@customElement("hassio-addon-audio")
|
||||
class HassioAddonAudio extends LitElement {
|
||||
@@ -34,15 +36,15 @@ class HassioAddonAudio extends LitElement {
|
||||
|
||||
@property({ attribute: false }) public addon!: HassioAddonDetails;
|
||||
|
||||
@property() private _error?: string;
|
||||
@internalProperty() private _error?: string;
|
||||
|
||||
@property() private _inputDevices?: HassioHardwareAudioDevice[];
|
||||
@internalProperty() private _inputDevices?: HassioHardwareAudioDevice[];
|
||||
|
||||
@property() private _outputDevices?: HassioHardwareAudioDevice[];
|
||||
@internalProperty() private _outputDevices?: HassioHardwareAudioDevice[];
|
||||
|
||||
@property() private _selectedInput!: null | string;
|
||||
@internalProperty() private _selectedInput!: null | string;
|
||||
|
||||
@property() private _selectedOutput!: null | string;
|
||||
@internalProperty() private _selectedOutput!: null | string;
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
@@ -90,7 +92,9 @@ class HassioAddonAudio extends LitElement {
|
||||
</paper-dropdown-menu>
|
||||
</div>
|
||||
<div class="card-actions">
|
||||
<mwc-button @click=${this._saveSettings}>Save</mwc-button>
|
||||
<ha-progress-button @click=${this._saveSettings}>
|
||||
Save
|
||||
</ha-progress-button>
|
||||
</div>
|
||||
</ha-card>
|
||||
`;
|
||||
@@ -107,7 +111,7 @@ class HassioAddonAudio extends LitElement {
|
||||
display: block;
|
||||
}
|
||||
.errors {
|
||||
color: var(--google-red-500);
|
||||
color: var(--error-color);
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
paper-item {
|
||||
@@ -171,7 +175,10 @@ class HassioAddonAudio extends LitElement {
|
||||
}
|
||||
}
|
||||
|
||||
private async _saveSettings(): Promise<void> {
|
||||
private async _saveSettings(ev: CustomEvent): Promise<void> {
|
||||
const button = ev.currentTarget as any;
|
||||
button.progress = true;
|
||||
|
||||
this._error = undefined;
|
||||
const data: HassioAddonSetOptionParams = {
|
||||
audio_input:
|
||||
@@ -181,12 +188,14 @@ class HassioAddonAudio extends LitElement {
|
||||
};
|
||||
try {
|
||||
await setHassioAddonOption(this.hass, this.addon.slug, data);
|
||||
if (this.addon?.state === "started") {
|
||||
await suggestAddonRestart(this, this.hass, this.addon);
|
||||
}
|
||||
} catch {
|
||||
this._error = "Failed to set addon audio device";
|
||||
}
|
||||
if (!this._error && this.addon?.state === "started") {
|
||||
await suggestAddonRestart(this, this.hass, this.addon);
|
||||
}
|
||||
|
||||
button.progress = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -1,4 +1,3 @@
|
||||
import "@polymer/paper-spinner/paper-spinner-lite";
|
||||
import {
|
||||
css,
|
||||
CSSResult,
|
||||
@@ -12,6 +11,7 @@ import { HassioAddonDetails } from "../../../../src/data/hassio/addon";
|
||||
import { haStyle } from "../../../../src/resources/styles";
|
||||
import { HomeAssistant } from "../../../../src/types";
|
||||
import { hassioStyle } from "../../resources/hassio-style";
|
||||
import "../../../../src/components/ha-circular-progress";
|
||||
import "./hassio-addon-audio";
|
||||
import "./hassio-addon-config";
|
||||
import "./hassio-addon-network";
|
||||
@@ -24,7 +24,7 @@ class HassioAddonConfigDashboard extends LitElement {
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (!this.addon) {
|
||||
return html` <paper-spinner-lite active></paper-spinner-lite> `;
|
||||
return html`<ha-circular-progress active></ha-circular-progress>`;
|
||||
}
|
||||
return html`
|
||||
<div class="content">
|
||||
|
@@ -5,6 +5,7 @@ import {
|
||||
CSSResult,
|
||||
customElement,
|
||||
html,
|
||||
internalProperty,
|
||||
LitElement,
|
||||
property,
|
||||
PropertyValues,
|
||||
@@ -12,6 +13,7 @@ import {
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import { fireEvent } from "../../../../src/common/dom/fire_event";
|
||||
import "../../../../src/components/buttons/ha-progress-button";
|
||||
import "../../../../src/components/ha-card";
|
||||
import "../../../../src/components/ha-yaml-editor";
|
||||
import type { HaYamlEditor } from "../../../../src/components/ha-yaml-editor";
|
||||
@@ -20,6 +22,7 @@ import {
|
||||
HassioAddonSetOptionParams,
|
||||
setHassioAddonOption,
|
||||
} from "../../../../src/data/hassio/addon";
|
||||
import { extractApiErrorMessage } from "../../../../src/data/hassio/common";
|
||||
import { showConfirmationDialog } from "../../../../src/dialogs/generic/show-dialog-box";
|
||||
import { haStyle } from "../../../../src/resources/styles";
|
||||
import type { HomeAssistant } from "../../../../src/types";
|
||||
@@ -32,7 +35,7 @@ class HassioAddonConfig extends LitElement {
|
||||
|
||||
@property({ attribute: false }) public addon!: HassioAddonDetails;
|
||||
|
||||
@property() private _error?: string;
|
||||
@internalProperty() private _error?: string;
|
||||
|
||||
@property({ type: Boolean }) private _configHasChanged = false;
|
||||
|
||||
@@ -54,50 +57,20 @@ class HassioAddonConfig extends LitElement {
|
||||
${valid ? "" : html` <div class="errors">Invalid YAML</div> `}
|
||||
</div>
|
||||
<div class="card-actions">
|
||||
<mwc-button class="warning" @click=${this._resetTapped}>
|
||||
<ha-progress-button class="warning" @click=${this._resetTapped}>
|
||||
Reset to defaults
|
||||
</mwc-button>
|
||||
<mwc-button
|
||||
</ha-progress-button>
|
||||
<ha-progress-button
|
||||
@click=${this._saveTapped}
|
||||
.disabled=${!this._configHasChanged || !valid}
|
||||
>
|
||||
Save
|
||||
</mwc-button>
|
||||
</ha-progress-button>
|
||||
</div>
|
||||
</ha-card>
|
||||
`;
|
||||
}
|
||||
|
||||
static get styles(): CSSResult[] {
|
||||
return [
|
||||
haStyle,
|
||||
hassioStyle,
|
||||
css`
|
||||
:host {
|
||||
display: block;
|
||||
}
|
||||
ha-card {
|
||||
display: block;
|
||||
}
|
||||
.card-actions {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
.errors {
|
||||
color: var(--google-red-500);
|
||||
margin-top: 16px;
|
||||
}
|
||||
iron-autogrow-textarea {
|
||||
width: 100%;
|
||||
font-family: monospace;
|
||||
}
|
||||
.syntaxerror {
|
||||
color: var(--google-red-500);
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
||||
protected updated(changedProperties: PropertyValues): void {
|
||||
super.updated(changedProperties);
|
||||
if (changedProperties.has("addon")) {
|
||||
@@ -110,7 +83,10 @@ class HassioAddonConfig extends LitElement {
|
||||
this.requestUpdate();
|
||||
}
|
||||
|
||||
private async _resetTapped(): Promise<void> {
|
||||
private async _resetTapped(ev: CustomEvent): Promise<void> {
|
||||
const button = ev.currentTarget as any;
|
||||
button.progress = true;
|
||||
|
||||
const confirmed = await showConfirmationDialog(this, {
|
||||
title: this.addon.name,
|
||||
text: "Are you sure you want to reset all your options?",
|
||||
@@ -119,6 +95,7 @@ class HassioAddonConfig extends LitElement {
|
||||
});
|
||||
|
||||
if (!confirmed) {
|
||||
button.progress = false;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -136,13 +113,17 @@ class HassioAddonConfig extends LitElement {
|
||||
};
|
||||
fireEvent(this, "hass-api-called", eventdata);
|
||||
} catch (err) {
|
||||
this._error = `Failed to reset addon configuration, ${
|
||||
err.body?.message || err
|
||||
}`;
|
||||
this._error = `Failed to reset addon configuration, ${extractApiErrorMessage(
|
||||
err
|
||||
)}`;
|
||||
}
|
||||
button.progress = false;
|
||||
}
|
||||
|
||||
private async _saveTapped(): Promise<void> {
|
||||
private async _saveTapped(ev: CustomEvent): Promise<void> {
|
||||
const button = ev.currentTarget as any;
|
||||
button.progress = true;
|
||||
|
||||
let data: HassioAddonSetOptionParams;
|
||||
this._error = undefined;
|
||||
try {
|
||||
@@ -162,14 +143,45 @@ class HassioAddonConfig extends LitElement {
|
||||
path: "options",
|
||||
};
|
||||
fireEvent(this, "hass-api-called", eventdata);
|
||||
if (this.addon?.state === "started") {
|
||||
await suggestAddonRestart(this, this.hass, this.addon);
|
||||
}
|
||||
} catch (err) {
|
||||
this._error = `Failed to save addon configuration, ${
|
||||
err.body?.message || err
|
||||
}`;
|
||||
}
|
||||
if (!this._error && this.addon?.state === "started") {
|
||||
await suggestAddonRestart(this, this.hass, this.addon);
|
||||
this._error = `Failed to save addon configuration, ${extractApiErrorMessage(
|
||||
err
|
||||
)}`;
|
||||
}
|
||||
button.progress = false;
|
||||
}
|
||||
|
||||
static get styles(): CSSResult[] {
|
||||
return [
|
||||
haStyle,
|
||||
hassioStyle,
|
||||
css`
|
||||
:host {
|
||||
display: block;
|
||||
}
|
||||
ha-card {
|
||||
display: block;
|
||||
}
|
||||
.card-actions {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
.errors {
|
||||
color: var(--error-color);
|
||||
margin-top: 16px;
|
||||
}
|
||||
iron-autogrow-textarea {
|
||||
width: 100%;
|
||||
font-family: monospace;
|
||||
}
|
||||
.syntaxerror {
|
||||
color: var(--error-color);
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -4,18 +4,21 @@ import {
|
||||
CSSResult,
|
||||
customElement,
|
||||
html,
|
||||
internalProperty,
|
||||
LitElement,
|
||||
property,
|
||||
PropertyValues,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import { fireEvent } from "../../../../src/common/dom/fire_event";
|
||||
import "../../../../src/components/buttons/ha-progress-button";
|
||||
import "../../../../src/components/ha-card";
|
||||
import {
|
||||
HassioAddonDetails,
|
||||
HassioAddonSetOptionParams,
|
||||
setHassioAddonOption,
|
||||
} from "../../../../src/data/hassio/addon";
|
||||
import { extractApiErrorMessage } from "../../../../src/data/hassio/common";
|
||||
import { haStyle } from "../../../../src/resources/styles";
|
||||
import { HomeAssistant } from "../../../../src/types";
|
||||
import { suggestAddonRestart } from "../../dialogs/suggestAddonRestart";
|
||||
@@ -37,9 +40,9 @@ class HassioAddonNetwork extends LitElement {
|
||||
|
||||
@property({ attribute: false }) public addon!: HassioAddonDetails;
|
||||
|
||||
@property() private _error?: string;
|
||||
@internalProperty() private _error?: string;
|
||||
|
||||
@property() private _config?: NetworkItem[];
|
||||
@internalProperty() private _config?: NetworkItem[];
|
||||
|
||||
public connectedCallback(): void {
|
||||
super.connectedCallback();
|
||||
@@ -71,7 +74,7 @@ class HassioAddonNetwork extends LitElement {
|
||||
<paper-input
|
||||
@value-changed=${this._configChanged}
|
||||
placeholder="disabled"
|
||||
.value=${String(item.host)}
|
||||
.value=${item.host ? String(item.host) : ""}
|
||||
.container=${item.container}
|
||||
no-label-float
|
||||
></paper-input>
|
||||
@@ -84,38 +87,17 @@ class HassioAddonNetwork extends LitElement {
|
||||
</table>
|
||||
</div>
|
||||
<div class="card-actions">
|
||||
<mwc-button class="warning" @click=${this._resetTapped}>
|
||||
<ha-progress-button class="warning" @click=${this._resetTapped}>
|
||||
Reset to defaults
|
||||
</mwc-button>
|
||||
<mwc-button @click=${this._saveTapped}>Save</mwc-button>
|
||||
</ha-progress-button>
|
||||
<ha-progress-button @click=${this._saveTapped}>
|
||||
Save
|
||||
</ha-progress-button>
|
||||
</div>
|
||||
</ha-card>
|
||||
`;
|
||||
}
|
||||
|
||||
static get styles(): CSSResult[] {
|
||||
return [
|
||||
haStyle,
|
||||
hassioStyle,
|
||||
css`
|
||||
:host {
|
||||
display: block;
|
||||
}
|
||||
ha-card {
|
||||
display: block;
|
||||
}
|
||||
.errors {
|
||||
color: var(--google-red-500);
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.card-actions {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
||||
protected update(changedProperties: PropertyValues): void {
|
||||
super.update(changedProperties);
|
||||
if (changedProperties.has("addon")) {
|
||||
@@ -148,7 +130,10 @@ class HassioAddonNetwork extends LitElement {
|
||||
});
|
||||
}
|
||||
|
||||
private async _resetTapped(): Promise<void> {
|
||||
private async _resetTapped(ev: CustomEvent): Promise<void> {
|
||||
const button = ev.currentTarget as any;
|
||||
button.progress = true;
|
||||
|
||||
const data: HassioAddonSetOptionParams = {
|
||||
network: null,
|
||||
};
|
||||
@@ -161,17 +146,22 @@ class HassioAddonNetwork extends LitElement {
|
||||
path: "option",
|
||||
};
|
||||
fireEvent(this, "hass-api-called", eventdata);
|
||||
if (this.addon?.state === "started") {
|
||||
await suggestAddonRestart(this, this.hass, this.addon);
|
||||
}
|
||||
} catch (err) {
|
||||
this._error = `Failed to set addon network configuration, ${
|
||||
err.body?.message || err
|
||||
}`;
|
||||
}
|
||||
if (!this._error && this.addon?.state === "started") {
|
||||
await suggestAddonRestart(this, this.hass, this.addon);
|
||||
this._error = `Failed to set addon network configuration, ${extractApiErrorMessage(
|
||||
err
|
||||
)}`;
|
||||
}
|
||||
|
||||
button.progress = false;
|
||||
}
|
||||
|
||||
private async _saveTapped(): Promise<void> {
|
||||
private async _saveTapped(ev: CustomEvent): Promise<void> {
|
||||
const button = ev.currentTarget as any;
|
||||
button.progress = true;
|
||||
|
||||
this._error = undefined;
|
||||
const networkconfiguration = {};
|
||||
this._config!.forEach((item) => {
|
||||
@@ -190,14 +180,38 @@ class HassioAddonNetwork extends LitElement {
|
||||
path: "option",
|
||||
};
|
||||
fireEvent(this, "hass-api-called", eventdata);
|
||||
if (this.addon?.state === "started") {
|
||||
await suggestAddonRestart(this, this.hass, this.addon);
|
||||
}
|
||||
} catch (err) {
|
||||
this._error = `Failed to set addon network configuration, ${
|
||||
err.body?.message || err
|
||||
}`;
|
||||
}
|
||||
if (!this._error && this.addon?.state === "started") {
|
||||
await suggestAddonRestart(this, this.hass, this.addon);
|
||||
this._error = `Failed to set addon network configuration, ${extractApiErrorMessage(
|
||||
err
|
||||
)}`;
|
||||
}
|
||||
button.progress = false;
|
||||
}
|
||||
|
||||
static get styles(): CSSResult[] {
|
||||
return [
|
||||
haStyle,
|
||||
hassioStyle,
|
||||
css`
|
||||
:host {
|
||||
display: block;
|
||||
}
|
||||
ha-card {
|
||||
display: block;
|
||||
}
|
||||
.errors {
|
||||
color: var(--error-color);
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.card-actions {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -1,19 +1,21 @@
|
||||
import "@polymer/paper-spinner/paper-spinner-lite";
|
||||
import {
|
||||
css,
|
||||
CSSResult,
|
||||
customElement,
|
||||
html,
|
||||
internalProperty,
|
||||
LitElement,
|
||||
property,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import "../../../../src/components/ha-circular-progress";
|
||||
import "../../../../src/components/ha-markdown";
|
||||
import {
|
||||
fetchHassioAddonDocumentation,
|
||||
HassioAddonDetails,
|
||||
} from "../../../../src/data/hassio/addon";
|
||||
import "../../../../src/layouts/loading-screen";
|
||||
import { extractApiErrorMessage } from "../../../../src/data/hassio/common";
|
||||
import "../../../../src/layouts/hass-loading-screen";
|
||||
import { haStyle } from "../../../../src/resources/styles";
|
||||
import { HomeAssistant } from "../../../../src/types";
|
||||
import { hassioStyle } from "../../resources/hassio-style";
|
||||
@@ -24,9 +26,9 @@ class HassioAddonDocumentationDashboard extends LitElement {
|
||||
|
||||
@property({ attribute: false }) public addon?: HassioAddonDetails;
|
||||
|
||||
@property() private _error?: string;
|
||||
@internalProperty() private _error?: string;
|
||||
|
||||
@property() private _content?: string;
|
||||
@internalProperty() private _content?: string;
|
||||
|
||||
public async connectedCallback(): Promise<void> {
|
||||
super.connectedCallback();
|
||||
@@ -35,7 +37,7 @@ class HassioAddonDocumentationDashboard extends LitElement {
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (!this.addon) {
|
||||
return html` <paper-spinner-lite active></paper-spinner-lite> `;
|
||||
return html`<ha-circular-progress active></ha-circular-progress>`;
|
||||
}
|
||||
return html`
|
||||
<div class="content">
|
||||
@@ -44,7 +46,7 @@ class HassioAddonDocumentationDashboard extends LitElement {
|
||||
<div class="card-content">
|
||||
${this._content
|
||||
? html`<ha-markdown .content=${this._content}></ha-markdown>`
|
||||
: html`<loading-screen></loading-screen>`}
|
||||
: html`<hass-loading-screen no-toolbar></hass-loading-screen>`}
|
||||
</div>
|
||||
</ha-card>
|
||||
</div>
|
||||
@@ -79,9 +81,9 @@ class HassioAddonDocumentationDashboard extends LitElement {
|
||||
this.addon!.slug
|
||||
);
|
||||
} catch (err) {
|
||||
this._error = `Failed to get addon documentation, ${
|
||||
err.body?.message || err
|
||||
}`;
|
||||
this._error = `Failed to get addon documentation, ${extractApiErrorMessage(
|
||||
err
|
||||
)}`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -4,7 +4,6 @@ import {
|
||||
mdiInformationVariant,
|
||||
mdiMathLog,
|
||||
} from "@mdi/js";
|
||||
import "@polymer/paper-spinner/paper-spinner-lite";
|
||||
import {
|
||||
css,
|
||||
CSSResult,
|
||||
@@ -20,6 +19,7 @@ import {
|
||||
HassioAddonDetails,
|
||||
} from "../../../src/data/hassio/addon";
|
||||
import "../../../src/layouts/hass-tabs-subpage";
|
||||
import "../../../src/components/ha-circular-progress";
|
||||
import type { PageNavigation } from "../../../src/layouts/hass-tabs-subpage";
|
||||
import { haStyle } from "../../../src/resources/styles";
|
||||
import { HomeAssistant, Route } from "../../../src/types";
|
||||
@@ -56,7 +56,7 @@ class HassioAddonDashboard extends LitElement {
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (!this.addon) {
|
||||
return html` <paper-spinner-lite active></paper-spinner-lite> `;
|
||||
return html`<ha-circular-progress active></ha-circular-progress>`;
|
||||
}
|
||||
|
||||
const addonTabs: PageNavigation[] = [
|
||||
|
@@ -1,4 +1,3 @@
|
||||
import "@polymer/paper-spinner/paper-spinner-lite";
|
||||
import {
|
||||
css,
|
||||
CSSResult,
|
||||
@@ -9,6 +8,7 @@ import {
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import { HassioAddonDetails } from "../../../../src/data/hassio/addon";
|
||||
import "../../../../src/components/ha-circular-progress";
|
||||
import { haStyle } from "../../../../src/resources/styles";
|
||||
import { HomeAssistant } from "../../../../src/types";
|
||||
import { hassioStyle } from "../../resources/hassio-style";
|
||||
@@ -24,7 +24,7 @@ class HassioAddonInfoDashboard extends LitElement {
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (!this.addon) {
|
||||
return html` <paper-spinner-lite active></paper-spinner-lite> `;
|
||||
return html`<ha-circular-progress active></ha-circular-progress>`;
|
||||
}
|
||||
|
||||
return html`
|
||||
|
@@ -9,18 +9,17 @@ import {
|
||||
mdiExclamationThick,
|
||||
mdiFlask,
|
||||
mdiHomeAssistant,
|
||||
mdiInformation,
|
||||
mdiKey,
|
||||
mdiNetwork,
|
||||
mdiPound,
|
||||
mdiShield,
|
||||
} from "@mdi/js";
|
||||
import "@polymer/paper-tooltip/paper-tooltip";
|
||||
import {
|
||||
css,
|
||||
CSSResult,
|
||||
customElement,
|
||||
html,
|
||||
internalProperty,
|
||||
LitElement,
|
||||
property,
|
||||
TemplateResult,
|
||||
@@ -34,19 +33,27 @@ import "../../../../src/components/buttons/ha-progress-button";
|
||||
import "../../../../src/components/ha-card";
|
||||
import "../../../../src/components/ha-label-badge";
|
||||
import "../../../../src/components/ha-markdown";
|
||||
import "../../../../src/components/ha-settings-row";
|
||||
import "../../../../src/components/ha-svg-icon";
|
||||
import "../../../../src/components/ha-switch";
|
||||
import {
|
||||
fetchHassioAddonChangelog,
|
||||
fetchHassioAddonInfo,
|
||||
HassioAddonDetails,
|
||||
HassioAddonSetOptionParams,
|
||||
HassioAddonSetSecurityParams,
|
||||
installHassioAddon,
|
||||
setHassioAddonOption,
|
||||
setHassioAddonSecurity,
|
||||
startHassioAddon,
|
||||
uninstallHassioAddon,
|
||||
validateHassioAddonOption,
|
||||
} from "../../../../src/data/hassio/addon";
|
||||
import { showConfirmationDialog } from "../../../../src/dialogs/generic/show-dialog-box";
|
||||
import { extractApiErrorMessage } from "../../../../src/data/hassio/common";
|
||||
import {
|
||||
showAlertDialog,
|
||||
showConfirmationDialog,
|
||||
} from "../../../../src/dialogs/generic/show-dialog-box";
|
||||
import { haStyle } from "../../../../src/resources/styles";
|
||||
import { HomeAssistant } from "../../../../src/types";
|
||||
import "../../components/hassio-card-content";
|
||||
@@ -124,9 +131,7 @@ class HassioAddonInfo extends LitElement {
|
||||
|
||||
@property({ attribute: false }) public addon!: HassioAddonDetails;
|
||||
|
||||
@property() private _error?: string;
|
||||
|
||||
@property({ type: Boolean }) private _installing = false;
|
||||
@internalProperty() private _error?: string;
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
@@ -241,19 +246,23 @@ class HassioAddonInfo extends LitElement {
|
||||
`
|
||||
: ""}
|
||||
<div class="security">
|
||||
<ha-label-badge
|
||||
class=${classMap({
|
||||
green: this.addon.stage === "stable",
|
||||
yellow: this.addon.stage === "experimental",
|
||||
red: this.addon.stage === "deprecated",
|
||||
})}
|
||||
@click=${this._showMoreInfo}
|
||||
id="stage"
|
||||
label="stage"
|
||||
description=""
|
||||
>
|
||||
<ha-svg-icon .path=${STAGE_ICON[this.addon.stage]}></ha-svg-icon>
|
||||
</ha-label-badge>
|
||||
${this.addon.stage !== "stable"
|
||||
? html` <ha-label-badge
|
||||
class=${classMap({
|
||||
yellow: this.addon.stage === "experimental",
|
||||
red: this.addon.stage === "deprecated",
|
||||
})}
|
||||
@click=${this._showMoreInfo}
|
||||
id="stage"
|
||||
label="stage"
|
||||
description=""
|
||||
>
|
||||
<ha-svg-icon
|
||||
.path=${STAGE_ICON[this.addon.stage]}
|
||||
></ha-svg-icon>
|
||||
</ha-label-badge>`
|
||||
: ""}
|
||||
|
||||
<ha-label-badge
|
||||
class=${classMap({
|
||||
green: [5, 6].includes(Number(this.addon.rating)),
|
||||
@@ -381,67 +390,94 @@ class HassioAddonInfo extends LitElement {
|
||||
|
||||
${this.addon.version
|
||||
? html`
|
||||
<div class="state">
|
||||
<div>Start on boot</div>
|
||||
<ha-switch
|
||||
@change=${this._startOnBootToggled}
|
||||
.checked=${this.addon.boot === "auto"}
|
||||
haptic
|
||||
></ha-switch>
|
||||
</div>
|
||||
${this.addon.auto_update || this.hass.userData?.showAdvanced
|
||||
? html`
|
||||
<div class="state">
|
||||
<div>Auto update</div>
|
||||
<ha-switch
|
||||
@change=${this._autoUpdateToggled}
|
||||
.checked=${this.addon.auto_update}
|
||||
haptic
|
||||
></ha-switch>
|
||||
</div>
|
||||
`
|
||||
: ""}
|
||||
${this.addon.ingress
|
||||
? html`
|
||||
<div class="state">
|
||||
<div>Show in sidebar</div>
|
||||
<ha-switch
|
||||
@change=${this._panelToggled}
|
||||
.checked=${this.addon.ingress_panel}
|
||||
.disabled=${this._computeCannotIngressSidebar}
|
||||
haptic
|
||||
></ha-switch>
|
||||
${this._computeCannotIngressSidebar
|
||||
? html`
|
||||
<span>
|
||||
This option requires Home Assistant 0.92 or
|
||||
later.
|
||||
</span>
|
||||
`
|
||||
: ""}
|
||||
</div>
|
||||
`
|
||||
: ""}
|
||||
${this._computeUsesProtectedOptions
|
||||
? html`
|
||||
<div class="state">
|
||||
<div>
|
||||
Protection mode
|
||||
<span>
|
||||
<ha-svg-icon path=${mdiInformation}></ha-svg-icon>
|
||||
<paper-tooltip>
|
||||
Grant the add-on elevated system access.
|
||||
</paper-tooltip>
|
||||
<div class="addon-options">
|
||||
<ha-settings-row ?three-line=${this.narrow}>
|
||||
<span slot="heading">
|
||||
Start on boot
|
||||
</span>
|
||||
<span slot="description">
|
||||
Make the add-on start during a system boot
|
||||
</span>
|
||||
<ha-switch
|
||||
@change=${this._startOnBootToggled}
|
||||
.checked=${this.addon.boot === "auto"}
|
||||
haptic
|
||||
></ha-switch>
|
||||
</ha-settings-row>
|
||||
|
||||
${this.addon.startup !== "once"
|
||||
? html`
|
||||
<ha-settings-row ?three-line=${this.narrow}>
|
||||
<span slot="heading">
|
||||
Watchdog
|
||||
</span>
|
||||
</div>
|
||||
<ha-switch
|
||||
@change=${this._protectionToggled}
|
||||
.checked=${this.addon.protected}
|
||||
haptic
|
||||
></ha-switch>
|
||||
</div>
|
||||
`
|
||||
: ""}
|
||||
<span slot="description">
|
||||
This will start the add-on if it crashes
|
||||
</span>
|
||||
<ha-switch
|
||||
@change=${this._watchdogToggled}
|
||||
.checked=${this.addon.watchdog}
|
||||
haptic
|
||||
></ha-switch>
|
||||
</ha-settings-row>
|
||||
`
|
||||
: ""}
|
||||
${this.addon.auto_update || this.hass.userData?.showAdvanced
|
||||
? html`
|
||||
<ha-settings-row ?three-line=${this.narrow}>
|
||||
<span slot="heading">
|
||||
Auto update
|
||||
</span>
|
||||
<span slot="description">
|
||||
Auto update the add-on when there is a new version
|
||||
available
|
||||
</span>
|
||||
<ha-switch
|
||||
@change=${this._autoUpdateToggled}
|
||||
.checked=${this.addon.auto_update}
|
||||
haptic
|
||||
></ha-switch>
|
||||
</ha-settings-row>
|
||||
`
|
||||
: ""}
|
||||
${this.addon.ingress
|
||||
? html`
|
||||
<ha-settings-row ?three-line=${this.narrow}>
|
||||
<span slot="heading">
|
||||
Show in sidebar
|
||||
</span>
|
||||
<span slot="description">
|
||||
${this._computeCannotIngressSidebar
|
||||
? "This option requires Home Assistant 0.92 or later."
|
||||
: "Add this add-on to your sidebar"}
|
||||
</span>
|
||||
<ha-switch
|
||||
@change=${this._panelToggled}
|
||||
.checked=${this.addon.ingress_panel}
|
||||
.disabled=${this._computeCannotIngressSidebar}
|
||||
haptic
|
||||
></ha-switch>
|
||||
</ha-settings-row>
|
||||
`
|
||||
: ""}
|
||||
${this._computeUsesProtectedOptions
|
||||
? html`
|
||||
<ha-settings-row ?three-line=${this.narrow}>
|
||||
<span slot="heading">
|
||||
Protection mode
|
||||
</span>
|
||||
<span slot="description">
|
||||
Blocks elevated system access from the add-on
|
||||
</span>
|
||||
<ha-switch
|
||||
@change=${this._protectionToggled}
|
||||
.checked=${this.addon.protected}
|
||||
haptic
|
||||
></ha-switch>
|
||||
</ha-settings-row>
|
||||
`
|
||||
: ""}
|
||||
</div>
|
||||
`
|
||||
: ""}
|
||||
${this._error ? html` <div class="errors">${this._error}</div> ` : ""}
|
||||
@@ -467,12 +503,9 @@ class HassioAddonInfo extends LitElement {
|
||||
</ha-call-api-button>
|
||||
`
|
||||
: html`
|
||||
<ha-call-api-button
|
||||
.hass=${this.hass}
|
||||
.path="hassio/addons/${this.addon.slug}/start"
|
||||
>
|
||||
<ha-progress-button @click=${this._startClicked}>
|
||||
Start
|
||||
</ha-call-api-button>
|
||||
</ha-progress-button>
|
||||
`}
|
||||
${this._computeShowWebUI
|
||||
? html`
|
||||
@@ -496,12 +529,12 @@ class HassioAddonInfo extends LitElement {
|
||||
</mwc-button>
|
||||
`
|
||||
: ""}
|
||||
<mwc-button
|
||||
<ha-progress-button
|
||||
class=" right warning"
|
||||
@click=${this._uninstallClicked}
|
||||
>
|
||||
Uninstall
|
||||
</mwc-button>
|
||||
</ha-progress-button>
|
||||
${this.addon.build
|
||||
? html`
|
||||
<ha-call-api-button
|
||||
@@ -523,8 +556,7 @@ class HassioAddonInfo extends LitElement {
|
||||
`
|
||||
: ""}
|
||||
<ha-progress-button
|
||||
.disabled=${!this.addon.available || this._installing}
|
||||
.progress=${this._installing}
|
||||
.disabled=${!this.addon.available}
|
||||
@click=${this._installClicked}
|
||||
>
|
||||
Install
|
||||
@@ -547,137 +579,6 @@ class HassioAddonInfo extends LitElement {
|
||||
`;
|
||||
}
|
||||
|
||||
static get styles(): CSSResult[] {
|
||||
return [
|
||||
haStyle,
|
||||
hassioStyle,
|
||||
css`
|
||||
:host {
|
||||
display: block;
|
||||
}
|
||||
ha-card {
|
||||
display: block;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
ha-card.warning {
|
||||
background-color: var(--google-red-500);
|
||||
color: white;
|
||||
}
|
||||
ha-card.warning .card-header {
|
||||
color: white;
|
||||
}
|
||||
ha-card.warning .card-content {
|
||||
color: white;
|
||||
}
|
||||
ha-card.warning mwc-button {
|
||||
--mdc-theme-primary: white !important;
|
||||
}
|
||||
.warning {
|
||||
color: var(--google-red-500);
|
||||
--mdc-theme-primary: var(--google-red-500);
|
||||
}
|
||||
.light-color {
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
.addon-header {
|
||||
padding-left: 8px;
|
||||
font-size: 24px;
|
||||
color: var(--ha-card-header-color, --primary-text-color);
|
||||
}
|
||||
.addon-version {
|
||||
float: right;
|
||||
font-size: 15px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
.errors {
|
||||
color: var(--google-red-500);
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.description {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
img.logo {
|
||||
max-height: 60px;
|
||||
margin: 16px 0;
|
||||
display: block;
|
||||
}
|
||||
.state {
|
||||
display: flex;
|
||||
margin: 33px 0;
|
||||
}
|
||||
.state div {
|
||||
width: 180px;
|
||||
display: inline-block;
|
||||
}
|
||||
.state ha-svg-icon {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
ha-switch {
|
||||
display: flex;
|
||||
}
|
||||
ha-svg-icon.running {
|
||||
color: var(--paper-green-400);
|
||||
}
|
||||
ha-svg-icon.stopped {
|
||||
color: var(--google-red-300);
|
||||
}
|
||||
ha-call-api-button {
|
||||
font-weight: 500;
|
||||
color: var(--primary-color);
|
||||
}
|
||||
.right {
|
||||
float: right;
|
||||
}
|
||||
protection-enable mwc-button {
|
||||
--mdc-theme-primary: white;
|
||||
}
|
||||
.description a {
|
||||
color: var(--primary-color);
|
||||
}
|
||||
.red {
|
||||
--ha-label-badge-color: var(--label-badge-red, #df4c1e);
|
||||
}
|
||||
.blue {
|
||||
--ha-label-badge-color: var(--label-badge-blue, #039be5);
|
||||
}
|
||||
.green {
|
||||
--ha-label-badge-color: var(--label-badge-green, #0da035);
|
||||
}
|
||||
.yellow {
|
||||
--ha-label-badge-color: var(--label-badge-yellow, #f4b400);
|
||||
}
|
||||
.security {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.card-actions {
|
||||
display: flow-root;
|
||||
}
|
||||
.security h3 {
|
||||
margin-bottom: 8px;
|
||||
font-weight: normal;
|
||||
}
|
||||
.security ha-label-badge {
|
||||
cursor: pointer;
|
||||
margin-right: 4px;
|
||||
--ha-label-badge-padding: 8px 0 0 0;
|
||||
}
|
||||
.changelog {
|
||||
display: contents;
|
||||
}
|
||||
.changelog-link {
|
||||
color: var(--primary-color);
|
||||
text-decoration: underline;
|
||||
cursor: pointer;
|
||||
}
|
||||
ha-markdown {
|
||||
padding: 16px;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
||||
private get _computeHassioApi(): boolean {
|
||||
return (
|
||||
this.addon.hassio_api &&
|
||||
@@ -762,7 +663,29 @@ class HassioAddonInfo extends LitElement {
|
||||
};
|
||||
fireEvent(this, "hass-api-called", eventdata);
|
||||
} catch (err) {
|
||||
this._error = `Failed to set addon option, ${err.body?.message || err}`;
|
||||
this._error = `Failed to set addon option, ${extractApiErrorMessage(
|
||||
err
|
||||
)}`;
|
||||
}
|
||||
}
|
||||
|
||||
private async _watchdogToggled(): Promise<void> {
|
||||
this._error = undefined;
|
||||
const data: HassioAddonSetOptionParams = {
|
||||
watchdog: !this.addon.watchdog,
|
||||
};
|
||||
try {
|
||||
await setHassioAddonOption(this.hass, this.addon.slug, data);
|
||||
const eventdata = {
|
||||
success: true,
|
||||
response: undefined,
|
||||
path: "option",
|
||||
};
|
||||
fireEvent(this, "hass-api-called", eventdata);
|
||||
} catch (err) {
|
||||
this._error = `Failed to set addon option, ${extractApiErrorMessage(
|
||||
err
|
||||
)}`;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -780,7 +703,9 @@ class HassioAddonInfo extends LitElement {
|
||||
};
|
||||
fireEvent(this, "hass-api-called", eventdata);
|
||||
} catch (err) {
|
||||
this._error = `Failed to set addon option, ${err.body?.message || err}`;
|
||||
this._error = `Failed to set addon option, ${extractApiErrorMessage(
|
||||
err
|
||||
)}`;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -798,9 +723,9 @@ class HassioAddonInfo extends LitElement {
|
||||
};
|
||||
fireEvent(this, "hass-api-called", eventdata);
|
||||
} catch (err) {
|
||||
this._error = `Failed to set addon security option, ${
|
||||
err.body?.message || err
|
||||
}`;
|
||||
this._error = `Failed to set addon security option, ${extractApiErrorMessage(
|
||||
err
|
||||
)}`;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -818,12 +743,13 @@ class HassioAddonInfo extends LitElement {
|
||||
};
|
||||
fireEvent(this, "hass-api-called", eventdata);
|
||||
} catch (err) {
|
||||
this._error = `Failed to set addon option, ${err.body?.message || err}`;
|
||||
this._error = `Failed to set addon option, ${extractApiErrorMessage(
|
||||
err
|
||||
)}`;
|
||||
}
|
||||
}
|
||||
|
||||
private async _openChangelog(): Promise<void> {
|
||||
this._error = undefined;
|
||||
try {
|
||||
const content = await fetchHassioAddonChangelog(
|
||||
this.hass,
|
||||
@@ -834,15 +760,17 @@ class HassioAddonInfo extends LitElement {
|
||||
content,
|
||||
});
|
||||
} catch (err) {
|
||||
this._error = `Failed to get addon changelog, ${
|
||||
err.body?.message || err
|
||||
}`;
|
||||
showAlertDialog(this, {
|
||||
title: "Failed to get addon changelog",
|
||||
text: extractApiErrorMessage(err),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private async _installClicked(): Promise<void> {
|
||||
this._error = undefined;
|
||||
this._installing = true;
|
||||
private async _installClicked(ev: CustomEvent): Promise<void> {
|
||||
const button = ev.currentTarget as any;
|
||||
button.progress = true;
|
||||
|
||||
try {
|
||||
await installHassioAddon(this.hass, this.addon.slug);
|
||||
const eventdata = {
|
||||
@@ -852,12 +780,62 @@ class HassioAddonInfo extends LitElement {
|
||||
};
|
||||
fireEvent(this, "hass-api-called", eventdata);
|
||||
} catch (err) {
|
||||
this._error = `Failed to install addon, ${err.body?.message || err}`;
|
||||
showAlertDialog(this, {
|
||||
title: "Failed to install addon",
|
||||
text: extractApiErrorMessage(err),
|
||||
});
|
||||
}
|
||||
this._installing = false;
|
||||
button.progress = false;
|
||||
}
|
||||
|
||||
private async _uninstallClicked(): Promise<void> {
|
||||
private async _startClicked(ev: CustomEvent): Promise<void> {
|
||||
const button = ev.currentTarget as any;
|
||||
button.progress = true;
|
||||
try {
|
||||
const validate = await validateHassioAddonOption(
|
||||
this.hass,
|
||||
this.addon.slug
|
||||
);
|
||||
if (!validate.data.valid) {
|
||||
await showConfirmationDialog(this, {
|
||||
title: "Failed to start addon - configruation validation faled!",
|
||||
text: validate.data.message.split(" Got ")[0],
|
||||
confirm: () => this._openConfiguration(),
|
||||
confirmText: "Go to configruation",
|
||||
dismissText: "Cancel",
|
||||
});
|
||||
button.progress = false;
|
||||
return;
|
||||
}
|
||||
} catch (err) {
|
||||
showAlertDialog(this, {
|
||||
title: "Failed to validate addon configuration",
|
||||
text: extractApiErrorMessage(err),
|
||||
});
|
||||
button.progress = false;
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await startHassioAddon(this.hass, this.addon.slug);
|
||||
this.addon = await fetchHassioAddonInfo(this.hass, this.addon.slug);
|
||||
} catch (err) {
|
||||
showAlertDialog(this, {
|
||||
title: "Failed to start addon",
|
||||
text: extractApiErrorMessage(err),
|
||||
});
|
||||
}
|
||||
button.progress = false;
|
||||
}
|
||||
|
||||
private _openConfiguration(): void {
|
||||
navigate(this, `/hassio/addon/${this.addon.slug}/config`);
|
||||
}
|
||||
|
||||
private async _uninstallClicked(ev: CustomEvent): Promise<void> {
|
||||
const button = ev.currentTarget as any;
|
||||
button.progress = true;
|
||||
|
||||
const confirmed = await showConfirmationDialog(this, {
|
||||
title: this.addon.name,
|
||||
text: "Are you sure you want to uninstall this add-on?",
|
||||
@@ -866,6 +844,7 @@ class HassioAddonInfo extends LitElement {
|
||||
});
|
||||
|
||||
if (!confirmed) {
|
||||
button.progress = false;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -879,8 +858,152 @@ class HassioAddonInfo extends LitElement {
|
||||
};
|
||||
fireEvent(this, "hass-api-called", eventdata);
|
||||
} catch (err) {
|
||||
this._error = `Failed to uninstall addon, ${err.body?.message || err}`;
|
||||
showAlertDialog(this, {
|
||||
title: "Failed to uninstall addon",
|
||||
text: extractApiErrorMessage(err),
|
||||
});
|
||||
}
|
||||
button.progress = false;
|
||||
}
|
||||
|
||||
static get styles(): CSSResult[] {
|
||||
return [
|
||||
haStyle,
|
||||
hassioStyle,
|
||||
css`
|
||||
:host {
|
||||
display: block;
|
||||
}
|
||||
ha-card {
|
||||
display: block;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
ha-card.warning {
|
||||
background-color: var(--error-color);
|
||||
color: white;
|
||||
}
|
||||
ha-card.warning .card-header {
|
||||
color: white;
|
||||
}
|
||||
ha-card.warning .card-content {
|
||||
color: white;
|
||||
}
|
||||
ha-card.warning mwc-button {
|
||||
--mdc-theme-primary: white !important;
|
||||
}
|
||||
.warning {
|
||||
color: var(--error-color);
|
||||
--mdc-theme-primary: var(--error-color);
|
||||
}
|
||||
.light-color {
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
.addon-header {
|
||||
padding-left: 8px;
|
||||
font-size: 24px;
|
||||
color: var(--ha-card-header-color, --primary-text-color);
|
||||
}
|
||||
.addon-version {
|
||||
float: right;
|
||||
font-size: 15px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
.errors {
|
||||
color: var(--error-color);
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.description {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
img.logo {
|
||||
max-height: 60px;
|
||||
margin: 16px 0;
|
||||
display: block;
|
||||
}
|
||||
|
||||
ha-switch {
|
||||
display: flex;
|
||||
}
|
||||
ha-svg-icon.running {
|
||||
color: var(--paper-green-400);
|
||||
}
|
||||
ha-svg-icon.stopped {
|
||||
color: var(--google-red-300);
|
||||
}
|
||||
ha-call-api-button {
|
||||
font-weight: 500;
|
||||
color: var(--primary-color);
|
||||
}
|
||||
.right {
|
||||
float: right;
|
||||
}
|
||||
protection-enable mwc-button {
|
||||
--mdc-theme-primary: white;
|
||||
}
|
||||
.description a {
|
||||
color: var(--primary-color);
|
||||
}
|
||||
.red {
|
||||
--ha-label-badge-color: var(--label-badge-red, #df4c1e);
|
||||
}
|
||||
.blue {
|
||||
--ha-label-badge-color: var(--label-badge-blue, #039be5);
|
||||
}
|
||||
.green {
|
||||
--ha-label-badge-color: var(--label-badge-green, #0da035);
|
||||
}
|
||||
.yellow {
|
||||
--ha-label-badge-color: var(--label-badge-yellow, #f4b400);
|
||||
}
|
||||
.security {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.card-actions {
|
||||
display: flow-root;
|
||||
}
|
||||
.security h3 {
|
||||
margin-bottom: 8px;
|
||||
font-weight: normal;
|
||||
}
|
||||
.security ha-label-badge {
|
||||
cursor: pointer;
|
||||
margin-right: 4px;
|
||||
--ha-label-badge-padding: 8px 0 0 0;
|
||||
}
|
||||
.changelog {
|
||||
display: contents;
|
||||
}
|
||||
.changelog-link {
|
||||
color: var(--primary-color);
|
||||
text-decoration: underline;
|
||||
cursor: pointer;
|
||||
}
|
||||
ha-markdown {
|
||||
padding: 16px;
|
||||
}
|
||||
ha-settings-row {
|
||||
padding: 0;
|
||||
height: 54px;
|
||||
width: 100%;
|
||||
}
|
||||
ha-settings-row > span[slot="description"] {
|
||||
white-space: normal;
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
ha-settings-row[three-line] {
|
||||
height: 74px;
|
||||
}
|
||||
|
||||
.addon-options {
|
||||
max-width: 50%;
|
||||
}
|
||||
@media (max-width: 720px) {
|
||||
.addon-options {
|
||||
max-width: 100%;
|
||||
}
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
}
|
||||
declare global {
|
||||
|
@@ -1,4 +1,3 @@
|
||||
import "@polymer/paper-spinner/paper-spinner-lite";
|
||||
import {
|
||||
css,
|
||||
CSSResult,
|
||||
@@ -9,6 +8,7 @@ import {
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import { HassioAddonDetails } from "../../../../src/data/hassio/addon";
|
||||
import "../../../../src/components/ha-circular-progress";
|
||||
import { haStyle } from "../../../../src/resources/styles";
|
||||
import { HomeAssistant } from "../../../../src/types";
|
||||
import { hassioStyle } from "../../resources/hassio-style";
|
||||
@@ -22,7 +22,7 @@ class HassioAddonLogDashboard extends LitElement {
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (!this.addon) {
|
||||
return html` <paper-spinner-lite active></paper-spinner-lite> `;
|
||||
return html` <ha-circular-progress active></ha-circular-progress> `;
|
||||
}
|
||||
return html`
|
||||
<div class="content">
|
||||
|
@@ -4,6 +4,7 @@ import {
|
||||
CSSResult,
|
||||
customElement,
|
||||
html,
|
||||
internalProperty,
|
||||
LitElement,
|
||||
property,
|
||||
TemplateResult,
|
||||
@@ -13,6 +14,7 @@ import {
|
||||
fetchHassioAddonLogs,
|
||||
HassioAddonDetails,
|
||||
} from "../../../../src/data/hassio/addon";
|
||||
import { extractApiErrorMessage } from "../../../../src/data/hassio/common";
|
||||
import { haStyle } from "../../../../src/resources/styles";
|
||||
import { HomeAssistant } from "../../../../src/types";
|
||||
import "../../components/hassio-ansi-to-html";
|
||||
@@ -24,9 +26,9 @@ class HassioAddonLogs extends LitElement {
|
||||
|
||||
@property({ attribute: false }) public addon!: HassioAddonDetails;
|
||||
|
||||
@property() private _error?: string;
|
||||
@internalProperty() private _error?: string;
|
||||
|
||||
@property() private _content?: string;
|
||||
@internalProperty() private _content?: string;
|
||||
|
||||
public async connectedCallback(): Promise<void> {
|
||||
super.connectedCallback();
|
||||
@@ -62,7 +64,7 @@ class HassioAddonLogs extends LitElement {
|
||||
display: block;
|
||||
}
|
||||
.errors {
|
||||
color: var(--google-red-500);
|
||||
color: var(--error-color);
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
`,
|
||||
@@ -74,7 +76,7 @@ class HassioAddonLogs extends LitElement {
|
||||
try {
|
||||
this._content = await fetchHassioAddonLogs(this.hass, this.addon.slug);
|
||||
} catch (err) {
|
||||
this._error = `Failed to get addon logs, ${err.body?.message || err}`;
|
||||
this._error = `Failed to get addon logs, ${extractApiErrorMessage(err)}`;
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -21,7 +21,7 @@ interface State {
|
||||
class HassioAnsiToHtml extends LitElement {
|
||||
@property() public content!: string;
|
||||
|
||||
public render(): TemplateResult | void {
|
||||
protected render(): TemplateResult | void {
|
||||
return html`${this._parseTextToColoredPre(this.content)}`;
|
||||
}
|
||||
|
||||
|
@@ -14,7 +14,7 @@ import { HomeAssistant } from "../../../src/types";
|
||||
|
||||
@customElement("hassio-card-content")
|
||||
class HassioCardContent extends LitElement {
|
||||
@property() public hass!: HomeAssistant;
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property() public title!: string;
|
||||
|
||||
@@ -98,7 +98,7 @@ class HassioCardContent extends LitElement {
|
||||
color: var(--paper-item-icon-color);
|
||||
}
|
||||
ha-svg-icon.not_available {
|
||||
color: var(--google-red-500);
|
||||
color: var(--error-color);
|
||||
}
|
||||
.title {
|
||||
color: var(--primary-text-color);
|
||||
|
@@ -19,7 +19,7 @@ import { hassioStyle } from "../resources/hassio-style";
|
||||
|
||||
@customElement("hassio-addons")
|
||||
class HassioAddons extends LitElement {
|
||||
@property() public hass!: HomeAssistant;
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property() public addons?: HassioAddonInfo[];
|
||||
|
||||
|
@@ -15,7 +15,7 @@ import {
|
||||
import "../../../src/layouts/hass-tabs-subpage";
|
||||
import { haStyle } from "../../../src/resources/styles";
|
||||
import { HomeAssistant, Route } from "../../../src/types";
|
||||
import { supervisorTabs } from "../hassio-panel";
|
||||
import { supervisorTabs } from "../hassio-tabs";
|
||||
import "./hassio-addons";
|
||||
import "./hassio-update";
|
||||
|
||||
|
@@ -5,33 +5,43 @@ import {
|
||||
CSSResult,
|
||||
customElement,
|
||||
html,
|
||||
internalProperty,
|
||||
LitElement,
|
||||
property,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import "../../../src/components/buttons/ha-call-api-button";
|
||||
import "../../../src/components/buttons/ha-progress-button";
|
||||
import "../../../src/components/ha-card";
|
||||
import "../../../src/components/ha-svg-icon";
|
||||
import {
|
||||
extractApiErrorMessage,
|
||||
HassioResponse,
|
||||
ignoredStatusCodes,
|
||||
} from "../../../src/data/hassio/common";
|
||||
import { HassioHassOSInfo } from "../../../src/data/hassio/host";
|
||||
import {
|
||||
HassioHomeAssistantInfo,
|
||||
HassioSupervisorInfo,
|
||||
} from "../../../src/data/hassio/supervisor";
|
||||
import {
|
||||
showAlertDialog,
|
||||
showConfirmationDialog,
|
||||
} from "../../../src/dialogs/generic/show-dialog-box";
|
||||
import { haStyle } from "../../../src/resources/styles";
|
||||
import { HomeAssistant } from "../../../src/types";
|
||||
import { hassioStyle } from "../resources/hassio-style";
|
||||
|
||||
@customElement("hassio-update")
|
||||
export class HassioUpdate extends LitElement {
|
||||
@property() public hass!: HomeAssistant;
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property() public hassInfo: HassioHomeAssistantInfo;
|
||||
@property({ attribute: false }) public hassInfo: HassioHomeAssistantInfo;
|
||||
|
||||
@property() public hassOsInfo?: HassioHassOSInfo;
|
||||
@property({ attribute: false }) public hassOsInfo?: HassioHassOSInfo;
|
||||
|
||||
@property() public supervisorInfo: HassioSupervisorInfo;
|
||||
|
||||
@property() private _error?: string;
|
||||
@internalProperty() private _error?: string;
|
||||
|
||||
protected render(): TemplateResult {
|
||||
const updatesAvailable: number = [
|
||||
@@ -125,31 +135,46 @@ export class HassioUpdate extends LitElement {
|
||||
<a href="${releaseNotesUrl}" target="_blank" rel="noreferrer">
|
||||
<mwc-button>Release notes</mwc-button>
|
||||
</a>
|
||||
<ha-call-api-button
|
||||
.hass=${this.hass}
|
||||
.path=${apiPath}
|
||||
@hass-api-called=${this._apiCalled}
|
||||
<ha-progress-button
|
||||
.apiPath=${apiPath}
|
||||
.name=${name}
|
||||
.version=${lastVersion}
|
||||
@click=${this._confirmUpdate}
|
||||
>
|
||||
Update
|
||||
</ha-call-api-button>
|
||||
</ha-progress-button>
|
||||
</div>
|
||||
</ha-card>
|
||||
`;
|
||||
}
|
||||
|
||||
private _apiCalled(ev): void {
|
||||
if (ev.detail.success) {
|
||||
this._error = "";
|
||||
private async _confirmUpdate(ev): Promise<void> {
|
||||
const item = ev.currentTarget;
|
||||
item.progress = true;
|
||||
const confirmed = await showConfirmationDialog(this, {
|
||||
title: `Update ${item.name}`,
|
||||
text: `Are you sure you want to update ${item.name} to version ${item.version}?`,
|
||||
confirmText: "update",
|
||||
dismissText: "cancel",
|
||||
});
|
||||
|
||||
if (!confirmed) {
|
||||
item.progress = false;
|
||||
return;
|
||||
}
|
||||
|
||||
const response = ev.detail.response;
|
||||
|
||||
if (typeof response.body === "object") {
|
||||
this._error = response.body.message || "Unknown error";
|
||||
} else {
|
||||
this._error = response.body;
|
||||
try {
|
||||
await this.hass.callApi<HassioResponse<void>>("POST", item.apiPath);
|
||||
} catch (err) {
|
||||
// Only show an error if the status code was not expected (user behind proxy)
|
||||
// or no status at all(connection terminated)
|
||||
if (err.status_code && !ignoredStatusCodes.has(err.status_code)) {
|
||||
showAlertDialog(this, {
|
||||
title: "Update failed",
|
||||
text: extractApiErrorMessage(err),
|
||||
});
|
||||
}
|
||||
}
|
||||
item.progress = false;
|
||||
}
|
||||
|
||||
static get styles(): CSSResult[] {
|
||||
@@ -180,7 +205,7 @@ export class HassioUpdate extends LitElement {
|
||||
text-align: right;
|
||||
}
|
||||
.errors {
|
||||
color: var(--google-red-500);
|
||||
color: var(--error-color);
|
||||
padding: 16px;
|
||||
}
|
||||
a {
|
||||
|
@@ -5,6 +5,7 @@ import {
|
||||
html,
|
||||
LitElement,
|
||||
property,
|
||||
internalProperty,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import { createCloseHeading } from "../../../../src/components/ha-dialog";
|
||||
@@ -16,13 +17,13 @@ import { HassioMarkdownDialogParams } from "./show-dialog-hassio-markdown";
|
||||
|
||||
@customElement("dialog-hassio-markdown")
|
||||
class HassioMarkdownDialog extends LitElement {
|
||||
@property() public hass!: HomeAssistant;
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property() public title!: string;
|
||||
|
||||
@property() public content!: string;
|
||||
|
||||
@property() private _opened = false;
|
||||
@internalProperty() private _opened = false;
|
||||
|
||||
public showDialog(params: HassioMarkdownDialogParams) {
|
||||
this.title = params.title;
|
||||
@@ -30,6 +31,10 @@ class HassioMarkdownDialog extends LitElement {
|
||||
this._opened = true;
|
||||
}
|
||||
|
||||
public closeDialog() {
|
||||
this._opened = false;
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (!this._opened) {
|
||||
return html``;
|
||||
@@ -37,7 +42,7 @@ class HassioMarkdownDialog extends LitElement {
|
||||
return html`
|
||||
<ha-dialog
|
||||
open
|
||||
@closing=${this._closeDialog}
|
||||
@closed=${this.closeDialog}
|
||||
.heading=${createCloseHeading(this.hass, this.title)}
|
||||
>
|
||||
<ha-markdown .content=${this.content || ""}></ha-markdown>
|
||||
@@ -45,10 +50,6 @@ class HassioMarkdownDialog extends LitElement {
|
||||
`;
|
||||
}
|
||||
|
||||
private _closeDialog(): void {
|
||||
this._opened = false;
|
||||
}
|
||||
|
||||
static get styles(): CSSResult[] {
|
||||
return [
|
||||
haStyleDialog,
|
||||
|
333
hassio/src/dialogs/network/dialog-hassio-network.ts
Normal file
333
hassio/src/dialogs/network/dialog-hassio-network.ts
Normal file
@@ -0,0 +1,333 @@
|
||||
import "@material/mwc-button/mwc-button";
|
||||
import "@material/mwc-icon-button";
|
||||
import "@material/mwc-tab";
|
||||
import "@material/mwc-tab-bar";
|
||||
import { mdiClose } from "@mdi/js";
|
||||
import { PaperInputElement } from "@polymer/paper-input/paper-input";
|
||||
import {
|
||||
css,
|
||||
CSSResult,
|
||||
customElement,
|
||||
html,
|
||||
internalProperty,
|
||||
LitElement,
|
||||
property,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import { cache } from "lit-html/directives/cache";
|
||||
import { fireEvent } from "../../../../src/common/dom/fire_event";
|
||||
import "../../../../src/components/ha-circular-progress";
|
||||
import "../../../../src/components/ha-dialog";
|
||||
import "../../../../src/components/ha-formfield";
|
||||
import "../../../../src/components/ha-header-bar";
|
||||
import "../../../../src/components/ha-radio";
|
||||
import type { HaRadio } from "../../../../src/components/ha-radio";
|
||||
import "../../../../src/components/ha-related-items";
|
||||
import "../../../../src/components/ha-svg-icon";
|
||||
import { extractApiErrorMessage } from "../../../../src/data/hassio/common";
|
||||
import {
|
||||
NetworkInterface,
|
||||
updateNetworkInterface,
|
||||
} from "../../../../src/data/hassio/network";
|
||||
import {
|
||||
showAlertDialog,
|
||||
showConfirmationDialog,
|
||||
} from "../../../../src/dialogs/generic/show-dialog-box";
|
||||
import { HassDialog } from "../../../../src/dialogs/make-dialog-manager";
|
||||
import { haStyleDialog } from "../../../../src/resources/styles";
|
||||
import type { HomeAssistant } from "../../../../src/types";
|
||||
import { HassioNetworkDialogParams } from "./show-dialog-network";
|
||||
|
||||
@customElement("dialog-hassio-network")
|
||||
export class DialogHassioNetwork extends LitElement implements HassDialog {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@internalProperty() private _prosessing = false;
|
||||
|
||||
@internalProperty() private _params?: HassioNetworkDialogParams;
|
||||
|
||||
@internalProperty() private _network!: {
|
||||
interface: string;
|
||||
data: NetworkInterface;
|
||||
}[];
|
||||
|
||||
@internalProperty() private _curTabIndex = 0;
|
||||
|
||||
@internalProperty() private _device?: {
|
||||
interface: string;
|
||||
data: NetworkInterface;
|
||||
};
|
||||
|
||||
@internalProperty() private _dirty = false;
|
||||
|
||||
public async showDialog(params: HassioNetworkDialogParams): Promise<void> {
|
||||
this._params = params;
|
||||
this._dirty = false;
|
||||
this._curTabIndex = 0;
|
||||
this._network = Object.keys(params.network?.interfaces)
|
||||
.map((device) => ({
|
||||
interface: device,
|
||||
data: params.network.interfaces[device],
|
||||
}))
|
||||
.sort((a, b) => {
|
||||
return a.data.primary > b.data.primary ? -1 : 1;
|
||||
});
|
||||
this._device = this._network[this._curTabIndex];
|
||||
this._device.data.nameservers = String(this._device.data.nameservers);
|
||||
await this.updateComplete;
|
||||
}
|
||||
|
||||
public closeDialog(): void {
|
||||
this._params = undefined;
|
||||
this._prosessing = false;
|
||||
fireEvent(this, "dialog-closed", { dialog: this.localName });
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (!this._params || !this._network) {
|
||||
return html``;
|
||||
}
|
||||
|
||||
return html`
|
||||
<ha-dialog
|
||||
open
|
||||
scrimClickAction
|
||||
escapeKeyAction
|
||||
.heading=${true}
|
||||
hideActions
|
||||
@closed=${this.closeDialog}
|
||||
>
|
||||
<div slot="heading">
|
||||
<ha-header-bar>
|
||||
<span slot="title">
|
||||
Network settings
|
||||
</span>
|
||||
<mwc-icon-button slot="actionItems" dialogAction="cancel">
|
||||
<ha-svg-icon .path=${mdiClose}></ha-svg-icon>
|
||||
</mwc-icon-button>
|
||||
</ha-header-bar>
|
||||
${this._network.length > 1
|
||||
? html` <mwc-tab-bar
|
||||
.activeIndex=${this._curTabIndex}
|
||||
@MDCTabBar:activated=${this._handleTabActivated}
|
||||
>${this._network.map(
|
||||
(device) =>
|
||||
html`<mwc-tab
|
||||
.id=${device.interface}
|
||||
.label=${device.interface}
|
||||
>
|
||||
</mwc-tab>`
|
||||
)}
|
||||
</mwc-tab-bar>`
|
||||
: ""}
|
||||
</div>
|
||||
${cache(this._renderTab())}
|
||||
</ha-dialog>
|
||||
`;
|
||||
}
|
||||
|
||||
private _renderTab() {
|
||||
return html` <div class="form container">
|
||||
<ha-formfield label="DHCP">
|
||||
<ha-radio
|
||||
@change=${this._handleRadioValueChanged}
|
||||
value="dhcp"
|
||||
name="method"
|
||||
?checked=${this._device!.data.method === "dhcp"}
|
||||
>
|
||||
</ha-radio>
|
||||
</ha-formfield>
|
||||
<ha-formfield label="Static">
|
||||
<ha-radio
|
||||
@change=${this._handleRadioValueChanged}
|
||||
value="static"
|
||||
name="method"
|
||||
?checked=${this._device!.data.method === "static"}
|
||||
>
|
||||
</ha-radio>
|
||||
</ha-formfield>
|
||||
${this._device!.data.method !== "dhcp"
|
||||
? html` <paper-input
|
||||
class="flex-auto"
|
||||
id="ip_address"
|
||||
label="IP address/Netmask"
|
||||
.value="${this._device!.data.ip_address}"
|
||||
@value-changed=${this._handleInputValueChanged}
|
||||
></paper-input>
|
||||
<paper-input
|
||||
class="flex-auto"
|
||||
id="gateway"
|
||||
label="Gateway address"
|
||||
.value="${this._device!.data.gateway}"
|
||||
@value-changed=${this._handleInputValueChanged}
|
||||
></paper-input>
|
||||
<paper-input
|
||||
class="flex-auto"
|
||||
id="nameservers"
|
||||
label="DNS servers"
|
||||
.value="${this._device!.data.nameservers as string}"
|
||||
@value-changed=${this._handleInputValueChanged}
|
||||
></paper-input>
|
||||
NB!: If you are changing IP or gateway addresses, you might lose
|
||||
the connection.`
|
||||
: ""}
|
||||
</div>
|
||||
<div class="buttons">
|
||||
<mwc-button label="close" @click=${this.closeDialog}> </mwc-button>
|
||||
<mwc-button @click=${this._updateNetwork} ?disabled=${!this._dirty}>
|
||||
${this._prosessing
|
||||
? html`<ha-circular-progress active></ha-circular-progress>`
|
||||
: "Update"}
|
||||
</mwc-button>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
private async _updateNetwork() {
|
||||
this._prosessing = true;
|
||||
let options: Partial<NetworkInterface> = {
|
||||
method: this._device!.data.method,
|
||||
};
|
||||
if (options.method !== "dhcp") {
|
||||
options = {
|
||||
...options,
|
||||
address: this._device!.data.ip_address,
|
||||
gateway: this._device!.data.gateway,
|
||||
dns: String(this._device!.data.nameservers).split(","),
|
||||
};
|
||||
}
|
||||
try {
|
||||
await updateNetworkInterface(this.hass, this._device!.interface, options);
|
||||
} catch (err) {
|
||||
showAlertDialog(this, {
|
||||
title: "Failed to change network settings",
|
||||
text: extractApiErrorMessage(err),
|
||||
});
|
||||
this._prosessing = false;
|
||||
return;
|
||||
}
|
||||
this._params?.loadData();
|
||||
this.closeDialog();
|
||||
}
|
||||
|
||||
private async _handleTabActivated(ev: CustomEvent): Promise<void> {
|
||||
if (this._dirty) {
|
||||
const confirm = await showConfirmationDialog(this, {
|
||||
text:
|
||||
"You have unsaved changes, these will get lost if you change tabs, do you want to continue?",
|
||||
confirmText: "yes",
|
||||
dismissText: "no",
|
||||
});
|
||||
if (!confirm) {
|
||||
this.requestUpdate("_device");
|
||||
return;
|
||||
}
|
||||
}
|
||||
this._curTabIndex = ev.detail.index;
|
||||
this._device = this._network[ev.detail.index];
|
||||
this._device.data.nameservers = String(this._device.data.nameservers);
|
||||
}
|
||||
|
||||
private _handleRadioValueChanged(ev: CustomEvent): void {
|
||||
const value = (ev.target as HaRadio).value as "dhcp" | "static";
|
||||
|
||||
if (!value || !this._device || this._device!.data.method === value) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._dirty = true;
|
||||
|
||||
this._device!.data.method = value;
|
||||
this.requestUpdate("_device");
|
||||
}
|
||||
|
||||
private _handleInputValueChanged(ev: CustomEvent): void {
|
||||
const value: string | null | undefined = (ev.target as PaperInputElement)
|
||||
.value;
|
||||
const id = (ev.target as PaperInputElement).id;
|
||||
|
||||
if (!value || !this._device || this._device.data[id] === value) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._dirty = true;
|
||||
|
||||
this._device.data[id] = value;
|
||||
}
|
||||
|
||||
static get styles(): CSSResult[] {
|
||||
return [
|
||||
haStyleDialog,
|
||||
css`
|
||||
ha-header-bar {
|
||||
--mdc-theme-on-primary: var(--primary-text-color);
|
||||
--mdc-theme-primary: var(--mdc-theme-surface);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
mwc-tab-bar {
|
||||
border-bottom: 1px solid
|
||||
var(--mdc-dialog-scroll-divider-color, rgba(0, 0, 0, 0.12));
|
||||
}
|
||||
|
||||
ha-dialog {
|
||||
--dialog-content-position: static;
|
||||
--dialog-content-padding: 0;
|
||||
--dialog-z-index: 6;
|
||||
}
|
||||
|
||||
@media all and (min-width: 451px) and (min-height: 501px) {
|
||||
.container {
|
||||
width: 400px;
|
||||
}
|
||||
}
|
||||
|
||||
.content {
|
||||
display: block;
|
||||
padding: 20px 24px;
|
||||
}
|
||||
|
||||
/* overrule the ha-style-dialog max-height on small screens */
|
||||
@media all and (max-width: 450px), all and (max-height: 500px) {
|
||||
ha-header-bar {
|
||||
--mdc-theme-primary: var(--app-header-background-color);
|
||||
--mdc-theme-on-primary: var(--app-header-text-color, white);
|
||||
}
|
||||
}
|
||||
|
||||
mwc-button.warning {
|
||||
--mdc-theme-primary: var(--error-color);
|
||||
}
|
||||
|
||||
:host([rtl]) app-toolbar {
|
||||
direction: rtl;
|
||||
text-align: right;
|
||||
}
|
||||
.container {
|
||||
padding: 20px 24px;
|
||||
}
|
||||
.form {
|
||||
margin-bottom: 53px;
|
||||
}
|
||||
.buttons {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
border-top: 1px solid
|
||||
var(--mdc-dialog-scroll-divider-color, rgba(0, 0, 0, 0.12));
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: 8px;
|
||||
padding-bottom: max(env(safe-area-inset-bottom), 8px);
|
||||
background-color: var(--mdc-theme-surface, #fff);
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"dialog-hassio-network": DialogHassioNetwork;
|
||||
}
|
||||
}
|
22
hassio/src/dialogs/network/show-dialog-network.ts
Normal file
22
hassio/src/dialogs/network/show-dialog-network.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import { fireEvent } from "../../../../src/common/dom/fire_event";
|
||||
import { NetworkInfo } from "../../../../src/data/hassio/network";
|
||||
import "./dialog-hassio-network";
|
||||
|
||||
export interface HassioNetworkDialogParams {
|
||||
network: NetworkInfo;
|
||||
loadData: () => Promise<void>;
|
||||
}
|
||||
|
||||
export const showNetworkDialog = (
|
||||
element: HTMLElement,
|
||||
dialogParams: HassioNetworkDialogParams
|
||||
): void => {
|
||||
fireEvent(element, "show-dialog", {
|
||||
dialogTag: "dialog-hassio-network",
|
||||
dialogImport: () =>
|
||||
import(
|
||||
/* webpackChunkName: "dialog-hassio-network" */ "./dialog-hassio-network"
|
||||
),
|
||||
dialogParams,
|
||||
});
|
||||
};
|
@@ -5,24 +5,26 @@ import "@polymer/paper-input/paper-input";
|
||||
import type { PaperInputElement } from "@polymer/paper-input/paper-input";
|
||||
import "@polymer/paper-item/paper-item";
|
||||
import "@polymer/paper-item/paper-item-body";
|
||||
import "@polymer/paper-spinner/paper-spinner";
|
||||
import {
|
||||
css,
|
||||
CSSResult,
|
||||
customElement,
|
||||
html,
|
||||
internalProperty,
|
||||
LitElement,
|
||||
property,
|
||||
query,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import memoizeOne from "memoize-one";
|
||||
import "../../../../src/components/ha-circular-progress";
|
||||
import "../../../../src/components/ha-dialog";
|
||||
import "../../../../src/components/ha-svg-icon";
|
||||
import {
|
||||
fetchHassioAddonsInfo,
|
||||
HassioAddonRepository,
|
||||
} from "../../../../src/data/hassio/addon";
|
||||
import { extractApiErrorMessage } from "../../../../src/data/hassio/common";
|
||||
import { setSupervisorOption } from "../../../../src/data/hassio/supervisor";
|
||||
import { haStyle, haStyleDialog } from "../../../../src/resources/styles";
|
||||
import type { HomeAssistant } from "../../../../src/types";
|
||||
@@ -39,11 +41,11 @@ class HassioRepositoriesDialog extends LitElement {
|
||||
|
||||
@query("#repository_input") private _optionInput?: PaperInputElement;
|
||||
|
||||
@property() private _opened = false;
|
||||
@internalProperty() private _opened = false;
|
||||
|
||||
@property() private _prosessing = false;
|
||||
@internalProperty() private _prosessing = false;
|
||||
|
||||
@property() private _error?: string;
|
||||
@internalProperty() private _error?: string;
|
||||
|
||||
public async showDialog(_dialogParams: any): Promise<void> {
|
||||
this._dialogParams = _dialogParams;
|
||||
@@ -108,7 +110,7 @@ class HassioRepositoriesDialog extends LitElement {
|
||||
></paper-input>
|
||||
<mwc-button @click=${this._addRepository}>
|
||||
${this._prosessing
|
||||
? html`<paper-spinner active></paper-spinner>`
|
||||
? html`<ha-circular-progress active></ha-circular-progress>`
|
||||
: "Add"}
|
||||
</mwc-button>
|
||||
</div>
|
||||
@@ -189,7 +191,7 @@ class HassioRepositoriesDialog extends LitElement {
|
||||
|
||||
input.value = "";
|
||||
} catch (err) {
|
||||
this._error = err.message;
|
||||
this._error = extractApiErrorMessage(err);
|
||||
}
|
||||
this._prosessing = false;
|
||||
}
|
||||
@@ -221,7 +223,7 @@ class HassioRepositoriesDialog extends LitElement {
|
||||
|
||||
await this._dialogParams!.loadData();
|
||||
} catch (err) {
|
||||
this._error = err.message;
|
||||
this._error = extractApiErrorMessage(err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -7,6 +7,7 @@ import {
|
||||
CSSResult,
|
||||
customElement,
|
||||
html,
|
||||
internalProperty,
|
||||
LitElement,
|
||||
property,
|
||||
TemplateResult,
|
||||
@@ -14,10 +15,12 @@ import {
|
||||
import { createCloseHeading } from "../../../../src/components/ha-dialog";
|
||||
import "../../../../src/components/ha-svg-icon";
|
||||
import { getSignedPath } from "../../../../src/data/auth";
|
||||
import { extractApiErrorMessage } from "../../../../src/data/hassio/common";
|
||||
import {
|
||||
fetchHassioSnapshotInfo,
|
||||
HassioSnapshotDetail,
|
||||
} from "../../../../src/data/hassio/snapshot";
|
||||
import { showConfirmationDialog } from "../../../../src/dialogs/generic/show-dialog-box";
|
||||
import { PolymerChangedEvent } from "../../../../src/polymer-types";
|
||||
import { haStyleDialog } from "../../../../src/resources/styles";
|
||||
import { HomeAssistant } from "../../../../src/types";
|
||||
@@ -68,21 +71,21 @@ interface FolderItem {
|
||||
|
||||
@customElement("dialog-hassio-snapshot")
|
||||
class HassioSnapshotDialog extends LitElement {
|
||||
@property() public hass!: HomeAssistant;
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property() private _error?: string;
|
||||
@internalProperty() private _error?: string;
|
||||
|
||||
@property() private _snapshot?: HassioSnapshotDetail;
|
||||
@internalProperty() private _snapshot?: HassioSnapshotDetail;
|
||||
|
||||
@property() private _folders!: FolderItem[];
|
||||
@internalProperty() private _folders!: FolderItem[];
|
||||
|
||||
@property() private _addons!: AddonItem[];
|
||||
@internalProperty() private _addons!: AddonItem[];
|
||||
|
||||
@property() private _dialogParams?: HassioSnapshotDialogParams;
|
||||
@internalProperty() private _dialogParams?: HassioSnapshotDialogParams;
|
||||
|
||||
@property() private _snapshotPassword!: string;
|
||||
@internalProperty() private _snapshotPassword!: string;
|
||||
|
||||
@property() private _restoreHass: boolean | null | undefined = true;
|
||||
@internalProperty() private _restoreHass: boolean | null | undefined = true;
|
||||
|
||||
public async showDialog(params: HassioSnapshotDialogParams) {
|
||||
this._snapshot = await fetchHassioSnapshotInfo(this.hass, params.slug);
|
||||
@@ -224,7 +227,7 @@ class HassioSnapshotDialog extends LitElement {
|
||||
}
|
||||
.warning,
|
||||
.error {
|
||||
color: var(--google-red-500);
|
||||
color: var(--error-color);
|
||||
}
|
||||
.buttons {
|
||||
display: flex;
|
||||
@@ -265,8 +268,12 @@ class HassioSnapshotDialog extends LitElement {
|
||||
this._snapshotPassword = ev.detail.value;
|
||||
}
|
||||
|
||||
private _partialRestoreClicked() {
|
||||
if (!confirm("Are you sure you want to restore this snapshot?")) {
|
||||
private async _partialRestoreClicked() {
|
||||
if (
|
||||
!(await showConfirmationDialog(this, {
|
||||
title: "Are you sure you want partially to restore this snapshot?",
|
||||
}))
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -311,8 +318,13 @@ class HassioSnapshotDialog extends LitElement {
|
||||
);
|
||||
}
|
||||
|
||||
private _fullRestoreClicked() {
|
||||
if (!confirm("Are you sure you want to restore this snapshot?")) {
|
||||
private async _fullRestoreClicked() {
|
||||
if (
|
||||
!(await showConfirmationDialog(this, {
|
||||
title:
|
||||
"Are you sure you want to wipe your system and restore this snapshot?",
|
||||
}))
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -337,8 +349,12 @@ class HassioSnapshotDialog extends LitElement {
|
||||
);
|
||||
}
|
||||
|
||||
private _deleteClicked() {
|
||||
if (!confirm("Are you sure you want to delete this snapshot?")) {
|
||||
private async _deleteClicked() {
|
||||
if (
|
||||
!(await showConfirmationDialog(this, {
|
||||
title: "Are you sure you want to delete this snapshot?",
|
||||
}))
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -364,7 +380,7 @@ class HassioSnapshotDialog extends LitElement {
|
||||
`/api/hassio/snapshots/${this._snapshot!.slug}/download`
|
||||
);
|
||||
} catch (err) {
|
||||
alert(`Error: ${err.message}`);
|
||||
alert(`Error: ${extractApiErrorMessage(err)}`);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@@ -3,6 +3,7 @@ import {
|
||||
HassioAddonDetails,
|
||||
restartHassioAddon,
|
||||
} from "../../../src/data/hassio/addon";
|
||||
import { extractApiErrorMessage } from "../../../src/data/hassio/common";
|
||||
import {
|
||||
showAlertDialog,
|
||||
showConfirmationDialog,
|
||||
@@ -26,7 +27,7 @@ export const suggestAddonRestart = async (
|
||||
} catch (err) {
|
||||
showAlertDialog(element, {
|
||||
title: "Failed to restart",
|
||||
text: err.body.message,
|
||||
text: extractApiErrorMessage(err),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@@ -1,9 +1,7 @@
|
||||
window.loadES5Adapter().then(() => {
|
||||
// eslint-disable-next-line
|
||||
import(/* webpackChunkName: "roboto" */ "../../src/resources/roboto");
|
||||
// eslint-disable-next-line
|
||||
import(/* webpackChunkName: "hassio-main" */ "./hassio-main");
|
||||
});
|
||||
import "../../src/resources/compatibility";
|
||||
import "../../src/resources/safari-14-attachshadow-patch";
|
||||
import "../../src/resources/roboto";
|
||||
import "./hassio-main";
|
||||
|
||||
const styleEl = document.createElement("style");
|
||||
styleEl.innerHTML = `
|
||||
|
@@ -1,107 +1,35 @@
|
||||
import { PolymerElement } from "@polymer/polymer";
|
||||
import { customElement, property, PropertyValues } from "lit-element";
|
||||
import {
|
||||
html,
|
||||
PropertyValues,
|
||||
customElement,
|
||||
LitElement,
|
||||
property,
|
||||
} from "lit-element";
|
||||
import "./hassio-router";
|
||||
import { urlSyncMixin } from "../../src/state/url-sync-mixin";
|
||||
import { ProvideHassLitMixin } from "../../src/mixins/provide-hass-lit-mixin";
|
||||
import { HomeAssistant, Route } from "../../src/types";
|
||||
import { HassioPanelInfo } from "../../src/data/hassio/supervisor";
|
||||
import { applyThemesOnElement } from "../../src/common/dom/apply_themes_on_element";
|
||||
import { fireEvent } from "../../src/common/dom/fire_event";
|
||||
import { navigate } from "../../src/common/navigate";
|
||||
import { fetchHassioAddonInfo } from "../../src/data/hassio/addon";
|
||||
import {
|
||||
fetchHassioHassOsInfo,
|
||||
fetchHassioHostInfo,
|
||||
HassioHassOSInfo,
|
||||
HassioHostInfo,
|
||||
} from "../../src/data/hassio/host";
|
||||
import {
|
||||
createHassioSession,
|
||||
fetchHassioHomeAssistantInfo,
|
||||
fetchHassioSupervisorInfo,
|
||||
HassioHomeAssistantInfo,
|
||||
HassioPanelInfo,
|
||||
HassioSupervisorInfo,
|
||||
} from "../../src/data/hassio/supervisor";
|
||||
import {
|
||||
AlertDialogParams,
|
||||
showAlertDialog,
|
||||
} from "../../src/dialogs/generic/show-dialog-box";
|
||||
import { makeDialogManager } from "../../src/dialogs/make-dialog-manager";
|
||||
import {
|
||||
HassRouterPage,
|
||||
RouterOptions,
|
||||
} from "../../src/layouts/hass-router-page";
|
||||
import { ProvideHassLitMixin } from "../../src/mixins/provide-hass-lit-mixin";
|
||||
import "../../src/resources/ha-style";
|
||||
import { HomeAssistant } from "../../src/types";
|
||||
// Don't codesplit it, that way the dashboard always loads fast.
|
||||
import "./hassio-panel";
|
||||
import { atLeastVersion } from "../../src/common/config/version";
|
||||
|
||||
@customElement("hassio-main")
|
||||
class HassioMain extends ProvideHassLitMixin(HassRouterPage) {
|
||||
@property() public hass!: HomeAssistant;
|
||||
export class HassioMain extends urlSyncMixin(ProvideHassLitMixin(LitElement)) {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property() public panel!: HassioPanelInfo;
|
||||
|
||||
@property() public narrow!: boolean;
|
||||
|
||||
protected routerOptions: RouterOptions = {
|
||||
// Hass.io has a page with tabs, so we route all non-matching routes to it.
|
||||
defaultPage: "dashboard",
|
||||
initialLoad: () => this._fetchData(),
|
||||
showLoading: true,
|
||||
routes: {
|
||||
dashboard: {
|
||||
tag: "hassio-panel",
|
||||
cache: true,
|
||||
},
|
||||
snapshots: "dashboard",
|
||||
store: "dashboard",
|
||||
system: "dashboard",
|
||||
addon: {
|
||||
tag: "hassio-addon-dashboard",
|
||||
load: () =>
|
||||
import(
|
||||
/* webpackChunkName: "hassio-addon-dashboard" */ "./addon-view/hassio-addon-dashboard"
|
||||
),
|
||||
},
|
||||
ingress: {
|
||||
tag: "hassio-ingress-view",
|
||||
load: () =>
|
||||
import(
|
||||
/* webpackChunkName: "hassio-ingress-view" */ "./ingress-view/hassio-ingress-view"
|
||||
),
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@property() private _supervisorInfo: HassioSupervisorInfo;
|
||||
|
||||
@property() private _hostInfo: HassioHostInfo;
|
||||
|
||||
@property() private _hassOsInfo?: HassioHassOSInfo;
|
||||
|
||||
@property() private _hassInfo: HassioHomeAssistantInfo;
|
||||
@property() public route?: Route;
|
||||
|
||||
protected firstUpdated(changedProps: PropertyValues) {
|
||||
super.firstUpdated(changedProps);
|
||||
|
||||
applyThemesOnElement(
|
||||
this.parentElement,
|
||||
this.hass.themes,
|
||||
this.hass.selectedTheme || this.hass.themes.default_theme
|
||||
);
|
||||
this._applyTheme();
|
||||
|
||||
this.style.setProperty(
|
||||
"--app-header-background-color",
|
||||
"var(--sidebar-background-color)"
|
||||
);
|
||||
this.style.setProperty(
|
||||
"--app-header-text-color",
|
||||
"var(--sidebar-text-color)"
|
||||
);
|
||||
this.style.setProperty(
|
||||
"--app-header-border-bottom",
|
||||
"1px solid var(--divider-color)"
|
||||
);
|
||||
|
||||
this.addEventListener("hass-api-called", (ev) => this._apiCalled(ev));
|
||||
// Paulus - March 17, 2019
|
||||
// We went to a single hass-toggle-menu event in HA 0.90. However, the
|
||||
// supervisor UI can also run under older versions of Home Assistant.
|
||||
@@ -134,148 +62,61 @@ class HassioMain extends ProvideHassLitMixin(HassRouterPage) {
|
||||
});
|
||||
});
|
||||
|
||||
makeDialogManager(this, document.body);
|
||||
makeDialogManager(this, this.shadowRoot!);
|
||||
}
|
||||
|
||||
protected updatePageEl(el) {
|
||||
// the tabs page does its own routing so needs full route.
|
||||
const route = el.nodeName === "HASSIO-PANEL" ? this.route : this.routeTail;
|
||||
protected updated(changedProps: PropertyValues) {
|
||||
super.updated(changedProps);
|
||||
const oldHass = changedProps.get("hass") as HomeAssistant | undefined;
|
||||
if (!oldHass) {
|
||||
return;
|
||||
}
|
||||
if (oldHass.themes !== this.hass.themes) {
|
||||
this._applyTheme();
|
||||
}
|
||||
}
|
||||
|
||||
if ("setProperties" in el) {
|
||||
// As long as we have Polymer pages
|
||||
(el as PolymerElement).setProperties({
|
||||
hass: this.hass,
|
||||
narrow: this.narrow,
|
||||
supervisorInfo: this._supervisorInfo,
|
||||
hostInfo: this._hostInfo,
|
||||
hassInfo: this._hassInfo,
|
||||
hassOsInfo: this._hassOsInfo,
|
||||
route,
|
||||
});
|
||||
protected render() {
|
||||
return html`
|
||||
<hassio-router
|
||||
.hass=${this.hass}
|
||||
.route=${this.route}
|
||||
.panel=${this.panel}
|
||||
.narrow=${this.narrow}
|
||||
></hassio-router>
|
||||
`;
|
||||
}
|
||||
|
||||
private _applyTheme() {
|
||||
let themeName: string;
|
||||
let options: Partial<HomeAssistant["selectedTheme"]> | undefined;
|
||||
|
||||
if (atLeastVersion(this.hass.config.version, 0, 114)) {
|
||||
themeName =
|
||||
this.hass.selectedTheme?.theme ||
|
||||
(this.hass.themes.darkMode && this.hass.themes.default_dark_theme
|
||||
? this.hass.themes.default_dark_theme!
|
||||
: this.hass.themes.default_theme);
|
||||
|
||||
options = this.hass.selectedTheme;
|
||||
if (themeName === "default" && options?.dark === undefined) {
|
||||
options = {
|
||||
...this.hass.selectedTheme,
|
||||
dark: this.hass.themes.darkMode,
|
||||
};
|
||||
}
|
||||
} else {
|
||||
el.hass = this.hass;
|
||||
el.narrow = this.narrow;
|
||||
el.supervisorInfo = this._supervisorInfo;
|
||||
el.hostInfo = this._hostInfo;
|
||||
el.hassInfo = this._hassInfo;
|
||||
el.hassOsInfo = this._hassOsInfo;
|
||||
el.route = route;
|
||||
}
|
||||
}
|
||||
|
||||
private async _fetchData() {
|
||||
if (this.panel.config && this.panel.config.ingress) {
|
||||
await this._redirectIngress(this.panel.config.ingress);
|
||||
return;
|
||||
themeName =
|
||||
((this.hass.selectedTheme as unknown) as string) ||
|
||||
this.hass.themes.default_theme;
|
||||
}
|
||||
|
||||
const [supervisorInfo, hostInfo, hassInfo] = await Promise.all([
|
||||
fetchHassioSupervisorInfo(this.hass),
|
||||
fetchHassioHostInfo(this.hass),
|
||||
fetchHassioHomeAssistantInfo(this.hass),
|
||||
]);
|
||||
this._supervisorInfo = supervisorInfo;
|
||||
this._hostInfo = hostInfo;
|
||||
this._hassInfo = hassInfo;
|
||||
|
||||
if (this._hostInfo.features && this._hostInfo.features.includes("hassos")) {
|
||||
this._hassOsInfo = await fetchHassioHassOsInfo(this.hass);
|
||||
}
|
||||
}
|
||||
|
||||
private async _redirectIngress(addonSlug: string) {
|
||||
// When we trigger a navigation, we sleep to make sure we don't
|
||||
// show the hassio dashboard before navigating away.
|
||||
const awaitAlert = async (
|
||||
alertParams: AlertDialogParams,
|
||||
action: () => void
|
||||
) => {
|
||||
await new Promise((resolve) => {
|
||||
alertParams.confirm = resolve;
|
||||
showAlertDialog(this, alertParams);
|
||||
});
|
||||
action();
|
||||
await new Promise((resolve) => setTimeout(resolve, 1000));
|
||||
};
|
||||
|
||||
const createSessionPromise = createHassioSession(this.hass).then(
|
||||
() => true,
|
||||
() => false
|
||||
applyThemesOnElement(
|
||||
this.parentElement,
|
||||
this.hass.themes,
|
||||
themeName,
|
||||
options
|
||||
);
|
||||
|
||||
let addon;
|
||||
|
||||
try {
|
||||
addon = await fetchHassioAddonInfo(this.hass, addonSlug);
|
||||
} catch (err) {
|
||||
await awaitAlert(
|
||||
{
|
||||
text: "Unable to fetch add-on info to start Ingress",
|
||||
title: "Supervisor",
|
||||
},
|
||||
() => history.back()
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (!addon.ingress_url) {
|
||||
await awaitAlert(
|
||||
{
|
||||
text: "Add-on does not support Ingress",
|
||||
title: addon.name,
|
||||
},
|
||||
() => history.back()
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (addon.state !== "started") {
|
||||
await awaitAlert(
|
||||
{
|
||||
text: "Add-on is not running. Please start it first",
|
||||
title: addon.name,
|
||||
},
|
||||
() => navigate(this, `/hassio/addon/${addon.slug}/info`, true)
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (!(await createSessionPromise)) {
|
||||
await awaitAlert(
|
||||
{
|
||||
text: "Unable to create an Ingress session",
|
||||
title: addon.name,
|
||||
},
|
||||
() => history.back()
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
location.assign(addon.ingress_url);
|
||||
// await a promise that doesn't resolve, so we show the loading screen
|
||||
// while we load the next page.
|
||||
await new Promise(() => undefined);
|
||||
}
|
||||
|
||||
private _apiCalled(ev) {
|
||||
if (!ev.detail.success) {
|
||||
return;
|
||||
}
|
||||
|
||||
let tries = 1;
|
||||
|
||||
const tryUpdate = () => {
|
||||
this._fetchData().catch(() => {
|
||||
tries += 1;
|
||||
setTimeout(tryUpdate, Math.min(tries, 5) * 1000);
|
||||
});
|
||||
};
|
||||
|
||||
tryUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -3,6 +3,7 @@ import { HassioHassOSInfo, HassioHostInfo } from "../../src/data/hassio/host";
|
||||
import {
|
||||
HassioHomeAssistantInfo,
|
||||
HassioSupervisorInfo,
|
||||
HassioInfo,
|
||||
} from "../../src/data/hassio/supervisor";
|
||||
import {
|
||||
HassRouterPage,
|
||||
@@ -26,6 +27,8 @@ class HassioPanelRouter extends HassRouterPage {
|
||||
|
||||
@property({ attribute: false }) public supervisorInfo: HassioSupervisorInfo;
|
||||
|
||||
@property({ attribute: false }) public hassioInfo!: HassioInfo;
|
||||
|
||||
@property({ attribute: false }) public hostInfo: HassioHostInfo;
|
||||
|
||||
@property({ attribute: false }) public hassInfo: HassioHomeAssistantInfo;
|
||||
@@ -54,6 +57,7 @@ class HassioPanelRouter extends HassRouterPage {
|
||||
el.route = this.route;
|
||||
el.narrow = this.narrow;
|
||||
el.supervisorInfo = this.supervisorInfo;
|
||||
el.hassioInfo = this.hassioInfo;
|
||||
el.hostInfo = this.hostInfo;
|
||||
el.hassInfo = this.hassInfo;
|
||||
el.hassOsInfo = this.hassOsInfo;
|
||||
|
@@ -1,43 +1,21 @@
|
||||
import { mdiBackupRestore, mdiCogs, mdiStore, mdiViewDashboard } from "@mdi/js";
|
||||
import {
|
||||
customElement,
|
||||
html,
|
||||
LitElement,
|
||||
property,
|
||||
TemplateResult,
|
||||
css,
|
||||
CSSResult,
|
||||
} from "lit-element";
|
||||
import { HassioHassOSInfo, HassioHostInfo } from "../../src/data/hassio/host";
|
||||
import {
|
||||
HassioHomeAssistantInfo,
|
||||
HassioSupervisorInfo,
|
||||
HassioInfo,
|
||||
} from "../../src/data/hassio/supervisor";
|
||||
import type { PageNavigation } from "../../src/layouts/hass-tabs-subpage";
|
||||
import { HomeAssistant, Route } from "../../src/types";
|
||||
import "./hassio-panel-router";
|
||||
|
||||
export const supervisorTabs: PageNavigation[] = [
|
||||
{
|
||||
name: "Dashboard",
|
||||
path: `/hassio/dashboard`,
|
||||
iconPath: mdiViewDashboard,
|
||||
},
|
||||
{
|
||||
name: "Add-on store",
|
||||
path: `/hassio/store`,
|
||||
iconPath: mdiStore,
|
||||
},
|
||||
{
|
||||
name: "Snapshots",
|
||||
path: `/hassio/snapshots`,
|
||||
iconPath: mdiBackupRestore,
|
||||
},
|
||||
{
|
||||
name: "System",
|
||||
path: `/hassio/system`,
|
||||
iconPath: mdiCogs,
|
||||
},
|
||||
];
|
||||
|
||||
@customElement("hassio-panel")
|
||||
class HassioPanel extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
@@ -48,6 +26,8 @@ class HassioPanel extends LitElement {
|
||||
|
||||
@property({ attribute: false }) public supervisorInfo!: HassioSupervisorInfo;
|
||||
|
||||
@property({ attribute: false }) public hassioInfo!: HassioInfo;
|
||||
|
||||
@property({ attribute: false }) public hostInfo!: HassioHostInfo;
|
||||
|
||||
@property({ attribute: false }) public hassInfo!: HassioHomeAssistantInfo;
|
||||
@@ -55,18 +35,32 @@ class HassioPanel extends LitElement {
|
||||
@property({ attribute: false }) public hassOsInfo!: HassioHassOSInfo;
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (!this.supervisorInfo) {
|
||||
return html``;
|
||||
}
|
||||
return html`
|
||||
<hassio-panel-router
|
||||
.route=${this.route}
|
||||
.hass=${this.hass}
|
||||
.narrow=${this.narrow}
|
||||
.supervisorInfo=${this.supervisorInfo}
|
||||
.hassioInfo=${this.hassioInfo}
|
||||
.hostInfo=${this.hostInfo}
|
||||
.hassInfo=${this.hassInfo}
|
||||
.hassOsInfo=${this.hassOsInfo}
|
||||
></hassio-panel-router>
|
||||
`;
|
||||
}
|
||||
|
||||
static get styles(): CSSResult {
|
||||
return css`
|
||||
:host {
|
||||
--app-header-background-color: var(--sidebar-background-color);
|
||||
--app-header-text-color: var(--sidebar-text-color);
|
||||
--app-header-border-bottom: 1px solid var(--divider-color);
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
|
150
hassio/src/hassio-router.ts
Normal file
150
hassio/src/hassio-router.ts
Normal file
@@ -0,0 +1,150 @@
|
||||
import {
|
||||
customElement,
|
||||
property,
|
||||
internalProperty,
|
||||
PropertyValues,
|
||||
} from "lit-element";
|
||||
import {
|
||||
fetchHassioHassOsInfo,
|
||||
fetchHassioHostInfo,
|
||||
HassioHassOSInfo,
|
||||
HassioHostInfo,
|
||||
} from "../../src/data/hassio/host";
|
||||
import {
|
||||
fetchHassioHomeAssistantInfo,
|
||||
fetchHassioSupervisorInfo,
|
||||
fetchHassioInfo,
|
||||
HassioHomeAssistantInfo,
|
||||
HassioInfo,
|
||||
HassioPanelInfo,
|
||||
HassioSupervisorInfo,
|
||||
} from "../../src/data/hassio/supervisor";
|
||||
import {
|
||||
HassRouterPage,
|
||||
RouterOptions,
|
||||
} from "../../src/layouts/hass-router-page";
|
||||
import "../../src/resources/ha-style";
|
||||
import { HomeAssistant } from "../../src/types";
|
||||
// Don't codesplit it, that way the dashboard always loads fast.
|
||||
import "./hassio-panel";
|
||||
|
||||
@customElement("hassio-router")
|
||||
class HassioRouter extends HassRouterPage {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property() public panel!: HassioPanelInfo;
|
||||
|
||||
@property() public narrow!: boolean;
|
||||
|
||||
protected routerOptions: RouterOptions = {
|
||||
// Hass.io has a page with tabs, so we route all non-matching routes to it.
|
||||
defaultPage: "dashboard",
|
||||
initialLoad: () => this._fetchData(),
|
||||
showLoading: true,
|
||||
routes: {
|
||||
dashboard: {
|
||||
tag: "hassio-panel",
|
||||
cache: true,
|
||||
},
|
||||
snapshots: "dashboard",
|
||||
store: "dashboard",
|
||||
system: "dashboard",
|
||||
addon: {
|
||||
tag: "hassio-addon-dashboard",
|
||||
load: () =>
|
||||
import(
|
||||
/* webpackChunkName: "hassio-addon-dashboard" */ "./addon-view/hassio-addon-dashboard"
|
||||
),
|
||||
},
|
||||
ingress: {
|
||||
tag: "hassio-ingress-view",
|
||||
load: () =>
|
||||
import(
|
||||
/* webpackChunkName: "hassio-ingress-view" */ "./ingress-view/hassio-ingress-view"
|
||||
),
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@internalProperty() private _supervisorInfo: HassioSupervisorInfo;
|
||||
|
||||
@internalProperty() private _hostInfo: HassioHostInfo;
|
||||
|
||||
@internalProperty() private _hassioInfo?: HassioInfo;
|
||||
|
||||
@internalProperty() private _hassOsInfo?: HassioHassOSInfo;
|
||||
|
||||
@internalProperty() private _hassInfo: HassioHomeAssistantInfo;
|
||||
|
||||
protected firstUpdated(changedProps: PropertyValues) {
|
||||
super.firstUpdated(changedProps);
|
||||
this.addEventListener("hass-api-called", (ev) => this._apiCalled(ev));
|
||||
}
|
||||
|
||||
protected updatePageEl(el) {
|
||||
// the tabs page does its own routing so needs full route.
|
||||
const route = el.nodeName === "HASSIO-PANEL" ? this.route : this.routeTail;
|
||||
|
||||
el.hass = this.hass;
|
||||
el.narrow = this.narrow;
|
||||
el.supervisorInfo = this._supervisorInfo;
|
||||
el.hassioInfo = this._hassioInfo;
|
||||
el.hostInfo = this._hostInfo;
|
||||
el.hassInfo = this._hassInfo;
|
||||
el.hassOsInfo = this._hassOsInfo;
|
||||
el.route = route;
|
||||
|
||||
if (el.localName === "hassio-ingress-view") {
|
||||
el.ingressPanel = this.panel.config && this.panel.config.ingress;
|
||||
}
|
||||
}
|
||||
|
||||
private async _fetchData() {
|
||||
if (this.panel.config && this.panel.config.ingress) {
|
||||
this._redirectIngress(this.panel.config.ingress);
|
||||
return;
|
||||
}
|
||||
|
||||
const [supervisorInfo, hostInfo, hassInfo, hassioInfo] = await Promise.all([
|
||||
fetchHassioSupervisorInfo(this.hass),
|
||||
fetchHassioHostInfo(this.hass),
|
||||
fetchHassioHomeAssistantInfo(this.hass),
|
||||
fetchHassioInfo(this.hass),
|
||||
]);
|
||||
this._supervisorInfo = supervisorInfo;
|
||||
this._hassioInfo = hassioInfo;
|
||||
this._hostInfo = hostInfo;
|
||||
this._hassInfo = hassInfo;
|
||||
|
||||
if (this._hostInfo.features && this._hostInfo.features.includes("hassos")) {
|
||||
this._hassOsInfo = await fetchHassioHassOsInfo(this.hass);
|
||||
}
|
||||
}
|
||||
|
||||
private _redirectIngress(addonSlug: string) {
|
||||
this.route = { prefix: "/hassio", path: `/ingress/${addonSlug}` };
|
||||
}
|
||||
|
||||
private _apiCalled(ev) {
|
||||
if (!ev.detail.success) {
|
||||
return;
|
||||
}
|
||||
|
||||
let tries = 1;
|
||||
|
||||
const tryUpdate = () => {
|
||||
this._fetchData().catch(() => {
|
||||
tries += 1;
|
||||
setTimeout(tryUpdate, Math.min(tries, 5) * 1000);
|
||||
});
|
||||
};
|
||||
|
||||
tryUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"hassio-router": HassioRouter;
|
||||
}
|
||||
}
|
25
hassio/src/hassio-tabs.ts
Normal file
25
hassio/src/hassio-tabs.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import { mdiBackupRestore, mdiCogs, mdiStore, mdiViewDashboard } from "@mdi/js";
|
||||
import type { PageNavigation } from "../../src/layouts/hass-tabs-subpage";
|
||||
|
||||
export const supervisorTabs: PageNavigation[] = [
|
||||
{
|
||||
name: "Dashboard",
|
||||
path: `/hassio/dashboard`,
|
||||
iconPath: mdiViewDashboard,
|
||||
},
|
||||
{
|
||||
name: "Add-on store",
|
||||
path: `/hassio/store`,
|
||||
iconPath: mdiStore,
|
||||
},
|
||||
{
|
||||
name: "Snapshots",
|
||||
path: `/hassio/snapshots`,
|
||||
iconPath: mdiBackupRestore,
|
||||
},
|
||||
{
|
||||
name: "System",
|
||||
path: `/hassio/system`,
|
||||
iconPath: mdiCogs,
|
||||
},
|
||||
];
|
@@ -5,6 +5,7 @@ import {
|
||||
html,
|
||||
LitElement,
|
||||
property,
|
||||
internalProperty,
|
||||
PropertyValues,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
@@ -16,29 +17,56 @@ import { createHassioSession } from "../../../src/data/hassio/supervisor";
|
||||
import "../../../src/layouts/hass-loading-screen";
|
||||
import "../../../src/layouts/hass-subpage";
|
||||
import { HomeAssistant, Route } from "../../../src/types";
|
||||
import { showAlertDialog } from "../../../src/dialogs/generic/show-dialog-box";
|
||||
import { navigate } from "../../../src/common/navigate";
|
||||
import { mdiMenu } from "@mdi/js";
|
||||
import { fireEvent } from "../../../src/common/dom/fire_event";
|
||||
|
||||
@customElement("hassio-ingress-view")
|
||||
class HassioIngressView extends LitElement {
|
||||
@property() public hass!: HomeAssistant;
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property() public route!: Route;
|
||||
|
||||
@property() private _addon?: HassioAddonDetails;
|
||||
@property() public ingressPanel = false;
|
||||
|
||||
@internalProperty() private _addon?: HassioAddonDetails;
|
||||
|
||||
@property({ type: Boolean })
|
||||
public narrow = false;
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (!this._addon) {
|
||||
return html` <hass-loading-screen></hass-loading-screen> `;
|
||||
}
|
||||
|
||||
return html`
|
||||
<hass-subpage .header=${this._addon.name} hassio>
|
||||
<iframe src=${this._addon.ingress_url}></iframe>
|
||||
</hass-subpage>
|
||||
`;
|
||||
const iframe = html`<iframe src=${this._addon.ingress_url!}></iframe>`;
|
||||
|
||||
if (!this.ingressPanel) {
|
||||
return html`<hass-subpage
|
||||
.header=${this._addon.name}
|
||||
.narrow=${this.narrow}
|
||||
>
|
||||
${iframe}
|
||||
</hass-subpage>`;
|
||||
}
|
||||
|
||||
return html`${this.narrow || this.hass.dockedSidebar === "always_hidden"
|
||||
? html`<div class="header">
|
||||
<mwc-icon-button
|
||||
aria-label=${this.hass.localize("ui.sidebar.sidebar_toggle")}
|
||||
@click=${this._toggleMenu}
|
||||
>
|
||||
<ha-svg-icon path=${mdiMenu}></ha-svg-icon>
|
||||
</mwc-icon-button>
|
||||
<div class="main-title">${this._addon.name}</div>
|
||||
</div>
|
||||
${iframe}`
|
||||
: iframe}`;
|
||||
}
|
||||
|
||||
protected updated(changedProps: PropertyValues) {
|
||||
super.firstUpdated(changedProps);
|
||||
super.updated(changedProps);
|
||||
|
||||
if (!changedProps.has("route")) {
|
||||
return;
|
||||
@@ -55,27 +83,56 @@ class HassioIngressView extends LitElement {
|
||||
}
|
||||
|
||||
private async _fetchData(addonSlug: string) {
|
||||
const createSessionPromise = createHassioSession(this.hass).then(
|
||||
() => true,
|
||||
() => false
|
||||
);
|
||||
|
||||
let addon;
|
||||
|
||||
try {
|
||||
const [addon] = await Promise.all([
|
||||
fetchHassioAddonInfo(this.hass, addonSlug).catch(() => {
|
||||
throw new Error("Failed to fetch add-on info");
|
||||
}),
|
||||
createHassioSession(this.hass).catch(() => {
|
||||
throw new Error("Failed to create an ingress session");
|
||||
}),
|
||||
]);
|
||||
|
||||
if (!addon.ingress) {
|
||||
throw new Error("This add-on does not support ingress");
|
||||
}
|
||||
|
||||
this._addon = addon;
|
||||
addon = await fetchHassioAddonInfo(this.hass, addonSlug);
|
||||
} catch (err) {
|
||||
// eslint-disable-next-line
|
||||
console.error(err);
|
||||
alert(err.message || "Unknown error starting ingress.");
|
||||
await showAlertDialog(this, {
|
||||
text: "Unable to fetch add-on info to start Ingress",
|
||||
title: "Supervisor",
|
||||
});
|
||||
history.back();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!addon.ingress_url) {
|
||||
await showAlertDialog(this, {
|
||||
text: "Add-on does not support Ingress",
|
||||
title: addon.name,
|
||||
});
|
||||
history.back();
|
||||
return;
|
||||
}
|
||||
|
||||
if (addon.state !== "started") {
|
||||
await showAlertDialog(this, {
|
||||
text: "Add-on is not running. Please start it first",
|
||||
title: addon.name,
|
||||
});
|
||||
navigate(this, `/hassio/addon/${addon.slug}/info`, true);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!(await createSessionPromise)) {
|
||||
await showAlertDialog(this, {
|
||||
text: "Unable to create an Ingress session",
|
||||
title: addon.name,
|
||||
});
|
||||
history.back();
|
||||
return;
|
||||
}
|
||||
|
||||
this._addon = addon;
|
||||
}
|
||||
|
||||
private _toggleMenu(): void {
|
||||
fireEvent(this, "hass-toggle-menu");
|
||||
}
|
||||
|
||||
static get styles(): CSSResult {
|
||||
@@ -86,6 +143,41 @@ class HassioIngressView extends LitElement {
|
||||
height: 100%;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
.header + iframe {
|
||||
height: calc(100% - 40px);
|
||||
}
|
||||
|
||||
.header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 16px;
|
||||
height: 40px;
|
||||
padding: 0 16px;
|
||||
pointer-events: none;
|
||||
background-color: var(--app-header-background-color);
|
||||
font-weight: 400;
|
||||
color: var(--app-header-text-color, white);
|
||||
border-bottom: var(--app-header-border-bottom, none);
|
||||
box-sizing: border-box;
|
||||
--mdc-icon-size: 20px;
|
||||
}
|
||||
|
||||
.main-title {
|
||||
margin: 0 0 0 24px;
|
||||
line-height: 20px;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
mwc-icon-button {
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
hass-subpage {
|
||||
--app-header-background-color: var(--sidebar-background-color);
|
||||
--app-header-text-color: var(--sidebar-text-color);
|
||||
--app-header-border-bottom: 1px solid var(--divider-color);
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
@@ -13,14 +13,17 @@ import {
|
||||
CSSResultArray,
|
||||
customElement,
|
||||
html,
|
||||
internalProperty,
|
||||
LitElement,
|
||||
property,
|
||||
PropertyValues,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import { fireEvent } from "../../../src/common/dom/fire_event";
|
||||
import "../../../src/components/buttons/ha-progress-button";
|
||||
import "../../../src/components/ha-card";
|
||||
import "../../../src/components/ha-svg-icon";
|
||||
import { extractApiErrorMessage } from "../../../src/data/hassio/common";
|
||||
import {
|
||||
createHassioFullSnapshot,
|
||||
createHassioPartialSnapshot,
|
||||
@@ -37,7 +40,7 @@ import { haStyle } from "../../../src/resources/styles";
|
||||
import { HomeAssistant, Route } from "../../../src/types";
|
||||
import "../components/hassio-card-content";
|
||||
import { showHassioSnapshotDialog } from "../dialogs/snapshot/show-dialog-hassio-snapshot";
|
||||
import { supervisorTabs } from "../hassio-panel";
|
||||
import { supervisorTabs } from "../hassio-tabs";
|
||||
import { hassioStyle } from "../resources/hassio-style";
|
||||
|
||||
interface CheckboxItem {
|
||||
@@ -56,19 +59,19 @@ class HassioSnapshots extends LitElement {
|
||||
|
||||
@property({ attribute: false }) public supervisorInfo!: HassioSupervisorInfo;
|
||||
|
||||
@property() private _snapshotName = "";
|
||||
@internalProperty() private _snapshotName = "";
|
||||
|
||||
@property() private _snapshotPassword = "";
|
||||
@internalProperty() private _snapshotPassword = "";
|
||||
|
||||
@property() private _snapshotHasPassword = false;
|
||||
@internalProperty() private _snapshotHasPassword = false;
|
||||
|
||||
@property() private _snapshotType: HassioSnapshot["type"] = "full";
|
||||
@internalProperty() private _snapshotType: HassioSnapshot["type"] = "full";
|
||||
|
||||
@property() private _snapshots?: HassioSnapshot[] = [];
|
||||
@internalProperty() private _snapshots?: HassioSnapshot[] = [];
|
||||
|
||||
@property() private _addonList: CheckboxItem[] = [];
|
||||
@internalProperty() private _addonList: CheckboxItem[] = [];
|
||||
|
||||
@property() private _folderList: CheckboxItem[] = [
|
||||
@internalProperty() private _folderList: CheckboxItem[] = [
|
||||
{
|
||||
slug: "homeassistant",
|
||||
name: "Home Assistant configuration",
|
||||
@@ -76,12 +79,11 @@ class HassioSnapshots extends LitElement {
|
||||
},
|
||||
{ slug: "ssl", name: "SSL", checked: true },
|
||||
{ slug: "share", name: "Share", checked: true },
|
||||
{ slug: "media", name: "Media", checked: true },
|
||||
{ slug: "addons/local", name: "Local add-ons", checked: true },
|
||||
];
|
||||
|
||||
@property() private _creatingSnapshot = false;
|
||||
|
||||
@property() private _error = "";
|
||||
@internalProperty() private _error = "";
|
||||
|
||||
public async refreshData() {
|
||||
await reloadHassioSnapshots(this.hass);
|
||||
@@ -191,12 +193,9 @@ class HassioSnapshots extends LitElement {
|
||||
: undefined}
|
||||
</div>
|
||||
<div class="card-actions">
|
||||
<mwc-button
|
||||
.disabled=${this._creatingSnapshot}
|
||||
@click=${this._createSnapshot}
|
||||
>
|
||||
<ha-progress-button @click=${this._createSnapshot}>
|
||||
Create
|
||||
</mwc-button>
|
||||
</ha-progress-button>
|
||||
</div>
|
||||
</ha-card>
|
||||
</div>
|
||||
@@ -229,7 +228,7 @@ class HassioSnapshots extends LitElement {
|
||||
.icon=${snapshot.type === "full"
|
||||
? mdiPackageVariantClosed
|
||||
: mdiPackageVariant}
|
||||
.icon-class="snapshot"
|
||||
icon-class="snapshot"
|
||||
></hassio-card-content>
|
||||
</div>
|
||||
</ha-card>
|
||||
@@ -243,7 +242,7 @@ class HassioSnapshots extends LitElement {
|
||||
|
||||
protected firstUpdated(changedProps: PropertyValues) {
|
||||
super.firstUpdated(changedProps);
|
||||
this._updateSnapshots();
|
||||
this.refreshData();
|
||||
}
|
||||
|
||||
protected updated(changedProps: PropertyValues) {
|
||||
@@ -292,17 +291,20 @@ class HassioSnapshots extends LitElement {
|
||||
this._snapshots = await fetchHassioSnapshots(this.hass);
|
||||
this._snapshots.sort((a, b) => (a.date < b.date ? 1 : -1));
|
||||
} catch (err) {
|
||||
this._error = err.message;
|
||||
this._error = extractApiErrorMessage(err);
|
||||
}
|
||||
}
|
||||
|
||||
private async _createSnapshot() {
|
||||
private async _createSnapshot(ev: CustomEvent): Promise<void> {
|
||||
const button = ev.currentTarget as any;
|
||||
button.progress = true;
|
||||
|
||||
this._error = "";
|
||||
if (this._snapshotHasPassword && !this._snapshotPassword.length) {
|
||||
this._error = "Please enter a password.";
|
||||
button.progress = false;
|
||||
return;
|
||||
}
|
||||
this._creatingSnapshot = true;
|
||||
await this.updateComplete;
|
||||
|
||||
const name =
|
||||
@@ -342,10 +344,9 @@ class HassioSnapshots extends LitElement {
|
||||
this._updateSnapshots();
|
||||
fireEvent(this, "hass-api-called", { success: true, response: null });
|
||||
} catch (err) {
|
||||
this._error = err.message;
|
||||
} finally {
|
||||
this._creatingSnapshot = false;
|
||||
this._error = extractApiErrorMessage(err);
|
||||
}
|
||||
button.progress = false;
|
||||
}
|
||||
|
||||
private _computeDetails(snapshot: HassioSnapshot) {
|
||||
|
@@ -1,17 +1,31 @@
|
||||
import "@material/mwc-button";
|
||||
import { ActionDetail } from "@material/mwc-list/mwc-list-foundation";
|
||||
import "@material/mwc-list/mwc-list-item";
|
||||
import { mdiDotsVertical } from "@mdi/js";
|
||||
import { safeDump } from "js-yaml";
|
||||
import {
|
||||
css,
|
||||
CSSResult,
|
||||
customElement,
|
||||
html,
|
||||
internalProperty,
|
||||
LitElement,
|
||||
property,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import "../../../src/components/buttons/ha-call-api-button";
|
||||
import memoizeOne from "memoize-one";
|
||||
import "../../../src/components/buttons/ha-progress-button";
|
||||
import "../../../src/components/ha-button-menu";
|
||||
import "../../../src/components/ha-card";
|
||||
import "../../../src/components/ha-settings-row";
|
||||
import {
|
||||
extractApiErrorMessage,
|
||||
ignoredStatusCodes,
|
||||
} from "../../../src/data/hassio/common";
|
||||
import { fetchHassioHardwareInfo } from "../../../src/data/hassio/hardware";
|
||||
import {
|
||||
changeHostOptions,
|
||||
configSyncOS,
|
||||
fetchHassioHostInfo,
|
||||
HassioHassOSInfo,
|
||||
HassioHostInfo as HassioHostInfoType,
|
||||
@@ -19,6 +33,11 @@ import {
|
||||
shutdownHost,
|
||||
updateOS,
|
||||
} from "../../../src/data/hassio/host";
|
||||
import {
|
||||
fetchNetworkInfo,
|
||||
NetworkInfo,
|
||||
} from "../../../src/data/hassio/network";
|
||||
import { HassioInfo } from "../../../src/data/hassio/supervisor";
|
||||
import {
|
||||
showAlertDialog,
|
||||
showConfirmationDialog,
|
||||
@@ -27,168 +46,191 @@ import {
|
||||
import { haStyle } from "../../../src/resources/styles";
|
||||
import { HomeAssistant } from "../../../src/types";
|
||||
import { showHassioMarkdownDialog } from "../dialogs/markdown/show-dialog-hassio-markdown";
|
||||
import { showNetworkDialog } from "../dialogs/network/show-dialog-network";
|
||||
import { hassioStyle } from "../resources/hassio-style";
|
||||
|
||||
@customElement("hassio-host-info")
|
||||
class HassioHostInfo extends LitElement {
|
||||
@property() public hass!: HomeAssistant;
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property() public hostInfo!: HassioHostInfoType;
|
||||
|
||||
@property() public hassOsInfo!: HassioHassOSInfo;
|
||||
@property({ attribute: false }) public hassioInfo!: HassioInfo;
|
||||
|
||||
@property() private _errors?: string;
|
||||
@property({ attribute: false }) public hassOsInfo!: HassioHassOSInfo;
|
||||
|
||||
public render(): TemplateResult | void {
|
||||
@internalProperty() public _networkInfo?: NetworkInfo;
|
||||
|
||||
protected render(): TemplateResult | void {
|
||||
const primaryIpAddress = this.hostInfo.features.includes("network")
|
||||
? this._primaryIpAddress(this._networkInfo!)
|
||||
: "";
|
||||
return html`
|
||||
<ha-card>
|
||||
<ha-card header="Host System">
|
||||
<div class="card-content">
|
||||
<h2>Host system</h2>
|
||||
<table class="info">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Hostname</td>
|
||||
<td>${this.hostInfo.hostname}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>System</td>
|
||||
<td>${this.hostInfo.operating_system}</td>
|
||||
</tr>
|
||||
${this.hostInfo.deployment
|
||||
? html`
|
||||
<tr>
|
||||
<td>Deployment</td>
|
||||
<td>${this.hostInfo.deployment}</td>
|
||||
</tr>
|
||||
`
|
||||
: ""}
|
||||
</tbody>
|
||||
</table>
|
||||
<mwc-button raised @click=${this._showHardware} class="info">
|
||||
Hardware
|
||||
</mwc-button>
|
||||
${this.hostInfo.features.includes("hostname")
|
||||
? html`
|
||||
? html`<ha-settings-row>
|
||||
<span slot="heading">
|
||||
Hostname
|
||||
</span>
|
||||
<span slot="description">
|
||||
${this.hostInfo.hostname}
|
||||
</span>
|
||||
<mwc-button
|
||||
raised
|
||||
title="Change the hostname"
|
||||
label="Change"
|
||||
@click=${this._changeHostnameClicked}
|
||||
class="info"
|
||||
>
|
||||
Change hostname
|
||||
</mwc-button>
|
||||
`
|
||||
</ha-settings-row>`
|
||||
: ""}
|
||||
${this._errors
|
||||
? html` <div class="errors">Error: ${this._errors}</div> `
|
||||
${this.hostInfo.features.includes("network")
|
||||
? html` <ha-settings-row>
|
||||
<span slot="heading">
|
||||
IP address
|
||||
</span>
|
||||
<span slot="description">
|
||||
${primaryIpAddress}
|
||||
</span>
|
||||
<mwc-button
|
||||
title="Change the network"
|
||||
label="Change"
|
||||
@click=${this._changeNetworkClicked}
|
||||
>
|
||||
</mwc-button>
|
||||
</ha-settings-row>`
|
||||
: ""}
|
||||
|
||||
<ha-settings-row>
|
||||
<span slot="heading">
|
||||
Operating system
|
||||
</span>
|
||||
<span slot="description">
|
||||
${this.hostInfo.operating_system}
|
||||
</span>
|
||||
${this.hostInfo.version !== this.hostInfo.version_latest &&
|
||||
this.hostInfo.features.includes("hassos")
|
||||
? html`
|
||||
<ha-progress-button
|
||||
title="Update the host OS"
|
||||
@click=${this._osUpdate}
|
||||
>
|
||||
Update
|
||||
</ha-progress-button>
|
||||
`
|
||||
: ""}
|
||||
</ha-settings-row>
|
||||
${!this.hostInfo.features.includes("hassos")
|
||||
? html`<ha-settings-row>
|
||||
<span slot="heading">
|
||||
Docker version
|
||||
</span>
|
||||
<span slot="description">
|
||||
${this.hassioInfo.docker}
|
||||
</span>
|
||||
</ha-settings-row>`
|
||||
: ""}
|
||||
${this.hostInfo.deployment
|
||||
? html`<ha-settings-row>
|
||||
<span slot="heading">
|
||||
Deployment
|
||||
</span>
|
||||
<span slot="description">
|
||||
${this.hostInfo.deployment}
|
||||
</span>
|
||||
</ha-settings-row>`
|
||||
: ""}
|
||||
</div>
|
||||
<div class="card-actions">
|
||||
${this.hostInfo.features.includes("reboot")
|
||||
? html`
|
||||
<mwc-button class="warning" @click=${this._rebootHost}
|
||||
>Reboot</mwc-button
|
||||
<ha-progress-button
|
||||
title="Reboot the host OS"
|
||||
class="warning"
|
||||
@click=${this._hostReboot}
|
||||
>
|
||||
Reboot
|
||||
</ha-progress-button>
|
||||
`
|
||||
: ""}
|
||||
${this.hostInfo.features.includes("shutdown")
|
||||
? html`
|
||||
<mwc-button class="warning" @click=${this._shutdownHost}
|
||||
>Shutdown</mwc-button
|
||||
>
|
||||
`
|
||||
: ""}
|
||||
${this.hostInfo.features.includes("hassos")
|
||||
? html`
|
||||
<ha-call-api-button
|
||||
<ha-progress-button
|
||||
title="Shutdown the host OS"
|
||||
class="warning"
|
||||
.hass=${this.hass}
|
||||
path="hassio/os/config/sync"
|
||||
title="Load HassOS configs or updates from USB"
|
||||
>Import from USB</ha-call-api-button
|
||||
@click=${this._hostShutdown}
|
||||
>
|
||||
Shutdown
|
||||
</ha-progress-button>
|
||||
`
|
||||
: ""}
|
||||
${this.hostInfo.version !== this.hostInfo.version_latest
|
||||
? html` <mwc-button @click=${this._updateOS}>Update</mwc-button> `
|
||||
: ""}
|
||||
|
||||
<ha-button-menu
|
||||
corner="BOTTOM_START"
|
||||
@action=${this._handleMenuAction}
|
||||
>
|
||||
<mwc-icon-button slot="trigger">
|
||||
<ha-svg-icon .path=${mdiDotsVertical}></ha-svg-icon>
|
||||
</mwc-icon-button>
|
||||
<mwc-list-item title="Show a list of hardware">
|
||||
Hardware
|
||||
</mwc-list-item>
|
||||
${this.hostInfo.features.includes("hassos")
|
||||
? html`<mwc-list-item
|
||||
title="Load HassOS configs or updates from USB"
|
||||
>
|
||||
Import from USB
|
||||
</mwc-list-item>`
|
||||
: ""}
|
||||
</ha-button-menu>
|
||||
</div>
|
||||
</ha-card>
|
||||
`;
|
||||
}
|
||||
|
||||
static get styles(): CSSResult[] {
|
||||
return [
|
||||
haStyle,
|
||||
hassioStyle,
|
||||
css`
|
||||
ha-card {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
.card-content {
|
||||
color: var(--primary-text-color);
|
||||
box-sizing: border-box;
|
||||
height: calc(100% - 47px);
|
||||
}
|
||||
.info {
|
||||
width: 100%;
|
||||
}
|
||||
.info td:nth-child(2) {
|
||||
text-align: right;
|
||||
}
|
||||
.errors {
|
||||
color: var(--google-red-500);
|
||||
margin-top: 16px;
|
||||
}
|
||||
mwc-button.info {
|
||||
max-width: calc(50% - 12px);
|
||||
}
|
||||
table.info {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.warning {
|
||||
--mdc-theme-primary: var(--google-red-500);
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
||||
protected firstUpdated(): void {
|
||||
this.addEventListener("hass-api-called", (ev) => this._apiCalled(ev));
|
||||
this._loadData();
|
||||
}
|
||||
|
||||
private _apiCalled(ev): void {
|
||||
if (ev.detail.success) {
|
||||
this._errors = undefined;
|
||||
return;
|
||||
private _primaryIpAddress = memoizeOne((network_info: NetworkInfo) => {
|
||||
if (!network_info) {
|
||||
return "";
|
||||
}
|
||||
return Object.keys(network_info?.interfaces)
|
||||
.map((device) => network_info.interfaces[device])
|
||||
.find((device) => device.primary)?.ip_address;
|
||||
});
|
||||
|
||||
const response = ev.detail.response;
|
||||
|
||||
this._errors =
|
||||
typeof response.body === "object"
|
||||
? response.body.message || "Unknown error"
|
||||
: response.body;
|
||||
private async _handleMenuAction(ev: CustomEvent<ActionDetail>) {
|
||||
switch (ev.detail.index) {
|
||||
case 0:
|
||||
await this._showHardware();
|
||||
break;
|
||||
case 1:
|
||||
await this._importFromUSB();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private async _showHardware(): Promise<void> {
|
||||
try {
|
||||
const content = this._objectToMarkdown(
|
||||
await fetchHassioHardwareInfo(this.hass)
|
||||
);
|
||||
const content = await fetchHassioHardwareInfo(this.hass);
|
||||
showHassioMarkdownDialog(this, {
|
||||
title: "Hardware",
|
||||
content,
|
||||
content: `<pre>${safeDump(content, { indent: 2 })}</pre>`,
|
||||
});
|
||||
} catch (err) {
|
||||
showHassioMarkdownDialog(this, {
|
||||
title: "Hardware",
|
||||
content: "Error getting hardware info",
|
||||
showAlertDialog(this, {
|
||||
title: "Failed to get Hardware list",
|
||||
text: extractApiErrorMessage(err),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private async _rebootHost(): Promise<void> {
|
||||
private async _hostReboot(ev: CustomEvent): Promise<void> {
|
||||
const button = ev.currentTarget as any;
|
||||
button.progress = true;
|
||||
|
||||
const confirmed = await showConfirmationDialog(this, {
|
||||
title: "Reboot",
|
||||
text: "Are you sure you want to reboot the host?",
|
||||
@@ -197,20 +239,28 @@ class HassioHostInfo extends LitElement {
|
||||
});
|
||||
|
||||
if (!confirmed) {
|
||||
button.progress = false;
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await rebootHost(this.hass);
|
||||
} catch (err) {
|
||||
showAlertDialog(this, {
|
||||
title: "Failed to reboot",
|
||||
text: err.body.message,
|
||||
});
|
||||
// Ignore connection errors, these are all expected
|
||||
if (err.status_code && !ignoredStatusCodes.has(err.status_code)) {
|
||||
showAlertDialog(this, {
|
||||
title: "Failed to reboot",
|
||||
text: extractApiErrorMessage(err),
|
||||
});
|
||||
}
|
||||
}
|
||||
button.progress = false;
|
||||
}
|
||||
|
||||
private async _shutdownHost(): Promise<void> {
|
||||
private async _hostShutdown(ev: CustomEvent): Promise<void> {
|
||||
const button = ev.currentTarget as any;
|
||||
button.progress = true;
|
||||
|
||||
const confirmed = await showConfirmationDialog(this, {
|
||||
title: "Shutdown",
|
||||
text: "Are you sure you want to shutdown the host?",
|
||||
@@ -219,20 +269,28 @@ class HassioHostInfo extends LitElement {
|
||||
});
|
||||
|
||||
if (!confirmed) {
|
||||
button.progress = false;
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await shutdownHost(this.hass);
|
||||
} catch (err) {
|
||||
showAlertDialog(this, {
|
||||
title: "Failed to shutdown",
|
||||
text: err.body.message,
|
||||
});
|
||||
// Ignore connection errors, these are all expected
|
||||
if (err.status_code && !ignoredStatusCodes.has(err.status_code)) {
|
||||
showAlertDialog(this, {
|
||||
title: "Failed to shutdown",
|
||||
text: extractApiErrorMessage(err),
|
||||
});
|
||||
}
|
||||
}
|
||||
button.progress = false;
|
||||
}
|
||||
|
||||
private async _updateOS(): Promise<void> {
|
||||
private async _osUpdate(ev: CustomEvent): Promise<void> {
|
||||
const button = ev.currentTarget as any;
|
||||
button.progress = true;
|
||||
|
||||
const confirmed = await showConfirmationDialog(this, {
|
||||
title: "Update",
|
||||
text: "Are you sure you want to update the OS?",
|
||||
@@ -241,6 +299,7 @@ class HassioHostInfo extends LitElement {
|
||||
});
|
||||
|
||||
if (!confirmed) {
|
||||
button.progress = false;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -249,30 +308,17 @@ class HassioHostInfo extends LitElement {
|
||||
} catch (err) {
|
||||
showAlertDialog(this, {
|
||||
title: "Failed to update",
|
||||
text: err.body.message,
|
||||
text: extractApiErrorMessage(err),
|
||||
});
|
||||
}
|
||||
button.progress = false;
|
||||
}
|
||||
|
||||
private _objectToMarkdown(obj, indent = ""): string {
|
||||
let data = "";
|
||||
Object.keys(obj).forEach((key) => {
|
||||
if (typeof obj[key] !== "object") {
|
||||
data += `${indent}- ${key}: ${obj[key]}\n`;
|
||||
} else {
|
||||
data += `${indent}- ${key}:\n`;
|
||||
if (Array.isArray(obj[key])) {
|
||||
if (obj[key].length) {
|
||||
data +=
|
||||
`${indent} - ` + obj[key].join(`\n${indent} - `) + "\n";
|
||||
}
|
||||
} else {
|
||||
data += this._objectToMarkdown(obj[key], ` ${indent}`);
|
||||
}
|
||||
}
|
||||
private async _changeNetworkClicked(): Promise<void> {
|
||||
showNetworkDialog(this, {
|
||||
network: this._networkInfo!,
|
||||
loadData: () => this._loadData(),
|
||||
});
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
private async _changeHostnameClicked(): Promise<void> {
|
||||
@@ -291,11 +337,83 @@ class HassioHostInfo extends LitElement {
|
||||
} catch (err) {
|
||||
showAlertDialog(this, {
|
||||
title: "Setting hostname failed",
|
||||
text: err.body.message,
|
||||
text: extractApiErrorMessage(err),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async _importFromUSB(): Promise<void> {
|
||||
try {
|
||||
await configSyncOS(this.hass);
|
||||
this.hostInfo = await fetchHassioHostInfo(this.hass);
|
||||
} catch (err) {
|
||||
showAlertDialog(this, {
|
||||
title: "Failed to import from USB",
|
||||
text: extractApiErrorMessage(err),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private async _loadData(): Promise<void> {
|
||||
this._networkInfo = await fetchNetworkInfo(this.hass);
|
||||
}
|
||||
|
||||
static get styles(): CSSResult[] {
|
||||
return [
|
||||
haStyle,
|
||||
hassioStyle,
|
||||
css`
|
||||
ha-card {
|
||||
height: 100%;
|
||||
justify-content: space-between;
|
||||
flex-direction: column;
|
||||
display: flex;
|
||||
}
|
||||
.card-actions {
|
||||
height: 48px;
|
||||
border-top: none;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
ha-settings-row {
|
||||
padding: 0;
|
||||
height: 54px;
|
||||
width: 100%;
|
||||
}
|
||||
ha-settings-row[three-line] {
|
||||
height: 74px;
|
||||
}
|
||||
ha-settings-row > span[slot="description"] {
|
||||
white-space: normal;
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
|
||||
.warning {
|
||||
--mdc-theme-primary: var(--error-color);
|
||||
}
|
||||
|
||||
ha-button-menu {
|
||||
color: var(--secondary-text-color);
|
||||
--mdc-menu-min-width: 200px;
|
||||
}
|
||||
@media (min-width: 563px) {
|
||||
paper-listbox {
|
||||
max-height: 150px;
|
||||
overflow: auto;
|
||||
}
|
||||
}
|
||||
paper-item {
|
||||
cursor: pointer;
|
||||
min-height: 35px;
|
||||
}
|
||||
mwc-list-item ha-svg-icon {
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
|
@@ -1,4 +1,3 @@
|
||||
import "@material/mwc-button";
|
||||
import {
|
||||
css,
|
||||
CSSResult,
|
||||
@@ -8,94 +7,262 @@ import {
|
||||
property,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import { fireEvent } from "../../../src/common/dom/fire_event";
|
||||
import "../../../src/components/buttons/ha-call-api-button";
|
||||
import "../../../src/components/buttons/ha-progress-button";
|
||||
import "../../../src/components/ha-card";
|
||||
import "../../../src/components/ha-settings-row";
|
||||
import "../../../src/components/ha-switch";
|
||||
import { HassioHostInfo as HassioHostInfoType } from "../../../src/data/hassio/host";
|
||||
import {
|
||||
HassioSupervisorInfo as HassioSupervisorInfoType,
|
||||
reloadSupervisor,
|
||||
setSupervisorOption,
|
||||
SupervisorOptions,
|
||||
updateSupervisor,
|
||||
fetchHassioSupervisorInfo,
|
||||
} from "../../../src/data/hassio/supervisor";
|
||||
import { showConfirmationDialog } from "../../../src/dialogs/generic/show-dialog-box";
|
||||
import {
|
||||
showAlertDialog,
|
||||
showConfirmationDialog,
|
||||
} from "../../../src/dialogs/generic/show-dialog-box";
|
||||
import { haStyle } from "../../../src/resources/styles";
|
||||
import { HomeAssistant } from "../../../src/types";
|
||||
import { hassioStyle } from "../resources/hassio-style";
|
||||
import { extractApiErrorMessage } from "../../../src/data/hassio/common";
|
||||
|
||||
@customElement("hassio-supervisor-info")
|
||||
class HassioSupervisorInfo extends LitElement {
|
||||
@property() public hass!: HomeAssistant;
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property() public supervisorInfo!: HassioSupervisorInfoType;
|
||||
|
||||
@property() private _errors?: string;
|
||||
@property() public hostInfo!: HassioHostInfoType;
|
||||
|
||||
public render(): TemplateResult | void {
|
||||
protected render(): TemplateResult | void {
|
||||
return html`
|
||||
<ha-card>
|
||||
<ha-card header="Supervisor">
|
||||
<div class="card-content">
|
||||
<h2>Supervisor</h2>
|
||||
<table class="info">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Version</td>
|
||||
<td>${this.supervisorInfo.version}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Latest version</td>
|
||||
<td>${this.supervisorInfo.version_latest}</td>
|
||||
</tr>
|
||||
${this.supervisorInfo.channel !== "stable"
|
||||
? html`
|
||||
<tr>
|
||||
<td>Channel</td>
|
||||
<td>${this.supervisorInfo.channel}</td>
|
||||
</tr>
|
||||
`
|
||||
: ""}
|
||||
</tbody>
|
||||
</table>
|
||||
${this._errors
|
||||
? html` <div class="errors">Error: ${this._errors}</div> `
|
||||
: ""}
|
||||
<ha-settings-row>
|
||||
<span slot="heading">
|
||||
Version
|
||||
</span>
|
||||
<span slot="description">
|
||||
${this.supervisorInfo.version}
|
||||
</span>
|
||||
</ha-settings-row>
|
||||
<ha-settings-row>
|
||||
<span slot="heading">
|
||||
Newest version
|
||||
</span>
|
||||
<span slot="description">
|
||||
${this.supervisorInfo.version_latest}
|
||||
</span>
|
||||
${this.supervisorInfo.version !== this.supervisorInfo.version_latest
|
||||
? html`
|
||||
<ha-progress-button
|
||||
title="Update the supervisor"
|
||||
@click=${this._supervisorUpdate}
|
||||
>
|
||||
Update
|
||||
</ha-progress-button>
|
||||
`
|
||||
: ""}
|
||||
</ha-settings-row>
|
||||
<ha-settings-row>
|
||||
<span slot="heading">
|
||||
Channel
|
||||
</span>
|
||||
<span slot="description">
|
||||
${this.supervisorInfo.channel}
|
||||
</span>
|
||||
${this.supervisorInfo.channel === "beta"
|
||||
? html`
|
||||
<ha-progress-button
|
||||
@click=${this._toggleBeta}
|
||||
title="Get stable updates for Home Assistant, supervisor and host"
|
||||
>
|
||||
Leave beta channel
|
||||
</ha-progress-button>
|
||||
`
|
||||
: this.supervisorInfo.channel === "stable"
|
||||
? html`
|
||||
<ha-progress-button
|
||||
@click=${this._toggleBeta}
|
||||
title="Get beta updates for Home Assistant (RCs), supervisor and host"
|
||||
>
|
||||
Join beta channel
|
||||
</ha-progress-button>
|
||||
`
|
||||
: ""}
|
||||
</ha-settings-row>
|
||||
|
||||
${this.supervisorInfo?.supported
|
||||
? html` <ha-settings-row three-line>
|
||||
<span slot="heading">
|
||||
Share diagnostics
|
||||
</span>
|
||||
<div slot="description" class="diagnostics-description">
|
||||
Share crash reports and diagnostic information.
|
||||
<button
|
||||
class="link"
|
||||
title="Show more information about this"
|
||||
@click=${this._diagnosticsInformationDialog}
|
||||
>
|
||||
Learn more
|
||||
</button>
|
||||
</div>
|
||||
<ha-switch
|
||||
haptic
|
||||
.checked=${this.supervisorInfo.diagnostics}
|
||||
@change=${this._toggleDiagnostics}
|
||||
></ha-switch>
|
||||
</ha-settings-row>`
|
||||
: html`<div class="error">
|
||||
You are running an unsupported installation.
|
||||
<a
|
||||
href="https://github.com/home-assistant/architecture/blob/master/adr/${this.hostInfo.features.includes(
|
||||
"hassos"
|
||||
)
|
||||
? "0015-home-assistant-os.md"
|
||||
: "0014-home-assistant-supervised.md"}"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
title="Learn more about how you can make your system compliant"
|
||||
>
|
||||
Learn More
|
||||
</a>
|
||||
</div>`}
|
||||
</div>
|
||||
<div class="card-actions">
|
||||
<ha-call-api-button .hass=${this.hass} path="hassio/supervisor/reload"
|
||||
>Reload</ha-call-api-button
|
||||
<ha-progress-button
|
||||
@click=${this._supervisorReload}
|
||||
title="Reload parts of the supervisor."
|
||||
>
|
||||
${this.supervisorInfo.version !== this.supervisorInfo.version_latest
|
||||
? html`
|
||||
<ha-call-api-button
|
||||
.hass=${this.hass}
|
||||
path="hassio/supervisor/update"
|
||||
>Update</ha-call-api-button
|
||||
>
|
||||
`
|
||||
: ""}
|
||||
${this.supervisorInfo.channel === "beta"
|
||||
? html`
|
||||
<ha-call-api-button
|
||||
.hass=${this.hass}
|
||||
path="hassio/supervisor/options"
|
||||
.data=${{ channel: "stable" }}
|
||||
>Leave beta channel</ha-call-api-button
|
||||
>
|
||||
`
|
||||
: ""}
|
||||
${this.supervisorInfo.channel === "stable"
|
||||
? html`
|
||||
<mwc-button
|
||||
@click=${this._joinBeta}
|
||||
class="warning"
|
||||
title="Get beta updates for Home Assistant (RCs), supervisor and host"
|
||||
>Join beta channel</mwc-button
|
||||
>
|
||||
`
|
||||
: ""}
|
||||
Reload
|
||||
</ha-progress-button>
|
||||
</div>
|
||||
</ha-card>
|
||||
`;
|
||||
}
|
||||
|
||||
private async _toggleBeta(ev: CustomEvent): Promise<void> {
|
||||
const button = ev.currentTarget as any;
|
||||
button.progress = true;
|
||||
|
||||
if (this.supervisorInfo.channel === "stable") {
|
||||
const confirmed = await showConfirmationDialog(this, {
|
||||
title: "WARNING",
|
||||
text: html` Beta releases are for testers and early adopters and can
|
||||
contain unstable code changes.
|
||||
<br />
|
||||
<b>
|
||||
Make sure you have backups of your data before you activate this
|
||||
feature.
|
||||
</b>
|
||||
<br /><br />
|
||||
This includes beta releases for:
|
||||
<li>Home Assistant Core</li>
|
||||
<li>Home Assistant Supervisor</li>
|
||||
<li>Home Assistant Operating System</li>
|
||||
<br />
|
||||
Do you want to join the beta channel?`,
|
||||
confirmText: "join beta",
|
||||
dismissText: "no",
|
||||
});
|
||||
|
||||
if (!confirmed) {
|
||||
button.progress = false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
const data: Partial<SupervisorOptions> = {
|
||||
channel: this.supervisorInfo.channel === "stable" ? "beta" : "stable",
|
||||
};
|
||||
await setSupervisorOption(this.hass, data);
|
||||
await reloadSupervisor(this.hass);
|
||||
this.supervisorInfo = await fetchHassioSupervisorInfo(this.hass);
|
||||
} catch (err) {
|
||||
showAlertDialog(this, {
|
||||
title: "Failed to set supervisor option",
|
||||
text: extractApiErrorMessage(err),
|
||||
});
|
||||
}
|
||||
button.progress = false;
|
||||
}
|
||||
|
||||
private async _supervisorReload(ev: CustomEvent): Promise<void> {
|
||||
const button = ev.currentTarget as any;
|
||||
button.progress = true;
|
||||
|
||||
try {
|
||||
await reloadSupervisor(this.hass);
|
||||
this.supervisorInfo = await fetchHassioSupervisorInfo(this.hass);
|
||||
} catch (err) {
|
||||
showAlertDialog(this, {
|
||||
title: "Failed to reload the supervisor",
|
||||
text: extractApiErrorMessage(err),
|
||||
});
|
||||
}
|
||||
button.progress = false;
|
||||
}
|
||||
|
||||
private async _supervisorUpdate(ev: CustomEvent): Promise<void> {
|
||||
const button = ev.currentTarget as any;
|
||||
button.progress = true;
|
||||
|
||||
const confirmed = await showConfirmationDialog(this, {
|
||||
title: "Update supervisor",
|
||||
text: `Are you sure you want to update supervisor to version ${this.supervisorInfo.version_latest}?`,
|
||||
confirmText: "update",
|
||||
dismissText: "cancel",
|
||||
});
|
||||
|
||||
if (!confirmed) {
|
||||
button.progress = false;
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await updateSupervisor(this.hass);
|
||||
} catch (err) {
|
||||
showAlertDialog(this, {
|
||||
title: "Failed to update the supervisor",
|
||||
text: extractApiErrorMessage(err),
|
||||
});
|
||||
}
|
||||
button.progress = false;
|
||||
}
|
||||
|
||||
private async _diagnosticsInformationDialog(): Promise<void> {
|
||||
await showAlertDialog(this, {
|
||||
title: "Help Improve Home Assistant",
|
||||
text: html`Would you want to automatically share crash reports and
|
||||
diagnostic information when the supervisor encounters unexpected errors?
|
||||
<br /><br />
|
||||
This will allow us to fix the problems, the information is only
|
||||
accessible to the Home Assistant Core team and will not be shared with
|
||||
others.
|
||||
<br /><br />
|
||||
The data does not include any private/sensitive information and you can
|
||||
disable this in settings at any time you want.`,
|
||||
});
|
||||
}
|
||||
|
||||
private async _toggleDiagnostics(): Promise<void> {
|
||||
try {
|
||||
const data: SupervisorOptions = {
|
||||
diagnostics: !this.supervisorInfo?.diagnostics,
|
||||
};
|
||||
await setSupervisorOption(this.hass, data);
|
||||
} catch (err) {
|
||||
showAlertDialog(this, {
|
||||
title: "Failed to set supervisor option",
|
||||
text: extractApiErrorMessage(err),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
static get styles(): CSSResult[] {
|
||||
return [
|
||||
haStyle,
|
||||
@@ -103,83 +270,35 @@ class HassioSupervisorInfo extends LitElement {
|
||||
css`
|
||||
ha-card {
|
||||
height: 100%;
|
||||
justify-content: space-between;
|
||||
flex-direction: column;
|
||||
display: flex;
|
||||
}
|
||||
.card-actions {
|
||||
height: 48px;
|
||||
border-top: none;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
button.link {
|
||||
color: var(--primary-color);
|
||||
}
|
||||
ha-settings-row {
|
||||
padding: 0;
|
||||
height: 54px;
|
||||
width: 100%;
|
||||
}
|
||||
.card-content {
|
||||
color: var(--primary-text-color);
|
||||
box-sizing: border-box;
|
||||
height: calc(100% - 47px);
|
||||
ha-settings-row[three-line] {
|
||||
height: 74px;
|
||||
}
|
||||
.info {
|
||||
width: 100%;
|
||||
}
|
||||
.info td:nth-child(2) {
|
||||
text-align: right;
|
||||
}
|
||||
.errors {
|
||||
color: var(--google-red-500);
|
||||
margin-top: 16px;
|
||||
ha-settings-row > div[slot="description"] {
|
||||
white-space: normal;
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
||||
protected firstUpdated(): void {
|
||||
this.addEventListener("hass-api-called", (ev) => this._apiCalled(ev));
|
||||
}
|
||||
|
||||
private _apiCalled(ev): void {
|
||||
if (ev.detail.success) {
|
||||
this._errors = undefined;
|
||||
return;
|
||||
}
|
||||
|
||||
const response = ev.detail.response;
|
||||
|
||||
this._errors =
|
||||
typeof response.body === "object"
|
||||
? response.body.message || "Unknown error"
|
||||
: response.body;
|
||||
}
|
||||
|
||||
private async _joinBeta() {
|
||||
const confirmed = await showConfirmationDialog(this, {
|
||||
title: "WARNING",
|
||||
text: html` Beta releases are for testers and early adopters and can
|
||||
contain unstable code changes.
|
||||
<br />
|
||||
<b>
|
||||
Make sure you have backups of your data before you activate this
|
||||
feature.
|
||||
</b>
|
||||
<br /><br />
|
||||
This includes beta releases for:
|
||||
<li>Home Assistant Core</li>
|
||||
<li>Home Assistant Supervisor</li>
|
||||
<li>Home Assistant Operating System</li>
|
||||
<br />
|
||||
Do you want to join the beta channel?`,
|
||||
confirmText: "join beta",
|
||||
dismissText: "no",
|
||||
});
|
||||
|
||||
if (!confirmed) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const data: SupervisorOptions = { channel: "beta" };
|
||||
await setSupervisorOption(this.hass, data);
|
||||
const eventdata = {
|
||||
success: true,
|
||||
response: undefined,
|
||||
path: "option",
|
||||
};
|
||||
fireEvent(this, "hass-api-called", eventdata);
|
||||
} catch (err) {
|
||||
this._errors = `Error joining beta channel, ${err.body?.message || err}`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
|
@@ -7,13 +7,16 @@ import {
|
||||
CSSResult,
|
||||
customElement,
|
||||
html,
|
||||
internalProperty,
|
||||
LitElement,
|
||||
property,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import "../../../src/components/buttons/ha-progress-button";
|
||||
import "../../../src/components/ha-card";
|
||||
import { extractApiErrorMessage } from "../../../src/data/hassio/common";
|
||||
import { fetchHassioLogs } from "../../../src/data/hassio/supervisor";
|
||||
import "../../../src/layouts/loading-screen";
|
||||
import "../../../src/layouts/hass-loading-screen";
|
||||
import { haStyle } from "../../../src/resources/styles";
|
||||
import { HomeAssistant } from "../../../src/types";
|
||||
import "../components/hassio-ansi-to-html";
|
||||
@@ -55,18 +58,18 @@ const logProviders: LogProvider[] = [
|
||||
class HassioSupervisorLog extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property() private _error?: string;
|
||||
@internalProperty() private _error?: string;
|
||||
|
||||
@property() private _selectedLogProvider = "supervisor";
|
||||
@internalProperty() private _selectedLogProvider = "supervisor";
|
||||
|
||||
@property() private _content?: string;
|
||||
@internalProperty() private _content?: string;
|
||||
|
||||
public async connectedCallback(): Promise<void> {
|
||||
super.connectedCallback();
|
||||
await this._loadData();
|
||||
}
|
||||
|
||||
public render(): TemplateResult | void {
|
||||
protected render(): TemplateResult | void {
|
||||
return html`
|
||||
<ha-card>
|
||||
${this._error ? html` <div class="errors">${this._error}</div> ` : ""}
|
||||
@@ -98,21 +101,52 @@ class HassioSupervisorLog extends LitElement {
|
||||
? html`<hassio-ansi-to-html
|
||||
.content=${this._content}
|
||||
></hassio-ansi-to-html>`
|
||||
: html`<loading-screen></loading-screen>`}
|
||||
: html`<hass-loading-screen no-toolbar></hass-loading-screen>`}
|
||||
</div>
|
||||
<div class="card-actions">
|
||||
<mwc-button @click=${this._refresh}>Refresh</mwc-button>
|
||||
<ha-progress-button @click=${this._refresh}>
|
||||
Refresh
|
||||
</ha-progress-button>
|
||||
</div>
|
||||
</ha-card>
|
||||
`;
|
||||
}
|
||||
|
||||
private async _setLogProvider(ev): Promise<void> {
|
||||
const provider = ev.detail.item.getAttribute("provider");
|
||||
this._selectedLogProvider = provider;
|
||||
this._loadData();
|
||||
}
|
||||
|
||||
private async _refresh(ev: CustomEvent): Promise<void> {
|
||||
const button = ev.currentTarget as any;
|
||||
button.progress = true;
|
||||
await this._loadData();
|
||||
button.progress = false;
|
||||
}
|
||||
|
||||
private async _loadData(): Promise<void> {
|
||||
this._error = undefined;
|
||||
|
||||
try {
|
||||
this._content = await fetchHassioLogs(
|
||||
this.hass,
|
||||
this._selectedLogProvider
|
||||
);
|
||||
} catch (err) {
|
||||
this._error = `Failed to get supervisor logs, ${extractApiErrorMessage(
|
||||
err
|
||||
)}`;
|
||||
}
|
||||
}
|
||||
|
||||
static get styles(): CSSResult[] {
|
||||
return [
|
||||
haStyle,
|
||||
hassioStyle,
|
||||
css`
|
||||
ha-card {
|
||||
margin-top: 8px;
|
||||
width: 100%;
|
||||
}
|
||||
pre {
|
||||
@@ -123,41 +157,12 @@ class HassioSupervisorLog extends LitElement {
|
||||
width: 96%;
|
||||
}
|
||||
.errors {
|
||||
color: var(--google-red-500);
|
||||
color: var(--error-color);
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.card-content {
|
||||
padding-top: 0px;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
||||
private async _setLogProvider(ev): Promise<void> {
|
||||
const provider = ev.detail.item.getAttribute("provider");
|
||||
this._selectedLogProvider = provider;
|
||||
await this._loadData();
|
||||
}
|
||||
|
||||
private async _loadData(): Promise<void> {
|
||||
this._error = undefined;
|
||||
this._content = undefined;
|
||||
|
||||
try {
|
||||
this._content = await fetchHassioLogs(
|
||||
this.hass,
|
||||
this._selectedLogProvider
|
||||
);
|
||||
} catch (err) {
|
||||
this._error = `Failed to get supervisor logs, ${
|
||||
err.body?.message || err
|
||||
}`;
|
||||
}
|
||||
}
|
||||
|
||||
private async _refresh(): Promise<void> {
|
||||
await this._loadData();
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
|
@@ -1,4 +1,3 @@
|
||||
import "@polymer/paper-menu-button/paper-menu-button";
|
||||
import {
|
||||
css,
|
||||
CSSResult,
|
||||
@@ -12,11 +11,14 @@ import {
|
||||
HassioHassOSInfo,
|
||||
HassioHostInfo,
|
||||
} from "../../../src/data/hassio/host";
|
||||
import { HassioSupervisorInfo } from "../../../src/data/hassio/supervisor";
|
||||
import {
|
||||
HassioInfo,
|
||||
HassioSupervisorInfo,
|
||||
} from "../../../src/data/hassio/supervisor";
|
||||
import "../../../src/layouts/hass-tabs-subpage";
|
||||
import { haStyle } from "../../../src/resources/styles";
|
||||
import { HomeAssistant, Route } from "../../../src/types";
|
||||
import { supervisorTabs } from "../hassio-panel";
|
||||
import { supervisorTabs } from "../hassio-tabs";
|
||||
import { hassioStyle } from "../resources/hassio-style";
|
||||
import "./hassio-host-info";
|
||||
import "./hassio-supervisor-info";
|
||||
@@ -32,11 +34,13 @@ class HassioSystem extends LitElement {
|
||||
|
||||
@property() public supervisorInfo!: HassioSupervisorInfo;
|
||||
|
||||
@property({ attribute: false }) public hassioInfo!: HassioInfo;
|
||||
|
||||
@property() public hostInfo!: HassioHostInfo;
|
||||
|
||||
@property() public hassOsInfo!: HassioHassOSInfo;
|
||||
@property({ attribute: false }) public hassOsInfo!: HassioHassOSInfo;
|
||||
|
||||
public render(): TemplateResult | void {
|
||||
protected render(): TemplateResult | void {
|
||||
return html`
|
||||
<hass-tabs-subpage
|
||||
.hass=${this.hass}
|
||||
@@ -48,19 +52,19 @@ class HassioSystem extends LitElement {
|
||||
>
|
||||
<span slot="header">System</span>
|
||||
<div class="content">
|
||||
<h1>Information</h1>
|
||||
<div class="card-group">
|
||||
<hassio-supervisor-info
|
||||
.hass=${this.hass}
|
||||
.hostInfo=${this.hostInfo}
|
||||
.supervisorInfo=${this.supervisorInfo}
|
||||
></hassio-supervisor-info>
|
||||
<hassio-host-info
|
||||
.hass=${this.hass}
|
||||
.hassioInfo=${this.hassioInfo}
|
||||
.hostInfo=${this.hostInfo}
|
||||
.hassOsInfo=${this.hassOsInfo}
|
||||
></hassio-host-info>
|
||||
</div>
|
||||
<h1>System log</h1>
|
||||
<hassio-supervisor-log .hass=${this.hass}></hassio-supervisor-log>
|
||||
</div>
|
||||
</hass-tabs-subpage>
|
||||
|
@@ -1,11 +1,8 @@
|
||||
const { createHassioConfig } = require("../build-scripts/webpack.js");
|
||||
const { isProdBuild } = require("../build-scripts/env.js");
|
||||
|
||||
// File just used for stats builds
|
||||
|
||||
const latestBuild = false;
|
||||
const { isProdBuild, isStatsBuild } = require("../build-scripts/env.js");
|
||||
|
||||
module.exports = createHassioConfig({
|
||||
isProdBuild: isProdBuild(),
|
||||
latestBuild,
|
||||
isStatsBuild: isStatsBuild(),
|
||||
latestBuild: true,
|
||||
});
|
||||
|
104
package.json
104
package.json
@@ -8,38 +8,44 @@
|
||||
"version": "1.0.0",
|
||||
"scripts": {
|
||||
"build": "script/build_frontend",
|
||||
"lint:eslint": "eslint '**/src/**/*.{js,ts,html}' --ignore-path .gitignore",
|
||||
"format:eslint": "eslint '**/src/**/*.{js,ts,html}' --fix --ignore-path .gitignore",
|
||||
"lint:prettier": "prettier '**/src/**/*.{js,ts,json,css,md}' --check",
|
||||
"format:prettier": "prettier '**/src/**/*.{js,ts,json,css,md}' --write",
|
||||
"lint:eslint": "eslint \"**/src/**/*.{js,ts,html}\" --ignore-path .gitignore",
|
||||
"format:eslint": "eslint \"**/src/**/*.{js,ts,html}\" --fix --ignore-path .gitignore",
|
||||
"lint:prettier": "prettier \"**/src/**/*.{js,ts,json,css,md}\" --check",
|
||||
"format:prettier": "prettier \"**/src/**/*.{js,ts,json,css,md}\" --write",
|
||||
"lint:types": "tsc",
|
||||
"lint:lit": "lit-analyzer '**/src/**/*.ts'",
|
||||
"lint:lit": "lit-analyzer \"**/src/**/*.ts\" --format markdown --outFile result.md",
|
||||
"lint": "npm run lint:eslint && npm run lint:prettier && npm run lint:types",
|
||||
"format": "npm run format:eslint && npm run format:prettier",
|
||||
"mocha": "node_modules/.bin/ts-mocha -p test-mocha/tsconfig.test.json --opts test-mocha/mocha.opts",
|
||||
"test": "npm run lint && npm run mocha",
|
||||
"docker_build": "sh ./script/docker_run.sh build $npm_package_version",
|
||||
"bash": "sh ./script/docker_run.sh bash $npm_package_version"
|
||||
"test": "npm run lint && npm run mocha"
|
||||
},
|
||||
"author": "Paulus Schoutsen <Paulus@PaulusSchoutsen.nl> (http://paulusschoutsen.nl)",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@formatjs/intl-pluralrules": "^1.5.8",
|
||||
"@fullcalendar/core": "^5.0.0-beta.2",
|
||||
"@fullcalendar/daygrid": "^5.0.0-beta.2",
|
||||
"@material/chips": "7.0.0-canary.d92d8c93e.0",
|
||||
"@material/mwc-button": "^0.15.0",
|
||||
"@material/mwc-checkbox": "^0.15.0",
|
||||
"@material/mwc-dialog": "^0.15.0",
|
||||
"@material/mwc-fab": "^0.15.0",
|
||||
"@material/mwc-formfield": "^0.15.0",
|
||||
"@material/mwc-icon-button": "^0.15.0",
|
||||
"@material/mwc-list": "^0.15.0",
|
||||
"@material/mwc-menu": "^0.15.0",
|
||||
"@material/mwc-ripple": "^0.15.0",
|
||||
"@material/mwc-switch": "^0.15.0",
|
||||
"@mdi/js": "4.9.95",
|
||||
"@mdi/svg": "4.9.95",
|
||||
"@fullcalendar/common": "5.1.0",
|
||||
"@fullcalendar/core": "5.1.0",
|
||||
"@fullcalendar/daygrid": "5.1.0",
|
||||
"@fullcalendar/interaction": "5.1.0",
|
||||
"@fullcalendar/list": "5.1.0",
|
||||
"@material/chips": "=8.0.0-canary.096a7a066.0",
|
||||
"@material/circular-progress": "=8.0.0-canary.a78ceb112.0",
|
||||
"@material/mwc-button": "^0.18.0",
|
||||
"@material/mwc-checkbox": "^0.18.0",
|
||||
"@material/mwc-dialog": "^0.18.0",
|
||||
"@material/mwc-fab": "^0.18.0",
|
||||
"@material/mwc-formfield": "^0.18.0",
|
||||
"@material/mwc-icon-button": "^0.18.0",
|
||||
"@material/mwc-list": "^0.18.0",
|
||||
"@material/mwc-menu": "^0.18.0",
|
||||
"@material/mwc-radio": "^0.18.0",
|
||||
"@material/mwc-ripple": "^0.18.0",
|
||||
"@material/mwc-switch": "^0.18.0",
|
||||
"@material/mwc-tab": "^0.18.0",
|
||||
"@material/mwc-tab-bar": "^0.18.0",
|
||||
"@material/top-app-bar": "=8.0.0-canary.096a7a066.0",
|
||||
"@mdi/js": "5.5.55",
|
||||
"@mdi/svg": "5.5.55",
|
||||
"@polymer/app-layout": "^3.0.2",
|
||||
"@polymer/app-route": "^3.0.2",
|
||||
"@polymer/app-storage": "^3.0.2",
|
||||
@@ -49,7 +55,6 @@
|
||||
"@polymer/iron-image": "^3.0.1",
|
||||
"@polymer/iron-input": "^3.0.1",
|
||||
"@polymer/iron-label": "^3.0.1",
|
||||
"@polymer/iron-media-query": "^3.0.1",
|
||||
"@polymer/iron-overlay-behavior": "^3.0.2",
|
||||
"@polymer/iron-resizable-behavior": "^3.0.1",
|
||||
"@polymer/paper-card": "^3.0.1",
|
||||
@@ -67,29 +72,32 @@
|
||||
"@polymer/paper-radio-group": "^3.0.1",
|
||||
"@polymer/paper-ripple": "^3.0.1",
|
||||
"@polymer/paper-slider": "^3.0.1",
|
||||
"@polymer/paper-spinner": "^3.0.2",
|
||||
"@polymer/paper-styles": "^3.0.1",
|
||||
"@polymer/paper-tabs": "^3.0.1",
|
||||
"@polymer/paper-toast": "^3.0.1",
|
||||
"@polymer/paper-tooltip": "^3.0.1",
|
||||
"@polymer/polymer": "3.1.0",
|
||||
"@thomasloven/round-slider": "0.5.0",
|
||||
"@types/chromecast-caf-sender": "^1.0.3",
|
||||
"@types/sortablejs": "^1.10.6",
|
||||
"@vaadin/vaadin-combo-box": "^5.0.10",
|
||||
"@vaadin/vaadin-date-picker": "^4.0.7",
|
||||
"@vue/web-component-wrapper": "^1.2.0",
|
||||
"@webcomponents/webcomponentsjs": "^2.2.7",
|
||||
"chart.js": "~2.8.0",
|
||||
"chartjs-chart-timeline": "^0.3.0",
|
||||
"codemirror": "^5.49.0",
|
||||
"comlink": "^4.3.0",
|
||||
"cpx": "^1.5.0",
|
||||
"cropperjs": "^1.5.7",
|
||||
"deep-clone-simple": "^1.1.1",
|
||||
"deep-freeze": "^0.0.1",
|
||||
"es6-object-assign": "^1.1.0",
|
||||
"fecha": "^4.2.0",
|
||||
"fuse.js": "^6.0.0",
|
||||
"google-timezones-json": "^1.0.2",
|
||||
"hls.js": "^0.12.4",
|
||||
"home-assistant-js-websocket": "^5.2.1",
|
||||
"hls.js": "^0.13.2",
|
||||
"home-assistant-js-websocket": "^5.4.1",
|
||||
"idb-keyval": "^3.2.0",
|
||||
"intl-messageformat": "^8.3.9",
|
||||
"js-yaml": "^3.13.1",
|
||||
@@ -98,16 +106,20 @@
|
||||
"lit-element": "^2.3.1",
|
||||
"lit-html": "^1.2.1",
|
||||
"lit-virtualizer": "^0.4.2",
|
||||
"marked": "^0.6.1",
|
||||
"marked": "^1.1.1",
|
||||
"mdn-polyfills": "^5.16.0",
|
||||
"memoize-one": "^5.0.2",
|
||||
"node-vibrant": "^3.1.5",
|
||||
"proxy-polyfill": "^0.3.1",
|
||||
"punycode": "^2.1.1",
|
||||
"regenerator-runtime": "^0.13.2",
|
||||
"resize-observer-polyfill": "^1.5.1",
|
||||
"roboto-fontface": "^0.10.0",
|
||||
"superstruct": "^0.6.1",
|
||||
"sortablejs": "^1.10.2",
|
||||
"superstruct": "^0.10.12",
|
||||
"unfetch": "^4.1.0",
|
||||
"vue": "^2.6.11",
|
||||
"vue2-daterange-picker": "^0.5.1",
|
||||
"web-animations-js": "^2.3.2",
|
||||
"workbox-core": "^5.1.3",
|
||||
"workbox-precaching": "^5.1.3",
|
||||
@@ -132,13 +144,14 @@
|
||||
"@rollup/plugin-replace": "^2.3.2",
|
||||
"@types/chai": "^4.1.7",
|
||||
"@types/chromecast-caf-receiver": "^3.0.12",
|
||||
"@types/codemirror": "^0.0.78",
|
||||
"@types/codemirror": "^0.0.97",
|
||||
"@types/hls.js": "^0.12.3",
|
||||
"@types/js-yaml": "^3.12.1",
|
||||
"@types/leaflet": "^1.4.3",
|
||||
"@types/leaflet-draw": "^1.0.1",
|
||||
"@types/marked": "^1.1.0",
|
||||
"@types/memoize-one": "4.1.0",
|
||||
"@types/mocha": "^5.2.6",
|
||||
"@types/mocha": "^7.0.2",
|
||||
"@types/resize-observer-browser": "^0.1.3",
|
||||
"@types/webspeechapi": "^0.0.29",
|
||||
"@typescript-eslint/eslint-plugin": "^2.28.0",
|
||||
@@ -149,7 +162,7 @@
|
||||
"eslint": "^6.8.0",
|
||||
"eslint-config-airbnb-typescript": "^7.2.1",
|
||||
"eslint-config-prettier": "^6.10.1",
|
||||
"eslint-import-resolver-webpack": "^0.12.1",
|
||||
"eslint-import-resolver-webpack": "^0.12.2",
|
||||
"eslint-plugin-disable": "^2.0.1",
|
||||
"eslint-plugin-import": "^2.20.2",
|
||||
"eslint-plugin-lit": "^1.2.0",
|
||||
@@ -167,12 +180,12 @@
|
||||
"html-minifier": "^4.0.0",
|
||||
"husky": "^1.3.1",
|
||||
"lint-staged": "^8.1.5",
|
||||
"lit-analyzer": "^1.1.10",
|
||||
"lit-analyzer": "^1.2.0",
|
||||
"lodash.template": "^4.5.0",
|
||||
"magic-string": "^0.25.7",
|
||||
"map-stream": "^0.0.7",
|
||||
"merge-stream": "^1.0.1",
|
||||
"mocha": "^6.0.2",
|
||||
"mocha": "^7.2.0",
|
||||
"object-hash": "^2.0.3",
|
||||
"open": "^7.0.4",
|
||||
"prettier": "^2.0.4",
|
||||
@@ -187,9 +200,9 @@
|
||||
"sinon": "^7.3.1",
|
||||
"source-map-url": "^0.4.0",
|
||||
"systemjs": "^6.3.2",
|
||||
"terser-webpack-plugin": "^1.2.3",
|
||||
"ts-lit-plugin": "^1.1.10",
|
||||
"ts-mocha": "^6.0.0",
|
||||
"terser-webpack-plugin": "^3.0.6",
|
||||
"ts-lit-plugin": "^1.2.0",
|
||||
"ts-mocha": "^7.0.0",
|
||||
"typescript": "^3.8.3",
|
||||
"vinyl-buffer": "^1.0.1",
|
||||
"vinyl-source-stream": "^2.0.0",
|
||||
@@ -207,19 +220,10 @@
|
||||
"@polymer/polymer": "3.1.0",
|
||||
"lit-html": "1.2.1",
|
||||
"lit-element": "2.3.1",
|
||||
"@material/animation": "7.0.0-canary.d92d8c93e.0",
|
||||
"@material/base": "7.0.0-canary.d92d8c93e.0",
|
||||
"@material/checkbox": "7.0.0-canary.d92d8c93e.0",
|
||||
"@material/density": "7.0.0-canary.d92d8c93e.0",
|
||||
"@material/dom": "7.0.0-canary.d92d8c93e.0",
|
||||
"@material/elevation": "7.0.0-canary.d92d8c93e.0",
|
||||
"@material/feature-targeting": "7.0.0-canary.d92d8c93e.0",
|
||||
"@material/ripple": "7.0.0-canary.d92d8c93e.0",
|
||||
"@material/rtl": "7.0.0-canary.d92d8c93e.0",
|
||||
"@material/shape": "7.0.0-canary.d92d8c93e.0",
|
||||
"@material/theme": "7.0.0-canary.d92d8c93e.0",
|
||||
"@material/touch-target": "7.0.0-canary.d92d8c93e.0",
|
||||
"@material/typography": "7.0.0-canary.d92d8c93e.0"
|
||||
"@material/animation": "8.0.0-canary.096a7a066.0",
|
||||
"@material/base": "8.0.0-canary.096a7a066.0",
|
||||
"@material/feature-targeting": "8.0.0-canary.096a7a066.0",
|
||||
"@material/theme": "8.0.0-canary.096a7a066.0"
|
||||
},
|
||||
"main": "src/home-assistant.js",
|
||||
"husky": {
|
||||
|
@@ -1,14 +0,0 @@
|
||||
#!/bin/bash
|
||||
# Docker entry point inspired by travis build and script/build_frontend
|
||||
|
||||
# Stop on errors
|
||||
set -e
|
||||
|
||||
# Build the frontend but not used the npm run build
|
||||
/bin/bash script/build_frontend
|
||||
|
||||
# TEST
|
||||
npm run test
|
||||
|
||||
#
|
||||
#xvfb-run wct
|
@@ -1,103 +0,0 @@
|
||||
#!/bin/bash
|
||||
# Basic Docker Management scripts
|
||||
# With this script you can build software, or enter an agnostic development environment and run commands interactively.
|
||||
|
||||
|
||||
|
||||
check_mandatory_tools(){
|
||||
if [ "x$(which docker)" == "x" ]; then
|
||||
echo "UNKNOWN - Missing docker binary! Are you sure it is installed and reachable?"
|
||||
exit 3
|
||||
fi
|
||||
|
||||
docker info > /dev/null 2>&1
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "UNKNOWN - Unable to talk to the docker daemon! Maybe the docker daemon is not running"
|
||||
exit 3
|
||||
fi
|
||||
}
|
||||
|
||||
check_dev_image(){
|
||||
if [[ "$(docker images -q ${IMAGE_NAME}:$IMAGE_TAG 2> /dev/null)" == "" ]]; then
|
||||
echo "UNKNOWN - Can't find the development docker image ${IMAGE_NAME}:$IMAGE_TAG"
|
||||
while true; do
|
||||
read -p "Do you want to create it now?" yn
|
||||
case $yn in
|
||||
[Yy]* ) create_image; break;;
|
||||
[Nn]* ) exit 3;;
|
||||
* ) echo "Please answer y or n";;
|
||||
esac
|
||||
done
|
||||
fi
|
||||
}
|
||||
|
||||
# Building the basic image for compiling the production frontend
|
||||
create_image(){
|
||||
docker build -t ${IMAGE_NAME}:${IMAGE_TAG} .
|
||||
}
|
||||
|
||||
#
|
||||
# Execute interactive bash on basic image
|
||||
#
|
||||
run_bash_on_docker(){
|
||||
|
||||
check_dev_image
|
||||
|
||||
docker run -it \
|
||||
-v $PWD/:/frontend/ \
|
||||
-v /frontend/node_modules \
|
||||
-v /frontend/bower_components \
|
||||
${IMAGE_NAME}:${IMAGE_TAG} /bin/bash
|
||||
}
|
||||
|
||||
#
|
||||
# Execute the basic image for compiling the production frontend
|
||||
#
|
||||
build_all(){
|
||||
|
||||
check_dev_image
|
||||
|
||||
docker run -it \
|
||||
-v $PWD/:/frontend/ \
|
||||
-v /frontend/node_modules \
|
||||
-v /frontend/bower_components \
|
||||
${IMAGE_NAME}:${IMAGE_TAG} /bin/bash script/build_frontend
|
||||
|
||||
}
|
||||
|
||||
# Init Global Variable
|
||||
IMAGE_NAME=home_assistant_fe_image
|
||||
IMAGE_TAG=${2:-latest}
|
||||
|
||||
check_mandatory_tools
|
||||
|
||||
case "$1" in
|
||||
setup_env)
|
||||
create_image
|
||||
;;
|
||||
bash)
|
||||
run_bash_on_docker
|
||||
;;
|
||||
build)
|
||||
build_all
|
||||
;;
|
||||
*)
|
||||
echo "NAME"
|
||||
echo " Docker Management."
|
||||
echo ""
|
||||
echo "SYNOPSIS"
|
||||
echo " ${0} command [version]"
|
||||
echo ""
|
||||
echo "DESCRIPTION"
|
||||
echo " With this script you can build software, or enter an agnostic development environment and run commands interactively."
|
||||
echo ""
|
||||
echo " The command are:"
|
||||
echo " setup_env Create develop images"
|
||||
echo " bash Run bash on develop enviroments"
|
||||
echo " build Run silent build"
|
||||
echo ""
|
||||
echo " The version is optional, if not inserted it assumes \"latest\". "
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
exit 0
|
@@ -16,14 +16,31 @@ function today() {
|
||||
)}${String(now.getDate()).padStart(2, "0")}.0`;
|
||||
}
|
||||
|
||||
function auto(version) {
|
||||
const todayVersion = today();
|
||||
if (todayVersion !== version) {
|
||||
return todayVersion;
|
||||
}
|
||||
return patch(version);
|
||||
}
|
||||
|
||||
const methods = {
|
||||
patch,
|
||||
today,
|
||||
auto,
|
||||
};
|
||||
|
||||
async function main(args) {
|
||||
const method = args.length > 0 && methods[args[0]];
|
||||
const commit = args.length > 1 && args[1] == "--commit";
|
||||
let method;
|
||||
let commit;
|
||||
|
||||
if (args.length === 0) {
|
||||
method = methods.auto;
|
||||
commit = true;
|
||||
} else {
|
||||
method = args.length > 0 && methods[args[0]];
|
||||
commit = args.length > 1 && args[1] == "--commit";
|
||||
}
|
||||
|
||||
if (!method) {
|
||||
console.error(
|
||||
|
2
setup.py
2
setup.py
@@ -2,7 +2,7 @@ from setuptools import setup, find_packages
|
||||
|
||||
setup(
|
||||
name="home-assistant-frontend",
|
||||
version="20200603.1",
|
||||
version="20200917.0",
|
||||
description="The Home Assistant frontend",
|
||||
url="https://github.com/home-assistant/home-assistant-polymer",
|
||||
author="The Home Assistant Authors",
|
||||
|
@@ -5,6 +5,7 @@ import {
|
||||
html,
|
||||
LitElement,
|
||||
property,
|
||||
internalProperty,
|
||||
PropertyValues,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
@@ -28,13 +29,13 @@ class HaAuthFlow extends litLocalizeLiteMixin(LitElement) {
|
||||
|
||||
@property() public oauth2State?: string;
|
||||
|
||||
@property() private _state: State = "loading";
|
||||
@internalProperty() private _state: State = "loading";
|
||||
|
||||
@property() private _stepData: any = {};
|
||||
@internalProperty() private _stepData: any = {};
|
||||
|
||||
@property() private _step?: DataEntryFlowStep;
|
||||
@internalProperty() private _step?: DataEntryFlowStep;
|
||||
|
||||
@property() private _errorMessage?: string;
|
||||
@internalProperty() private _errorMessage?: string;
|
||||
|
||||
protected render() {
|
||||
return html`
|
||||
|
@@ -4,21 +4,22 @@ import {
|
||||
html,
|
||||
LitElement,
|
||||
property,
|
||||
internalProperty,
|
||||
PropertyValues,
|
||||
} from "lit-element";
|
||||
import { AuthProvider, fetchAuthProviders } from "../data/auth";
|
||||
import {
|
||||
AuthProvider,
|
||||
fetchAuthProviders,
|
||||
AuthUrlSearchParams,
|
||||
} from "../data/auth";
|
||||
import { litLocalizeLiteMixin } from "../mixins/lit-localize-lite-mixin";
|
||||
import { registerServiceWorker } from "../util/register-service-worker";
|
||||
import "./ha-auth-flow";
|
||||
import { extractSearchParamsObject } from "../common/url/search-params";
|
||||
import punycode from "punycode";
|
||||
|
||||
import(/* webpackChunkName: "pick-auth-provider" */ "./ha-pick-auth-provider");
|
||||
|
||||
interface QueryParams {
|
||||
client_id?: string;
|
||||
redirect_uri?: string;
|
||||
state?: string;
|
||||
}
|
||||
|
||||
class HaAuthorize extends litLocalizeLiteMixin(LitElement) {
|
||||
@property() public clientId?: string;
|
||||
|
||||
@@ -26,21 +27,14 @@ class HaAuthorize extends litLocalizeLiteMixin(LitElement) {
|
||||
|
||||
@property() public oauth2State?: string;
|
||||
|
||||
@property() private _authProvider?: AuthProvider;
|
||||
@internalProperty() private _authProvider?: AuthProvider;
|
||||
|
||||
@property() private _authProviders?: AuthProvider[];
|
||||
@internalProperty() private _authProviders?: AuthProvider[];
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.translationFragment = "page-authorize";
|
||||
const query: QueryParams = {};
|
||||
const values = location.search.substr(1).split("&");
|
||||
for (const item of values) {
|
||||
const value = item.split("=");
|
||||
if (value.length > 1) {
|
||||
query[decodeURIComponent(value[0])] = decodeURIComponent(value[1]);
|
||||
}
|
||||
}
|
||||
const query = extractSearchParamsObject() as AuthUrlSearchParams;
|
||||
if (query.client_id) {
|
||||
this.clientId = query.client_id;
|
||||
}
|
||||
@@ -82,7 +76,7 @@ class HaAuthorize extends litLocalizeLiteMixin(LitElement) {
|
||||
${this.localize(
|
||||
"ui.panel.page-authorize.authorizing_client",
|
||||
"clientId",
|
||||
this.clientId
|
||||
this.clientId ? punycode.toASCII(this.clientId) : this.clientId
|
||||
)}
|
||||
</p>
|
||||
${loggingInWith}
|
||||
@@ -145,7 +139,7 @@ class HaAuthorize extends litLocalizeLiteMixin(LitElement) {
|
||||
response.status === 400 &&
|
||||
authProviders.code === "onboarding_required"
|
||||
) {
|
||||
location.href = "/?";
|
||||
location.href = `/onboarding.html${location.search}`;
|
||||
return;
|
||||
}
|
||||
|
||||
|
@@ -1,131 +0,0 @@
|
||||
import "@polymer/paper-card/paper-card";
|
||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||
/* eslint-plugin-disable lit */
|
||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||
import { computeStateName } from "../common/entity/compute_state_name";
|
||||
import "../components/state-history-charts";
|
||||
import "../data/ha-state-history-data";
|
||||
import { EventsMixin } from "../mixins/events-mixin";
|
||||
|
||||
/*
|
||||
* @appliesMixin EventsMixin
|
||||
*/
|
||||
class HaHistoryGraphCard extends EventsMixin(PolymerElement) {
|
||||
static get template() {
|
||||
return html`
|
||||
<style>
|
||||
paper-card:not([dialog]) .content {
|
||||
padding: 0 16px 16px;
|
||||
}
|
||||
paper-card[dialog] {
|
||||
padding-top: 16px;
|
||||
background-color: transparent;
|
||||
}
|
||||
paper-card {
|
||||
width: 100%;
|
||||
/* prevent new stacking context, chart tooltip needs to overflow */
|
||||
position: static;
|
||||
}
|
||||
.header {
|
||||
@apply --paper-font-headline;
|
||||
line-height: 40px;
|
||||
color: var(--primary-text-color);
|
||||
padding: 20px 16px 12px;
|
||||
@apply --paper-font-common-nowrap;
|
||||
}
|
||||
paper-card[dialog] .header {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
<ha-state-history-data
|
||||
hass="[[hass]]"
|
||||
filter-type="recent-entity"
|
||||
entity-id="[[computeHistoryEntities(stateObj)]]"
|
||||
data="{{stateHistory}}"
|
||||
is-loading="{{stateHistoryLoading}}"
|
||||
cache-config="[[cacheConfig]]"
|
||||
></ha-state-history-data>
|
||||
<paper-card
|
||||
dialog$="[[inDialog]]"
|
||||
on-click="cardTapped"
|
||||
elevation="[[computeElevation(inDialog)]]"
|
||||
>
|
||||
<div class="header">[[computeTitle(stateObj)]]</div>
|
||||
<div class="content">
|
||||
<state-history-charts
|
||||
hass="[[hass]]"
|
||||
history-data="[[stateHistory]]"
|
||||
is-loading-data="[[stateHistoryLoading]]"
|
||||
up-to-now
|
||||
no-single
|
||||
>
|
||||
</state-history-charts>
|
||||
</div>
|
||||
</paper-card>
|
||||
`;
|
||||
}
|
||||
|
||||
static get properties() {
|
||||
return {
|
||||
hass: Object,
|
||||
stateObj: {
|
||||
type: Object,
|
||||
observer: "stateObjObserver",
|
||||
},
|
||||
inDialog: {
|
||||
type: Boolean,
|
||||
value: false,
|
||||
},
|
||||
stateHistory: Object,
|
||||
stateHistoryLoading: Boolean,
|
||||
cacheConfig: {
|
||||
type: Object,
|
||||
value: {
|
||||
refresh: 0,
|
||||
cacheKey: null,
|
||||
hoursToShow: 24,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
stateObjObserver(stateObj) {
|
||||
if (!stateObj) return;
|
||||
if (
|
||||
this.cacheConfig.cacheKey !== stateObj.entity_id ||
|
||||
this.cacheConfig.refresh !== (stateObj.attributes.refresh || 0) ||
|
||||
this.cacheConfig.hoursToShow !== (stateObj.attributes.hours_to_show || 24)
|
||||
) {
|
||||
this.cacheConfig = {
|
||||
refresh: stateObj.attributes.refresh || 0,
|
||||
cacheKey: stateObj.entity_id,
|
||||
hoursToShow: stateObj.attributes.hours_to_show || 24,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
computeTitle(stateObj) {
|
||||
return computeStateName(stateObj);
|
||||
}
|
||||
|
||||
computeContentClass(inDialog) {
|
||||
return inDialog ? "" : "content";
|
||||
}
|
||||
|
||||
computeHistoryEntities(stateObj) {
|
||||
return stateObj.attributes.entity_id;
|
||||
}
|
||||
|
||||
computeElevation(inDialog) {
|
||||
return inDialog ? 0 : 1;
|
||||
}
|
||||
|
||||
cardTapped(ev) {
|
||||
const mq = window.matchMedia("(min-width: 610px) and (min-height: 550px)");
|
||||
if (mq.matches) {
|
||||
ev.stopPropagation();
|
||||
this.fire("hass-more-info", { entityId: this.stateObj.entity_id });
|
||||
}
|
||||
}
|
||||
}
|
||||
customElements.define("ha-history_graph-card", HaHistoryGraphCard);
|
@@ -1,4 +1,8 @@
|
||||
/* eslint-disable no-undef, no-console */
|
||||
import {
|
||||
CastStateEventData,
|
||||
SessionStateEventData,
|
||||
} from "chromecast-caf-receiver/cast.framework";
|
||||
import { Auth } from "home-assistant-js-websocket";
|
||||
import { castApiAvailable } from "./cast_framework";
|
||||
import { CAST_APP_ID, CAST_DEV, CAST_NS } from "./const";
|
||||
@@ -40,16 +44,13 @@ export class CastManager {
|
||||
const context = this.castContext;
|
||||
context.setOptions({
|
||||
receiverApplicationId: CAST_APP_ID,
|
||||
// @ts-ignore
|
||||
autoJoinPolicy: chrome.cast.AutoJoinPolicy.ORIGIN_SCOPED,
|
||||
});
|
||||
context.addEventListener(
|
||||
// @ts-ignore
|
||||
cast.framework.CastContextEventType.SESSION_STATE_CHANGED,
|
||||
(ev) => this._sessionStateChanged(ev)
|
||||
);
|
||||
context.addEventListener(
|
||||
// @ts-ignore
|
||||
cast.framework.CastContextEventType.CAST_STATE_CHANGED,
|
||||
(ev) => this._castStateChanged(ev)
|
||||
);
|
||||
@@ -118,7 +119,7 @@ export class CastManager {
|
||||
}
|
||||
}
|
||||
|
||||
private _sessionStateChanged(ev) {
|
||||
private _sessionStateChanged(ev: SessionStateEventData) {
|
||||
if (__DEV__) {
|
||||
console.log("Cast session state changed", ev.sessionState);
|
||||
}
|
||||
@@ -141,7 +142,7 @@ export class CastManager {
|
||||
}
|
||||
}
|
||||
|
||||
private _castStateChanged(ev) {
|
||||
private _castStateChanged(ev: CastStateEventData) {
|
||||
if (__DEV__) {
|
||||
console.log("Cast state changed", ev.castState);
|
||||
}
|
||||
|
113
src/common/color/convert-color.ts
Normal file
113
src/common/color/convert-color.ts
Normal file
@@ -0,0 +1,113 @@
|
||||
const expand_hex = (hex: string): string => {
|
||||
let result = "";
|
||||
for (const val of hex) {
|
||||
result += val + val;
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
const rgb_hex = (component: number): string => {
|
||||
const hex = Math.round(Math.min(Math.max(component, 0), 255)).toString(16);
|
||||
return hex.length === 1 ? `0${hex}` : hex;
|
||||
};
|
||||
|
||||
// Conversion between HEX and RGB
|
||||
|
||||
export const hex2rgb = (hex: string): [number, number, number] => {
|
||||
hex = hex.replace("#", "");
|
||||
if (hex.length === 3 || hex.length === 4) {
|
||||
hex = expand_hex(hex);
|
||||
}
|
||||
|
||||
return [
|
||||
parseInt(hex.substring(0, 2), 16),
|
||||
parseInt(hex.substring(2, 4), 16),
|
||||
parseInt(hex.substring(4, 6), 16),
|
||||
];
|
||||
};
|
||||
|
||||
export const rgb2hex = (rgb: [number, number, number]): string => {
|
||||
return `#${rgb_hex(rgb[0])}${rgb_hex(rgb[1])}${rgb_hex(rgb[2])}`;
|
||||
};
|
||||
|
||||
// Conversion between LAB, XYZ and RGB from https://github.com/gka/chroma.js
|
||||
// Copyright (c) 2011-2019, Gregor Aisch
|
||||
|
||||
// Constants for XYZ and LAB conversion
|
||||
const Xn = 0.95047;
|
||||
const Yn = 1;
|
||||
const Zn = 1.08883;
|
||||
|
||||
const t0 = 0.137931034; // 4 / 29
|
||||
const t1 = 0.206896552; // 6 / 29
|
||||
const t2 = 0.12841855; // 3 * t1 * t1
|
||||
const t3 = 0.008856452; // t1 * t1 * t1
|
||||
|
||||
const rgb_xyz = (r: number) => {
|
||||
r /= 255;
|
||||
if (r <= 0.04045) {
|
||||
return r / 12.92;
|
||||
}
|
||||
return ((r + 0.055) / 1.055) ** 2.4;
|
||||
};
|
||||
|
||||
const xyz_lab = (t: number) => {
|
||||
if (t > t3) {
|
||||
return t ** (1 / 3);
|
||||
}
|
||||
return t / t2 + t0;
|
||||
};
|
||||
|
||||
const xyz_rgb = (r: number) => {
|
||||
return 255 * (r <= 0.00304 ? 12.92 * r : 1.055 * r ** (1 / 2.4) - 0.055);
|
||||
};
|
||||
|
||||
const lab_xyz = (t: number) => {
|
||||
return t > t1 ? t * t * t : t2 * (t - t0);
|
||||
};
|
||||
|
||||
// Conversions between RGB and LAB
|
||||
|
||||
const rgb2xyz = (rgb: [number, number, number]): [number, number, number] => {
|
||||
let [r, g, b] = rgb;
|
||||
r = rgb_xyz(r);
|
||||
g = rgb_xyz(g);
|
||||
b = rgb_xyz(b);
|
||||
const x = xyz_lab((0.4124564 * r + 0.3575761 * g + 0.1804375 * b) / Xn);
|
||||
const y = xyz_lab((0.2126729 * r + 0.7151522 * g + 0.072175 * b) / Yn);
|
||||
const z = xyz_lab((0.0193339 * r + 0.119192 * g + 0.9503041 * b) / Zn);
|
||||
return [x, y, z];
|
||||
};
|
||||
|
||||
export const rgb2lab = (
|
||||
rgb: [number, number, number]
|
||||
): [number, number, number] => {
|
||||
const [x, y, z] = rgb2xyz(rgb);
|
||||
const l = 116 * y - 16;
|
||||
return [l < 0 ? 0 : l, 500 * (x - y), 200 * (y - z)];
|
||||
};
|
||||
|
||||
export const lab2rgb = (
|
||||
lab: [number, number, number]
|
||||
): [number, number, number] => {
|
||||
const [l, a, b] = lab;
|
||||
|
||||
let y = (l + 16) / 116;
|
||||
let x = isNaN(a) ? y : y + a / 500;
|
||||
let z = isNaN(b) ? y : y - b / 200;
|
||||
|
||||
y = Yn * lab_xyz(y);
|
||||
x = Xn * lab_xyz(x);
|
||||
z = Zn * lab_xyz(z);
|
||||
|
||||
const r = xyz_rgb(3.2404542 * x - 1.5371385 * y - 0.4985314 * z); // D65 -> sRGB
|
||||
const g = xyz_rgb(-0.969266 * x + 1.8760108 * y + 0.041556 * z);
|
||||
const b_ = xyz_rgb(0.0556434 * x - 0.2040259 * y + 1.0572252 * z);
|
||||
|
||||
return [r, g, b_];
|
||||
};
|
||||
|
||||
export const lab2hex = (lab: [number, number, number]): string => {
|
||||
const rgb = lab2rgb(lab);
|
||||
return rgb2hex(rgb);
|
||||
};
|
16
src/common/color/lab.ts
Normal file
16
src/common/color/lab.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
// From https://github.com/gka/chroma.js
|
||||
// Copyright (c) 2011-2019, Gregor Aisch
|
||||
|
||||
export const labDarken = (
|
||||
lab: [number, number, number],
|
||||
amount = 1
|
||||
): [number, number, number] => {
|
||||
return [lab[0] - 18 * amount, lab[1], lab[2]];
|
||||
};
|
||||
|
||||
export const labBrighten = (
|
||||
lab: [number, number, number],
|
||||
amount = 1
|
||||
): [number, number, number] => {
|
||||
return labDarken(lab, -amount);
|
||||
};
|
24
src/common/color/rgb.ts
Normal file
24
src/common/color/rgb.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
const luminosity = (rgb: [number, number, number]): number => {
|
||||
// http://www.w3.org/TR/WCAG20/#relativeluminancedef
|
||||
const lum: [number, number, number] = [0, 0, 0];
|
||||
for (let i = 0; i < rgb.length; i++) {
|
||||
const chan = rgb[i] / 255;
|
||||
lum[i] = chan <= 0.03928 ? chan / 12.92 : ((chan + 0.055) / 1.055) ** 2.4;
|
||||
}
|
||||
|
||||
return 0.2126 * lum[0] + 0.7152 * lum[1] + 0.0722 * lum[2];
|
||||
};
|
||||
|
||||
export const rgbContrast = (
|
||||
color1: [number, number, number],
|
||||
color2: [number, number, number]
|
||||
) => {
|
||||
const lum1 = luminosity(color1);
|
||||
const lum2 = luminosity(color2);
|
||||
|
||||
if (lum1 > lum2) {
|
||||
return (lum1 + 0.05) / (lum2 + 0.05);
|
||||
}
|
||||
|
||||
return (lum2 + 0.05) / (lum1 + 0.05);
|
||||
};
|
9
src/common/config/components_with_service.ts
Normal file
9
src/common/config/components_with_service.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import { HomeAssistant } from "../../types";
|
||||
|
||||
/** Return an array of domains with the service. */
|
||||
export const componentsWithService = (
|
||||
hass: HomeAssistant,
|
||||
service: string
|
||||
): Array<string> =>
|
||||
hass &&
|
||||
Object.keys(hass.services).filter((key) => service in hass.services[key]);
|
9
src/common/config/is_service_loaded.ts
Normal file
9
src/common/config/is_service_loaded.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import { HomeAssistant } from "../../types";
|
||||
|
||||
/** Return if a service is loaded. */
|
||||
export const isServiceLoaded = (
|
||||
hass: HomeAssistant,
|
||||
domain: string,
|
||||
service: string
|
||||
): boolean =>
|
||||
hass && domain in hass.services && service in hass.services[domain];
|
@@ -22,7 +22,6 @@ export const DOMAINS_WITH_CARD = [
|
||||
"timer",
|
||||
"vacuum",
|
||||
"water_heater",
|
||||
"weblink",
|
||||
];
|
||||
|
||||
/** Domains with separate more info dialog. */
|
||||
@@ -36,7 +35,7 @@ export const DOMAINS_WITH_MORE_INFO = [
|
||||
"cover",
|
||||
"fan",
|
||||
"group",
|
||||
"history_graph",
|
||||
"humidifier",
|
||||
"input_datetime",
|
||||
"light",
|
||||
"lock",
|
||||
@@ -45,7 +44,6 @@ export const DOMAINS_WITH_MORE_INFO = [
|
||||
"script",
|
||||
"sun",
|
||||
"timer",
|
||||
"updater",
|
||||
"vacuum",
|
||||
"water_heater",
|
||||
"weather",
|
||||
@@ -57,16 +55,10 @@ export const DOMAINS_HIDE_MORE_INFO = [
|
||||
"input_select",
|
||||
"input_text",
|
||||
"scene",
|
||||
"weblink",
|
||||
];
|
||||
|
||||
/** Domains that should have the history hidden in the more info dialog. */
|
||||
export const DOMAINS_MORE_INFO_NO_HISTORY = [
|
||||
"camera",
|
||||
"configurator",
|
||||
"history_graph",
|
||||
"scene",
|
||||
];
|
||||
export const DOMAINS_MORE_INFO_NO_HISTORY = ["camera", "configurator", "scene"];
|
||||
|
||||
/** States that we consider "off". */
|
||||
export const STATES_OFF = ["closed", "locked", "off"];
|
||||
@@ -79,6 +71,7 @@ export const DOMAINS_TOGGLE = new Set([
|
||||
"switch",
|
||||
"group",
|
||||
"automation",
|
||||
"humidifier",
|
||||
]);
|
||||
|
||||
/** Temperature units. */
|
||||
|
@@ -20,31 +20,29 @@ export default function relativeTime(
|
||||
let delta = (compareTime.getTime() - dateObj.getTime()) / 1000;
|
||||
const tense = delta >= 0 ? "past" : "future";
|
||||
delta = Math.abs(delta);
|
||||
let roundedDelta = Math.round(delta);
|
||||
|
||||
let timeDesc;
|
||||
if (roundedDelta === 0) {
|
||||
return localize("ui.components.relative_time.just_now");
|
||||
}
|
||||
|
||||
let unit = "week";
|
||||
|
||||
for (let i = 0; i < tests.length; i++) {
|
||||
if (delta < tests[i]) {
|
||||
delta = Math.floor(delta);
|
||||
timeDesc = localize(
|
||||
`ui.components.relative_time.duration.${langKey[i]}`,
|
||||
"count",
|
||||
delta
|
||||
);
|
||||
if (roundedDelta < tests[i]) {
|
||||
unit = langKey[i];
|
||||
break;
|
||||
}
|
||||
|
||||
delta /= tests[i];
|
||||
roundedDelta = Math.round(delta);
|
||||
}
|
||||
|
||||
if (timeDesc === undefined) {
|
||||
delta = Math.floor(delta);
|
||||
timeDesc = localize(
|
||||
"ui.components.relative_time.duration.week",
|
||||
"count",
|
||||
delta
|
||||
);
|
||||
}
|
||||
const timeDesc = localize(
|
||||
`ui.components.relative_time.duration.${unit}`,
|
||||
"count",
|
||||
roundedDelta
|
||||
);
|
||||
|
||||
return options.includeTense === false
|
||||
? timeDesc
|
||||
|
155
src/common/decorators/local-storage.ts
Normal file
155
src/common/decorators/local-storage.ts
Normal file
@@ -0,0 +1,155 @@
|
||||
import { UnsubscribeFunc } from "home-assistant-js-websocket";
|
||||
import { PropertyDeclaration, UpdatingElement } from "lit-element";
|
||||
import type { ClassElement } from "../../types";
|
||||
|
||||
type Callback = (oldValue: any, newValue: any) => void;
|
||||
|
||||
class Storage {
|
||||
constructor() {
|
||||
window.addEventListener("storage", (ev: StorageEvent) => {
|
||||
if (ev.key && this.hasKey(ev.key)) {
|
||||
this._storage[ev.key] = ev.newValue
|
||||
? JSON.parse(ev.newValue)
|
||||
: ev.newValue;
|
||||
if (this._listeners[ev.key]) {
|
||||
this._listeners[ev.key].forEach((listener) =>
|
||||
listener(
|
||||
ev.oldValue ? JSON.parse(ev.oldValue) : ev.oldValue,
|
||||
this._storage[ev.key!]
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private _storage: { [storageKey: string]: any } = {};
|
||||
|
||||
private _listeners: {
|
||||
[storageKey: string]: Callback[];
|
||||
} = {};
|
||||
|
||||
public addFromStorage(storageKey: any): void {
|
||||
if (!this._storage[storageKey]) {
|
||||
const data = window.localStorage.getItem(storageKey);
|
||||
if (data) {
|
||||
this._storage[storageKey] = JSON.parse(data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public subscribeChanges(
|
||||
storageKey: string,
|
||||
callback: Callback
|
||||
): UnsubscribeFunc {
|
||||
if (this._listeners[storageKey]) {
|
||||
this._listeners[storageKey].push(callback);
|
||||
} else {
|
||||
this._listeners[storageKey] = [callback];
|
||||
}
|
||||
return () => {
|
||||
this.unsubscribeChanges(storageKey, callback);
|
||||
};
|
||||
}
|
||||
|
||||
public unsubscribeChanges(storageKey: string, callback: Callback) {
|
||||
if (!(storageKey in this._listeners)) {
|
||||
return;
|
||||
}
|
||||
const index = this._listeners[storageKey].indexOf(callback);
|
||||
if (index !== -1) {
|
||||
this._listeners[storageKey].splice(index, 1);
|
||||
}
|
||||
}
|
||||
|
||||
public hasKey(storageKey: string): any {
|
||||
return storageKey in this._storage;
|
||||
}
|
||||
|
||||
public getValue(storageKey: string): any {
|
||||
return this._storage[storageKey];
|
||||
}
|
||||
|
||||
public setValue(storageKey: string, value: any): any {
|
||||
this._storage[storageKey] = value;
|
||||
try {
|
||||
window.localStorage.setItem(storageKey, JSON.stringify(value));
|
||||
} catch (err) {
|
||||
// Safari in private mode doesn't allow localstorage
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const storage = new Storage();
|
||||
|
||||
export const LocalStorage = (
|
||||
storageKey?: string,
|
||||
property?: boolean,
|
||||
propertyOptions?: PropertyDeclaration
|
||||
): any => {
|
||||
return (clsElement: ClassElement) => {
|
||||
const key = String(clsElement.key);
|
||||
storageKey = storageKey || String(clsElement.key);
|
||||
const initVal = clsElement.initializer
|
||||
? clsElement.initializer()
|
||||
: undefined;
|
||||
|
||||
storage.addFromStorage(storageKey);
|
||||
|
||||
const subscribe = (el: UpdatingElement): UnsubscribeFunc =>
|
||||
storage.subscribeChanges(storageKey!, (oldValue) => {
|
||||
el.requestUpdate(clsElement.key, oldValue);
|
||||
});
|
||||
|
||||
const getValue = (): any => {
|
||||
return storage.hasKey(storageKey!)
|
||||
? storage.getValue(storageKey!)
|
||||
: initVal;
|
||||
};
|
||||
|
||||
const setValue = (el: UpdatingElement, value: any) => {
|
||||
let oldValue: unknown | undefined;
|
||||
if (property) {
|
||||
oldValue = getValue();
|
||||
}
|
||||
storage.setValue(storageKey!, value);
|
||||
if (property) {
|
||||
el.requestUpdate(clsElement.key, oldValue);
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
kind: "method",
|
||||
placement: "prototype",
|
||||
key: clsElement.key,
|
||||
descriptor: {
|
||||
set(this: UpdatingElement, value: unknown) {
|
||||
setValue(this, value);
|
||||
},
|
||||
get() {
|
||||
return getValue();
|
||||
},
|
||||
enumerable: true,
|
||||
configurable: true,
|
||||
},
|
||||
finisher(cls: typeof UpdatingElement) {
|
||||
if (property) {
|
||||
const connectedCallback = cls.prototype.connectedCallback;
|
||||
const disconnectedCallback = cls.prototype.disconnectedCallback;
|
||||
cls.prototype.connectedCallback = function () {
|
||||
connectedCallback.call(this);
|
||||
this[`__unbsubLocalStorage${key}`] = subscribe(this);
|
||||
};
|
||||
cls.prototype.disconnectedCallback = function () {
|
||||
disconnectedCallback.call(this);
|
||||
this[`__unbsubLocalStorage${key}`]();
|
||||
};
|
||||
cls.createProperty(clsElement.key, {
|
||||
noAccessor: true,
|
||||
...propertyOptions,
|
||||
});
|
||||
}
|
||||
},
|
||||
};
|
||||
};
|
||||
};
|
33
src/common/decorators/restore-scroll.ts
Normal file
33
src/common/decorators/restore-scroll.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import type { LitElement } from "lit-element";
|
||||
import type { ClassElement } from "../../types";
|
||||
|
||||
export const restoreScroll = (selector: string): any => {
|
||||
return (element: ClassElement) => ({
|
||||
kind: "method",
|
||||
placement: "prototype",
|
||||
key: element.key,
|
||||
descriptor: {
|
||||
set(this: LitElement, value: number) {
|
||||
this[`__${String(element.key)}`] = value;
|
||||
},
|
||||
get(this: LitElement) {
|
||||
return this[`__${String(element.key)}`];
|
||||
},
|
||||
enumerable: true,
|
||||
configurable: true,
|
||||
},
|
||||
finisher(cls: typeof LitElement) {
|
||||
const connectedCallback = cls.prototype.connectedCallback;
|
||||
cls.prototype.connectedCallback = function () {
|
||||
connectedCallback.call(this);
|
||||
if (this[element.key]) {
|
||||
const target = this.renderRoot.querySelector(selector);
|
||||
if (!target) {
|
||||
return;
|
||||
}
|
||||
target.scrollTop = this[element.key];
|
||||
}
|
||||
};
|
||||
},
|
||||
});
|
||||
};
|
@@ -1,26 +1,20 @@
|
||||
import { derivedStyles } from "../../resources/styles";
|
||||
import { darkStyles, derivedStyles } from "../../resources/styles";
|
||||
import { HomeAssistant, Theme } from "../../types";
|
||||
import {
|
||||
hex2rgb,
|
||||
lab2hex,
|
||||
lab2rgb,
|
||||
rgb2hex,
|
||||
rgb2lab,
|
||||
} from "../color/convert-color";
|
||||
import { labBrighten, labDarken } from "../color/lab";
|
||||
import { rgbContrast } from "../color/rgb";
|
||||
|
||||
interface ProcessedTheme {
|
||||
keys: { [key: string]: "" };
|
||||
styles: { [key: string]: string };
|
||||
}
|
||||
|
||||
const hexToRgb = (hex: string): string | null => {
|
||||
const shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i;
|
||||
const checkHex = hex.replace(shorthandRegex, (_m, r, g, b) => {
|
||||
return r + r + g + g + b + b;
|
||||
});
|
||||
|
||||
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(checkHex);
|
||||
return result
|
||||
? `${parseInt(result[1], 16)}, ${parseInt(result[2], 16)}, ${parseInt(
|
||||
result[3],
|
||||
16
|
||||
)}`
|
||||
: null;
|
||||
};
|
||||
|
||||
let PROCESSED_THEMES: { [key: string]: ProcessedTheme } = {};
|
||||
|
||||
/**
|
||||
@@ -33,17 +27,56 @@ let PROCESSED_THEMES: { [key: string]: ProcessedTheme } = {};
|
||||
export const applyThemesOnElement = (
|
||||
element,
|
||||
themes: HomeAssistant["themes"],
|
||||
selectedTheme?: string
|
||||
selectedTheme?: string,
|
||||
themeOptions?: Partial<HomeAssistant["selectedTheme"]>
|
||||
) => {
|
||||
const newTheme = selectedTheme
|
||||
? PROCESSED_THEMES[selectedTheme] || processTheme(selectedTheme, themes)
|
||||
: undefined;
|
||||
let cacheKey = selectedTheme;
|
||||
let themeRules: Partial<Theme> = {};
|
||||
|
||||
if (!element._themes && !newTheme) {
|
||||
if (selectedTheme === "default" && themeOptions) {
|
||||
if (themeOptions.dark) {
|
||||
cacheKey = `${cacheKey}__dark`;
|
||||
themeRules = darkStyles;
|
||||
}
|
||||
if (themeOptions.primaryColor) {
|
||||
cacheKey = `${cacheKey}__primary_${themeOptions.primaryColor}`;
|
||||
const rgbPrimaryColor = hex2rgb(themeOptions.primaryColor);
|
||||
const labPrimaryColor = rgb2lab(rgbPrimaryColor);
|
||||
themeRules["primary-color"] = themeOptions.primaryColor;
|
||||
const rgbLigthPrimaryColor = lab2rgb(labBrighten(labPrimaryColor));
|
||||
themeRules["light-primary-color"] = rgb2hex(rgbLigthPrimaryColor);
|
||||
themeRules["dark-primary-color"] = lab2hex(labDarken(labPrimaryColor));
|
||||
themeRules["text-primary-color"] =
|
||||
rgbContrast(rgbPrimaryColor, [33, 33, 33]) < 6 ? "#fff" : "#212121";
|
||||
themeRules["text-light-primary-color"] =
|
||||
rgbContrast(rgbLigthPrimaryColor, [33, 33, 33]) < 6
|
||||
? "#fff"
|
||||
: "#212121";
|
||||
themeRules["state-icon-color"] = themeRules["dark-primary-color"];
|
||||
}
|
||||
if (themeOptions.accentColor) {
|
||||
cacheKey = `${cacheKey}__accent_${themeOptions.accentColor}`;
|
||||
themeRules["accent-color"] = themeOptions.accentColor;
|
||||
const rgbAccentColor = hex2rgb(themeOptions.accentColor);
|
||||
themeRules["text-accent-color"] =
|
||||
rgbContrast(rgbAccentColor, [33, 33, 33]) < 6 ? "#fff" : "#212121";
|
||||
}
|
||||
}
|
||||
|
||||
if (selectedTheme && themes.themes[selectedTheme]) {
|
||||
themeRules = themes.themes[selectedTheme];
|
||||
}
|
||||
|
||||
if (!element._themes && !Object.keys(themeRules).length) {
|
||||
// No styles to reset, and no styles to set
|
||||
return;
|
||||
}
|
||||
|
||||
const newTheme =
|
||||
themeRules && cacheKey
|
||||
? PROCESSED_THEMES[cacheKey] || processTheme(cacheKey, themeRules)
|
||||
: undefined;
|
||||
|
||||
// Add previous set keys to reset them, and new theme
|
||||
const styles = { ...element._themes, ...newTheme?.styles };
|
||||
element._themes = newTheme?.keys;
|
||||
@@ -58,42 +91,45 @@ export const applyThemesOnElement = (
|
||||
};
|
||||
|
||||
const processTheme = (
|
||||
themeName: string,
|
||||
themes: HomeAssistant["themes"]
|
||||
cacheKey: string,
|
||||
theme: Partial<Theme>
|
||||
): ProcessedTheme | undefined => {
|
||||
if (!themes.themes[themeName]) {
|
||||
if (!theme || !Object.keys(theme).length) {
|
||||
return undefined;
|
||||
}
|
||||
const theme: Theme = {
|
||||
const combinedTheme: Partial<Theme> = {
|
||||
...derivedStyles,
|
||||
...themes.themes[themeName],
|
||||
...theme,
|
||||
};
|
||||
const styles = {};
|
||||
const keys = {};
|
||||
for (const key of Object.keys(theme)) {
|
||||
for (const key of Object.keys(combinedTheme)) {
|
||||
const prefixedKey = `--${key}`;
|
||||
const value = theme[key];
|
||||
const value = String(combinedTheme[key]);
|
||||
styles[prefixedKey] = value;
|
||||
keys[prefixedKey] = "";
|
||||
|
||||
// Try to create a rgb value for this key if it is a hex color
|
||||
// Try to create a rgb value for this key if it is not a var
|
||||
if (!value.startsWith("#")) {
|
||||
// Not a hex color
|
||||
// Can't convert non hex value
|
||||
continue;
|
||||
}
|
||||
|
||||
const rgbKey = `rgb-${key}`;
|
||||
if (theme[rgbKey] !== undefined) {
|
||||
if (combinedTheme[rgbKey] !== undefined) {
|
||||
// Theme has it's own rgb value
|
||||
continue;
|
||||
}
|
||||
const rgbValue = hexToRgb(value);
|
||||
if (rgbValue !== null) {
|
||||
try {
|
||||
const rgbValue = hex2rgb(value).join(",");
|
||||
const prefixedRgbKey = `--${rgbKey}`;
|
||||
styles[prefixedRgbKey] = rgbValue;
|
||||
keys[prefixedRgbKey] = "";
|
||||
} catch (e) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
PROCESSED_THEMES[themeName] = { styles, keys };
|
||||
PROCESSED_THEMES[cacheKey] = { styles, keys };
|
||||
return { styles, keys };
|
||||
};
|
||||
|
||||
|
8
src/common/dom/deep-active-element.ts
Normal file
8
src/common/dom/deep-active-element.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
export const deepActiveElement = (
|
||||
root: DocumentOrShadowRoot = document
|
||||
): Element | null => {
|
||||
if (root.activeElement?.shadowRoot?.activeElement) {
|
||||
return deepActiveElement(root.activeElement.shadowRoot);
|
||||
}
|
||||
return root.activeElement;
|
||||
};
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user